refactor: persist mileage drill context in URL

This commit is contained in:
kkfluous
2026-08-07 14:00:43 +08:00
parent ce50265771
commit f24a18e459
3 changed files with 221 additions and 7 deletions
+57 -7
View File
@@ -18,6 +18,11 @@ import Blur from '../../components/Blur';
import PlateMultiSelect from './PlateMultiSelect';
import { exportMileageXlsx } from './xlsx-export';
import VehicleDetailModal from './VehicleDetailModal';
import {
buildMileageDrillUrl,
parseMileageDrillContext,
type MileageDrillContext,
} from './drill-context';
const HIGH_MILEAGE_ALERT_TARGETS = new Set([
'交投40辆4.5T普货',
@@ -444,6 +449,9 @@ const BatchMultiSelect = ({
};
export default function MonitoringView() {
const [drillContext, setDrillContext] = useState<MileageDrillContext>(() => (
parseMileageDrillContext(window.location.search)
));
const [searchTerm, setSearchTerm] = useState('');
const [filterDept, setFilterDept] = useState('All');
const [sortBy, setSortBy] = useState<'today' | 'total' | 'statisticTime'>('today');
@@ -456,7 +464,9 @@ export default function MonitoringView() {
const [fullscreenLoading, setFullscreenLoading] = useState(false);
// New filters from image
const [filterPlates, setFilterPlates] = useState<string[]>([]);
const [filterPlates, setFilterPlates] = useState<string[]>(() => (
drillContext.level === 'vehicle' && drillContext.plate ? [drillContext.plate] : []
));
const [filterCustomer, setFilterCustomer] = useState('All');
const [filterProject, setFilterProject] = useState('All');
const [filterEntity, setFilterEntity] = useState('All');
@@ -468,10 +478,9 @@ export default function MonitoringView() {
const [filterMileageRange, setFilterMileageRange] = useState({ min: '', max: '' });
const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' });
const [exporting, setExporting] = useState(false);
const [detailVehicle, setDetailVehicle] = useState<MonitoringVehicle | null>(null);
const [rangeStart, setRangeStart] = useState(defaultMileageDate);
const [rangeEnd, setRangeEnd] = useState(defaultMileageDate);
const [sourcePriority, setSourcePriority] = useState<MileageSourceGroup[]>(['instrument']);
const [rangeStart, setRangeStart] = useState(() => drillContext.startDate || defaultMileageDate());
const [rangeEnd, setRangeEnd] = useState(() => drillContext.endDate || defaultMileageDate());
const [sourcePriority, setSourcePriority] = useState<MileageSourceGroup[]>(drillContext.sourcePriority);
const [vehicles, setVehicles] = useState<MonitoringVehicle[]>([]);
const [stats, setStats] = useState<MonitoringStats>({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 });
@@ -488,12 +497,53 @@ export default function MonitoringView() {
const [showBackToTop, setShowBackToTop] = useState(false);
const [relativeNow, setRelativeNow] = useState(() => Date.now());
const PAGE_SIZE = 50;
const detailVehicle = drillContext.level === 'vehicle' && drillContext.plate
? vehicles.find(vehicle => vehicle.plate === drillContext.plate) || null
: null;
const commitDrillContext = useCallback((next: MileageDrillContext, mode: 'push' | 'replace') => {
const url = buildMileageDrillUrl(window.location, next);
window.history[mode === 'push' ? 'pushState' : 'replaceState'](null, '', url);
setDrillContext(next);
}, []);
const openVehicleDetail = useCallback((vehicle: MonitoringVehicle) => {
commitDrillContext({
level: 'vehicle',
plate: vehicle.plate,
startDate: effectiveRange.start,
endDate: effectiveRange.end,
sourcePriority,
}, 'push');
}, [commitDrillContext, effectiveRange.end, effectiveRange.start, sourcePriority]);
const closeVehicleDetail = useCallback(() => {
commitDrillContext({
level: 'overview',
startDate: effectiveRange.start,
endDate: effectiveRange.end,
sourcePriority,
}, 'replace');
}, [commitDrillContext, effectiveRange.end, effectiveRange.start, sourcePriority]);
useEffect(() => {
const timer = setInterval(() => setRelativeNow(Date.now()), 5000);
return () => clearInterval(timer);
}, []);
useEffect(() => {
const handlePopState = () => {
const next = parseMileageDrillContext(window.location.search);
setDrillContext(next);
if (next.startDate) setRangeStart(next.startDate);
if (next.endDate) setRangeEnd(next.endDate);
setSourcePriority(next.sourcePriority);
if (next.level === 'vehicle' && next.plate) setFilterPlates([next.plate]);
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);
const departments = filterOptions.departments;
const plateNumbers = filterOptions.plates;
const rangeLabel = normalizeRangeLabel(effectiveRange.start, effectiveRange.end);
@@ -1494,7 +1544,7 @@ export default function MonitoringView() {
animate={{ opacity: 1 }}
key={v.plate}
className="grid cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-xl border border-slate-200 bg-white px-3 py-2.5 shadow-sm transition-all hover:border-blue-200 hover:shadow-md active:bg-slate-50 md:grid-cols-[minmax(190px,1.1fr)_minmax(160px,1fr)_130px_130px] md:px-4"
onClick={() => setDetailVehicle(v)}
onClick={() => openVehicleDetail(v)}
>
<div className="flex min-w-0 items-center gap-3 overflow-hidden">
<div className="relative flex-shrink-0">
@@ -1588,7 +1638,7 @@ export default function MonitoringView() {
<VehicleDetailModal
vehicle={detailVehicle}
onClose={() => setDetailVehicle(null)}
onClose={closeVehicleDetail}
sourcePriority={sourcePriority}
/>