feat(insta): 카드 탭 트렌딩 키워드 중복 제거 + 10개씩 페이지네이션

KeywordsPanel이 전체 목록을 세로로 길게 표시하던 것을, 동일 keyword
중복 제거(최고 score 유지)·score 내림차순 후 페이지당 10개만 렌더하고
이전(←)/다음(→) 페이저로 탐색하도록 변경. 카테고리 변경 시 첫 페이지 리셋.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-23 03:03:36 +09:00
parent a846ab89e6
commit c998753eea
2 changed files with 69 additions and 18 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import PullToRefresh from '../../components/PullToRefresh';
import {
getInstaStatus,
@@ -521,11 +521,13 @@ function TriggerPanel() {
/* ══════════════════════ 키워드 목록 ══════════════════════════════════════ */
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;
@@ -533,6 +535,23 @@ function KeywordsPanel({ onCreateSlate }) {
}, [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) {
@@ -568,27 +587,47 @@ function KeywordsPanel({ onCreateSlate }) {
{/* progress 표시는 상단 ic-slate-progress 배너에서 일괄 처리 */}
{keywords.length === 0 ? (
{deduped.length === 0 ? (
<div className="ic-empty">키워드가 없습니다. 키워드 추출을 실행하세요.</div>
) : (
<div className="ic-keywords">
{keywords.map((kw) => (
<div key={kw.id} className="ic-keyword-row">
<span className="ic-keyword-row__kw">{kw.keyword}</span>
<span className="ic-keyword-row__meta">
{kw.category} · {kw.articles_count ?? 0}
</span>
<span className="ic-keyword-row__score">{kw.score?.toFixed(1) ?? '-'}</span>
<>
<div className="ic-keywords">
{pageItems.map((kw) => (
<div key={kw.id} className="ic-keyword-row">
<span className="ic-keyword-row__kw">{kw.keyword}</span>
<span className="ic-keyword-row__meta">
{kw.category} · {kw.articles_count ?? 0}
</span>
<span className="ic-keyword-row__score">{kw.score?.toFixed(1) ?? '-'}</span>
<button
className="ic-btn ic-btn--primary ic-btn--sm"
onClick={() => handleCreate(kw)}
disabled={!!creating}
>
{creating === kw.id ? <span className="ic-spinner" /> : '🎴'}
</button>
</div>
))}
</div>
{totalPages > 1 && (
<div className="ic-keywords__pager">
<button
className="ic-btn ic-btn--primary ic-btn--sm"
onClick={() => handleCreate(kw)}
disabled={!!creating}
>
{creating === kw.id ? <span className="ic-spinner" /> : '🎴'}
</button>
className="ic-pager-btn"
onClick={() => setPage((p) => Math.max(0, p - 1))}
disabled={safePage === 0}
aria-label="이전 키워드"
></button>
<span className="ic-pager-info">{safePage + 1} / {totalPages}</span>
<button
className="ic-pager-btn"
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
disabled={safePage >= totalPages - 1}
aria-label="다음 키워드"
></button>
</div>
))}
</div>
)}
</>
)}
</div>
);