feat(insta): Trends tab — account focus + external trends + impact preview
- api.js: add getInstaTrends, instaCollectTrends, getInstaPreferences, putInstaPreferences helpers - InstaCards.jsx: add Cards/Trends tab bar with URL sync (?tab=trends); add AccountFocusPanel (category weight sliders + save), ExternalTrendsPanel (Naver+Google trend rows + manual collect), PreferenceImpactPanel (next extract preview); existing 5-panel Cards tab preserved intact - InstaCards.css: add ic-tabbar, ic-tab, ic-trends-grid, ic-panel base, ic-focus/ic-trend/ic-impact component styles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
21
src/api.js
21
src/api.js
@@ -544,6 +544,27 @@ export function putInstaPrompt(name, template, description = '') {
|
|||||||
return apiPut(`/api/insta/templates/prompts/${encodeURIComponent(name)}`, { template, description });
|
return apiPut(`/api/insta/templates/prompts/${encodeURIComponent(name)}`, { template, description });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── insta-lab trends ──
|
||||||
|
export function getInstaTrends({ source, category, days = 1 } = {}) {
|
||||||
|
const q = new URLSearchParams();
|
||||||
|
if (source) q.set('source', source);
|
||||||
|
if (category) q.set('category', category);
|
||||||
|
q.set('days', String(days));
|
||||||
|
return apiGet(`/api/insta/trends?${q.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function instaCollectTrends(categories) {
|
||||||
|
return apiPost('/api/insta/trends/collect', categories ? { categories } : {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstaPreferences() {
|
||||||
|
return apiGet('/api/insta/preferences');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function putInstaPreferences(categories) {
|
||||||
|
return apiPut('/api/insta/preferences', { categories });
|
||||||
|
}
|
||||||
|
|
||||||
// ── Agent Office ──────────────────────────────────
|
// ── Agent Office ──────────────────────────────────
|
||||||
export const getAgents = () => apiGet('/api/agent-office/agents');
|
export const getAgents = () => apiGet('/api/agent-office/agents');
|
||||||
export const getAgentDetail = (id) => apiGet(`/api/agent-office/agents/${id}`);
|
export const getAgentDetail = (id) => apiGet(`/api/agent-office/agents/${id}`);
|
||||||
|
|||||||
@@ -100,3 +100,70 @@
|
|||||||
|
|
||||||
/* 빈 상태 */
|
/* 빈 상태 */
|
||||||
.ic-empty { text-align: center; padding: 40px 20px; color: rgba(255,255,255,.3); font-size: 0.85rem; }
|
.ic-empty { text-align: center; padding: 40px 20px; color: rgba(255,255,255,.3); font-size: 0.85rem; }
|
||||||
|
|
||||||
|
/* ── tabs ── */
|
||||||
|
.ic-tabbar { display: flex; gap: 8px; border-bottom: 1px solid #e2e8f0; margin-bottom: 16px; }
|
||||||
|
.ic-tab {
|
||||||
|
background: transparent; border: 0; padding: 10px 16px;
|
||||||
|
cursor: pointer; font-size: 14px; font-weight: 600;
|
||||||
|
color: #64748b; border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
.ic-tab.is-active { color: #ec4899; border-bottom-color: #ec4899; }
|
||||||
|
|
||||||
|
/* ── trends grid ── */
|
||||||
|
.ic-trends-grid { display: grid; gap: 16px; grid-template-columns: 1fr; }
|
||||||
|
@media (min-width: 1024px) { .ic-trends-grid { grid-template-columns: 320px 1fr; } }
|
||||||
|
|
||||||
|
/* ── ic-panel base ── */
|
||||||
|
.ic-panel { background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.06); border-radius: 12px; padding: 16px; }
|
||||||
|
.ic-panel__title { font-size: 0.95rem; font-weight: 700; color: var(--text-primary, #e4e4e7); margin: 0 0 8px; }
|
||||||
|
.ic-panel__hint { font-size: 0.78rem; color: rgba(255,255,255,.4); margin: 0 0 10px; }
|
||||||
|
|
||||||
|
/* ── focus panel ── */
|
||||||
|
.ic-panel--focus .ic-focus__list { display: flex; flex-direction: column; gap: 10px; margin: 12px 0; }
|
||||||
|
.ic-focus__row { display: grid; grid-template-columns: 110px 1fr 50px; align-items: center; gap: 8px; }
|
||||||
|
.ic-focus__label { font-weight: 600; color: #475569; text-transform: capitalize; }
|
||||||
|
.ic-focus__slider { width: 100%; accent-color: #ec4899; }
|
||||||
|
.ic-focus__num { text-align: right; font-variant-numeric: tabular-nums; color: #475569; }
|
||||||
|
.ic-focus__add { display: flex; gap: 8px; margin-top: 12px; }
|
||||||
|
.ic-focus__add input { flex: 1; padding: 8px; border: 1px solid #cbd5e1; border-radius: 6px; background: rgba(255,255,255,.06); color: var(--text-primary, #e4e4e7); }
|
||||||
|
.ic-focus__add button { padding: 8px 14px; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.12); border-radius: 6px; color: rgba(255,255,255,.7); cursor: pointer; font-size: 0.85rem; }
|
||||||
|
.ic-focus__save {
|
||||||
|
width: 100%; padding: 10px; margin-top: 12px;
|
||||||
|
background: #ec4899; color: #fff; border: 0; border-radius: 6px; cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.ic-focus__save:disabled { opacity: .5; cursor: not-allowed; }
|
||||||
|
.ic-focus__hint { margin-top: 12px; padding: 10px; background: rgba(245,158,11,.1); border-left: 3px solid #f59e0b; font-size: 12px; color: rgba(255,255,255,.6); line-height: 1.5; }
|
||||||
|
.ic-focus__hint code { background: rgba(0,0,0,.2); padding: 1px 4px; border-radius: 3px; }
|
||||||
|
|
||||||
|
/* ── trends panel ── */
|
||||||
|
.ic-trends__cols { display: grid; grid-template-columns: 1fr; gap: 16px; }
|
||||||
|
@media (min-width: 768px) { .ic-trends__cols { grid-template-columns: 1fr 1fr; } }
|
||||||
|
.ic-trends__col h4 { margin: 0 0 8px; font-size: 14px; color: rgba(255,255,255,.5); }
|
||||||
|
.ic-trend__group { margin-bottom: 14px; }
|
||||||
|
.ic-trend__group-head { font-size: 12px; font-weight: 700; text-transform: uppercase; margin-bottom: 4px; letter-spacing: 0.5px; }
|
||||||
|
.ic-trend__row {
|
||||||
|
display: grid; grid-template-columns: 10px 1fr 50px 36px;
|
||||||
|
align-items: center; gap: 8px; padding: 6px 4px;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,.06);
|
||||||
|
}
|
||||||
|
.ic-trend__cat-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||||
|
.ic-trend__kw { font-weight: 500; color: var(--text-primary, #e4e4e7); font-size: 0.85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.ic-trend__score { text-align: right; color: rgba(255,255,255,.4); font-variant-numeric: tabular-nums; font-size: 12px; }
|
||||||
|
.ic-trend__make { background: #ec4899; border: 0; color: #fff; border-radius: 4px; cursor: pointer; padding: 4px; font-size: 14px; }
|
||||||
|
.ic-trend__make:hover { background: #db2777; }
|
||||||
|
|
||||||
|
.ic-panel__head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
||||||
|
.ic-panel__actions { display: flex; gap: 8px; align-items: center; }
|
||||||
|
.ic-panel__actions button { padding: 6px 12px; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.1); border-radius: 6px; color: rgba(255,255,255,.7); cursor: pointer; font-size: 0.8rem; }
|
||||||
|
.ic-panel__actions button:disabled { opacity: .5; cursor: not-allowed; }
|
||||||
|
|
||||||
|
/* ── impact panel ── */
|
||||||
|
.ic-impact__row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
|
||||||
|
.ic-impact__chip {
|
||||||
|
display: flex; align-items: baseline; gap: 6px;
|
||||||
|
padding: 6px 12px; background: rgba(255,255,255,.06); border-radius: 999px;
|
||||||
|
}
|
||||||
|
.ic-impact__cat { font-weight: 600; text-transform: capitalize; color: rgba(255,255,255,.6); font-size: 0.82rem; }
|
||||||
|
.ic-impact__count { color: #ec4899; font-weight: 700; font-size: 0.82rem; }
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import {
|
|||||||
getInstaTask,
|
getInstaTask,
|
||||||
getInstaPrompt,
|
getInstaPrompt,
|
||||||
putInstaPrompt,
|
putInstaPrompt,
|
||||||
|
getInstaTrends,
|
||||||
|
instaCollectTrends,
|
||||||
|
getInstaPreferences,
|
||||||
|
putInstaPreferences,
|
||||||
} from '../../api';
|
} from '../../api';
|
||||||
import './InstaCards.css';
|
import './InstaCards.css';
|
||||||
|
|
||||||
@@ -92,11 +96,225 @@ function TaskStatusBox({ task }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ══════════════════════ Trends 탭 패널 1: AccountFocusPanel ══════════════ */
|
||||||
|
function AccountFocusPanel() {
|
||||||
|
const [prefs, setPrefs] = useState([]);
|
||||||
|
const [draft, setDraft] = useState({});
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [newCat, setNewCat] = useState('');
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
const data = await getInstaPreferences();
|
||||||
|
setPrefs(data.categories || []);
|
||||||
|
const m = {};
|
||||||
|
(data.categories || []).forEach(p => { m[p.category] = Math.round(p.weight * 100); });
|
||||||
|
setDraft(m);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const payload = {};
|
||||||
|
Object.entries(draft).forEach(([k, v]) => { payload[k] = (Number(v) || 0) / 100; });
|
||||||
|
await putInstaPreferences(payload);
|
||||||
|
await load();
|
||||||
|
} finally { setSaving(false); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const addCat = () => {
|
||||||
|
const name = newCat.trim().toLowerCase();
|
||||||
|
if (!name || draft[name] !== undefined) return;
|
||||||
|
setDraft({ ...draft, [name]: 0 });
|
||||||
|
setNewCat('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="ic-panel ic-panel--focus">
|
||||||
|
<h3 className="ic-panel__title">🎯 이 계정의 주제 (카테고리 가중치)</h3>
|
||||||
|
<p className="ic-panel__hint">슬라이더는 각 카테고리에 자동 추출 키워드 비율을 결정합니다. 합계는 자동 정규화됩니다.</p>
|
||||||
|
<div className="ic-focus__list">
|
||||||
|
{Object.entries(draft).map(([cat, val]) => (
|
||||||
|
<div key={cat} className="ic-focus__row">
|
||||||
|
<label className="ic-focus__label">{cat}</label>
|
||||||
|
<input
|
||||||
|
type="range" min="0" max="100" value={val}
|
||||||
|
onChange={e => setDraft({ ...draft, [cat]: Number(e.target.value) })}
|
||||||
|
className="ic-focus__slider"
|
||||||
|
/>
|
||||||
|
<span className="ic-focus__num">{val}%</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="ic-focus__add">
|
||||||
|
<input
|
||||||
|
type="text" placeholder="신규 카테고리 (영문 소문자)"
|
||||||
|
value={newCat} onChange={e => setNewCat(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={addCat}>+ 추가</button>
|
||||||
|
</div>
|
||||||
|
<button className="ic-focus__save" onClick={save} disabled={saving}>
|
||||||
|
{saving ? '저장 중...' : '저장'}
|
||||||
|
</button>
|
||||||
|
<div className="ic-focus__hint">
|
||||||
|
💡 신규 카테고리를 추가했다면 Cards 탭의 Prompt Templates Editor에서
|
||||||
|
<code>category_seeds</code>에 시드 키워드도 함께 정의해야 자동 추출에 반영됩니다.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ══════════════════════ Trends 탭 패널 2: ExternalTrendsPanel ══════════ */
|
||||||
|
const CATEGORY_COLORS = {
|
||||||
|
economy: '#0F62FE', psychology: '#A66CFF',
|
||||||
|
celebrity: '#FF5C8A', uncategorized: '#6B7280',
|
||||||
|
};
|
||||||
|
|
||||||
|
function ExternalTrendsPanel({ onCreateSlate }) {
|
||||||
|
const [naver, setNaver] = useState([]);
|
||||||
|
const [google, setGoogle] = useState([]);
|
||||||
|
const [lastFetched, setLastFetched] = useState(null);
|
||||||
|
const [collecting, setCollecting] = useState(false);
|
||||||
|
const [task, setTask] = useState(null);
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
const [n, g] = await Promise.all([
|
||||||
|
getInstaTrends({ source: 'naver_popular', days: 2 }),
|
||||||
|
getInstaTrends({ source: 'google_trends', days: 2 }),
|
||||||
|
]);
|
||||||
|
setNaver(n.items || []);
|
||||||
|
setGoogle(g.items || []);
|
||||||
|
const all = [...(n.items || []), ...(g.items || [])];
|
||||||
|
if (all.length) {
|
||||||
|
const latest = all.map(t => t.suggested_at).sort().reverse()[0];
|
||||||
|
setLastFetched(latest);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
const trigger = async () => {
|
||||||
|
setCollecting(true);
|
||||||
|
try {
|
||||||
|
const { task_id } = await instaCollectTrends();
|
||||||
|
let st = null;
|
||||||
|
for (let i = 0; i < 60; i++) {
|
||||||
|
st = await getInstaTask(task_id);
|
||||||
|
setTask(st);
|
||||||
|
if (st.status === 'succeeded' || st.status === 'failed') break;
|
||||||
|
await new Promise(r => setTimeout(r, 3000));
|
||||||
|
}
|
||||||
|
await load();
|
||||||
|
} finally { setCollecting(false); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupByCat = (items) => {
|
||||||
|
const g = {};
|
||||||
|
items.forEach(it => { (g[it.category] = g[it.category] || []).push(it); });
|
||||||
|
return g;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRow = (t) => (
|
||||||
|
<div className="ic-trend__row" key={`${t.source}-${t.id}`}>
|
||||||
|
<span className="ic-trend__cat-dot" style={{ background: CATEGORY_COLORS[t.category] || '#6B7280' }} />
|
||||||
|
<span className="ic-trend__kw">{t.keyword}</span>
|
||||||
|
<span className="ic-trend__score">{(t.score || 0).toFixed(2)}</span>
|
||||||
|
<button
|
||||||
|
className="ic-trend__make"
|
||||||
|
onClick={() => onCreateSlate?.({ keyword: t.keyword, category: t.category })}
|
||||||
|
>🎴</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const naverGrouped = groupByCat(naver);
|
||||||
|
return (
|
||||||
|
<section className="ic-panel ic-panel--trends">
|
||||||
|
<div className="ic-panel__head">
|
||||||
|
<h3 className="ic-panel__title">📈 외부 트렌드</h3>
|
||||||
|
<div className="ic-panel__actions">
|
||||||
|
<span className="ic-panel__hint">
|
||||||
|
{lastFetched ? `마지막 수집: ${fmtDate(lastFetched)}` : '아직 수집 없음'}
|
||||||
|
</span>
|
||||||
|
<button onClick={trigger} disabled={collecting}>
|
||||||
|
{collecting ? '수집 중...' : '🔄 수동 수집'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{task && <TaskStatusBox task={task} />}
|
||||||
|
<div className="ic-trends__cols">
|
||||||
|
<div className="ic-trends__col">
|
||||||
|
<h4>🔥 NAVER 인기</h4>
|
||||||
|
{Object.keys(naverGrouped).length === 0 && <p className="ic-empty">없음</p>}
|
||||||
|
{Object.entries(naverGrouped).map(([cat, items]) => (
|
||||||
|
<div key={cat} className="ic-trend__group">
|
||||||
|
<div className="ic-trend__group-head" style={{ color: CATEGORY_COLORS[cat] || '#6B7280' }}>{cat}</div>
|
||||||
|
{items.map(renderRow)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="ic-trends__col">
|
||||||
|
<h4>🌐 Google Trends</h4>
|
||||||
|
{google.length === 0 && <p className="ic-empty">없음</p>}
|
||||||
|
{google.map(renderRow)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ══════════════════════ Trends 탭 패널 3: PreferenceImpactPanel ══════ */
|
||||||
|
function PreferenceImpactPanel() {
|
||||||
|
const [prefs, setPrefs] = useState([]);
|
||||||
|
const TOTAL = 15;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const data = await getInstaPreferences();
|
||||||
|
setPrefs(data.categories || []);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const totalWeight = prefs.reduce((s, p) => s + (p.weight || 0), 0) || 1;
|
||||||
|
const breakdown = prefs.map(p => ({
|
||||||
|
category: p.category,
|
||||||
|
count: Math.round(TOTAL * (p.weight || 0) / totalWeight),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="ic-panel ic-panel--impact">
|
||||||
|
<h3 className="ic-panel__title">📊 다음 자동 추출 미리보기</h3>
|
||||||
|
<div className="ic-impact__row">
|
||||||
|
{breakdown.map(b => (
|
||||||
|
<div key={b.category} className="ic-impact__chip">
|
||||||
|
<span className="ic-impact__cat">{b.category}</span>
|
||||||
|
<span className="ic-impact__count">{b.count}개</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* ══════════════════════════════════════════════════════════════════════════ */
|
/* ══════════════════════════════════════════════════════════════════════════ */
|
||||||
export default function InstaCards() {
|
export default function InstaCards() {
|
||||||
const [status, setStatus] = useState(null);
|
const [status, setStatus] = useState(null);
|
||||||
const [selectedSlateId, setSelectedSlateId] = useState(null);
|
const [selectedSlateId, setSelectedSlateId] = useState(null);
|
||||||
|
|
||||||
|
/* ── 탭 상태 (URL 동기화) ── */
|
||||||
|
const [activeTab, setActiveTab] = useState(() => {
|
||||||
|
const u = new URL(window.location.href);
|
||||||
|
return u.searchParams.get('tab') === 'trends' ? 'trends' : 'cards';
|
||||||
|
});
|
||||||
|
|
||||||
|
const switchTab = (next) => {
|
||||||
|
setActiveTab(next);
|
||||||
|
const u = new URL(window.location.href);
|
||||||
|
if (next === 'cards') u.searchParams.delete('tab');
|
||||||
|
else u.searchParams.set('tab', next);
|
||||||
|
window.history.replaceState({}, '', u.toString());
|
||||||
|
};
|
||||||
|
|
||||||
const loadStatus = useCallback(() => {
|
const loadStatus = useCallback(() => {
|
||||||
return getInstaStatus().then(setStatus).catch(() => {});
|
return getInstaStatus().then(setStatus).catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
@@ -105,9 +323,35 @@ export default function InstaCards() {
|
|||||||
loadStatus();
|
loadStatus();
|
||||||
}, [loadStatus]);
|
}, [loadStatus]);
|
||||||
|
|
||||||
|
/* ── handleCreateSlate: 키워드 → 슬레이트 생성 (Trends 탭에서도 공유) ── */
|
||||||
|
const handleCreateSlate = useCallback(async ({ keyword, category, keyword_id } = {}) => {
|
||||||
|
try {
|
||||||
|
await createInstaSlate({ keyword, category, keyword_id });
|
||||||
|
setSelectedSlateId(null);
|
||||||
|
} catch (e) {
|
||||||
|
alert('카드 생성 실패: ' + e.message);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PullToRefresh onRefresh={loadStatus}>
|
|
||||||
<div className="ic">
|
<div className="ic">
|
||||||
|
{/* ── 탭 바 ── */}
|
||||||
|
<div className="ic-tabbar">
|
||||||
|
<button
|
||||||
|
className={`ic-tab ${activeTab === 'cards' ? 'is-active' : ''}`}
|
||||||
|
onClick={() => switchTab('cards')}
|
||||||
|
>🎴 Cards</button>
|
||||||
|
<button
|
||||||
|
className={`ic-tab ${activeTab === 'trends' ? 'is-active' : ''}`}
|
||||||
|
onClick={() => switchTab('trends')}
|
||||||
|
>📈 Trends</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Cards 탭 (기존 5-패널) ── */}
|
||||||
|
{activeTab === 'cards' && (
|
||||||
|
<>
|
||||||
|
<PullToRefresh onRefresh={loadStatus}>
|
||||||
|
<div>
|
||||||
{/* 헤더 + 상태 배너 */}
|
{/* 헤더 + 상태 배너 */}
|
||||||
<header className="ic-header">
|
<header className="ic-header">
|
||||||
<h1>Insta Cards</h1>
|
<h1>Insta Cards</h1>
|
||||||
@@ -143,6 +387,18 @@ export default function InstaCards() {
|
|||||||
<PromptTemplatesEditor />
|
<PromptTemplatesEditor />
|
||||||
</div>
|
</div>
|
||||||
</PullToRefresh>
|
</PullToRefresh>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ── Trends 탭 (3 new panels) ── */}
|
||||||
|
{activeTab === 'trends' && (
|
||||||
|
<div className="ic-trends-grid">
|
||||||
|
<AccountFocusPanel />
|
||||||
|
<ExternalTrendsPanel onCreateSlate={handleCreateSlate} />
|
||||||
|
<PreferenceImpactPanel />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user