From 67a269651e219262b1013c93938f664cc026b026 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Fri, 7 Aug 2026 17:37:05 +0800 Subject: [PATCH] feat: preserve mileage assessment years --- docs/bi-refactor-roadmap.md | 1 + src/modules/mileage/StatisticsView.tsx | 84 +++++++++++++++++++---- src/modules/mileage/drill-context.test.ts | 14 +++- src/modules/mileage/drill-context.ts | 8 +++ 4 files changed, 91 insertions(+), 16 deletions(-) diff --git a/docs/bi-refactor-roadmap.md b/docs/bi-refactor-roadmap.md index dc3a5ed..fcb4477 100644 --- a/docs/bi-refactor-roadmap.md +++ b/docs/bi-refactor-roadmap.md @@ -159,6 +159,7 @@ SUM(fee) / NULLIF(SUM(kwh), 0) ### 阶段 B:核心下钻闭环 - 已完成里程考核归因、车辆详情和每日汇报跳转。 +- 里程考核年度已纳入 `mileageYear` URL 状态;目标、部门/客户归因和车辆清单下钻继承同一年度,无效年度会按目标真实年度归一。 - 已完成氢能站点、客户下钻代码。 - 已完成氢能每日趋势到加氢订单的 URL 下钻、分页和结构化故障状态;待数据源恢复后验收真实明细。 - 已为电能总览与氢能每日趋势柱图统一 Enter/Space 键盘下钻;氢能有效日期柱提供可读名称、焦点和选中反馈。 diff --git a/src/modules/mileage/StatisticsView.tsx b/src/modules/mileage/StatisticsView.tsx index 0890645..1611749 100644 --- a/src/modules/mileage/StatisticsView.tsx +++ b/src/modules/mileage/StatisticsView.tsx @@ -49,6 +49,10 @@ function getTargetAssessment(target: TargetSummary, selectedYear?: number): Targ return target.yearlyAssessments.find(item => item.yearNumber === selectedYear) || target.yearlyAssessments[0]; } +function resolveAssessmentYear(target: TargetSummary, requestedYear?: number): number | undefined { + return getTargetAssessment(target, requestedYear)?.yearNumber; +} + function fmtDateLabel(date: string | null): string { if (!date) return ''; const [year, month, day] = date.split('-'); @@ -96,10 +100,12 @@ export default function StatisticsView() { const [viewAllLoading, setViewAllLoading] = useState(false); const [viewAllHistoricalVehicles, setViewAllHistoricalVehicles] = useState(null); const targetVehicleRequestsRef = useRef(new Set()); + const initialDrillContextRef = useRef(drillContext); const selectedTarget = targets.find(t => t.id === selectedTargetId); const selectedTargetVehicles = selectedTargetId === null ? [] : targetVehiclesMap[selectedTargetId] || []; const selectedAssessment = selectedTarget ? getTargetAssessment(selectedTarget, assessmentYearMap[selectedTarget.id]) : null; + const selectedAssessmentYear = selectedAssessment?.yearNumber; const selectedCompletion = selectedAssessment?.completionRate ?? selectedTarget?.avgCompletion ?? 0; const selectedRemaining = selectedAssessment?.remaining ?? 0; const selectedDaysLeft = selectedAssessment?.daysLeft ?? selectedTarget?.daysLeft ?? 0; @@ -136,6 +142,10 @@ export default function StatisticsView() { }, []); const selectTarget = useCallback((targetId: number) => { + const target = targets.find(item => item.id === targetId); + const assessmentYear = target + ? resolveAssessmentYear(target, assessmentYearMap[targetId]) + : undefined; setSelectedTargetId(targetId); setExpandedTargetId(targetId); setViewAllTargetId(null); @@ -143,9 +153,10 @@ export default function StatisticsView() { commitDrillContext({ level: 'overview', targetId, + assessmentYear, sourcePriority: ['instrument'], }, 'replace'); - }, [commitDrillContext]); + }, [assessmentYearMap, commitDrillContext, targets]); const openAttributionGroup = useCallback((value: string) => { if (selectedTargetId === null) return; @@ -157,11 +168,29 @@ export default function StatisticsView() { commitDrillContext({ level: 'breakdown', targetId: selectedTargetId, + assessmentYear: selectedAssessmentYear, dimension: attributionDimension, dimensionValue: value, sourcePriority: ['instrument'], }, 'push'); - }, [attributionDimension, commitDrillContext, selectedTarget?.targetName, selectedTargetId]); + }, [attributionDimension, commitDrillContext, selectedAssessmentYear, selectedTarget?.targetName, selectedTargetId]); + + const openTargetVehicles = useCallback((target: TargetSummary) => { + const assessmentYear = resolveAssessmentYear(target, assessmentYearMap[target.id]); + setViewAllTargetId(target.id); + setViewAllTargetName(target.targetName); + setViewAllSearch(''); + setViewAllDate(getDefaultDate()); + setViewAllHistoricalVehicles(null); + commitDrillContext({ + level: 'breakdown', + targetId: target.id, + assessmentYear, + dimension: 'assessment-target', + dimensionValue: target.targetName, + sourcePriority: ['instrument'], + }, 'push'); + }, [assessmentYearMap, commitDrillContext]); const closeVehiclePanel = useCallback(() => { setViewAllTargetId(null); @@ -170,10 +199,11 @@ export default function StatisticsView() { commitDrillContext({ level: 'overview', targetId: selectedTargetId || undefined, + assessmentYear: selectedAssessmentYear, sourcePriority: ['instrument'], }, 'replace'); } - }, [commitDrillContext, drillContext.level, selectedTargetId]); + }, [commitDrillContext, drillContext.level, selectedAssessmentYear, selectedTargetId]); const openVehicleDiagnostic = useCallback((vehicle: TargetVehicle) => { const url = buildMileageDrillUrl( @@ -181,6 +211,7 @@ export default function StatisticsView() { { level: 'vehicle', targetId: viewAllTargetId || selectedTargetId || undefined, + assessmentYear: selectedAssessmentYear, plate: vehicle.plateNumber, startDate: viewAllDate, endDate: viewAllDate, @@ -189,11 +220,12 @@ export default function StatisticsView() { ); window.history.pushState(null, '', url); window.dispatchEvent(new HashChangeEvent('hashchange')); - }, [selectedTargetId, viewAllDate, viewAllTargetId]); + }, [selectedAssessmentYear, selectedTargetId, viewAllDate, viewAllTargetId]); // Load targets on mount useEffect(() => { fetchTargets().then(data => { + const initialContext = initialDrillContextRef.current; const focused = data.find(item => item.targetName.includes('羚牛136')) || data[0]; const ordered = focused ? [focused, ...data.filter(item => item.id !== focused.id)] @@ -203,7 +235,20 @@ export default function StatisticsView() { if (initialTarget) { setSelectedTargetId(initialTarget.id); setExpandedTargetId(initialTarget.id); - setAssessmentYearMap(Object.fromEntries(ordered.map(item => [item.id, item.yearlyAssessments[0]?.yearNumber || 1]))); + const initialYears = Object.fromEntries(ordered.map(item => [ + item.id, + resolveAssessmentYear(item, item.id === initialContext.targetId ? initialContext.assessmentYear : undefined) || 1, + ])); + setAssessmentYearMap(initialYears); + + if (initialContext.targetId === initialTarget.id) { + const resolvedYear = initialYears[initialTarget.id]; + if (resolvedYear && resolvedYear !== initialContext.assessmentYear) { + const normalizedContext = { ...initialContext, assessmentYear: resolvedYear }; + window.history.replaceState(null, '', buildMileageDrillUrl(window.location, normalizedContext)); + setDrillContext(normalizedContext); + } + } } }).catch(() => {}); }, []); @@ -236,7 +281,14 @@ export default function StatisticsView() { const handlePopState = () => { const next = parseMileageDrillContext(window.location.search); setDrillContext(next); - if (next.targetId) setSelectedTargetId(next.targetId); + if (next.targetId) { + setSelectedTargetId(next.targetId); + const target = targets.find(item => item.id === next.targetId); + const resolvedYear = target ? resolveAssessmentYear(target, next.assessmentYear) : undefined; + if (resolvedYear) { + setAssessmentYearMap(prev => ({ ...prev, [next.targetId!]: resolvedYear })); + } + } if (next.level === 'breakdown' && next.targetId && next.dimensionValue) { setAttributionDimension(next.dimension === 'customer' ? 'customer' : 'department'); setViewAllTargetId(next.targetId); @@ -246,7 +298,7 @@ export default function StatisticsView() { }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); - }, []); + }, [targets]); const viewAllVehicles = useMemo(() => { const vehicles = viewAllHistoricalVehicles @@ -536,7 +588,13 @@ export default function StatisticsView() { onClick={(e) => e.stopPropagation()} onChange={(e) => { e.stopPropagation(); - setAssessmentYearMap(prev => ({ ...prev, [target.id]: Number(e.target.value) })); + const assessmentYear = Number(e.target.value); + setAssessmentYearMap(prev => ({ ...prev, [target.id]: assessmentYear })); + commitDrillContext({ + ...drillContext, + targetId: target.id, + assessmentYear, + }, 'replace'); }} className="bg-white border border-blue-100 rounded-lg px-2 py-1 text-[10px] font-bold text-blue-700 outline-none" > @@ -625,9 +683,7 @@ export default function StatisticsView() {