feat: improve asset inventory statistics and responsive UX (v1.1.16)
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import React from 'react';
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { useDialogFocus } from './useDialogFocus';
|
||||
import { VehicleMobileCards } from './VehicleMobileCards';
|
||||
import { AbnormalVehicleDetails, PendingVehicleDetails } from './AbnormalVehicleDetails';
|
||||
import { ChevronDown, Filter, Loader2, PlusCircle, Truck } from 'lucide-react';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import Blur from '../../../components/Blur';
|
||||
@@ -19,6 +22,8 @@ interface VehicleDetailModalProps {
|
||||
brands: string[];
|
||||
locations: string[];
|
||||
loading: boolean;
|
||||
error?: string | null;
|
||||
onRetry?: () => void;
|
||||
weeklyDetails: WeeklyDetailItem[];
|
||||
filteredWeeklyDetails: WeeklyDetailItem[];
|
||||
filteredVehicles: VehicleListItem[];
|
||||
@@ -35,24 +40,30 @@ export function VehicleDetailModal({
|
||||
brands: uniqueModalBrands,
|
||||
locations: uniqueModalLocations,
|
||||
loading: modalLoading,
|
||||
error,
|
||||
onRetry,
|
||||
weeklyDetails: modalWeeklyDetail,
|
||||
filteredWeeklyDetails: filteredModalWeeklyDetail,
|
||||
filteredVehicles: filteredModalVehicles,
|
||||
}: VehicleDetailModalProps) {
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
const close = useCallback(() => setShowPlateNumbers(null), [setShowPlateNumbers]);
|
||||
useDialogFocus(Boolean(showPlateNumbers), dialogRef, close);
|
||||
return (
|
||||
<>
|
||||
{/* Vehicle Detail Modal */}
|
||||
<AnimatePresence>
|
||||
{showPlateNumbers && (
|
||||
<div className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div className="fixed inset-0 bg-slate-900/40 backdrop-blur-sm z-[80] flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
ref={dialogRef} role="dialog" aria-modal="true" aria-labelledby="asset-vehicle-title" tabIndex={-1}
|
||||
initial={{ scale: 0.9, opacity: 0, y: 20 }}
|
||||
animate={{ scale: 1, opacity: 1, y: 0 }}
|
||||
className={`bg-white rounded-xl shadow-2xl w-full max-w-[95vw] ${showPlateNumbers.source === 'customer' ? 'lg:max-w-6xl' : 'lg:max-w-4xl'} overflow-hidden flex flex-col max-h-[85vh] sm:max-h-[90vh] min-h-[40vh]`}
|
||||
className={`bg-white rounded-xl shadow-2xl w-full max-w-[95vw] ${showPlateNumbers.source === 'customer' ? 'lg:max-w-6xl' : 'lg:max-w-4xl'} overflow-hidden flex flex-col max-h-[90dvh] sm:max-h-[90vh] min-h-[40vh]`}
|
||||
>
|
||||
<div className="p-4 border-b border-gray-100 flex justify-between items-center bg-slate-800 text-white shrink-0">
|
||||
<div>
|
||||
<h3 className="font-bold text-base flex items-center gap-2">
|
||||
<h3 id="asset-vehicle-title" className="font-bold text-base flex items-center gap-2">
|
||||
<Truck size={18} className="text-blue-400" />
|
||||
{showPlateNumbers.title || (
|
||||
(showPlateNumbers.manager ? `${showPlateNumbers.manager} 的${showPlateNumbers.type || ''}车辆` :
|
||||
@@ -67,11 +78,11 @@ export function VehicleDetailModal({
|
||||
showPlateNumbers.category === 'Returned' ? '已还车' :
|
||||
showPlateNumbers.category === 'Replaced' ? '已替换' :
|
||||
showPlateNumbers.category === 'Abnormal' ? '异动' :
|
||||
showPlateNumbers.category === 'Inventory' ? `${showPlateNumbers.location}库存` :
|
||||
showPlateNumbers.category === 'Inventory' ? `${showPlateNumbers.location === 'All' ? '总' : showPlateNumbers.location}库存` :
|
||||
showPlateNumbers.category === 'Operating' ? '正在运营' : '全部状态'}
|
||||
</p>
|
||||
</div>
|
||||
<button onClick={() => setShowPlateNumbers(null)} className="hover:bg-white/10 p-2 rounded-full transition-colors">
|
||||
<button aria-label="关闭车辆明细" onClick={close} className="hover:bg-white/10 p-2 rounded-full transition-colors">
|
||||
<PlusCircle className="rotate-45" size={24} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -90,7 +101,7 @@ export function VehicleDetailModal({
|
||||
{/* Quick Search always visible when collapsed */}
|
||||
{!isModalFilterExpanded && (
|
||||
<div className="w-40 sm:w-64" onClick={(e) => e.stopPropagation()}>
|
||||
<SearchSelect value={modalFilters.plateNumber} onChange={(v) => setModalFilters({...modalFilters, plateNumber: v})} options={uniqueModalPlates} placeholder="快速搜索车牌..." className="text-[11px] py-1 px-2" />
|
||||
<SearchSelect value={modalFilters.plateNumber} onChange={(v) => setModalFilters({...modalFilters, plateNumber: v})} options={uniqueModalPlates} placeholder="搜索车牌 / VIN" className="text-[11px] py-1 px-2" />
|
||||
</div>
|
||||
)}
|
||||
<motion.div
|
||||
@@ -151,10 +162,16 @@ export function VehicleDetailModal({
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto p-0 sm:p-4 bg-gray-50 min-h-0 overscroll-contain">
|
||||
{modalLoading ? (
|
||||
{error ? (
|
||||
<div role="alert" className="p-10 text-center text-sm text-slate-600"><p>{error}</p><button type="button" onClick={onRetry} className="mt-4 rounded-xl bg-blue-600 px-5 py-2 text-white">重新加载</button></div>
|
||||
) : modalLoading ? (
|
||||
<div className="flex items-center justify-center py-16">
|
||||
<Loader2 className="animate-spin text-blue-500" size={32} />
|
||||
</div>
|
||||
) : showPlateNumbers.category === 'Abnormal' ? (
|
||||
<AbnormalVehicleDetails vehicles={filteredModalVehicles} />
|
||||
) : showPlateNumbers.category === 'Pending' ? (
|
||||
<PendingVehicleDetails vehicles={filteredModalVehicles} />
|
||||
) : modalWeeklyDetail.length > 0 ? (
|
||||
<div className="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden w-full">
|
||||
<table className="w-full text-left border-collapse">
|
||||
@@ -177,7 +194,9 @@ export function VehicleDetailModal({
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg border border-gray-200 shadow-sm w-full">
|
||||
<>
|
||||
<VehicleMobileCards vehicles={filteredModalVehicles} customer={showPlateNumbers.source === 'customer'} />
|
||||
<div className="hidden md:block bg-white rounded-lg border border-gray-200 shadow-sm w-full">
|
||||
<table className="min-w-full w-max text-left border-collapse">
|
||||
<thead className="sticky top-0 z-20 shadow-sm">
|
||||
<tr className="bg-slate-700 text-white text-[10px] uppercase tracking-wider whitespace-nowrap">
|
||||
@@ -223,7 +242,7 @@ export function VehicleDetailModal({
|
||||
<td className="p-2 border-r border-gray-100 font-bold text-gray-800"><Blur>{v.customerName || '—'}</Blur></td>
|
||||
<td className={`p-2 border-r border-gray-100 font-mono font-bold ${v.plateNumber ? 'text-blue-700' : 'text-orange-500'}`}><Blur>{v.plateNumber || v.vin || '—'}</Blur></td>
|
||||
<td className="p-2 border-r border-gray-100 text-center">
|
||||
<span className={`px-1.5 py-0.5 rounded-full text-[9px] font-bold ${
|
||||
<span className={`px-1.5 py-0.5 rounded-full text-[11px] font-bold ${
|
||||
v.status === 'Operating' ? 'bg-green-100 text-green-700' :
|
||||
v.status === 'Inventory' ? 'bg-blue-100 text-blue-700' : 'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
@@ -259,12 +278,13 @@ export function VehicleDetailModal({
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-white border-t border-gray-100 flex justify-between items-center shrink-0">
|
||||
<div className="text-xs text-gray-500">
|
||||
共计 <span className="font-bold text-blue-600">{filteredModalWeeklyDetail.length > 0 ? filteredModalWeeklyDetail.length : filteredModalVehicles.length}</span> 台车辆
|
||||
共计 <span className="font-bold text-blue-600">{['Delivered','Returned','Replaced'].includes(showPlateNumbers.category || '') ? filteredModalWeeklyDetail.length : filteredModalVehicles.length}</span> 台车辆
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowPlateNumbers(null)}
|
||||
|
||||
Reference in New Issue
Block a user