feat: preserve mileage assessment years

This commit is contained in:
kkfluous
2026-08-07 17:37:05 +08:00
parent 52d79ba260
commit 67a269651e
4 changed files with 91 additions and 16 deletions
+71 -13
View File
@@ -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<TargetVehicle[] | null>(null);
const targetVehicleRequestsRef = useRef(new Set<number>());
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() {
<button
onClick={(e) => {
e.stopPropagation();
setViewAllTargetId(target.id);
setViewAllTargetName(target.targetName);
setViewAllDate(getDefaultDate());
openTargetVehicles(target);
}}
className="text-[8px] text-blue-500 font-bold hover:underline"
>
@@ -803,8 +859,10 @@ export default function StatisticsView() {
<h3 className="text-lg font-black text-slate-900">
{viewAllTargetName || selectedTarget?.targetName || '考核车辆'}
</h3>
<p className="text-xs font-bold text-slate-400 uppercase tracking-widest mt-1"></p>
{selectedBreakdownValue && (
<p className="mt-1 text-xs font-bold uppercase tracking-widest text-slate-400">
{selectedAssessment ? `${selectedAssessment.label} · ` : ''}
</p>
{selectedBreakdownValue && drillContext.dimension !== 'assessment-target' && (
<p className="mt-1 text-[10px] font-bold text-blue-600">{selectedBreakdownValue}</p>
)}
</div>