import { useEffect, useMemo, useState } from 'react' import { getDashboard, type DashboardData, type DashboardOrgRow, type DistributionItem } from '../api/client' type Mode = 'review' | 'process' interface Props { mode: Mode onOpenOrg: (orgId: number) => void } const format = (value: number) => value.toLocaleString('ko-KR') const percent = (done: number, total: number) => total ? Math.round((done / total) * 1000) / 10 : 0 function ProgressRing({ done, total, color }: { done: number; total: number; color: string }) { const rate = percent(done, total) return (
{rate}% {format(done)} / {format(total)}
) } const CHART_COLORS = ['#2f7ed8', '#19a974', '#efa500', '#0b8f35', '#8b5cf6', '#64748b'] function BarDistribution({ title, items }: { title: string; items: DistributionItem[] }) { const max = Math.max(...items.map((item) => item.count), 1) return

{title}

{items.length ? items.map((item) =>
{item.label}
{format(item.count)}
) :

집계 데이터가 없습니다.

}
} function DonutDistribution({ title, items }: { title: string; items: DistributionItem[] }) { const total = items.reduce((sum, item) => sum + item.count, 0) let cursor = 0 const stops = items.map((item, index) => { const start = cursor; cursor += total ? item.count / total * 100 : 0; return `${CHART_COLORS[index % CHART_COLORS.length]} ${start}% ${cursor}%` }).join(', ') return

{title}

합계{format(total)}
{items.map((item,index)=>
{item.label}{format(item.count)}{total ? Math.round(item.count/total*100) : 0}%
)}
} export default function RightsDashboard({ mode, onOpenOrg }: Props) { const [data, setData] = useState(null) const [error, setError] = useState(null) const [keyword, setKeyword] = useState('') const [status, setStatus] = useState('all') useEffect(() => { let active = true setData(null) setError(null) void getDashboard() .then((result) => active && setData(result)) .catch((cause: Error) => active && setError(cause.message)) return () => { active = false } }, [mode]) const rows = useMemo(() => { if (!data) return [] return data.orgs.filter((row) => { const total = mode === 'review' ? row.reviewTotal : row.processTotal const done = mode === 'review' ? row.reviewDone : row.processDone const matchesKeyword = row.orgName.includes(keyword.trim()) || row.orgNo.includes(keyword.trim()) const matchesStatus = status === 'all' || (status === 'complete' && total > 0 && done === total) || (status === 'progress' && done > 0 && done < total) || (status === 'waiting' && total > 0 && done === 0) return matchesKeyword && matchesStatus }) }, [data, keyword, mode, status]) if (error) return
{error}
if (!data) return

권리관리 현황을 불러오는 중…

const isReview = mode === 'review' const title = isReview ? '권리확인' : '권리처리' const total = isReview ? data.summary.reviewTotal : data.summary.processTotal const done = isReview ? data.summary.reviewDone : data.summary.processDone const remaining = Math.max(total - done, 0) const rate = percent(done, total) const accent = isReview ? '#ea8a00' : '#078d2a' const orgsWithWork = data.orgs.filter((row) => (isReview ? row.reviewTotal : row.processTotal) > 0).length const rights = data.rights return (

{title}

{isReview ? '검토 대상 게시물의 권리 확인 및 처리 방향 결정' : '권리확인 완료 건의 처리 진행 상황 관리'}

{[[`${rate}%`, `${title} 진척률`], [format(done), `${title} 완료`], [format(total), '전체 대상'], [format(remaining), '잔여 건수']].map(([value, label]) => (
{value} {label}
))}

전체 진행률

{rate}%

잔여 {format(remaining)}건

대상 기관

{orgsWithWork}

{title} 자료가 등록된 기관

진행 기관

{data.orgs.filter((row) => { const t=isReview?row.reviewTotal:row.processTotal; const d=isReview?row.reviewDone:row.processDone; return d>0&&d

일부 처리가 완료된 기관

{rights && (isReview ? (
) : (
))}

기관별 {title} 현황

기관을 선택하면 상세 {title} 화면으로 이동합니다.

setKeyword(e.target.value)} placeholder="기관명 검색…" className="h-9 w-44 rounded-md border border-gray-300 px-3 text-xs focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-100" />
{rows.map((row: DashboardOrgRow) => { const rowTotal=isReview?row.reviewTotal:row.processTotal; const rowDone=isReview?row.reviewDone:row.processDone; const rowRate=percent(rowDone,rowTotal); return ( onOpenOrg(row.id)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpenOrg(row.id) } }}> ) })} {!rows.length && }
기관명대상완료잔여진척률
{row.orgNo}{row.orgName}{format(rowTotal)}{format(rowDone)}{format(Math.max(rowTotal-rowDone,0))}
{rowRate}%
조건에 맞는 기관이 없습니다.
) }