diff --git a/src/pages/insta/InstaCards.jsx b/src/pages/insta/InstaCards.jsx
index f60d3e4..3859da6 100644
--- a/src/pages/insta/InstaCards.jsx
+++ b/src/pages/insta/InstaCards.jsx
@@ -1,10 +1,7 @@
-import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
+import React, { useState, useEffect, useCallback, useRef } from 'react';
import PullToRefresh from '../../components/PullToRefresh';
import {
getInstaStatus,
- instaCollectNews,
- instaExtractKeywords,
- getInstaKeywords,
createInstaSlate,
getInstaSlates,
getInstaSlate,
@@ -22,9 +19,10 @@ import {
} from '../../api';
import './InstaCards.css';
import { fmtDate } from './instaUtils';
-import usePollTask from './hooks/usePollTask';
import StatusBadge from './components/StatusBadge';
import TaskStatusBox from './components/TaskStatusBox';
+import TriggerPanel from './components/TriggerPanel';
+import KeywordsPanel from './components/KeywordsPanel';
/* ══════════════════════ Trends 탭 패널 1: AccountFocusPanel ══════════════ */
function AccountFocusPanel() {
@@ -396,173 +394,6 @@ export default function InstaCards() {
);
}
-/* ══════════════════════ 트리거 패널 ══════════════════════════════════════ */
-function TriggerPanel() {
- const collectPoll = usePollTask();
- const keywordsPoll = usePollTask();
-
- async function handleCollect() {
- try {
- const res = await instaCollectNews();
- collectPoll.start(res.task_id);
- } catch (e) {
- alert('뉴스 수집 실패: ' + e.message);
- }
- }
-
- async function handleKeywords() {
- try {
- const res = await instaExtractKeywords();
- keywordsPoll.start(res.task_id);
- } catch (e) {
- alert('키워드 추출 실패: ' + e.message);
- }
- }
-
- const collectBusy = !!collectPoll.taskId;
- const kwBusy = !!keywordsPoll.taskId;
-
- return (
-
-
트리거
-
-
-
-
-
-
-
- );
-}
-
-/* ══════════════════════ 키워드 목록 ══════════════════════════════════════ */
-const CATEGORIES = ['전체', 'economy', 'psychology', 'celebrity'];
-const KEYWORDS_PER_PAGE = 10;
-
-function KeywordsPanel({ onCreateSlate }) {
- const [category, setCategory] = useState('전체');
- const [keywords, setKeywords] = useState([]);
- const [creating, setCreating] = useState(null); // keyword_id being created
- const [page, setPage] = useState(0);
-
- const load = useCallback(() => {
- const cat = category === '전체' ? undefined : category;
- getInstaKeywords({ category: cat }).then((r) => setKeywords(r.items || [])).catch(() => {});
- }, [category]);
-
- useEffect(() => { load(); }, [load]);
- useEffect(() => { setPage(0); }, [category]); // 카테고리 변경 시 첫 페이지로
-
- // 동일 keyword 중복 제거(최고 score 1개만 유지) + score 내림차순
- const deduped = useMemo(() => {
- const best = new Map();
- for (const kw of keywords) {
- const name = (kw.keyword || '').trim();
- if (!name) continue;
- const prev = best.get(name);
- if (!prev || (kw.score ?? 0) > (prev.score ?? 0)) best.set(name, kw);
- }
- return [...best.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
- }, [keywords]);
-
- const totalPages = Math.max(1, Math.ceil(deduped.length / KEYWORDS_PER_PAGE));
- const safePage = Math.min(page, totalPages - 1);
- const pageItems = deduped.slice(safePage * KEYWORDS_PER_PAGE, safePage * KEYWORDS_PER_PAGE + KEYWORDS_PER_PAGE);
-
- // 부모(InstaCards)의 handleCreateSlate에 위임 — progress 배너 + 스크롤 + 자동 미리보기 공통화
- async function handleCreate(kw) {
- if (creating) return;
- setCreating(kw.id);
- try {
- await onCreateSlate?.({
- keyword: kw.keyword,
- category: kw.category,
- keyword_id: kw.id,
- });
- } finally {
- setCreating(null);
- }
- }
-
- return (
-
-
트렌딩 키워드
-
- {/* 카테고리 필터 */}
-
- {CATEGORIES.map((c) => (
-
- ))}
-
-
- {/* progress 표시는 상단 ic-slate-progress 배너에서 일괄 처리 */}
-
- {deduped.length === 0 ? (
-
키워드가 없습니다. 키워드 추출을 실행하세요.
- ) : (
- <>
-
- {pageItems.map((kw) => (
-
- {kw.keyword}
-
- {kw.category} · {kw.articles_count ?? 0}건
-
- {kw.score?.toFixed(1) ?? '-'}
-
-
- ))}
-
-
- {totalPages > 1 && (
-
-
- {safePage + 1} / {totalPages}
-
-
- )}
- >
- )}
-
- );
-}
-
/* ══════════════════════ 슬레이트 목록 ══════════════════════════════════ */
function SlatesPanel({ selectedId, onSelect }) {
const [slates, setSlates] = useState([]);
diff --git a/src/pages/insta/components/KeywordsPanel.jsx b/src/pages/insta/components/KeywordsPanel.jsx
new file mode 100644
index 0000000..96029aa
--- /dev/null
+++ b/src/pages/insta/components/KeywordsPanel.jsx
@@ -0,0 +1,118 @@
+import React, { useState, useEffect, useCallback, useMemo } from 'react';
+import { getInstaKeywords } from '../../../api';
+
+/* ══════════════════════ 키워드 목록 ══════════════════════════════════════ */
+const CATEGORIES = ['전체', 'economy', 'psychology', 'celebrity'];
+const KEYWORDS_PER_PAGE = 10;
+
+function KeywordsPanel({ onCreateSlate }) {
+ const [category, setCategory] = useState('전체');
+ const [keywords, setKeywords] = useState([]);
+ const [creating, setCreating] = useState(null); // keyword_id being created
+ const [page, setPage] = useState(0);
+
+ const load = useCallback(() => {
+ const cat = category === '전체' ? undefined : category;
+ getInstaKeywords({ category: cat }).then((r) => setKeywords(r.items || [])).catch(() => {});
+ }, [category]);
+
+ useEffect(() => { load(); }, [load]);
+ useEffect(() => { setPage(0); }, [category]); // 카테고리 변경 시 첫 페이지로
+
+ // 동일 keyword 중복 제거(최고 score 1개만 유지) + score 내림차순
+ const deduped = useMemo(() => {
+ const best = new Map();
+ for (const kw of keywords) {
+ const name = (kw.keyword || '').trim();
+ if (!name) continue;
+ const prev = best.get(name);
+ if (!prev || (kw.score ?? 0) > (prev.score ?? 0)) best.set(name, kw);
+ }
+ return [...best.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
+ }, [keywords]);
+
+ const totalPages = Math.max(1, Math.ceil(deduped.length / KEYWORDS_PER_PAGE));
+ const safePage = Math.min(page, totalPages - 1);
+ const pageItems = deduped.slice(safePage * KEYWORDS_PER_PAGE, safePage * KEYWORDS_PER_PAGE + KEYWORDS_PER_PAGE);
+
+ // 부모(InstaCards)의 handleCreateSlate에 위임 — progress 배너 + 스크롤 + 자동 미리보기 공통화
+ async function handleCreate(kw) {
+ if (creating) return;
+ setCreating(kw.id);
+ try {
+ await onCreateSlate?.({
+ keyword: kw.keyword,
+ category: kw.category,
+ keyword_id: kw.id,
+ });
+ } finally {
+ setCreating(null);
+ }
+ }
+
+ return (
+
+
트렌딩 키워드
+
+ {/* 카테고리 필터 */}
+
+ {CATEGORIES.map((c) => (
+
+ ))}
+
+
+ {/* progress 표시는 상단 ic-slate-progress 배너에서 일괄 처리 */}
+
+ {deduped.length === 0 ? (
+
키워드가 없습니다. 키워드 추출을 실행하세요.
+ ) : (
+ <>
+
+ {pageItems.map((kw) => (
+
+ {kw.keyword}
+
+ {kw.category} · {kw.articles_count ?? 0}건
+
+ {kw.score?.toFixed(1) ?? '-'}
+
+
+ ))}
+
+
+ {totalPages > 1 && (
+
+
+ {safePage + 1} / {totalPages}
+
+
+ )}
+ >
+ )}
+
+ );
+}
+
+export default KeywordsPanel;
diff --git a/src/pages/insta/components/TriggerPanel.jsx b/src/pages/insta/components/TriggerPanel.jsx
new file mode 100644
index 0000000..3cecf20
--- /dev/null
+++ b/src/pages/insta/components/TriggerPanel.jsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import usePollTask from '../hooks/usePollTask';
+import TaskStatusBox from './TaskStatusBox';
+import { instaCollectNews, instaExtractKeywords } from '../../../api';
+
+/* ══════════════════════ 트리거 패널 ══════════════════════════════════════ */
+function TriggerPanel() {
+ const collectPoll = usePollTask();
+ const keywordsPoll = usePollTask();
+
+ async function handleCollect() {
+ try {
+ const res = await instaCollectNews();
+ collectPoll.start(res.task_id);
+ } catch (e) {
+ alert('뉴스 수집 실패: ' + e.message);
+ }
+ }
+
+ async function handleKeywords() {
+ try {
+ const res = await instaExtractKeywords();
+ keywordsPoll.start(res.task_id);
+ } catch (e) {
+ alert('키워드 추출 실패: ' + e.message);
+ }
+ }
+
+ const collectBusy = !!collectPoll.taskId;
+ const kwBusy = !!keywordsPoll.taskId;
+
+ return (
+
+
트리거
+
+
+
+
+
+
+
+ );
+}
+
+export default TriggerPanel;