Files
ln-bi/src/App.tsx
2026-03-28 15:28:12 +08:00

2183 lines
141 KiB
TypeScript

import React, { useState, useEffect, useCallback } from 'react';
import {
Truck,
Warehouse,
Activity,
PlusCircle,
MinusCircle,
History,
ChevronDown,
ChevronRight,
Info,
Loader2,
Search,
Filter,
ArrowRightLeft,
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import type { SummaryData, TypeSummary, VehicleListItem, DeptGroup, RegionGroup, CustomerStats } from './types';
import { fetchSummary, fetchByType, fetchVehicleList, fetchWeeklyDetail, fetchDeptStats, fetchRegionStats, fetchCustomerStats } from './api';
import type { WeeklyDetailItem } from './api';
export default function App() {
const [theme, setTheme] = useState<'soft' | 'minimal' | 'vibrant'>('soft');
const [expandedModels, setExpandedModels] = useState<Set<string>>(new Set());
const [expandedAssetTypes, setExpandedAssetTypes] = useState<Set<string>>(new Set());
const [showPlateNumbers, setShowPlateNumbers] = useState<{
batch: string;
model: string;
location: string;
category?: 'Inventory' | 'Pending' | 'Delivered' | 'Returned' | 'Replaced' | 'Operating';
vehicleType?: string;
manager?: string;
customer?: string;
isColdChain?: boolean;
isTrailer?: boolean;
type?: string;
source?: string;
} | null>(null);
// Data state
const [summary, setSummary] = useState<SummaryData | null>(null);
const [processedData, setProcessedData] = useState<TypeSummary[]>([]);
const [modalVehicles, setModalVehicles] = useState<VehicleListItem[]>([]);
const [modalWeeklyDetail, setModalWeeklyDetail] = useState<WeeklyDetailItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [lastUpdate, setLastUpdate] = useState<string>('');
const [modalLoading, setModalLoading] = useState(false);
// Dept/Region/Customer data
const [deptData, setDeptData] = useState<DeptGroup[]>([]);
const [regionData, setRegionData] = useState<RegionGroup[]>([]);
const [customerData, setCustomerData] = useState<CustomerStats[]>([]);
// Dept section state
const [deptViewMode, setDeptViewMode] = useState<'department' | 'manager'>('department');
const [expandedDepts, setExpandedDepts] = useState<Set<string>>(new Set());
const [expandedManagerDetails, setExpandedManagerDetails] = useState<Set<string>>(new Set());
const [selectedManager, setSelectedManager] = useState<string>('All');
// Region section state
const [expandedRegions, setExpandedRegions] = useState<Set<string>>(new Set());
const [regionFilters, setRegionFilters] = useState({ region: '', city: '', customer: '' });
const [isRegionFilterOpen, setIsRegionFilterOpen] = useState(false);
// Customer section state
const [expandedCustomers, setExpandedCustomers] = useState<Set<string>>(new Set());
const [customerFilters, setCustomerFilters] = useState({ customer: '', brand: '', department: '', manager: '', region: '' });
const [isCustomerFilterOpen, setIsCustomerFilterOpen] = useState(false);
const loadData = useCallback(async () => {
try {
setLoading(true);
setError(null);
const [s, byType, dept, region, cust] = await Promise.all([
fetchSummary(),
fetchByType(),
fetchDeptStats(),
fetchRegionStats(),
fetchCustomerStats(),
]);
setSummary(s);
setProcessedData(byType);
setDeptData(dept);
setRegionData(region);
setCustomerData(cust);
setLastUpdate(new Date().toLocaleString('zh-CN'));
} catch (e) {
setError(e instanceof Error ? e.message : '数据加载失败');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadData();
const interval = setInterval(loadData, 60 * 1000);
return () => clearInterval(interval);
}, [loadData]);
// Load modal vehicles
useEffect(() => {
if (!showPlateNumbers) {
setModalVehicles([]);
setModalWeeklyDetail([]);
return;
}
setModalLoading(true);
const cat = showPlateNumbers.category;
// Weekly categories use the dedicated weekly-detail endpoint
const weeklyTypes: Record<string, string> = { Delivered: 'delivered', Returned: 'returned', Replaced: 'replaced', Pending: 'pending' };
if (cat && weeklyTypes[cat]) {
setModalVehicles([]);
fetchWeeklyDetail(weeklyTypes[cat])
.then(setModalWeeklyDetail)
.catch(() => setModalWeeklyDetail([]))
.finally(() => setModalLoading(false));
return;
}
// Normal vehicle list
setModalWeeklyDetail([]);
const params: Record<string, string> = {};
if (showPlateNumbers.vehicleType) params.vehicleType = showPlateNumbers.vehicleType;
if (showPlateNumbers.batch !== 'All') params.batch = showPlateNumbers.batch;
if (showPlateNumbers.model !== 'All') params.model = showPlateNumbers.model;
if (showPlateNumbers.location !== 'All') params.location = showPlateNumbers.location;
if (cat === 'Inventory') params.status = 'Inventory';
if (cat === 'Operating') params.category = 'Operating';
if (showPlateNumbers.manager) params.manager = showPlateNumbers.manager;
if (showPlateNumbers.customer) params.customer = showPlateNumbers.customer;
if (!showPlateNumbers.type) {
if (showPlateNumbers.isColdChain !== undefined) params.isColdChain = String(showPlateNumbers.isColdChain);
if (showPlateNumbers.isTrailer !== undefined) params.isTrailer = String(showPlateNumbers.isTrailer);
}
// Map prototype's type field to backend vehicleType
if (showPlateNumbers.type) {
if (showPlateNumbers.type === '4.5T') {
if (showPlateNumbers.isColdChain === true) {
params.vehicleType = '4.5T冷链';
} else if (showPlateNumbers.isColdChain === false) {
params.vehicleType = '4.5T普货';
}
} else if (showPlateNumbers.type === '18T') {
params.vehicleType = '18T';
} else if (showPlateNumbers.type === '49T') {
params.vehicleType = '49T';
} else if (showPlateNumbers.type === '其他车型') {
if (showPlateNumbers.isTrailer === true) {
params.isTrailer = 'true';
} else if (showPlateNumbers.isTrailer === false) {
params.vehicleType = '其他';
}
}
}
fetchVehicleList(params)
.then(setModalVehicles)
.catch(() => setModalVehicles([]))
.finally(() => setModalLoading(false));
}, [showPlateNumbers]);
const allTypesExpanded = processedData.length > 0 && processedData.every((t) => expandedAssetTypes.has(t.type));
const toggleAllAssetTypes = () => {
if (allTypesExpanded) {
setExpandedAssetTypes(new Set());
} else {
setExpandedAssetTypes(new Set(processedData.map((t) => t.type)));
}
};
const toggleAssetType = (type: string) => {
const newSet = new Set(expandedAssetTypes);
if (newSet.has(type)) newSet.delete(type);
else newSet.add(type);
setExpandedAssetTypes(newSet);
};
const toggleModel = (model: string) => {
const newSet = new Set(expandedModels);
if (newSet.has(model)) newSet.delete(model);
else newSet.add(model);
setExpandedModels(newSet);
};
const toggleDept = (dept: string) => {
const newSet = new Set(expandedDepts);
if (newSet.has(dept)) newSet.delete(dept);
else newSet.add(dept);
setExpandedDepts(newSet);
};
const toggleManagerDetails = (manager: string) => {
const newSet = new Set(expandedManagerDetails);
if (newSet.has(manager)) newSet.delete(manager);
else newSet.add(manager);
setExpandedManagerDetails(newSet);
};
const toggleRegion = (region: string) => {
const newSet = new Set(expandedRegions);
if (newSet.has(region)) newSet.delete(region);
else newSet.add(region);
setExpandedRegions(newSet);
};
const toggleCustomer = (customer: string) => {
const newSet = new Set(expandedCustomers);
if (newSet.has(customer)) newSet.delete(customer);
else newSet.add(customer);
setExpandedCustomers(newSet);
};
// Derived data for dept section
const allManagersList = deptData.flatMap((d) => d.managers.map((m) => m.manager)).filter((v, i, a) => a.indexOf(v) === i).sort();
const managerStats = deptData
.flatMap((d) => d.managers)
.filter((m) => selectedManager === 'All' || m.manager === selectedManager)
.sort((a, b) => b.total - a.total);
// Derived data for customer section
const filteredCustomerStats = customerData.filter((s) => {
const mc = !customerFilters.customer || s.customer.toLowerCase().includes(customerFilters.customer.toLowerCase());
const mb = !customerFilters.brand || s.brand === customerFilters.brand;
const md = !customerFilters.department || s.department === customerFilters.department;
const mm = !customerFilters.manager || s.manager.toLowerCase().includes(customerFilters.manager.toLowerCase());
const mr = !customerFilters.region || s.region === customerFilters.region;
return mc && mb && md && mm && mr;
});
const uniqueBrands = Array.from(new Set(customerData.map((s) => s.brand).filter(Boolean)));
const uniqueDepts = Array.from(new Set(customerData.map((s) => s.department).filter(Boolean)));
const uniqueRegions = Array.from(new Set(customerData.map((s) => s.region)));
const uniqueCities = Array.from(new Set(customerData.map((s) => s.city).filter(Boolean)));
// Derived data for region section
const filteredRegionData = regionData.filter((r) => !regionFilters.region || r.region === regionFilters.region);
if (loading && !summary) {
return (
<div className="min-h-screen bg-[#F8F9FB] flex items-center justify-center">
<div className="flex flex-col items-center gap-3">
<Loader2 className="animate-spin text-blue-500" size={32} />
<span className="text-sm text-gray-500">...</span>
</div>
</div>
);
}
if (error && !summary) {
return (
<div className="min-h-screen bg-[#F8F9FB] flex items-center justify-center">
<div className="flex flex-col items-center gap-3 text-center">
<div className="text-red-500 text-lg font-bold"></div>
<div className="text-sm text-gray-500">{error}</div>
<button onClick={loadData} className="mt-2 px-4 py-2 bg-blue-500 text-white rounded text-sm hover:bg-blue-600">
</button>
</div>
</div>
);
}
const SUMMARY = summary!;
return (
<div className="min-h-screen bg-[#F8F9FB] text-gray-800 font-sans p-6">
{/* Main Title and Global Descriptions */}
<div className="mb-6 text-center relative">
<h1 className="text-2xl font-bold text-gray-900 mb-2"></h1>
<div className="flex flex-wrap items-center justify-center gap-x-6 gap-y-1 text-[11px] text-gray-500">
<div className="flex items-center gap-1.5">
<span className="w-1 h-1 rounded-full bg-blue-500"></span>
: {lastUpdate}
</div>
<div className="flex items-center gap-1.5">
<span className="w-1 h-1 rounded-full bg-green-500"></span>
</div>
{loading && (
<div className="flex items-center gap-1.5">
<Loader2 className="animate-spin" size={10} />
...
</div>
)}
</div>
{/* Theme Switcher */}
<div className="absolute top-0 right-0 hidden sm:flex bg-gray-100 p-0.5 rounded-lg text-[10px]">
<button
onClick={() => setTheme('soft')}
className={`px-2 py-1 rounded-md transition-all ${theme === 'soft' ? 'bg-white text-blue-600 shadow-sm font-bold' : 'text-gray-500 hover:text-gray-700'}`}
>
</button>
<button
onClick={() => setTheme('minimal')}
className={`px-2 py-1 rounded-md transition-all ${theme === 'minimal' ? 'bg-white text-blue-600 shadow-sm font-bold' : 'text-gray-500 hover:text-gray-700'}`}
>
</button>
<button
onClick={() => setTheme('vibrant')}
className={`px-2 py-1 rounded-md transition-all ${theme === 'vibrant' ? 'bg-white text-blue-600 shadow-sm font-bold' : 'text-gray-500 hover:text-gray-700'}`}
>
</button>
</div>
</div>
{/* Header Summary - Ultra Compact */}
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-2 mb-6">
{/* Total Assets */}
<div className="bg-white p-2 rounded-sm border border-gray-100 shadow-sm flex items-center gap-2">
<div className="w-7 h-7 rounded bg-gray-50 flex items-center justify-center text-gray-400">
<Truck size={14} />
</div>
<div>
<div className="text-[9px] text-gray-400 font-medium uppercase leading-none mb-0.5"></div>
<div className="text-base font-bold text-gray-800 leading-none">{SUMMARY.totalAssets.toLocaleString()}</div>
</div>
</div>
{/* Operating */}
<div className="bg-white p-2 rounded-sm border border-gray-100 shadow-sm flex items-center gap-2">
<div className="w-7 h-7 rounded bg-blue-50 flex items-center justify-center text-blue-500">
<Activity size={14} />
</div>
<div>
<div className="text-[9px] text-gray-400 font-medium uppercase leading-none mb-0.5"></div>
<div className="flex items-baseline gap-1">
<span className="text-base font-bold text-blue-600 leading-none">{SUMMARY.operating.total}</span>
<span className="text-[8px] text-gray-400 leading-none">{SUMMARY.operating.self} {SUMMARY.operating.leased}</span>
</div>
</div>
</div>
{/* Inventory */}
<div className="bg-white p-2 rounded-sm border border-gray-100 shadow-sm flex items-center gap-2">
<div className="w-7 h-7 rounded bg-gray-50 flex items-center justify-center text-gray-500">
<Warehouse size={14} />
</div>
<div>
<div className="text-[9px] text-gray-400 font-medium uppercase leading-none mb-0.5"></div>
<div className="flex items-baseline gap-1">
<span className="text-base font-bold text-gray-800 leading-none">{SUMMARY.inventory.total}</span>
<span className="text-[8px] text-gray-400 leading-none">{SUMMARY.inventory.inStock} {SUMMARY.inventory.abnormal}</span>
</div>
</div>
</div>
{/* Pending */}
<div className="bg-white p-2 rounded-sm border border-gray-100 shadow-sm flex items-center gap-2 cursor-pointer hover:bg-blue-50 transition-colors"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending' })}>
<div className="w-7 h-7 rounded bg-blue-50 flex items-center justify-center text-blue-500">
<PlusCircle size={14} />
</div>
<div>
<div className="text-[9px] text-blue-500 font-bold uppercase leading-none mb-0.5"></div>
<div className="text-base font-bold text-blue-600 leading-none">{SUMMARY.pendingDelivery}</div>
</div>
</div>
{/* Dynamics */}
<div className="bg-white p-2 rounded-sm border border-gray-100 shadow-sm col-span-2">
<div className="flex items-center justify-between mb-1.5">
<div className="text-[9px] text-gray-400 font-bold uppercase tracking-tight"></div>
<div className="text-[7px] text-gray-300 font-normal italic">-</div>
</div>
<div className="flex justify-between items-center gap-1">
<div className="flex-1 flex flex-col items-center cursor-pointer hover:bg-green-50 py-1 rounded transition-all group">
<span className="text-xs font-bold text-gray-800 group-hover:text-green-600">{SUMMARY.weeklyNew}</span>
<span className="text-[8px] text-green-500/80 font-bold mt-0.5"></span>
</div>
<div className="w-[1px] h-3 bg-gray-100"></div>
<div className="flex-1 flex flex-col items-center cursor-pointer hover:bg-blue-50 py-1 rounded transition-all group"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Delivered' })}>
<span className="text-xs font-bold text-gray-800 group-hover:text-blue-600">{SUMMARY.weeklyDelivered}</span>
<span className="text-[8px] text-blue-500/80 font-bold mt-0.5"></span>
</div>
<div className="w-[1px] h-3 bg-gray-100"></div>
<div className="flex-1 flex flex-col items-center cursor-pointer hover:bg-orange-50 py-1 rounded transition-all group"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Returned' })}>
<span className="text-xs font-bold text-gray-800 group-hover:text-orange-600">{SUMMARY.weeklyReturned}</span>
<span className="text-[8px] text-orange-500/80 font-bold mt-0.5"></span>
</div>
<div className="w-[1px] h-3 bg-gray-100"></div>
<div className="flex-1 flex flex-col items-center cursor-pointer hover:bg-purple-50 py-1 rounded transition-all group"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Replaced' })}>
<span className="text-xs font-bold text-gray-800 group-hover:text-purple-600">{SUMMARY.weeklyReplaced}</span>
<span className="text-[8px] text-purple-500/80 font-bold mt-0.5"></span>
</div>
</div>
</div>
</div>
{/* Main Content Area */}
<div className="flex flex-col gap-6">
{/* Asset Summary Table with Dimension Switch */}
<div className="bg-white rounded-sm border border-gray-100 shadow-sm overflow-hidden mb-6">
<div className="p-4 border-b border-gray-50 bg-gray-50/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
<div className="flex flex-wrap items-center gap-4 sm:gap-6">
<h2 className="text-sm font-bold text-gray-700"></h2>
<div className="hidden md:flex items-center gap-1 text-[10px] text-blue-500 bg-blue-50 px-2 py-0.5 rounded">
<Info size={10} />
</div>
</div>
</div>
{/* Desktop View Table */}
<div className="hidden lg:block overflow-x-auto">
<table className="w-full text-left border-collapse table-fixed min-w-[1200px]">
<thead>
<tr className="bg-gray-50 text-[11px] text-gray-500 uppercase tracking-wider border-b border-gray-100">
<th className="p-3 font-semibold border-r border-gray-100 w-24">
<button onClick={toggleAllAssetTypes} className="flex items-center gap-1 hover:text-blue-600 transition-colors">
{allTypesExpanded ? <MinusCircle size={12} /> : <PlusCircle size={12} />}
<span></span>
</button>
</th>
<th className="p-3 font-semibold border-r border-gray-100 w-48"></th>
<th className="p-3 font-semibold border-r border-gray-100 text-center bg-blue-50/30 w-24"></th>
<th className="p-3 font-semibold border-r border-gray-100 text-center w-24"></th>
<th className="p-3 font-semibold border-r border-gray-100 text-center w-24">-</th>
<th className="p-3 font-semibold border-r border-gray-100 text-center w-24">-广</th>
<th className="p-3 font-semibold border-r border-gray-100 text-center w-24">-</th>
<th className="p-3 font-semibold border-r border-gray-100 text-center w-24">-</th>
<th className="p-3 font-semibold border-r border-gray-100 text-center w-24">-</th>
<th className="p-3 font-semibold border-r border-gray-100 text-center w-24"></th>
<th className="p-3 font-semibold border-r border-gray-100 text-center bg-green-50/30 w-24"></th>
<th className="p-3 font-semibold border-r border-gray-100 text-center bg-blue-50/20 w-24"></th>
<th className="p-3 font-semibold border-r border-gray-100 text-center bg-orange-50/20 w-24"></th>
<th className="p-3 font-semibold text-center bg-purple-50/20 w-24"></th>
</tr>
<tr className="bg-yellow-50/50 text-xs font-bold border-b border-gray-200">
<td className="p-3 border-r border-gray-100 text-gray-700"></td>
<td className="p-3 border-r border-gray-100"></td>
<td className="p-3 text-center border-r border-gray-100">
<button onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All' })} className="text-gray-800 hover:text-blue-600 hover:underline font-bold">
{processedData.reduce((s, t) => s + t.totalAssets, 0)}
</button>
</td>
<td className="p-3 text-center border-r border-gray-100">
<button onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Inventory' })} className="text-blue-700 hover:underline font-bold">
{processedData.reduce((s, t) => s + t.totalInventory, 0)}
</button>
</td>
{['嘉兴', '广东', '北京', '新疆', '其他'].map((reg) => {
const val = processedData.reduce((s, t) => s + (t.inventoryRegions?.[reg] || 0), 0);
return (
<td key={reg} className="p-3 text-center border-r border-gray-100">
{val > 0 ? (
<button onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: reg, category: 'Inventory' })} className="text-blue-700 hover:underline font-bold">
{val}
</button>
) : ''}
</td>
);
})}
<td className="p-3 text-center border-r border-gray-100">
{processedData.reduce((s, t) => s + t.pending, 0) > 0 ? (
<button onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending' })} className="text-gray-700 hover:text-blue-600 hover:underline font-bold">
{processedData.reduce((s, t) => s + t.pending, 0)}
</button>
) : ''}
</td>
<td className="p-3 text-center border-r border-gray-100 bg-green-50/10">
<button onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating' })} className="text-green-700 hover:underline font-bold">
{processedData.reduce((s, t) => s + t.totalOperating, 0)}
</button>
</td>
<td className="p-3 text-center border-r border-gray-100 bg-blue-50/5">
{processedData.reduce((s, t) => s + t.weeklyDelivered, 0) > 0 ? (
<button onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Delivered' })} className="text-blue-700 hover:underline font-bold">
{processedData.reduce((s, t) => s + t.weeklyDelivered, 0)}
</button>
) : ''}
</td>
<td className="p-3 text-center border-r border-gray-100 bg-orange-50/5">
{processedData.reduce((s, t) => s + t.weeklyReturned, 0) > 0 ? (
<button onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Returned' })} className="text-orange-700 hover:underline font-bold">
{processedData.reduce((s, t) => s + t.weeklyReturned, 0)}
</button>
) : ''}
</td>
<td className="p-3 text-center bg-purple-50/5">
{processedData.reduce((s, t) => s + t.weeklyReplaced, 0) > 0 ? (
<button onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Replaced' })} className="text-purple-700 hover:underline font-bold">
{processedData.reduce((s, t) => s + t.weeklyReplaced, 0)}
</button>
) : ''}
</td>
</tr>
</thead>
<tbody className="text-xs">
{processedData.map((typeGroup) => (
<React.Fragment key={typeGroup.type}>
{/* Category Header Row */}
<tr
className={`border-b border-gray-100 cursor-pointer transition-all ${
theme === 'vibrant'
? 'bg-blue-600 text-white hover:bg-blue-700'
: theme === 'minimal'
? 'bg-white border-l-4 border-blue-500 hover:bg-gray-50'
: 'bg-blue-50/50 hover:bg-blue-50 transition-colors'
}`}
onClick={() => toggleAssetType(typeGroup.type)}
>
{expandedAssetTypes.has(typeGroup.type) ? (
<td colSpan={14} className={`p-3 font-bold ${theme === 'vibrant' ? 'text-white' : 'text-blue-700'}`}>
<div className="flex items-center gap-2">
<ChevronDown size={16} className={theme === 'vibrant' ? 'text-white' : 'text-blue-500'} />
<span>{typeGroup.type}</span>
</div>
</td>
) : (
<>
<td className={`p-3 font-bold border-r border-gray-100 ${theme === 'vibrant' ? 'text-white' : 'text-blue-700'}`}>
<div className="flex items-center gap-2">
<ChevronRight size={16} className={theme === 'vibrant' ? 'text-white/70' : 'text-gray-400'} />
<span>{typeGroup.type}</span>
</div>
</td>
<td className={`p-3 border-r border-gray-100 text-[11px] ${theme === 'vibrant' ? 'text-white/70' : 'text-gray-400'} italic`}></td>
<td className={`p-3 text-center border-r border-gray-100 font-bold ${theme === 'vibrant' ? 'text-white' : ''}`}>
<button onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', vehicleType: typeGroup.type }); }} className={`hover:underline font-bold ${theme === 'vibrant' ? 'text-white' : 'text-gray-700 hover:text-blue-600'}`}>
{typeGroup.totalAssets}
</button>
</td>
<td className={`p-3 text-center border-r border-gray-100 font-bold ${theme === 'vibrant' ? 'text-white' : ''}`}>
<button onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Inventory', vehicleType: typeGroup.type }); }} className={`hover:underline font-bold ${theme === 'vibrant' ? 'text-white' : 'text-blue-600'}`}>
{typeGroup.totalInventory}
</button>
</td>
{['嘉兴', '广东', '北京', '新疆', '其他'].map((reg) => (
<td key={reg} className={`p-3 text-center border-r border-gray-100 font-bold ${theme === 'vibrant' ? 'text-white/80' : ''}`}>
{(typeGroup.inventoryRegions?.[reg] || 0) > 0 ? (
<button onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: reg, category: 'Inventory', vehicleType: typeGroup.type }); }} className={`hover:underline font-bold ${theme === 'vibrant' ? 'text-white/80' : 'text-blue-600'}`}>
{typeGroup.inventoryRegions[reg]}
</button>
) : ''}
</td>
))}
<td className={`p-3 text-center border-r border-gray-100 font-bold ${theme === 'vibrant' ? 'text-white' : ''}`}>
{typeGroup.pending > 0 ? (
<button onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending', vehicleType: typeGroup.type }); }} className={`hover:underline font-bold ${theme === 'vibrant' ? 'text-white' : 'text-gray-600 hover:text-blue-600'}`}>
{typeGroup.pending}
</button>
) : ''}
</td>
<td className={`p-3 text-center border-r border-gray-100 font-bold bg-green-50/10 ${theme === 'vibrant' ? 'text-white' : ''}`}>
<button onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', vehicleType: typeGroup.type }); }} className={`hover:underline font-bold ${theme === 'vibrant' ? 'text-white' : 'text-green-600'}`}>
{typeGroup.totalOperating}
</button>
</td>
<td className={`p-3 text-center border-r border-gray-100 font-bold bg-blue-50/5 ${theme === 'vibrant' ? 'text-white/80' : ''}`}>
{typeGroup.weeklyDelivered > 0 ? (
<button onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Delivered', vehicleType: typeGroup.type }); }} className={`hover:underline font-bold ${theme === 'vibrant' ? 'text-white/80' : 'text-blue-600'}`}>
{typeGroup.weeklyDelivered}
</button>
) : ''}
</td>
<td className={`p-3 text-center border-r border-gray-100 font-bold bg-orange-50/5 ${theme === 'vibrant' ? 'text-white/80' : ''}`}>
{typeGroup.weeklyReturned > 0 ? (
<button onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Returned', vehicleType: typeGroup.type }); }} className={`hover:underline font-bold ${theme === 'vibrant' ? 'text-white/80' : 'text-orange-600'}`}>
{typeGroup.weeklyReturned}
</button>
) : ''}
</td>
<td className={`p-3 text-center font-bold bg-purple-50/5 ${theme === 'vibrant' ? 'text-white/80' : ''}`}>
{typeGroup.weeklyReplaced > 0 ? (
<button onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Replaced', vehicleType: typeGroup.type }); }} className={`hover:underline font-bold ${theme === 'vibrant' ? 'text-white/80' : 'text-purple-600'}`}>
{typeGroup.weeklyReplaced}
</button>
) : ''}
</td>
</>
)}
</tr>
<AnimatePresence>
{expandedAssetTypes.has(typeGroup.type) &&
typeGroup.models.map((model) => (
<React.Fragment key={model.model}>
<motion.tr
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className={`border-b border-gray-50 hover:bg-gray-50/50 transition-colors cursor-pointer ${expandedModels.has(model.model) ? 'bg-blue-50/10' : ''}`}
onClick={() => toggleModel(model.model)}
>
<td className="p-3 border-r border-gray-100 text-gray-300 text-center italic">{typeGroup.type}</td>
<td className="p-3 border-r border-gray-100 flex items-center gap-2">
{expandedModels.has(model.model) ? (
<ChevronDown size={14} className="text-blue-500" />
) : (
<ChevronRight size={14} className="text-gray-300" />
)}
<span className={expandedModels.has(model.model) ? 'font-bold text-blue-700' : ''}>{model.model}</span>
</td>
<td className="p-3 text-center border-r border-gray-100 font-medium">
<button
onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All' }); }}
className="text-blue-500 hover:underline font-medium"
>{model.total}</button>
</td>
<td className="p-3 text-center border-r border-gray-100">
{model.inventory > 0 ? (
<button
onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Inventory' }); }}
className="text-blue-500 hover:underline font-medium"
>{model.inventory}</button>
) : model.inventory}
</td>
{['嘉兴', '广东', '北京', '新疆', '其他'].map((reg) => (
<td key={reg} className="p-3 text-center border-r border-gray-100">
{(model.inventoryRegions[reg] || 0) > 0 ? (
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: model.model, location: reg, category: 'Inventory' });
}}
className="text-blue-500 hover:underline font-medium"
>
{model.inventoryRegions[reg]}
</button>
) : (
''
)}
</td>
))}
<td className="p-3 text-center border-r border-gray-100">
{model.pending > 0 ? (
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Pending' });
}}
className="text-blue-500 hover:underline font-medium"
>
{model.pending}
</button>
) : (
''
)}
</td>
<td className="p-3 text-center border-r border-gray-100 font-bold bg-green-50/10">
<button
onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Operating' }); }}
className="text-green-600 hover:underline font-bold"
>{model.operating}</button>
</td>
<td className="p-3 text-center border-r border-gray-100 text-blue-600 bg-blue-50/5">
{model.weeklyDelivered > 0 ? (
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Delivered' });
}}
className="text-blue-500 hover:underline font-medium"
>
{model.weeklyDelivered}
</button>
) : (
''
)}
</td>
<td className="p-3 text-center border-r border-gray-100 text-orange-600 bg-orange-50/5">
{model.weeklyReturned > 0 ? (
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Returned' });
}}
className="text-blue-500 hover:underline font-medium"
>
{model.weeklyReturned}
</button>
) : (
''
)}
</td>
<td className="p-3 text-center text-purple-600 bg-purple-50/5 font-medium">
{model.weeklyReplaced > 0 ? (
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Replaced' });
}}
className="text-blue-500 hover:underline font-medium"
>
{model.weeklyReplaced}
</button>
) : (
''
)}
</td>
</motion.tr>
</React.Fragment>
))}
</AnimatePresence>
</React.Fragment>
))}
</tbody>
</table>
</div>
{/* Mobile View Cards for Asset Summary */}
<div className="lg:hidden p-4 space-y-4">
{processedData.map((typeGroup) => (
<div key={typeGroup.type} className="space-y-3">
<div
className={`px-3 py-2 rounded flex justify-between items-center shadow-sm cursor-pointer transition-all ${
theme === 'vibrant'
? 'bg-blue-600 text-white active:bg-blue-700'
: theme === 'minimal'
? 'bg-white border-l-4 border-blue-500 text-gray-800 active:bg-gray-50'
: 'bg-blue-50 border border-blue-100 text-blue-700 active:bg-blue-100'
}`}
onClick={() => toggleAssetType(typeGroup.type)}
>
<div className="flex items-center gap-2">
{expandedAssetTypes.has(typeGroup.type) ? (
<ChevronDown size={16} className={theme === 'vibrant' ? 'text-white' : 'text-blue-500'} />
) : (
<ChevronRight size={16} className={theme === 'vibrant' ? 'text-white/70' : 'text-blue-300'} />
)}
<span className="text-xs font-bold">{typeGroup.type}</span>
</div>
<div className={`flex gap-3 text-[9px] font-normal ${theme === 'vibrant' ? 'opacity-90' : 'text-gray-500'}`}>
<span>
<span className={theme === 'vibrant' ? 'font-bold' : 'font-bold text-gray-700'}>{typeGroup.totalAssets}</span>
</span>
<span>
{' '}
<span className={theme === 'vibrant' ? 'font-bold' : 'font-bold text-blue-600'}>{typeGroup.totalInventory}</span>
</span>
<span>
{' '}
<span className={theme === 'vibrant' ? 'font-bold' : 'font-bold text-green-600'}>{typeGroup.totalOperating}</span>
</span>
</div>
</div>
<AnimatePresence>
{expandedAssetTypes.has(typeGroup.type) && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="space-y-3 overflow-hidden"
>
{typeGroup.models.map((model) => (
<div key={model.model} className="bg-white rounded-lg border border-gray-100 shadow-sm overflow-hidden">
<div
className="p-3 flex justify-between items-center cursor-pointer active:bg-gray-50"
onClick={() => toggleModel(model.model)}
>
<div className="flex items-center gap-2">
{expandedModels.has(model.model) ? (
<ChevronDown size={14} className="text-blue-500" />
) : (
<ChevronRight size={14} className="text-gray-300" />
)}
<span className="text-xs font-bold text-gray-700">{model.model}</span>
</div>
<div className="flex gap-2">
<button
onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All' }); }}
className="text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-bold active:bg-blue-100"
>
{model.total}
</button>
<span className="text-[10px] bg-green-50 text-green-600 px-1.5 py-0.5 rounded font-bold">
{model.operating}
</span>
</div>
</div>
{expandedModels.has(model.model) && (
<div className="px-3 pb-3 pt-1 border-t border-gray-50 bg-gray-50/30">
<div className="grid grid-cols-2 gap-y-3 gap-x-4 mt-2">
<div
className="flex justify-between items-center cursor-pointer hover:bg-gray-100 p-1 rounded transition-colors"
onClick={() => setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Inventory' })}
>
<span className="text-[10px] text-gray-400"></span>
<span className="text-xs font-bold text-blue-600">{model.inventory}</span>
</div>
<div
className="flex justify-between items-center cursor-pointer hover:bg-gray-100 p-1 rounded transition-colors"
onClick={() =>
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Pending' })
}
>
<span className="text-[10px] text-gray-400"></span>
<span className="text-xs font-bold text-gray-600">{model.pending}</span>
</div>
<div className="col-span-2 grid grid-cols-5 gap-1 py-2 border-y border-gray-100">
{['嘉兴', '广东', '北京', '新疆', '其他'].map((reg) => (
<div key={reg} className="text-center">
<div className="text-[8px] text-gray-400 mb-0.5">
{reg === '嘉兴' ? '江浙沪' : reg}
</div>
{(model.inventoryRegions[reg] || 0) > 0 ? (
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: model.model, location: reg, category: 'Inventory' });
}}
className="text-[10px] font-bold text-blue-500 hover:underline"
>
{model.inventoryRegions[reg]}
</button>
) : (
<div className="text-[10px] font-bold text-gray-300">-</div>
)}
</div>
))}
</div>
<div className="col-span-2 grid grid-cols-3 gap-2 pt-1">
<div
className="bg-blue-50/50 p-2 rounded flex flex-col items-center cursor-pointer hover:bg-blue-100/50 transition-colors"
onClick={() =>
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Delivered' })
}
>
<span className="text-[8px] text-gray-400 mb-1"></span>
<span className="text-xs font-bold text-blue-600">{model.weeklyDelivered}</span>
</div>
<div
className="bg-orange-50/50 p-2 rounded flex flex-col items-center cursor-pointer hover:bg-orange-100/50 transition-colors"
onClick={() =>
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Returned' })
}
>
<span className="text-[8px] text-gray-400 mb-1"></span>
<span className="text-xs font-bold text-orange-600">{model.weeklyReturned}</span>
</div>
<div
className="bg-purple-50/50 p-2 rounded flex flex-col items-center cursor-pointer hover:bg-purple-100/50 transition-colors"
onClick={() =>
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Replaced' })
}
>
<span className="text-[8px] text-gray-400 mb-1"></span>
<span className="text-xs font-bold text-purple-600">{model.weeklyReplaced}</span>
</div>
</div>
</div>
</div>
)}
</div>
))}
</motion.div>
)}
</AnimatePresence>
</div>
))}
</div>
</div>
{/* Department Operations Statistics */}
<section className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6">
<div className="p-3 sm:p-4 border-b border-gray-50 flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-1.5 h-6 bg-blue-600 rounded-full"></div>
<div>
<h2 className="text-lg font-bold text-gray-800"></h2>
<p className="text-[10px] text-gray-400 font-medium"></p>
</div>
</div>
</div>
<div className="p-0 sm:p-2 bg-gray-50/30">
{/* Overall Total Summary (Compact) - Moved to Top */}
<div className="m-2 bg-slate-800 rounded-xl p-3 text-white shadow-lg">
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div className="flex flex-col">
<span className="text-[9px] opacity-50 uppercase font-bold tracking-widest mb-0.5"></span>
<span className="text-xl font-black">{deptData.reduce((s, d) => s + d.totalAssets, 0)}</span>
</div>
<div className="flex flex-col">
<span className="text-[9px] opacity-50 uppercase font-bold tracking-widest mb-0.5 text-green-400"></span>
<span className="text-xl font-black text-green-400">
{deptData.reduce((acc, d) => acc + d.operatingCount, 0)}
</span>
</div>
<div className="flex flex-col">
<span className="text-[9px] opacity-50 uppercase font-bold tracking-widest mb-0.5 text-slate-400"></span>
<span className="text-xl font-black text-slate-400">
{deptData.reduce((acc, d) => acc + d.idleCount, 0)}
</span>
</div>
<div className="flex flex-col">
<span className="text-[9px] opacity-50 uppercase font-bold tracking-widest mb-0.5 text-blue-400"></span>
<span className="text-xl font-black text-blue-400">
{'—'}
</span>
</div>
</div>
</div>
{/* Controls Row: Toggles Left, Filter Right */}
<div className="px-2 mb-2 flex items-center justify-between gap-4">
<div className="flex bg-gray-200/50 p-1 rounded-lg shadow-inner">
<button
onClick={() => setDeptViewMode('department')}
className={`px-4 py-1.5 text-xs font-bold rounded-md transition-all ${deptViewMode === 'department' ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
>
</button>
<button
onClick={() => setDeptViewMode('manager')}
className={`px-4 py-1.5 text-xs font-bold rounded-md transition-all ${deptViewMode === 'manager' ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}
>
</button>
</div>
<div className="flex-1 max-w-[240px]">
{deptViewMode === 'manager' && (
<div className="relative">
<Filter className="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" size={14} />
<select
value={selectedManager}
onChange={(e) => setSelectedManager(e.target.value)}
className="w-full pl-9 pr-8 py-1.5 bg-white border border-gray-200 rounded-lg text-xs focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm appearance-none cursor-pointer font-bold text-gray-700"
>
<option value="All"></option>
{allManagersList.map(m => (
<option key={m} value={m}>{m}</option>
))}
</select>
<ChevronDown className="absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none" size={14} />
</div>
)}
</div>
</div>
{/* Desktop Table View */}
<div className="hidden lg:block overflow-x-auto">
<table className="w-full text-left border-collapse min-w-[900px]">
<thead>
<tr className="bg-gray-100/50 text-[11px] text-gray-500 uppercase tracking-wider border-b border-gray-200">
<th className="p-2 font-bold border-r border-gray-100 w-48">{deptViewMode === 'department' ? '部门名称' : '业务员'}</th>
{deptViewMode === 'manager' && <th className="p-2 font-bold border-r border-gray-100 w-32"></th>}
<th className="p-2 font-bold border-r border-gray-100 text-center w-24">{deptViewMode === 'department' ? '出勤率' : '合计资产'}</th>
{deptViewMode === 'department' && (
<>
<th className="p-2 font-bold border-r border-gray-100 text-center w-24"></th>
<th className="p-2 font-bold border-r border-gray-100 text-center w-28"></th>
<th className="p-2 font-bold border-r border-gray-100 text-center w-24 text-green-500"></th>
<th className="p-2 font-bold border-r border-gray-100 text-center w-24 text-gray-400"></th>
</>
)}
{deptViewMode === 'manager' && (
<>
<th className="p-2 font-bold border-r border-gray-100 text-center w-20">4.5T</th>
<th className="p-2 font-bold border-r border-gray-100 text-center w-20"></th>
<th className="p-2 font-bold border-r border-gray-100 text-center w-20">18T</th>
<th className="p-2 font-bold border-r border-gray-100 text-center w-20">49T</th>
<th className="p-2 font-bold border-r border-gray-100 text-center w-20"></th>
<th className="p-2 font-bold border-r border-gray-100 text-center w-20"></th>
</>
)}
<th className="p-2 font-bold text-center w-16"></th>
</tr>
</thead>
<tbody className="text-xs">
{deptViewMode === 'department' ? (
deptData.map((dept) => {
const isExpanded = expandedDepts.has(dept.department);
return (
<React.Fragment key={dept.department}>
<tr
className={`cursor-pointer transition-all border-b border-gray-100 ${
isExpanded ? 'bg-blue-50/50' : 'hover:bg-gray-50'
}`}
onClick={() => toggleDept(dept.department)}
>
<td className="p-2 border-r border-gray-100 font-bold text-gray-800">
{dept.department}
</td>
<td className="p-2 border-r border-gray-100 text-center">
<span className="bg-blue-50 text-blue-600 text-[10px] font-bold px-2 py-0.5 rounded-full">
{'—'}
</span>
</td>
<td className="p-2 border-r border-gray-100 text-center font-black text-gray-800 text-sm">
{dept.totalAssets}
</td>
<td className="p-2 border-r border-gray-100 text-center">
<div className="flex items-baseline justify-center gap-1">
<span className="font-black text-gray-800 text-sm">{'—'}</span>
<span className="text-[9px] text-gray-400 font-bold">km</span>
</div>
</td>
<td className="p-2 border-r border-gray-100 text-center font-black text-green-500 text-sm">
{dept.operatingCount}
</td>
<td className="p-2 border-r border-gray-100 text-center font-black text-gray-400 text-sm">
{dept.idleCount}
</td>
<td className="p-2 text-center">
{isExpanded ? <ChevronDown size={16} className="text-blue-500 inline" /> : <ChevronRight size={16} className="text-gray-300 inline" />}
</td>
</tr>
{isExpanded && (
<tr className="bg-gray-50/50">
<td colSpan={7} className="p-2">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2">
{dept.managers.map(m => {
const isManagerExpanded = expandedManagerDetails.has(m.manager);
return (
<div key={m.manager} className="bg-white rounded-lg border border-gray-100 shadow-sm overflow-hidden">
<div
className="p-2 flex justify-between items-center cursor-pointer hover:bg-gray-50 transition-colors"
onClick={() => toggleManagerDetails(m.manager)}
>
<div className="flex items-center gap-2">
{isManagerExpanded ? <ChevronDown size={14} className="text-blue-500" /> : <ChevronRight size={14} className="text-gray-300" />}
<span className="font-bold text-gray-700 text-xs">{m.manager}</span>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating', source: 'department' });
}}
className="text-[10px] font-bold text-blue-600 bg-blue-50 px-2 py-0.5 rounded hover:bg-blue-100 transition-colors"
>
: {m.total}
</button>
</div>
{isManagerExpanded && (
<div className="p-2 pt-0 border-t border-gray-50 bg-gray-50/30">
<div className="grid grid-cols-3 gap-1 mt-2">
<div
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">4.5T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
</div>
<div
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
</div>
<div
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">18T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
</div>
<div
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">49T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
</div>
<div
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
</div>
<div
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
</div>
</div>
</div>
)}
</div>
);
})}
</div>
</td>
</tr>
)}
</React.Fragment>
);
})
) : (
managerStats.map((m) => {
const isManagerExpanded = expandedManagerDetails.has(m.manager);
return (
<React.Fragment key={m.manager}>
<tr
className="border-b border-gray-100 hover:bg-gray-50 transition-colors cursor-pointer"
onClick={() => toggleManagerDetails(m.manager)}
>
<td className="p-2 border-r border-gray-100 font-bold text-gray-800 flex items-center gap-1">
{isManagerExpanded ? <ChevronDown size={12} className="text-blue-400" /> : <ChevronRight size={12} className="text-gray-300" />}
{m.manager}
</td>
<td className="p-2 border-r border-gray-100 text-gray-600">{m.department}</td>
<td
className="p-2 border-r border-gray-100 text-center font-black text-blue-600 text-sm cursor-pointer hover:bg-blue-50"
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, source: 'department' });
}}
>
{m.total}
</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-400">-</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-400">-</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-400">-</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-400">-</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-400">-</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-400">-</td>
<td className="p-2 text-center">
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating', source: 'department' });
}}
className="text-blue-500 hover:text-blue-700 transition-colors"
>
<ArrowRightLeft size={14} className="inline" />
</button>
</td>
</tr>
{isManagerExpanded && (
<tr className="bg-gray-50/50 border-b border-gray-100">
<td colSpan={10} className="p-0">
<div className="grid grid-cols-6 text-[10px] bg-white/50">
<div className="p-2 border-r border-gray-100 flex flex-col items-center cursor-pointer hover:bg-blue-50" onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, source: 'department' })}>
<span className="text-gray-400 uppercase mb-1">4.5T</span>
<span className="font-bold text-gray-600">{m.t4_5}</span>
</div>
<div className="p-2 border-r border-gray-100 flex flex-col items-center cursor-pointer hover:bg-blue-50" onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, source: 'department' })}>
<span className="text-gray-400 uppercase mb-1"></span>
<span className="font-bold text-gray-600">{m.t4_5c}</span>
</div>
<div className="p-2 border-r border-gray-100 flex flex-col items-center cursor-pointer hover:bg-blue-50" onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', source: 'department' })}>
<span className="text-gray-400 uppercase mb-1">18T</span>
<span className="font-bold text-gray-600">{m.t18}</span>
</div>
<div className="p-2 border-r border-gray-100 flex flex-col items-center cursor-pointer hover:bg-blue-50" onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', source: 'department' })}>
<span className="text-gray-400 uppercase mb-1">49T</span>
<span className="font-bold text-gray-600">{m.t49}</span>
</div>
<div className="p-2 border-r border-gray-100 flex flex-col items-center cursor-pointer hover:bg-blue-50" onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, source: 'department' })}>
<span className="text-gray-400 uppercase mb-1"></span>
<span className="font-bold text-gray-600">{m.trailer}</span>
</div>
<div className="p-2 flex flex-col items-center cursor-pointer hover:bg-blue-50" onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, source: 'department' })}>
<span className="text-gray-400 uppercase mb-1"></span>
<span className="font-bold text-gray-600">{m.other}</span>
</div>
</div>
</td>
</tr>
)}
</React.Fragment>
);
})
)}
</tbody>
</table>
</div>
{/* Mobile Card View */}
<div className="lg:hidden p-2 space-y-2">
{deptViewMode === 'department' ? (
deptData.map((dept) => {
const isExpanded = expandedDepts.has(dept.department);
return (
<div key={dept.department} className="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
<div
className="p-3 cursor-pointer"
onClick={() => toggleDept(dept.department)}
>
<div className="flex justify-between items-center mb-2">
<h3 className="text-sm font-bold text-gray-800">{dept.department}</h3>
<span className="bg-blue-50 text-blue-600 text-[9px] font-bold px-2 py-0.5 rounded-full">
:
</span>
</div>
<div className="grid grid-cols-4 gap-2">
<div className="text-center">
<div className="text-[8px] text-gray-400 uppercase font-bold mb-0.5"></div>
<div className="text-xs font-black text-gray-800">{dept.totalAssets}</div>
</div>
<div className="text-center">
<div className="text-[8px] text-gray-400 uppercase font-bold mb-0.5"></div>
<div className="text-xs font-black text-gray-800">{'—'}</div>
</div>
<div className="text-center">
<div className="text-[8px] text-green-500 uppercase font-bold mb-0.5"></div>
<div className="text-xs font-black text-green-500">{dept.operatingCount}</div>
</div>
<div className="text-center">
<div className="text-[8px] text-gray-400 uppercase font-bold mb-0.5"></div>
<div className="text-xs font-black text-gray-400">{dept.idleCount}</div>
</div>
</div>
<div className="mt-1 flex justify-center">
{isExpanded ? <ChevronDown size={14} className="text-blue-500" /> : <ChevronRight size={14} className="text-gray-300" />}
</div>
</div>
{isExpanded && (
<div className="bg-gray-50/50 p-2 border-t border-gray-50 space-y-2">
{dept.managers.map(m => {
const isManagerExpanded = expandedManagerDetails.has(m.manager);
return (
<div key={m.manager} className="bg-white rounded border border-gray-100 shadow-sm overflow-hidden">
<div
className="p-2 flex justify-between items-center cursor-pointer"
onClick={() => toggleManagerDetails(m.manager)}
>
<div className="flex items-center gap-1">
{isManagerExpanded ? <ChevronDown size={12} className="text-blue-400" /> : <ChevronRight size={12} className="text-gray-300" />}
<span className="text-[11px] font-bold text-gray-700">{m.manager}</span>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, source: 'department' });
}}
className="text-[10px] font-bold text-blue-600 bg-blue-50 px-2 py-0.5 rounded"
>
: {m.total}
</button>
</div>
{isManagerExpanded && (
<div className="p-2 border-t border-gray-50 bg-gray-50/30 grid grid-cols-3 gap-1">
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">4.5T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">18T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">49T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
</div>
</div>
)}
</div>
);
})}
</div>
)}
</div>
);
})
) : (
managerStats.map((m) => {
const isManagerExpanded = expandedManagerDetails.has(m.manager);
return (
<div key={m.manager} className="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
<div
className="p-2 cursor-pointer flex items-center justify-between gap-2"
onClick={() => toggleManagerDetails(m.manager)}
>
<div className="flex items-center gap-2 flex-1 min-w-0">
{isManagerExpanded ? <ChevronDown size={14} className="text-blue-500" /> : <ChevronRight size={14} className="text-gray-300" />}
<div className="flex items-center gap-3 min-w-0">
<h3 className="text-sm font-bold text-gray-800 shrink-0">{m.manager}</h3>
<span className="text-[11px] text-gray-500 shrink-0">{m.department}</span>
<div
className="text-[11px] font-bold text-blue-600 whitespace-nowrap"
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, source: 'department' });
}}
>
: {m.total}
</div>
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating', source: 'department' });
}}
className="text-blue-500 p-1 hover:bg-blue-50 rounded transition-colors flex-shrink-0"
>
<ArrowRightLeft size={14} />
</button>
</div>
{isManagerExpanded && (
<div className="p-2 border-t border-gray-50 bg-gray-50/30 grid grid-cols-3 gap-1">
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">4.5T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">18T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase">49T</div>
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
</div>
<div
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, source: 'department' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
</div>
</div>
)}
</div>
);
})
)}
</div>
</div>
</section>
{/* Region - Vehicle - Customer Section */}
<section className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6">
<div className="p-3 sm:p-4 border-b border-gray-50 flex items-center justify-between bg-slate-50 relative">
<div className="flex items-center gap-3">
<div className="w-1.5 h-6 bg-slate-400 rounded-full"></div>
<div>
<h2 className="text-lg font-bold text-slate-800"></h2>
<p className="text-[10px] text-slate-400 font-medium">*</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setIsRegionFilterOpen(!isRegionFilterOpen)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
isRegionFilterOpen || Object.values(regionFilters).some(v => v !== '')
? 'bg-slate-200 text-slate-900 shadow-sm'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
<Filter size={14} />
<span></span>
{Object.values(regionFilters).some(v => v !== '') && (
<span className="w-2 h-2 bg-red-500 rounded-full animate-pulse"></span>
)}
</button>
<AnimatePresence>
{isRegionFilterOpen && (
<>
<div className="fixed inset-0 z-40" onClick={() => setIsRegionFilterOpen(false)} />
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute top-full right-4 mt-2 w-72 bg-white rounded-xl shadow-2xl border border-gray-100 z-50 p-4 text-gray-800"
>
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-bold text-gray-900"></h3>
<button
onClick={() => setRegionFilters({ region: '', city: '', customer: '' })}
className="text-[10px] text-slate-600 hover:text-slate-700 font-medium"
>
</button>
</div>
<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={14} />
<input
type="text"
placeholder="搜索客户名称..."
className="w-full bg-gray-50 border border-gray-200 rounded-lg py-2 pl-9 pr-3 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all"
value={regionFilters.customer}
onChange={(e) => setRegionFilters(prev => ({ ...prev, customer: e.target.value }))}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<select
className="w-full bg-gray-50 border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all cursor-pointer"
value={regionFilters.region}
onChange={(e) => setRegionFilters(prev => ({ ...prev, region: e.target.value }))}
>
<option value=""></option>
{uniqueRegions.map(r => <option key={r} value={r}>{r}</option>)}
</select>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<select
className="w-full bg-gray-50 border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all cursor-pointer"
value={regionFilters.city}
onChange={(e) => setRegionFilters(prev => ({ ...prev, city: e.target.value }))}
>
<option value=""></option>
{uniqueCities.map(c => <option key={c} value={c}>{c}</option>)}
</select>
</div>
</div>
</div>
<div className="mt-6 pt-4 border-t border-gray-50">
<button
onClick={() => setIsRegionFilterOpen(false)}
className="w-full bg-slate-800 text-white py-2 rounded-lg text-xs font-bold hover:bg-slate-900 transition-colors shadow-lg shadow-slate-900/20"
>
</button>
</div>
</motion.div>
</>
)}
</AnimatePresence>
</div>
</div>
<div className="p-2">
<div className="hidden lg:block overflow-x-auto">
<table className="w-full text-left border-collapse min-w-[1000px]">
<thead>
<tr className="bg-slate-50 text-slate-500 text-[11px] uppercase tracking-wider border-b border-slate-100">
<th className="p-2 font-semibold border-r border-slate-100 w-48"> / / </th>
<th className="p-2 font-semibold border-r border-slate-100 text-center w-24"></th>
<th className="p-2 font-semibold border-r border-slate-100 text-center w-24 text-green-600"></th>
<th className="p-2 font-semibold border-r border-slate-100 text-center w-24 text-orange-600"></th>
<th className="p-2 font-semibold text-center bg-slate-100/50 w-32"></th>
</tr>
</thead>
<tbody className="text-xs">
{uniqueRegions.filter(r => !regionFilters.region || r === regionFilters.region).map((region) => {
const regionStats = customerData.filter(s => {
const matchRegion = s.region === region;
const matchCity = !regionFilters.city || s.city === regionFilters.city;
const matchCustomer = !regionFilters.customer || s.customer.toLowerCase().includes(regionFilters.customer.toLowerCase());
return matchRegion && matchCity && matchCustomer;
});
const totalAssets = regionStats.reduce((acc, s) => acc + s.total, 0);
if (totalAssets === 0) return null;
const isExpanded = expandedRegions.has(region);
return (
<React.Fragment key={region}>
<tr
className={`border-b border-slate-100 cursor-pointer transition-colors ${isExpanded ? 'bg-slate-50' : 'bg-white hover:bg-slate-50/50'}`}
onClick={() => toggleRegion(region)}
>
<td className="p-2 font-bold text-slate-700 flex items-center gap-2">
{isExpanded ? <ChevronDown size={14} className="text-slate-400" /> : <ChevronRight size={14} className="text-slate-400" />}
<Truck size={14} className="text-slate-400" />
{region}
</td>
<td className="p-2 text-center font-bold text-slate-600">{totalAssets}</td>
<td
className="p-2 text-center text-green-600 font-bold cursor-pointer hover:bg-green-50"
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', source: 'asset' });
}}
>
{Math.floor(totalAssets * 0.8)}
</td>
<td
className="p-2 text-center text-orange-600 font-bold cursor-pointer hover:bg-orange-50"
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending', source: 'asset' });
}}
>
{Math.floor(totalAssets * 0.05)}
</td>
<td className="p-2 text-center text-slate-500 font-medium">
{regionStats.slice(0, 2).map(s => s.customer).join(', ')}
</td>
</tr>
{isExpanded && ['4.5T', '18T', '49T'].map(type => {
const typeTotal = regionStats.reduce((acc, s) => {
if (type === '4.5T') return acc + s.t4_5 + s.t4_5c;
if (type === '18T') return acc + s.t18;
if (type === '49T') return acc + s.t49;
return acc;
}, 0);
if (typeTotal === 0) return null;
return (
<React.Fragment key={type}>
<tr className="border-b border-gray-50 hover:bg-gray-50">
<td className="p-2 pl-8 text-gray-500 flex items-center gap-2">
<div className="w-1 h-1 bg-slate-300 rounded-full"></div>
{type}
</td>
<td className="p-2 text-center text-gray-600">{typeTotal}</td>
<td
className="p-2 text-center text-green-600 cursor-pointer hover:bg-green-50"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type, category: 'Operating', source: 'asset' })}
>
{Math.floor(typeTotal * 0.8)}
</td>
<td
className="p-2 text-center text-orange-600 cursor-pointer hover:bg-orange-50"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type, category: 'Pending', source: 'asset' })}
>
{Math.floor(typeTotal * 0.05)}
</td>
<td className="p-2 text-center text-gray-400 italic">
{regionStats.filter(s => {
if (type === '4.5T') return (s.t4_5 + s.t4_5c) > 0;
if (type === '18T') return s.t18 > 0;
if (type === '49T') return s.t49 > 0;
return false;
}).map(s => s.customer).join(', ')}
</td>
</tr>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</tbody>
</table>
</div>
{/* Mobile View (Region) */}
<div className="lg:hidden p-2 space-y-3">
{uniqueRegions.filter(r => !regionFilters.region || r === regionFilters.region).map((region) => {
const regionStats = customerData.filter(s => {
const matchRegion = s.region === region;
const matchCity = !regionFilters.city || s.city === regionFilters.city;
const matchCustomer = !regionFilters.customer || s.customer.toLowerCase().includes(regionFilters.customer.toLowerCase());
return matchRegion && matchCity && matchCustomer;
});
const totalAssets = regionStats.reduce((acc, s) => acc + s.total, 0);
if (totalAssets === 0) return null;
const isExpanded = expandedRegions.has(region);
return (
<div key={region} className="bg-slate-50/50 rounded-xl border border-slate-100 overflow-hidden">
<div
className="bg-white p-3 flex justify-between items-center cursor-pointer"
onClick={() => toggleRegion(region)}
>
<div className="flex items-center gap-2 font-bold text-slate-700">
{isExpanded ? <ChevronDown size={14} className="text-slate-400" /> : <ChevronRight size={14} className="text-slate-400" />}
<Truck size={14} className="text-slate-400" />
{region}
</div>
<div className="text-xs font-bold text-slate-500">: {totalAssets}</div>
</div>
{isExpanded && (
<>
<div className="p-2 grid grid-cols-2 gap-2 text-center border-t border-slate-100">
<div
className="bg-white p-2 rounded border border-slate-100 cursor-pointer active:bg-green-50"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', source: 'asset' })}
>
<div className="text-[9px] text-gray-400 uppercase"></div>
<div className="text-xs font-bold text-green-600">{Math.floor(totalAssets * 0.8)}</div>
</div>
<div
className="bg-white p-2 rounded border border-slate-100 cursor-pointer active:bg-orange-50"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending', source: 'asset' })}
>
<div className="text-[9px] text-gray-400 uppercase"></div>
<div className="text-xs font-bold text-orange-600">{Math.floor(totalAssets * 0.05)}</div>
</div>
</div>
<div className="px-2 pb-2 space-y-1">
{['4.5T', '18T', '49T'].map(type => {
const typeTotal = regionStats.reduce((acc, s) => {
if (type === '4.5T') return acc + s.t4_5 + s.t4_5c;
if (type === '18T') return acc + s.t18;
if (type === '49T') return acc + s.t49;
return acc;
}, 0);
if (typeTotal === 0) return null;
return (
<div key={type} className="flex justify-between items-center text-[10px] bg-white/80 px-2 py-1.5 rounded border border-slate-50">
<span className="text-gray-500">{type} </span>
<div className="flex gap-3">
<span
className="font-bold text-green-600 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type, category: 'Operating', source: 'asset' })}
>
:{Math.floor(typeTotal * 0.8)}
</span>
<span
className="font-bold text-orange-600 cursor-pointer"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type, category: 'Pending', source: 'asset' })}
>
:{Math.floor(typeTotal * 0.05)}
</span>
</div>
</div>
);
})}
</div>
</>
)}
</div>
);
})}
</div>
</div>
</section>
{/* Customer Operations Statistics Section */}
<section className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6">
<div className="p-3 sm:p-4 border-b border-gray-50 flex items-center justify-between bg-emerald-800 text-white relative">
<div className="flex items-center gap-3">
<div className="w-1.5 h-6 bg-emerald-400 rounded-full"></div>
<div>
<h2 className="text-lg font-bold"></h2>
<p className="text-[10px] opacity-70 font-medium">*</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setIsCustomerFilterOpen(!isCustomerFilterOpen)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
isCustomerFilterOpen || Object.values(customerFilters).some(v => v !== '')
? 'bg-emerald-400 text-emerald-900 shadow-lg shadow-emerald-900/20'
: 'bg-emerald-700/50 text-emerald-100 hover:bg-emerald-700'
}`}
>
<Filter size={14} />
<span></span>
{Object.values(customerFilters).some(v => v !== '') && (
<span className="w-2 h-2 bg-red-500 rounded-full animate-pulse"></span>
)}
</button>
<AnimatePresence>
{isCustomerFilterOpen && (
<>
{/* Backdrop for closing */}
<div
className="fixed inset-0 z-40"
onClick={() => setIsCustomerFilterOpen(false)}
/>
{/* Popover Content */}
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute top-full right-4 mt-2 w-72 bg-white rounded-xl shadow-2xl border border-gray-100 z-50 p-4 text-gray-800"
>
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-bold text-gray-900"></h3>
<button
onClick={() => setCustomerFilters({ customer: '', brand: '', department: '', manager: '', region: '' })}
className="text-[10px] text-emerald-600 hover:text-emerald-700 font-medium"
>
</button>
</div>
<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} />
<input
type="text"
placeholder="搜索客户..."
className="w-full bg-gray-50 border border-gray-200 rounded-lg py-2 pl-8 pr-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all"
value={customerFilters.customer}
onChange={(e) => setCustomerFilters(prev => ({ ...prev, customer: e.target.value }))}
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} />
<input
type="text"
placeholder="搜索负责人..."
className="w-full bg-gray-50 border border-gray-200 rounded-lg py-2 pl-8 pr-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all"
value={customerFilters.manager}
onChange={(e) => setCustomerFilters(prev => ({ ...prev, manager: e.target.value }))}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<select
className="w-full bg-gray-50 border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer"
value={customerFilters.brand}
onChange={(e) => setCustomerFilters(prev => ({ ...prev, brand: e.target.value }))}
>
<option value=""></option>
{uniqueBrands.map(b => <option key={b} value={b}>{b}</option>)}
</select>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<select
className="w-full bg-gray-50 border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer"
value={customerFilters.department}
onChange={(e) => setCustomerFilters(prev => ({ ...prev, department: e.target.value }))}
>
<option value=""></option>
{uniqueDepts.map(d => <option key={d} value={d}>{d}</option>)}
</select>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<select
className="w-full bg-gray-50 border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer"
value={customerFilters.region}
onChange={(e) => setCustomerFilters(prev => ({ ...prev, region: e.target.value }))}
>
<option value=""></option>
{uniqueRegions.map(r => <option key={r} value={r}>{r}</option>)}
</select>
</div>
</div>
</div>
<div className="mt-6 pt-4 border-t border-gray-50">
<button
onClick={() => setIsCustomerFilterOpen(false)}
className="w-full bg-emerald-600 text-white py-2 rounded-lg text-xs font-bold hover:bg-emerald-700 transition-colors shadow-lg shadow-emerald-600/20"
>
</button>
</div>
</motion.div>
</>
)}
</AnimatePresence>
</div>
</div>
<div className="p-0 sm:p-2">
{/* Desktop Table View (Customer) */}
<div className="hidden lg:block overflow-x-auto">
<table className="w-full text-left border-collapse min-w-[1000px]">
<thead>
<tr className="bg-emerald-700 text-white text-[11px] uppercase tracking-wider border-b border-emerald-800">
<th className="p-2 font-semibold border-r border-emerald-600 w-40"></th>
<th className="p-2 font-semibold border-r border-emerald-600 w-24"></th>
<th className="p-2 font-semibold border-r border-emerald-600 w-24"></th>
<th className="p-2 font-semibold border-r border-emerald-600 text-center w-20">4.5T</th>
<th className="p-2 font-semibold border-r border-emerald-600 text-center w-20">4.5T冷链</th>
<th className="p-2 font-semibold border-r border-emerald-600 text-center w-20">18T</th>
<th className="p-2 font-semibold border-r border-emerald-600 text-center w-20">49T</th>
<th className="p-2 font-semibold border-r border-emerald-600 text-center w-20"></th>
<th className="p-2 font-semibold border-r border-emerald-600 text-center w-20"></th>
<th className="p-2 font-semibold text-center bg-emerald-900/40 w-24"></th>
</tr>
</thead>
<tbody className="text-xs">
{filteredCustomerStats.map((cust) => {
const isExpanded = expandedCustomers.has(cust.customer);
return (
<React.Fragment key={cust.customer}>
<tr
className={`cursor-pointer transition-all border-b border-gray-100 ${
isExpanded ? 'bg-emerald-50/50' : 'hover:bg-gray-50'
}`}
onClick={() => toggleCustomer(cust.customer)}
>
<td className="p-2 border-r border-gray-100 font-bold text-gray-800 flex items-center gap-2">
{isExpanded ? <ChevronDown size={14} className="text-emerald-600" /> : <ChevronRight size={14} className="text-gray-400" />}
{cust.customer}
</td>
<td className="p-2 border-r border-gray-100 text-gray-600 text-center">
<span className="bg-gray-100 px-2 py-0.5 rounded text-[10px] font-medium">{cust.region}</span>
</td>
<td className="p-2 border-r border-gray-100 text-gray-600 text-center">{cust.manager}</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-500 cursor-pointer hover:bg-emerald-50 transition-colors" onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', source: 'customer' }); }}>{cust.t4_5}</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-500 cursor-pointer hover:bg-emerald-50 transition-colors" onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', source: 'customer' }); }}>{cust.t4_5c}</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-500 cursor-pointer hover:bg-emerald-50 transition-colors" onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '18T', source: 'customer' }); }}>{cust.t18}</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-500 cursor-pointer hover:bg-emerald-50 transition-colors" onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '49T', source: 'customer' }); }}>{cust.t49}</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-500 cursor-pointer hover:bg-emerald-50 transition-colors" onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer' }); }}>{cust.trailer}</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-500 cursor-pointer hover:bg-emerald-50 transition-colors" onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer' }); }}>{cust.other}</td>
<td className="p-2 text-center font-bold bg-emerald-50 text-emerald-800 cursor-pointer hover:bg-emerald-100 transition-colors" onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, source: 'customer' }); }}>{cust.total}</td>
</tr>
{isExpanded && (
<tr className="bg-gray-50/30">
<td colSpan={9} className="p-2">
<div className="grid grid-cols-4 gap-2">
<div className="bg-white p-2 rounded border border-gray-100 shadow-sm">
<div className="text-[10px] text-gray-400 uppercase mb-1"></div>
<div className="text-sm font-bold text-gray-700">{cust.customer}</div>
<div className="text-xs text-gray-500 mt-1">: {cust.manager}</div>
</div>
<div className="bg-white p-2 rounded border border-gray-100 shadow-sm">
<div className="text-[10px] text-gray-400 uppercase mb-1"></div>
<div className="text-sm font-bold text-emerald-600">
{cust.t49 > cust.t18 ? '49T 重卡' : (cust.t18 > cust.t4_5c ? '18T 货车' : '4.5T 轻卡')}
</div>
</div>
<div className="bg-white p-2 rounded border border-gray-100 shadow-sm">
<div className="text-[10px] text-gray-400 uppercase mb-1"></div>
<div className="text-sm font-bold text-green-600"></div>
</div>
<div className="bg-white p-2 rounded border border-gray-100 shadow-sm">
<div className="text-[10px] text-gray-400 uppercase mb-1"></div>
<div className="text-sm font-bold text-gray-700">
{((cust.total / deptData.reduce((s, d) => s + d.totalAssets, 0)) * 100).toFixed(1)}%
</div>
</div>
</div>
</td>
</tr>
)}
</React.Fragment>
);
})}
</tbody>
</table>
</div>
{/* Mobile Card View (Customer) */}
<div className="lg:hidden p-2 space-y-2">
{filteredCustomerStats.map((cust) => {
const isExpanded = expandedCustomers.has(cust.customer);
return (
<div key={cust.customer} className="bg-white rounded border border-gray-100 shadow-sm overflow-hidden">
<div
className={`p-2 flex justify-between items-center cursor-pointer transition-colors ${
isExpanded ? 'bg-emerald-50' : 'bg-gray-50'
}`}
onClick={() => toggleCustomer(cust.customer)}
>
<div className="flex items-center gap-2">
{isExpanded ? <ChevronDown size={16} className="text-emerald-600" /> : <ChevronRight size={16} className="text-gray-400" />}
<div className="flex flex-col">
<span className="font-bold text-gray-800 text-sm">{cust.customer}</span>
<span className="text-[10px] text-emerald-600 font-medium">{cust.region}</span>
</div>
</div>
<div className="text-xs font-bold text-emerald-700 bg-emerald-100 px-2 py-0.5 rounded-full">
: {cust.total}
</div>
</div>
{isExpanded && (
<div className="p-2 space-y-3 bg-white">
{/* Details Cards for Mobile */}
<div className="grid grid-cols-2 gap-2">
<div className="bg-gray-50 p-2 rounded border border-gray-100">
<div className="text-[8px] text-gray-400 uppercase mb-1"></div>
<div className="text-[10px] font-bold text-gray-700">{cust.customer}</div>
<div className="text-[8px] text-gray-500 mt-0.5">: {cust.manager}</div>
</div>
<div className="bg-gray-50 p-2 rounded border border-gray-100">
<div className="text-[8px] text-gray-400 uppercase mb-1"></div>
<div className="text-xs font-bold text-emerald-600">
{cust.t49 > cust.t18 ? '49T 重卡' : (cust.t18 > cust.t4_5c ? '18T 货车' : '4.5T 轻卡')}
</div>
</div>
<div className="bg-gray-50 p-2 rounded border border-gray-100">
<div className="text-[8px] text-gray-400 uppercase mb-1"></div>
<div className="text-xs font-bold text-green-600"></div>
</div>
<div className="bg-gray-50 p-2 rounded border border-gray-100">
<div className="text-[8px] text-gray-400 uppercase mb-1"></div>
<div className="text-xs font-bold text-gray-700">
{((cust.total / deptData.reduce((s, d) => s + d.totalAssets, 0)) * 100).toFixed(1)}%
</div>
</div>
</div>
<div className="border-t border-gray-50 pt-2">
<div className="text-[8px] text-gray-400 uppercase mb-2"></div>
<div className="grid grid-cols-3 gap-2">
<div
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', source: 'customer' })}
>
<div className="text-[8px] text-gray-400 uppercase">4.5T</div>
<div className="text-[10px] font-bold text-gray-600">{cust.t4_5}</div>
</div>
<div
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', source: 'customer' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{cust.t4_5c}</div>
</div>
<div
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '18T', source: 'customer' })}
>
<div className="text-[8px] text-gray-400 uppercase">18T</div>
<div className="text-[10px] font-bold text-gray-600">{cust.t18}</div>
</div>
<div
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '49T', source: 'customer' })}
>
<div className="text-[8px] text-gray-400 uppercase">49T</div>
<div className="text-[10px] font-bold text-gray-600">{cust.t49}</div>
</div>
<div
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{cust.trailer}</div>
</div>
<div
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer' })}
>
<div className="text-[8px] text-gray-400 uppercase"></div>
<div className="text-[10px] font-bold text-gray-600">{cust.other}</div>
</div>
</div>
</div>
</div>
)}
</div>
);
})}
</div>
</div>
</section>
{/* 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">
<motion.div
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]`}
>
<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">
<Truck size={18} className="text-blue-400" />
{showPlateNumbers.manager ? `${showPlateNumbers.manager}${showPlateNumbers.type || ''}车辆` :
showPlateNumbers.customer ? `${showPlateNumbers.customer}${showPlateNumbers.type || ''}车辆` :
showPlateNumbers.batch === 'All' ? '全量批次' : `${showPlateNumbers.batch} 批次`} -
</h3>
<p className="text-[10px] opacity-60 mt-0.5">
{showPlateNumbers.model === 'All' ? '全量型号' : showPlateNumbers.model} |
{showPlateNumbers.category === 'Pending' ? '待交车' :
showPlateNumbers.category === 'Delivered' ? '本周已交车' :
showPlateNumbers.category === 'Returned' ? '已还车' :
showPlateNumbers.category === 'Replaced' ? '已替换' :
showPlateNumbers.category === 'Inventory' ? `${showPlateNumbers.location}库存` :
showPlateNumbers.category === 'Operating' ? '正在运营' : '全部状态'}
</p>
</div>
<button onClick={() => setShowPlateNumbers(null)} className="hover:bg-white/10 p-2 rounded-full transition-colors">
<PlusCircle className="rotate-45" size={24} />
</button>
</div>
<div className="flex-1 overflow-auto p-0 sm:p-4 bg-gray-50 min-h-0 overscroll-contain">
{modalLoading ? (
<div className="flex items-center justify-center py-16">
<Loader2 className="animate-spin text-blue-500" size={32} />
</div>
) : 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">
<thead>
<tr className="bg-slate-700 text-white text-[10px] uppercase tracking-wider">
<th className="p-2 font-semibold border-r border-slate-600 text-center"></th>
<th className="p-2 font-semibold border-r border-slate-600 text-center"></th>
<th className="p-2 font-semibold text-center"></th>
</tr>
</thead>
<tbody className="text-[11px]">
{modalWeeklyDetail.map((v, i) => (
<tr key={`${v.truck_id}-${i}`} className={`border-b border-gray-100 hover:bg-blue-50/50 transition-colors ${i % 2 === 0 ? 'bg-white' : 'bg-gray-50/30'}`}>
<td className="p-2 border-r border-gray-100 font-mono font-bold text-blue-700 text-center">{v.plate_number}</td>
<td className="p-2 border-r border-gray-100 text-gray-600 text-center">{v.customer_name || '—'}</td>
<td className="p-2 text-gray-500 text-center">{v.handover_date || '—'}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden w-full">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-700 text-white text-[10px] uppercase tracking-wider">
{showPlateNumbers.source === 'customer' ? (
<>
<th className="p-2 font-semibold border-r border-slate-600 sticky left-0 bg-slate-700 z-10 w-10 text-center"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-24"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-24"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-16"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-16"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-48"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-48"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-24"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-16 text-center"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-24 text-center"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-24 text-center"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-24"></th>
<th className="p-2 font-semibold border-r border-slate-600 w-20 text-center"></th>
<th className="p-2 font-semibold w-48"></th>
</>
) : (
<>
<th className="p-2 font-semibold border-r border-slate-600 text-center"></th>
{showPlateNumbers.source !== 'asset' && (
<th className="p-2 font-semibold border-r border-slate-600 text-center"></th>
)}
<th className="p-2 font-semibold border-r border-slate-600 text-center"></th>
<th className="p-2 font-semibold border-r border-slate-600 text-center"></th>
<th className="p-2 font-semibold text-center"></th>
</>
)}
</tr>
</thead>
<tbody className="text-[11px]">
{modalVehicles.map((v, idx) => (
<tr key={v.id} className={`border-b border-gray-100 hover:bg-blue-50/50 transition-colors ${idx % 2 === 0 ? 'bg-white' : 'bg-gray-50/30'}`}>
{showPlateNumbers.source === 'customer' ? (
<>
<td className="p-2 border-r border-gray-100 text-center sticky left-0 bg-inherit z-10 font-bold text-gray-400">{'—'}</td>
<td className="p-2 border-r border-gray-100 text-gray-600">{v.departmentName || '—'}</td>
<td className="p-2 border-r border-gray-100 font-medium text-gray-700">{v.customerManager || '—'}</td>
<td className="p-2 border-r border-gray-100 text-gray-600">{v.brandLabel || '—'}</td>
<td className="p-2 border-r border-gray-100 text-gray-600">{v.type}</td>
<td className="p-2 border-r border-gray-100 text-gray-500 text-[10px] leading-tight">{v.subjectOrg || '—'}</td>
<td className="p-2 border-r border-gray-100 font-bold text-gray-800">{v.customerName || '—'}</td>
<td className="p-2 border-r border-gray-100 font-mono font-bold text-blue-700">{v.plateNumber}</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 ${
v.status === 'Operating' ? 'bg-green-100 text-green-700' :
v.status === 'Inventory' ? 'bg-blue-100 text-blue-700' : 'bg-red-100 text-red-700'
}`}>
{v.status === 'Operating' ? '在租' : v.status === 'Inventory' ? '库存' : '异常'}
</span>
</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-500">{'—'}</td>
<td className="p-2 border-r border-gray-100 text-center text-gray-500">{'—'}</td>
<td className="p-2 border-r border-gray-100 text-gray-600">{v.location}</td>
<td className="p-2 border-r border-gray-100 text-center font-bold text-orange-600">{'—'}</td>
<td className="p-2 text-gray-500 text-[10px] leading-tight">{v.orgName || '—'}</td>
</>
) : (
<>
<td className="p-2 border-r border-gray-100 font-mono font-bold text-blue-700 text-center">{v.plateNumber}</td>
{showPlateNumbers.source !== 'asset' && (
<td className="p-2 border-r border-gray-100 font-bold text-gray-800 text-center">{v.customerName || '—'}</td>
)}
<td className="p-2 border-r border-gray-100 text-gray-600 text-center">{v.brandLabel || '—'}</td>
<td className="p-2 border-r border-gray-100 text-gray-600 text-center">{v.type}</td>
<td className="p-2 text-gray-600 text-center">{v.location}</td>
</>
)}
</tr>
))}
{modalVehicles.length === 0 && (
<tr>
<td colSpan={showPlateNumbers.source === 'customer' ? 14 : (showPlateNumbers.source === 'asset' ? 4 : 5)} className="p-8 text-center text-gray-400 italic">
</td>
</tr>
)}
</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">{modalWeeklyDetail.length > 0 ? modalWeeklyDetail.length : modalVehicles.length}</span>
</div>
<button
onClick={() => setShowPlateNumbers(null)}
className="px-6 py-2 bg-slate-800 text-white rounded-lg text-xs font-bold hover:bg-slate-700 transition-colors shadow-sm"
>
</button>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
</div>
{/* Footer / Navigation */}
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 p-2 flex justify-around items-center md:hidden">
<button className="flex flex-col items-center text-blue-600">
<Activity size={20} />
<span className="text-[10px] mt-1"></span>
</button>
<button className="flex flex-col items-center text-gray-400">
<Warehouse size={20} />
<span className="text-[10px] mt-1"></span>
</button>
<button className="flex flex-col items-center text-gray-400">
<History size={20} />
<span className="text-[10px] mt-1"></span>
</button>
</div>
</div>
);
}