import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Card, CardContent } from '@shared/components/ui/card'; import { Badge } from '@shared/components/ui/badge'; import { DataTable, type DataColumn } from '@shared/components/common/DataTable'; import { BarChart3, Download } from 'lucide-react'; import { BarChart, AreaChart } from '@lib/charts'; import { getKpiMetrics, getMonthlyStats, toMonthlyTrend, toViolationTypes, type PredictionKpi, type PredictionStatsMonthly, } from '@/services/kpi'; import type { MonthlyTrend, ViolationType } from '@data/mock/kpi'; import { toDateParam } from '@shared/utils/dateFormat'; import { getViolationColor, getViolationLabel } from '@shared/constants/violationTypes'; import { useSettingsStore } from '@stores/settingsStore'; /* SFR-13: 통계·지표·성과 분석 */ interface KpiRow { id: string; name: string; target: string; current: string; status: string; [key: string]: unknown; } const kpiCols: DataColumn[] = [ { key: 'id', label: 'ID', width: '70px', render: (v) => ( {v as string} ), }, { key: 'name', label: '지표명', sortable: true, render: (v) => ( {v as string} ), }, { key: 'target', label: '목표', width: '80px', align: 'center' }, { key: 'current', label: '현재', width: '80px', align: 'center', render: (v) => ( {v as string} ), }, { key: 'status', label: '상태', width: '60px', align: 'center', render: (v) => ( {v as string} ), }, ]; export function Statistics() { const { t } = useTranslation('statistics'); const { t: tc } = useTranslation('common'); const lang = useSettingsStore((s) => s.language); const [monthly, setMonthly] = useState([]); const [violationTypes, setViolationTypes] = useState([]); const [kpiMetrics, setKpiMetrics] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; async function loadStats() { setLoading(true); setError(null); try { const now = new Date(); const from = new Date(now.getFullYear(), now.getMonth() - 6, 1); const [data, kpiData] = await Promise.all([ getMonthlyStats(toDateParam(from), toDateParam(now)), getKpiMetrics().catch(() => [] as PredictionKpi[]), ]); if (cancelled) return; setMonthly(data.map(toMonthlyTrend)); setViolationTypes(toViolationTypes(data)); setKpiMetrics(kpiData); } catch (err) { if (!cancelled) { setError( err instanceof Error ? err.message : '통계 데이터 로드 실패', ); } } finally { if (!cancelled) setLoading(false); } } loadStats(); return () => { cancelled = true; }; }, []); const MONTHLY = monthly.map((m) => ({ m: m.month, enforce: m.enforce, detect: m.detect, accuracy: m.accuracy, })); const BY_TYPE = violationTypes; const KPI_DATA: KpiRow[] = kpiMetrics.map((k, i) => { const trendLabel = k.trend === 'up' ? '상승' : k.trend === 'down' ? '하락' : k.trend === 'flat' ? '유지' : '-'; const deltaLabel = k.deltaPct != null ? ` (${k.deltaPct > 0 ? '+' : ''}${k.deltaPct}%)` : ''; return { id: `KPI-${String(i + 1).padStart(2, '0')}`, name: k.kpiLabel, target: '-', current: String(k.value), status: `${trendLabel}${deltaLabel}`, }; }); return (

{t('statistics.title')}

{t('statistics.desc')}

{loading && (
데이터를 불러오는 중...
)} {error && (
{error}
)} {!loading && !error && ( <>
월별 단속·탐지 추이
AI 정확도 추이
위반 유형별 분포
{BY_TYPE.map((item) => { const color = getViolationColor(item.type); const label = getViolationLabel(item.type, tc, lang); return (
{item.count}
{label}
{item.pct}%
); })}
)}
); }