diff --git a/src/modules/mileage/StatisticsView.tsx b/src/modules/mileage/StatisticsView.tsx index 1a1c178..d494ea8 100644 --- a/src/modules/mileage/StatisticsView.tsx +++ b/src/modules/mileage/StatisticsView.tsx @@ -1,3 +1,554 @@ +import { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import { + BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, + ResponsiveContainer, LineChart, Line, AreaChart, Area, + Cell, LabelList, +} from 'recharts'; +import { + Truck, ChevronDown, Maximize2, Minimize2, + Search, ArrowUpDown, X, +} from 'lucide-react'; +import type { TargetSummary, TargetVehicle, TrendPoint } from './types'; +import { fetchTargets, fetchTargetVehicles, fetchTrend } from './api'; + export default function StatisticsView() { - return
StatisticsView placeholder
; + const [targets, setTargets] = useState([]); + const [trendData, setTrendData] = useState([]); + const [targetVehiclesMap, setTargetVehiclesMap] = useState>({}); + const [selectedTargetId, setSelectedTargetId] = useState(null); + + const [chartType, setChartType] = useState<'bar' | 'line' | 'area'>('bar'); + const [isTableFullscreen, setIsTableFullscreen] = useState(false); + const [expandedModel, setExpandedModel] = useState(null); + const [viewAllTargetId, setViewAllTargetId] = useState(null); + const [viewAllTargetName, setViewAllTargetName] = useState(''); + const [viewAllSearch, setViewAllSearch] = useState(''); + const [viewAllSort, setViewAllSort] = useState<'asc' | 'desc'>('desc'); + + // Load targets on mount + useEffect(() => { + fetchTargets().then(data => { + setTargets(data); + if (data.length > 0 && !selectedTargetId) { + setSelectedTargetId(data[0].id); + } + }).catch(() => {}); + }, []); + + // Load trend when selectedTargetId changes + useEffect(() => { + if (selectedTargetId === null) return; + fetchTrend(selectedTargetId).then(setTrendData).catch(() => setTrendData([])); + }, [selectedTargetId]); + + return ( +
+ {/* Project Selector - Full width even in landscape */} +
+ {targets.map(target => ( + + ))} +
+ +
+ {/* Left Side: Trend Chart / Dashboard Sidebar */} +
+ {/* KPI Cards in Landscape */} +
+
+
今日总里程
+
+ {targets.reduce((sum, t) => sum + t.todayTotal, 0).toLocaleString()} + KM +
+
+
+
累计总里程
+
+ {targets.reduce((sum, t) => sum + t.cumulativeTotal, 0).toLocaleString()} + KM +
+
+
+
总考核车辆
+
+ {targets.reduce((sum, t) => sum + t.vehicleCount, 0)} + +
+
+
+
平均完成率
+
+ {targets.length > 0 ? (targets.reduce((sum, t) => sum + t.avgCompletion, 0) / targets.length).toFixed(1) : '0.0'} + % +
+
+
+ +
+
+
+
+

7天里程趋势

+
+
+ {(['bar', 'line', 'area'] as const).map(type => ( + + ))} +
+
+
+
+ + {chartType === 'bar' ? ( + + + val.split('-')[1]} /> + + + + + {trendData.map((_entry: TrendPoint, index: number) => ( + + ))} + + + ) : chartType === 'line' ? ( + + + val.split('-')[1]} /> + + + + + + + ) : ( + + + + + + + + + val.split('-')[1]} /> + + + + + + + )} + +
+
+
+
+ + {/* Right Side: Summary Section */} +
+
+
+
+

车型考核里程汇总

+
+ +
+ +
+ {targets.map((target, idx) => ( +
{ + const name = target.targetName; + setExpandedModel(expandedModel === name ? null : name); + if (!targetVehiclesMap[target.id]) { + fetchTargetVehicles(target.id).then(data => { + setTargetVehiclesMap(prev => ({ ...prev, [target.id]: data })); + }).catch(() => {}); + } + }} + > +
+
+
+ +
+
+
+ {target.targetName} + {target.vehicleCount}台 +
+
+
+ 完成率: + = 90 ? 'text-emerald-500' : 'text-blue-500'}`}>{target.avgCompletion}% +
+
+ 达标: + {target.yearQualifiedCount}台 +
+
+
+
+
+
+
+ {target.todayTotal.toLocaleString()} KM +
+
+ 累计: {target.cumulativeTotal.toLocaleString()} KM +
+
+ + + +
+
+ + + {expandedModel === target.targetName && ( + +
+
+

考核区间

+

{target.period}

+
+
+

总考核里程

+

{(target.totalMileagePerVehicle * target.vehicleCount).toLocaleString()} KM

+
+
+

年考核任务/辆

+

{target.annualMileagePerVehicle.toLocaleString()} KM

+
+
+

50%达标数

+

{target.halfQualifiedCount} 台

+
+
+

本年需完成

+

{target.currentYearTarget.toLocaleString()} KM

+
+
+

已完成(截止3.31)

+

{target.currentYearCompleted.toLocaleString()} KM

+
+
+

未完成总数

+

{target.remaining.toLocaleString()} KM

+
+
+

日均需完成

+

{target.dailyTarget} KM

+
+
+ 剩余考核天数 + {target.daysLeft} 天 +
+ + {/* Vehicle List Detail */} +
+
+ 车辆明细 (前5台) + +
+
+ {(targetVehiclesMap[target.id] || []).slice(0, 5).map(tv => ( +
+
+ {tv.plateNumber} + + 在线 + +
+
+ {tv.todayMileage} + KM +
+
+ ))} +
+
+
+
+ )} +
+
+ ))} +
+
+
+ + {/* Fullscreen Table Overlay */} + + {isTableFullscreen && ( + + {/* Sidebar with KPI Cards */} +
+
+
+
+

车型考核汇总

+
+ +
+ +
+
+
今日总里程
+
+ {targets.reduce((sum, t) => sum + t.todayTotal, 0).toLocaleString()} + KM +
+
+
+
累计总里程
+
+ {targets.reduce((sum, t) => sum + t.cumulativeTotal, 0).toLocaleString()} + KM +
+
+
+
总考核车辆
+
+ {targets.reduce((sum, t) => sum + t.vehicleCount, 0)} + +
+
+
+
平均完成率
+
+ {targets.length > 0 ? (targets.reduce((sum, t) => sum + t.avgCompletion, 0) / targets.length).toFixed(1) : '0.0'} + % +
+
+
+
+ +
+
+ 车型考核明细数据 + 最后更新: {new Date().toLocaleTimeString()} +
+ +
+ + + + + + + + + + + + + + + + + + + + + + {targets.map((target, idx) => ( + + + + + + + + + + + + + + + + + + ))} + +
车型车辆数总考核里程已行驶总里程总完成率考核区间年考核任务/辆达标车辆数50%达标数今日总里程本年需完成已完成(截止3.31)未完成总数剩余天数日均需完成
{target.targetName}{target.vehicleCount}{(target.totalMileagePerVehicle * target.vehicleCount).toLocaleString()}{target.cumulativeTotal.toLocaleString()} +
+
+
= 90 ? 'bg-emerald-500' : target.avgCompletion >= 80 ? 'bg-blue-500' : 'bg-amber-500'}`} + style={{ width: `${target.avgCompletion}%` }} + /> +
+ {target.avgCompletion}% +
+
{target.period}{target.annualMileagePerVehicle}{target.yearQualifiedCount}{target.halfQualifiedCount}{target.todayTotal.toLocaleString()}{target.currentYearTarget.toLocaleString()}{target.currentYearCompleted.toLocaleString()}{target.remaining.toLocaleString()}{target.daysLeft}{target.dailyTarget}
+
+
+
+ )} +
+ + {/* View All Vehicles Side Panel */} + + {viewAllTargetId !== null && ( + <> + setViewAllTargetId(null)} + className="fixed inset-0 z-[110] bg-slate-950/60 backdrop-blur-sm" + /> + +
+
+

{viewAllTargetName}

+

车辆实时明细清单

+
+ +
+ +
+
+ + setViewAllSearch(e.target.value)} + className="w-full pl-9 pr-4 py-2 bg-slate-50 border border-slate-100 rounded-xl text-xs font-bold focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all" + /> +
+
+ 排序方式: 今日里程 + +
+
+ +
+ {(viewAllTargetId !== null ? (targetVehiclesMap[viewAllTargetId] || []) : []).filter(tv => + tv.plateNumber.toLowerCase().includes(viewAllSearch.toLowerCase()) + ).sort((a, b) => { + const valA = a.todayMileage || 0; + const valB = b.todayMileage || 0; + return viewAllSort === 'desc' ? valB - valA : valA - valB; + }).map(tv => ( +
+
+
+ +
+
+
+ {tv.plateNumber} + + 在线 + +
+
 
+
+
+
+
{tv.todayMileage} KM
+
累计: {tv.totalMileage?.toLocaleString()}
+
+
+ ))} +
+ +
+ +
+
+ + )} +
+
+ ); }