import { useCallback, useEffect, useMemo, useState } from 'react' import { fetchWithRefresh } from '../../lib/api' import { crmUrl } from '../../lib/endpoints' import EfficiencyScatter from './EfficiencyScatter' import DistanceStrip from './DistanceStrip' import DriverHistoryLine from './DriverHistoryLine' import './DriverDailyStat.css' const POLL_MS = 5 * 60 * 1000 // 5분 const localToday = () => { const d = new Date() return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` } const asList = (json) => (Array.isArray(json) ? json : (json?.data ?? [])) const asObject = (json) => (json?.data ?? json ?? null) export default function DriverDailyStatPage() { const [date, setDate] = useState(localToday()) const [driverId, setDriverId] = useState('') // '' = 선택 안 함 const [rows, setRows] = useState([]) const [history, setHistory] = useState(null) const [ovLoading, setOvLoading] = useState(true) const [histLoading, setHistLoading] = useState(false) const [error, setError] = useState(null) const [updatedAt, setUpdatedAt] = useState(null) // 개요(전체) — 날짜에만 의존. 드라이버 선택은 여기 안 건드림. const loadOverview = useCallback(async (signal) => { try { const res = await fetchWithRefresh(crmUrl(`/driver-daily-stat?date=${date}`)) if (!res.ok) throw new Error(`개요 요청 실패 (${res.status})`) const list = asList(await res.json()) if (signal?.aborted) return setRows(list) setUpdatedAt(new Date()) setError(null) } catch (e) { if (!signal?.aborted) setError(e.message) } finally { if (!signal?.aborted) setOvLoading(false) } }, [date]) useEffect(() => { const ctrl = new AbortController() setOvLoading(true) loadOverview(ctrl.signal) const id = setInterval(() => loadOverview(ctrl.signal), POLL_MS) return () => { ctrl.abort(); clearInterval(id) } }, [loadOverview]) // 드릴다운(단일 드라이버) — 선택됐을 때만, 아래에 따로. const loadHistory = useCallback(async (signal) => { if (!driverId) { setHistory(null); return } try { setHistLoading(true) const res = await fetchWithRefresh(crmUrl(`/driver-daily-stat/history?driverId=${driverId}&date=${date}`)) if (!res.ok) throw new Error(`이력 요청 실패 (${res.status})`) const h = asObject(await res.json()) if (signal?.aborted) return setHistory(h) } catch (e) { if (!signal?.aborted) setError(e.message) } finally { if (!signal?.aborted) setHistLoading(false) } }, [date, driverId]) useEffect(() => { const ctrl = new AbortController() loadHistory(ctrl.signal) const id = setInterval(() => loadHistory(ctrl.signal), POLL_MS) return () => { ctrl.abort(); clearInterval(id) } }, [loadHistory]) const drivers = useMemo(() => { const seen = new Map() for (const r of rows) { if (r.ddsDriverId != null && !seen.has(r.ddsDriverId)) { seen.set(r.ddsDriverId, r.ddsMainDriver ?? `드라이버 ${r.ddsDriverId}`) } } return [...seen.entries()].map(([id, name]) => ({ id, name })) }, [rows]) const selectedName = drivers.find((d) => String(d.id) === String(driverId))?.name // 픽업 있음(산점도) / 픽업 0(칩) 분리 const activeRows = useMemo(() => rows.filter((r) => Number(r.ddsTotalQty ?? 0) > 0), [rows]) const zeroRows = useMemo(() => rows.filter((r) => Number(r.ddsTotalQty ?? 0) <= 0), [rows]) // 전체 요약 (선택과 무관, 날짜 기준 전체 합) const summary = useMemo(() => { let plannedQty = 0, totalQty = 0, completed = 0, target = 0 for (const r of rows) { plannedQty += Number(r.ddsPlannedQty ?? 0) totalQty += Number(r.ddsTotalQty ?? 0) completed += Number(r.ddsCompletedCount ?? 0) target += Number(r.ddsPlannedCount ?? 0) } const rate = plannedQty > 0 ? Math.round((totalQty / plannedQty) * 100) : null return { plannedQty: Math.round(plannedQty), totalQty: Math.round(totalQty), completed, target, rate, } }, [rows]) return (