Files
ln-bi/src/modules/mileage/StatisticsView.tsx
T

153 lines
5.9 KiB
TypeScript

import { useEffect, useState } from 'react';
import type { TargetSummary, TargetVehicle, TrendPoint } from './types';
import { fetchTargets, fetchTargetVehicles, fetchTrend } from './api';
import AllVehiclesPanel from './statistics/AllVehiclesPanel';
import FullscreenTargetTable from './statistics/FullscreenTargetTable';
import StatisticsOverview from './statistics/StatisticsOverview';
import TargetDetailPanel from './statistics/TargetDetailPanel';
import TrendPanel from './statistics/TrendPanel';
import {
deriveSelectedTargetMetrics,
getCurrentDateLabel,
getDefaultDate,
initialAssessmentYears,
orderTargets,
} from './statistics/model';
export default function StatisticsView() {
const currentDateLabel = getCurrentDateLabel();
const [targets, setTargets] = useState<TargetSummary[]>([]);
const [trendData, setTrendData] = useState<TrendPoint[]>([]);
const [targetVehiclesMap, setTargetVehiclesMap] = useState<Record<number, TargetVehicle[]>>({});
const [selectedTargetId, setSelectedTargetId] = useState<number | null>(null);
const [isTableFullscreen, setIsTableFullscreen] = useState(false);
const [expandedTargetId, setExpandedTargetId] = useState<number | null>(null);
const [assessmentYearMap, setAssessmentYearMap] = useState<Record<number, number>>({});
const [viewAllTargetId, setViewAllTargetId] = useState<number | null>(null);
const [viewAllTargetName, setViewAllTargetName] = useState<string>('');
const [viewAllDate, setViewAllDate] = useState(getDefaultDate);
const [viewAllLoading, setViewAllLoading] = useState(false);
const selectedTarget = targets.find(target => target.id === selectedTargetId);
const selectedMetrics = deriveSelectedTargetMetrics(
selectedTarget,
selectedTarget ? assessmentYearMap[selectedTarget.id] : undefined,
trendData,
);
// Load targets on mount
useEffect(() => {
fetchTargets().then(data => {
const focused = data.find(item => item.targetName.includes('羚牛136')) || data[0];
const ordered = orderTargets(data);
setTargets(ordered);
if (ordered.length > 0 && !selectedTargetId) {
setSelectedTargetId(focused.id);
setExpandedTargetId(focused.id);
setAssessmentYearMap(initialAssessmentYears(ordered));
fetchTargetVehicles(focused.id).then(vehicles => {
setTargetVehiclesMap(previous => ({ ...previous, [focused.id]: vehicles }));
}).catch(() => {});
}
}).catch(() => {});
}, []);
// Load trend when selectedTargetId changes
useEffect(() => {
if (selectedTargetId === null) return;
setExpandedTargetId(selectedTargetId);
if (!targetVehiclesMap[selectedTargetId]) {
fetchTargetVehicles(selectedTargetId).then(vehicles => {
setTargetVehiclesMap(previous => ({ ...previous, [selectedTargetId]: vehicles }));
}).catch(() => {});
}
fetchTrend(selectedTargetId).then(setTrendData).catch(() => setTrendData([]));
}, [selectedTargetId]);
// Re-fetch target vehicles when viewAllDate changes
useEffect(() => {
if (viewAllTargetId === null) return;
setViewAllLoading(true);
fetchTargetVehicles(viewAllTargetId, viewAllDate).then(data => {
setTargetVehiclesMap(previous => ({ ...previous, [viewAllTargetId]: data }));
}).catch(() => {}).finally(() => setViewAllLoading(false));
}, [viewAllTargetId, viewAllDate]);
function selectTarget(targetId: number) {
setSelectedTargetId(targetId);
setExpandedTargetId(targetId);
}
function expandTarget(targetId: number) {
setExpandedTargetId(targetId);
if (!targetVehiclesMap[targetId]) {
fetchTargetVehicles(targetId).then(data => {
setTargetVehiclesMap(previous => ({ ...previous, [targetId]: data }));
}).catch(() => {});
}
}
function openAllVehicles(target: TargetSummary) {
setViewAllTargetId(target.id);
setViewAllTargetName(target.targetName);
setViewAllDate(getDefaultDate());
}
function refreshTargets() {
fetchTargets().then(data => {
setTargets(data);
}).catch(() => {});
}
return (
<div className="space-y-2 pb-2 landscape:pb-4 landscape:h-full landscape:overflow-hidden landscape:flex landscape:flex-col flex-none landscape:flex-1 [overflow-anchor:none]" style={{ overflowX: 'clip' }}>
<StatisticsOverview
targets={targets}
selectedTargetId={selectedTargetId}
selectedTarget={selectedTarget}
metrics={selectedMetrics}
onSelectTarget={selectTarget}
/>
<div className="flex flex-col landscape:flex-row gap-4 flex-1 landscape:overflow-hidden">
<TrendPanel
selectedTarget={selectedTarget}
selectedCompletion={selectedMetrics.completion}
trendData={trendData}
/>
<TargetDetailPanel
target={selectedTarget}
expandedTargetId={expandedTargetId}
selectedYear={selectedTarget ? assessmentYearMap[selectedTarget.id] : undefined}
vehicles={selectedTarget ? targetVehiclesMap[selectedTarget.id] || [] : []}
currentDateLabel={currentDateLabel}
onOpenFullscreen={() => setIsTableFullscreen(true)}
onExpandTarget={expandTarget}
onAssessmentYearChange={(targetId, year) => {
setAssessmentYearMap(previous => ({ ...previous, [targetId]: year }));
}}
onViewAll={openAllVehicles}
/>
</div>
<FullscreenTargetTable
isOpen={isTableFullscreen}
targets={targets}
assessmentYearMap={assessmentYearMap}
onRefresh={refreshTargets}
onClose={() => setIsTableFullscreen(false)}
/>
<AllVehiclesPanel
targetId={viewAllTargetId}
targetName={viewAllTargetName}
vehicles={viewAllTargetId !== null ? targetVehiclesMap[viewAllTargetId] || [] : []}
date={viewAllDate}
loading={viewAllLoading}
onDateChange={setViewAllDate}
onClose={() => setViewAllTargetId(null)}
/>
</div>
);
}