153 lines
5.9 KiB
TypeScript
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>
|
|
);
|
|
}
|