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;