import { useEffect, useMemo, useState } from 'react'; import { BatteryCharging, CalendarDays, ChevronRight, Plug, TrendingUp, Wallet } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import TrendBadge from './TrendBadge'; import { fetchElectricMonthly } from './api'; import type { CustomerType, DateQuickPick, ElectricMonthGroup } from './types'; import RotatingFooterHint from '../../components/RotatingFooterHint'; import { EmptyState, ErrorState, LoadingState, MetricTile } from '../../components/ui/surface'; import DailyRangeControls from './daily-range/DailyRangeControls'; import { getQuickRange, getRangeModeLabel, normalizeRange, type RangeMode, } from './daily-range/model'; import { summarizeElectricMonths } from './electric-daily/model'; export default function ElectricDaily() { const [customer, setCustomer] = useState('lingniu'); const [pick, setPick] = useState('last15'); const [dateRange, setDateRange] = useState(() => getQuickRange('last15')); const [months, setMonths] = useState(null); const [openMonths, setOpenMonths] = useState>(new Set()); const [error, setError] = useState(null); const effectiveRange = useMemo(() => normalizeRange(dateRange.start, dateRange.end), [dateRange.start, dateRange.end]); useEffect(() => { let cancelled = false; setError(null); const query = pick === 'custom' ? { startDate: effectiveRange.start, endDate: effectiveRange.end } : { range: pick }; fetchElectricMonthly(customer, query) .then(m => { if (cancelled) return; setMonths(m); // 默认展开最新一个月 if (m.length > 0) setOpenMonths(new Set([m[0].month])); }) .catch(e => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); }); return () => { cancelled = true; }; }, [customer, pick, effectiveRange.start, effectiveRange.end]); const toggleMonth = (m: string) => setOpenMonths(prev => { const next = new Set(prev); next.has(m) ? next.delete(m) : next.add(m); return next; }); const { totalKwh, totalFee, activeDays, abnormalDays, avgKwh, avgPrice, hasFeeDetail, } = useMemo(() => summarizeElectricMonths(months), [months]); const scopeLabel = getRangeModeLabel(pick); const rangeText = `${effectiveRange.start} 至 ${effectiveRange.end}`; const showExternalEmpty = customer === 'external' && months !== null && totalKwh === 0; const applyQuickPick = (nextPick: DateQuickPick) => { setPick(nextPick); setDateRange(getQuickRange(nextPick)); }; const updateDateRange = (field: 'start' | 'end', value: string) => { if (!value) return; setPick('custom'); setDateRange(prev => ({ ...prev, [field]: value })); }; return (
setPick('custom')} onDateRangeChange={updateDateRange} onCustomerChange={setCustomer} />
0 ? 'rose' : 'slate'} />
{/* 外部车辆 数据未就绪 */} {showExternalEmpty && (
外部车辆 · 数据未就绪
新系统的外部车辆充电数据还在准备中
上线后此处将展示完整明细
)} {/* 月份分组表 */} {!showExternalEmpty && (
月份 / 日期 充电量 (度) 环比
{error ? (
) : months === null ? (
) : months.length === 0 ? (
) : months.map(m => { const open = openMonths.has(m.month); return (
{open && ( {m.rows.map(d => { const isAbnormal = Math.abs(d.chainPct) >= 0.3; const abnormalBg = isAbnormal ? d.chainPct > 0 ? 'bg-emerald-50/40' : 'bg-red-50/40' : 'bg-slate-50/50'; return (
{d.date.slice(5)} {d.kwh.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}
); })}
)}
); })}
)}
); }