/** * AnalysisTab — 워크스페이스 분석 탭 (리포트+플랜 통합). * 각 분석 run = 1:1 plan. 카드 본문 클릭은 리포트, 옆 버튼은 플랜. * * 리스트는 무한스크롤 — 서버가 전체 배열을 던지면 클라이언트에서 10개씩 잘라 노출. */ import { useMemo } from 'react'; import { Plus } from 'lucide-react'; import { Link } from 'react-router'; import { SectionWrapper } from '@/features/report/components/ui/SectionWrapper'; import { EmptyState } from '@/shared/ui/empty-state'; import { useInfiniteList } from '@/shared/hooks/useInfiniteList'; import { AnalysisCard } from '../cards/AnalysisCard'; import type { WorkspacePlan, WorkspaceRun } from '../../types/workspace'; interface AnalysisTabProps { clinicId: string; runs: WorkspaceRun[]; plans: WorkspacePlan[]; } const PAGE_SIZE = 10; export function AnalysisTab({ clinicId, runs, plans }: AnalysisTabProps) { const planByRun = useMemo(() => { const map: Record = {}; const priority = { active: 3, draft: 2, archived: 1 } as const; for (const plan of plans) { const prev = map[plan.baseRunId]; if (!prev || priority[plan.status] > priority[prev.status]) { map[plan.baseRunId] = plan; } } return map; }, [plans]); // plan 이 없는 run 도 노출 — 백엔드 plans 엔드포인트가 채워지면 자연스럽게 매칭됨. const rows = useMemo( () => [...runs] .sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()) .map((run) => ({ run, plan: planByRun[run.runId] })), [runs, planByRun], ); const { visible, hasMore, sentinelRef } = useInfiniteList(rows, { initialSize: PAGE_SIZE, pageSize: PAGE_SIZE, }); return (
새 분석 시작
{rows.length === 0 ? ( ) : ( <>
{visible.map(({ run, plan }, i) => ( ))}
{hasMore && (
더 불러오는 중...
)} )} ); }