fix: replace 3 operations sections with exact prototype code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
486
src/App.tsx
486
src/App.tsx
@@ -33,6 +33,8 @@ export default function App() {
|
|||||||
customer?: string;
|
customer?: string;
|
||||||
isColdChain?: boolean;
|
isColdChain?: boolean;
|
||||||
isTrailer?: boolean;
|
isTrailer?: boolean;
|
||||||
|
type?: string;
|
||||||
|
source?: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
// Data state
|
// Data state
|
||||||
@@ -128,8 +130,30 @@ export default function App() {
|
|||||||
if (cat === 'Operating') params.category = 'Operating';
|
if (cat === 'Operating') params.category = 'Operating';
|
||||||
if (showPlateNumbers.manager) params.manager = showPlateNumbers.manager;
|
if (showPlateNumbers.manager) params.manager = showPlateNumbers.manager;
|
||||||
if (showPlateNumbers.customer) params.customer = showPlateNumbers.customer;
|
if (showPlateNumbers.customer) params.customer = showPlateNumbers.customer;
|
||||||
if (showPlateNumbers.isColdChain !== undefined) params.isColdChain = String(showPlateNumbers.isColdChain);
|
if (!showPlateNumbers.type) {
|
||||||
if (showPlateNumbers.isTrailer !== undefined) params.isTrailer = String(showPlateNumbers.isTrailer);
|
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)
|
fetchVehicleList(params)
|
||||||
.then(setModalVehicles)
|
.then(setModalVehicles)
|
||||||
.catch(() => setModalVehicles([]))
|
.catch(() => setModalVehicles([]))
|
||||||
@@ -860,8 +884,9 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Department Operations Statistics */}
|
{/* Department Operations Statistics */}
|
||||||
<section className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden mb-6">
|
<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="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="flex items-center gap-3">
|
||||||
<div className="w-1.5 h-6 bg-blue-600 rounded-full"></div>
|
<div className="w-1.5 h-6 bg-blue-600 rounded-full"></div>
|
||||||
@@ -871,9 +896,9 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-0 sm:p-2 bg-gray-50/30">
|
<div className="p-0 sm:p-2 bg-gray-50/30">
|
||||||
{/* Overall Total Summary (Compact) */}
|
{/* Overall Total Summary (Compact) - Moved to Top */}
|
||||||
<div className="m-2 bg-slate-800 rounded-xl p-3 text-white shadow-lg">
|
<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="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@@ -894,7 +919,9 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<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-[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>
|
<span className="text-xl font-black text-blue-400">
|
||||||
|
{'—'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -902,13 +929,13 @@ export default function App() {
|
|||||||
{/* Controls Row: Toggles Left, Filter Right */}
|
{/* Controls Row: Toggles Left, Filter Right */}
|
||||||
<div className="px-2 mb-2 flex items-center justify-between gap-4">
|
<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">
|
<div className="flex bg-gray-200/50 p-1 rounded-lg shadow-inner">
|
||||||
<button
|
<button
|
||||||
onClick={() => setDeptViewMode('department')}
|
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'}`}
|
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>
|
||||||
<button
|
<button
|
||||||
onClick={() => setDeptViewMode('manager')}
|
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'}`}
|
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'}`}
|
||||||
>
|
>
|
||||||
@@ -920,7 +947,7 @@ export default function App() {
|
|||||||
{deptViewMode === 'manager' && (
|
{deptViewMode === 'manager' && (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Filter className="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" size={14} />
|
<Filter className="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" size={14} />
|
||||||
<select
|
<select
|
||||||
value={selectedManager}
|
value={selectedManager}
|
||||||
onChange={(e) => setSelectedManager(e.target.value)}
|
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"
|
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"
|
||||||
@@ -971,7 +998,7 @@ export default function App() {
|
|||||||
const isExpanded = expandedDepts.has(dept.department);
|
const isExpanded = expandedDepts.has(dept.department);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={dept.department}>
|
<React.Fragment key={dept.department}>
|
||||||
<tr
|
<tr
|
||||||
className={`cursor-pointer transition-all border-b border-gray-100 ${
|
className={`cursor-pointer transition-all border-b border-gray-100 ${
|
||||||
isExpanded ? 'bg-blue-50/50' : 'hover:bg-gray-50'
|
isExpanded ? 'bg-blue-50/50' : 'hover:bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
@@ -980,11 +1007,20 @@ export default function App() {
|
|||||||
<td className="p-2 border-r border-gray-100 font-bold text-gray-800">
|
<td className="p-2 border-r border-gray-100 font-bold text-gray-800">
|
||||||
{dept.department}
|
{dept.department}
|
||||||
</td>
|
</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">
|
||||||
|
<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">
|
<td className="p-2 border-r border-gray-100 text-center font-black text-gray-800 text-sm">
|
||||||
{dept.totalAssets}
|
{dept.totalAssets}
|
||||||
</td>
|
</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-400 text-sm">—</span></div></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">
|
<td className="p-2 border-r border-gray-100 text-center font-black text-green-500 text-sm">
|
||||||
{dept.operatingCount}
|
{dept.operatingCount}
|
||||||
</td>
|
</td>
|
||||||
@@ -1003,7 +1039,7 @@ export default function App() {
|
|||||||
const isManagerExpanded = expandedManagerDetails.has(m.manager);
|
const isManagerExpanded = expandedManagerDetails.has(m.manager);
|
||||||
return (
|
return (
|
||||||
<div key={m.manager} className="bg-white rounded-lg border border-gray-100 shadow-sm overflow-hidden">
|
<div key={m.manager} className="bg-white rounded-lg border border-gray-100 shadow-sm overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="p-2 flex justify-between items-center cursor-pointer hover:bg-gray-50 transition-colors"
|
className="p-2 flex justify-between items-center cursor-pointer hover:bg-gray-50 transition-colors"
|
||||||
onClick={() => toggleManagerDetails(m.manager)}
|
onClick={() => toggleManagerDetails(m.manager)}
|
||||||
>
|
>
|
||||||
@@ -1011,58 +1047,58 @@ export default function App() {
|
|||||||
{isManagerExpanded ? <ChevronDown size={14} className="text-blue-500" /> : <ChevronRight size={14} className="text-gray-300" />}
|
{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>
|
<span className="font-bold text-gray-700 text-xs">{m.manager}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating' });
|
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"
|
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}
|
合计: {m.total}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isManagerExpanded && (
|
{isManagerExpanded && (
|
||||||
<div className="p-2 pt-0 border-t border-gray-50 bg-gray-50/30">
|
<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="grid grid-cols-3 gap-1 mt-2">
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
|
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, vehicleType: '4.5T普货' })}
|
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-[8px] text-gray-400 uppercase">4.5T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
|
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, vehicleType: '4.5T冷链' })}
|
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-[8px] text-gray-400 uppercase">冷链</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
|
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, vehicleType: '18T' })}
|
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-[8px] text-gray-400 uppercase">18T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
|
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, vehicleType: '49T' })}
|
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-[8px] text-gray-400 uppercase">49T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
|
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, isTrailer: true })}
|
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-[8px] text-gray-400 uppercase">挂车</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded cursor-pointer hover:bg-blue-50 transition-colors border border-gray-100"
|
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, vehicleType: '其他' })}
|
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-[8px] text-gray-400 uppercase">其他</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
|
||||||
@@ -1085,7 +1121,7 @@ export default function App() {
|
|||||||
const isManagerExpanded = expandedManagerDetails.has(m.manager);
|
const isManagerExpanded = expandedManagerDetails.has(m.manager);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={m.manager}>
|
<React.Fragment key={m.manager}>
|
||||||
<tr
|
<tr
|
||||||
className="border-b border-gray-100 hover:bg-gray-50 transition-colors cursor-pointer"
|
className="border-b border-gray-100 hover:bg-gray-50 transition-colors cursor-pointer"
|
||||||
onClick={() => toggleManagerDetails(m.manager)}
|
onClick={() => toggleManagerDetails(m.manager)}
|
||||||
>
|
>
|
||||||
@@ -1094,11 +1130,11 @@ export default function App() {
|
|||||||
{m.manager}
|
{m.manager}
|
||||||
</td>
|
</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-gray-600">{m.department}</td>
|
||||||
<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"
|
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) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating' });
|
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, source: 'department' });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{m.total}
|
{m.total}
|
||||||
@@ -1110,10 +1146,10 @@ export default function App() {
|
|||||||
<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">
|
<td className="p-2 text-center">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating' });
|
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating', source: 'department' });
|
||||||
}}
|
}}
|
||||||
className="text-blue-500 hover:text-blue-700 transition-colors"
|
className="text-blue-500 hover:text-blue-700 transition-colors"
|
||||||
>
|
>
|
||||||
@@ -1125,27 +1161,27 @@ export default function App() {
|
|||||||
<tr className="bg-gray-50/50 border-b border-gray-100">
|
<tr className="bg-gray-50/50 border-b border-gray-100">
|
||||||
<td colSpan={10} className="p-0">
|
<td colSpan={10} className="p-0">
|
||||||
<div className="grid grid-cols-6 text-[10px] bg-white/50">
|
<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, vehicleType: '4.5T普货' })}>
|
<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="text-gray-400 uppercase mb-1">4.5T</span>
|
||||||
<span className="font-bold text-gray-600">{m.t4_5}</span>
|
<span className="font-bold text-gray-600">{m.t4_5}</span>
|
||||||
</div>
|
</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, vehicleType: '4.5T冷链' })}>
|
<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="text-gray-400 uppercase mb-1">冷链</span>
|
||||||
<span className="font-bold text-gray-600">{m.t4_5c}</span>
|
<span className="font-bold text-gray-600">{m.t4_5c}</span>
|
||||||
</div>
|
</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, vehicleType: '18T' })}>
|
<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="text-gray-400 uppercase mb-1">18T</span>
|
||||||
<span className="font-bold text-gray-600">{m.t18}</span>
|
<span className="font-bold text-gray-600">{m.t18}</span>
|
||||||
</div>
|
</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, vehicleType: '49T' })}>
|
<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="text-gray-400 uppercase mb-1">49T</span>
|
||||||
<span className="font-bold text-gray-600">{m.t49}</span>
|
<span className="font-bold text-gray-600">{m.t49}</span>
|
||||||
</div>
|
</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, isTrailer: true })}>
|
<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="text-gray-400 uppercase mb-1">挂车</span>
|
||||||
<span className="font-bold text-gray-600">{m.trailer}</span>
|
<span className="font-bold text-gray-600">{m.trailer}</span>
|
||||||
</div>
|
</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, vehicleType: '其他' })}>
|
<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="text-gray-400 uppercase mb-1">其他</span>
|
||||||
<span className="font-bold text-gray-600">{m.other}</span>
|
<span className="font-bold text-gray-600">{m.other}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1168,7 +1204,7 @@ export default function App() {
|
|||||||
const isExpanded = expandedDepts.has(dept.department);
|
const isExpanded = expandedDepts.has(dept.department);
|
||||||
return (
|
return (
|
||||||
<div key={dept.department} className="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
<div key={dept.department} className="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="p-3 cursor-pointer"
|
className="p-3 cursor-pointer"
|
||||||
onClick={() => toggleDept(dept.department)}
|
onClick={() => toggleDept(dept.department)}
|
||||||
>
|
>
|
||||||
@@ -1185,7 +1221,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-[8px] text-gray-400 uppercase font-bold mb-0.5">里程</div>
|
<div className="text-[8px] text-gray-400 uppercase font-bold mb-0.5">里程</div>
|
||||||
<div className="text-xs font-black text-gray-400">—</div>
|
<div className="text-xs font-black text-gray-800">{'—'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-[8px] text-green-500 uppercase font-bold mb-0.5">运营</div>
|
<div className="text-[8px] text-green-500 uppercase font-bold mb-0.5">运营</div>
|
||||||
@@ -1206,7 +1242,7 @@ export default function App() {
|
|||||||
const isManagerExpanded = expandedManagerDetails.has(m.manager);
|
const isManagerExpanded = expandedManagerDetails.has(m.manager);
|
||||||
return (
|
return (
|
||||||
<div key={m.manager} className="bg-white rounded border border-gray-100 shadow-sm overflow-hidden">
|
<div key={m.manager} className="bg-white rounded border border-gray-100 shadow-sm overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="p-2 flex justify-between items-center cursor-pointer"
|
className="p-2 flex justify-between items-center cursor-pointer"
|
||||||
onClick={() => toggleManagerDetails(m.manager)}
|
onClick={() => toggleManagerDetails(m.manager)}
|
||||||
>
|
>
|
||||||
@@ -1214,10 +1250,10 @@ export default function App() {
|
|||||||
{isManagerExpanded ? <ChevronDown size={12} className="text-blue-400" /> : <ChevronRight size={12} className="text-gray-300" />}
|
{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>
|
<span className="text-[11px] font-bold text-gray-700">{m.manager}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating' });
|
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"
|
className="text-[10px] font-bold text-blue-600 bg-blue-50 px-2 py-0.5 rounded"
|
||||||
>
|
>
|
||||||
@@ -1227,44 +1263,44 @@ export default function App() {
|
|||||||
|
|
||||||
{isManagerExpanded && (
|
{isManagerExpanded && (
|
||||||
<div className="p-2 border-t border-gray-50 bg-gray-50/30 grid grid-cols-3 gap-1">
|
<div className="p-2 border-t border-gray-50 bg-gray-50/30 grid grid-cols-3 gap-1">
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '4.5T普货' })}
|
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-[8px] text-gray-400 uppercase">4.5T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '4.5T冷链' })}
|
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-[8px] text-gray-400 uppercase">冷链</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '18T' })}
|
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-[8px] text-gray-400 uppercase">18T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '49T' })}
|
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-[8px] text-gray-400 uppercase">49T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, isTrailer: true })}
|
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-[8px] text-gray-400 uppercase">挂车</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '其他' })}
|
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-[8px] text-gray-400 uppercase">其他</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
|
||||||
@@ -1284,7 +1320,7 @@ export default function App() {
|
|||||||
const isManagerExpanded = expandedManagerDetails.has(m.manager);
|
const isManagerExpanded = expandedManagerDetails.has(m.manager);
|
||||||
return (
|
return (
|
||||||
<div key={m.manager} className="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
<div key={m.manager} className="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="p-2 cursor-pointer flex items-center justify-between gap-2"
|
className="p-2 cursor-pointer flex items-center justify-between gap-2"
|
||||||
onClick={() => toggleManagerDetails(m.manager)}
|
onClick={() => toggleManagerDetails(m.manager)}
|
||||||
>
|
>
|
||||||
@@ -1293,22 +1329,22 @@ export default function App() {
|
|||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<h3 className="text-sm font-bold text-gray-800 shrink-0">{m.manager}</h3>
|
<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>
|
<span className="text-[11px] text-gray-500 shrink-0">{m.department}</span>
|
||||||
<div
|
<div
|
||||||
className="text-[11px] font-bold text-blue-600 whitespace-nowrap"
|
className="text-[11px] font-bold text-blue-600 whitespace-nowrap"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating' });
|
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, source: 'department' });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
资产: {m.total}
|
资产: {m.total}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating' });
|
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"
|
className="text-blue-500 p-1 hover:bg-blue-50 rounded transition-colors flex-shrink-0"
|
||||||
>
|
>
|
||||||
@@ -1318,44 +1354,44 @@ export default function App() {
|
|||||||
|
|
||||||
{isManagerExpanded && (
|
{isManagerExpanded && (
|
||||||
<div className="p-2 border-t border-gray-50 bg-gray-50/30 grid grid-cols-3 gap-1">
|
<div className="p-2 border-t border-gray-50 bg-gray-50/30 grid grid-cols-3 gap-1">
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '4.5T普货' })}
|
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-[8px] text-gray-400 uppercase">4.5T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t4_5}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '4.5T冷链' })}
|
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-[8px] text-gray-400 uppercase">冷链</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t4_5c}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '18T' })}
|
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-[8px] text-gray-400 uppercase">18T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t18}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '49T' })}
|
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-[8px] text-gray-400 uppercase">49T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.t49}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, isTrailer: true })}
|
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-[8px] text-gray-400 uppercase">挂车</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.trailer}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-white p-1 rounded border border-gray-100 cursor-pointer"
|
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, vehicleType: '其他' })}
|
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-[8px] text-gray-400 uppercase">其他</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
|
<div className="text-[10px] font-bold text-gray-600">{m.other}</div>
|
||||||
@@ -1382,11 +1418,11 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsRegionFilterOpen(!isRegionFilterOpen)}
|
onClick={() => setIsRegionFilterOpen(!isRegionFilterOpen)}
|
||||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
|
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 !== '')
|
isRegionFilterOpen || Object.values(regionFilters).some(v => v !== '')
|
||||||
? 'bg-slate-200 text-slate-900 shadow-sm'
|
? 'bg-slate-200 text-slate-900 shadow-sm'
|
||||||
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -1401,7 +1437,7 @@ export default function App() {
|
|||||||
{isRegionFilterOpen && (
|
{isRegionFilterOpen && (
|
||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-40" onClick={() => setIsRegionFilterOpen(false)} />
|
<div className="fixed inset-0 z-40" onClick={() => setIsRegionFilterOpen(false)} />
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||||
@@ -1409,20 +1445,20 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-sm font-bold text-gray-900">区域筛选</h3>
|
<h3 className="text-sm font-bold text-gray-900">区域筛选</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setRegionFilters({ region: '', city: '', customer: '' })}
|
onClick={() => setRegionFilters({ region: '', city: '', customer: '' })}
|
||||||
className="text-[10px] text-slate-600 hover:text-slate-700 font-medium"
|
className="text-[10px] text-slate-600 hover:text-slate-700 font-medium"
|
||||||
>
|
>
|
||||||
重置所有
|
重置所有
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">客户名称</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">客户名称</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={14} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={14} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="搜索客户名称..."
|
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"
|
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"
|
||||||
@@ -1435,7 +1471,7 @@ export default function App() {
|
|||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">区域</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">区域</label>
|
||||||
<select
|
<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"
|
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}
|
value={regionFilters.region}
|
||||||
onChange={(e) => setRegionFilters(prev => ({ ...prev, region: e.target.value }))}
|
onChange={(e) => setRegionFilters(prev => ({ ...prev, region: e.target.value }))}
|
||||||
@@ -1446,7 +1482,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">城市</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">城市</label>
|
||||||
<select
|
<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"
|
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}
|
value={regionFilters.city}
|
||||||
onChange={(e) => setRegionFilters(prev => ({ ...prev, city: e.target.value }))}
|
onChange={(e) => setRegionFilters(prev => ({ ...prev, city: e.target.value }))}
|
||||||
@@ -1459,7 +1495,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 pt-4 border-t border-gray-50">
|
<div className="mt-6 pt-4 border-t border-gray-50">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsRegionFilterOpen(false)}
|
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"
|
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"
|
||||||
>
|
>
|
||||||
@@ -1486,70 +1522,88 @@ export default function App() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="text-xs">
|
<tbody className="text-xs">
|
||||||
{filteredRegionData.map((r) => {
|
{uniqueRegions.filter(r => !regionFilters.region || r === regionFilters.region).map((region) => {
|
||||||
if (r.totalAssets === 0) return null;
|
const regionStats = customerData.filter(s => {
|
||||||
const isExpanded = expandedRegions.has(r.region);
|
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 (
|
return (
|
||||||
<React.Fragment key={r.region}>
|
<React.Fragment key={region}>
|
||||||
<tr
|
<tr
|
||||||
className={`border-b border-slate-100 cursor-pointer transition-colors ${isExpanded ? 'bg-slate-50' : 'bg-white hover:bg-slate-50/50'}`}
|
className={`border-b border-slate-100 cursor-pointer transition-colors ${isExpanded ? 'bg-slate-50' : 'bg-white hover:bg-slate-50/50'}`}
|
||||||
onClick={() => toggleRegion(r.region)}
|
onClick={() => toggleRegion(region)}
|
||||||
>
|
>
|
||||||
<td className="p-2 font-bold text-slate-700 flex items-center gap-2">
|
<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" />}
|
{isExpanded ? <ChevronDown size={14} className="text-slate-400" /> : <ChevronRight size={14} className="text-slate-400" />}
|
||||||
<Truck size={14} className="text-slate-400" />
|
<Truck size={14} className="text-slate-400" />
|
||||||
{r.region}区域
|
{region}区域
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2 text-center font-bold text-slate-600">{r.totalAssets}</td>
|
<td className="p-2 text-center font-bold text-slate-600">{totalAssets}</td>
|
||||||
<td
|
<td
|
||||||
className="p-2 text-center text-green-600 font-bold cursor-pointer hover:bg-green-50"
|
className="p-2 text-center text-green-600 font-bold cursor-pointer hover:bg-green-50"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating' });
|
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', source: 'asset' });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{r.operatingCount}
|
{Math.floor(totalAssets * 0.8)}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="p-2 text-center text-orange-600 font-bold cursor-pointer hover:bg-orange-50"
|
className="p-2 text-center text-orange-600 font-bold cursor-pointer hover:bg-orange-50"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending' });
|
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending', source: 'asset' });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{r.inventoryCount}
|
{Math.floor(totalAssets * 0.05)}
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2 text-center text-slate-500 font-medium">
|
<td className="p-2 text-center text-slate-500 font-medium">
|
||||||
{r.customers.slice(0, 2).join(', ')}
|
{regionStats.slice(0, 2).map(s => s.customer).join(', ')}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{isExpanded && r.typeBreakdown.map(tb => {
|
{isExpanded && ['4.5T', '18T', '49T'].map(type => {
|
||||||
if (tb.total === 0) return null;
|
const typeTotal = regionStats.reduce((acc, s) => {
|
||||||
const vehicleType = tb.type === '4.5T' ? '4.5T普货' : tb.type;
|
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 (
|
return (
|
||||||
<React.Fragment key={tb.type}>
|
<React.Fragment key={type}>
|
||||||
<tr className="border-b border-gray-50 hover:bg-gray-50">
|
<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">
|
<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>
|
<div className="w-1 h-1 bg-slate-300 rounded-full"></div>
|
||||||
{tb.type} 车型
|
{type} 车型
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2 text-center text-gray-600">{tb.total}</td>
|
<td className="p-2 text-center text-gray-600">{typeTotal}</td>
|
||||||
<td
|
<td
|
||||||
className="p-2 text-center text-green-600 cursor-pointer hover:bg-green-50"
|
className="p-2 text-center text-green-600 cursor-pointer hover:bg-green-50"
|
||||||
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', vehicleType, category: 'Operating' })}
|
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type, category: 'Operating', source: 'asset' })}
|
||||||
>
|
>
|
||||||
{tb.operating}
|
{Math.floor(typeTotal * 0.8)}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="p-2 text-center text-orange-600 cursor-pointer hover:bg-orange-50"
|
className="p-2 text-center text-orange-600 cursor-pointer hover:bg-orange-50"
|
||||||
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', vehicleType, category: 'Pending' })}
|
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type, category: 'Pending', source: 'asset' })}
|
||||||
>
|
>
|
||||||
{tb.inventory}
|
{Math.floor(typeTotal * 0.05)}
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2 text-center text-gray-400 italic">
|
<td className="p-2 text-center text-gray-400 italic">
|
||||||
{tb.customers.join(', ')}
|
{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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
@@ -1564,60 +1618,73 @@ export default function App() {
|
|||||||
|
|
||||||
{/* Mobile View (Region) */}
|
{/* Mobile View (Region) */}
|
||||||
<div className="lg:hidden p-2 space-y-3">
|
<div className="lg:hidden p-2 space-y-3">
|
||||||
{filteredRegionData.map((r) => {
|
{uniqueRegions.filter(r => !regionFilters.region || r === regionFilters.region).map((region) => {
|
||||||
if (r.totalAssets === 0) return null;
|
const regionStats = customerData.filter(s => {
|
||||||
const isExpanded = expandedRegions.has(r.region);
|
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 (
|
return (
|
||||||
<div key={r.region} className="bg-slate-50/50 rounded-xl border border-slate-100 overflow-hidden">
|
<div key={region} className="bg-slate-50/50 rounded-xl border border-slate-100 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="bg-white p-3 flex justify-between items-center cursor-pointer"
|
className="bg-white p-3 flex justify-between items-center cursor-pointer"
|
||||||
onClick={() => toggleRegion(r.region)}
|
onClick={() => toggleRegion(region)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 font-bold text-slate-700">
|
<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" />}
|
{isExpanded ? <ChevronDown size={14} className="text-slate-400" /> : <ChevronRight size={14} className="text-slate-400" />}
|
||||||
<Truck size={14} className="text-slate-400" />
|
<Truck size={14} className="text-slate-400" />
|
||||||
{r.region}区域
|
{region}区域
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs font-bold text-slate-500">资产: {r.totalAssets}</div>
|
<div className="text-xs font-bold text-slate-500">资产: {totalAssets}</div>
|
||||||
</div>
|
</div>
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<>
|
<>
|
||||||
<div className="p-2 grid grid-cols-2 gap-2 text-center border-t border-slate-100">
|
<div className="p-2 grid grid-cols-2 gap-2 text-center border-t border-slate-100">
|
||||||
<div
|
<div
|
||||||
className="bg-white p-2 rounded border border-slate-100 cursor-pointer active:bg-green-50"
|
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' })}
|
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', source: 'asset' })}
|
||||||
>
|
>
|
||||||
<div className="text-[9px] text-gray-400 uppercase">运营中</div>
|
<div className="text-[9px] text-gray-400 uppercase">运营中</div>
|
||||||
<div className="text-xs font-bold text-green-600">{r.operatingCount}</div>
|
<div className="text-xs font-bold text-green-600">{Math.floor(totalAssets * 0.8)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="bg-white p-2 rounded border border-slate-100 cursor-pointer active:bg-orange-50"
|
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' })}
|
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending', source: 'asset' })}
|
||||||
>
|
>
|
||||||
<div className="text-[9px] text-gray-400 uppercase">待交车</div>
|
<div className="text-[9px] text-gray-400 uppercase">待交车</div>
|
||||||
<div className="text-xs font-bold text-orange-600">{r.inventoryCount}</div>
|
<div className="text-xs font-bold text-orange-600">{Math.floor(totalAssets * 0.05)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2 pb-2 space-y-1">
|
<div className="px-2 pb-2 space-y-1">
|
||||||
{r.typeBreakdown.map(tb => {
|
{['4.5T', '18T', '49T'].map(type => {
|
||||||
if (tb.total === 0) return null;
|
const typeTotal = regionStats.reduce((acc, s) => {
|
||||||
const vehicleType = tb.type === '4.5T' ? '4.5T普货' : tb.type;
|
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 (
|
return (
|
||||||
<div key={tb.type} className="flex justify-between items-center text-[10px] bg-white/80 px-2 py-1.5 rounded border border-slate-50">
|
<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">{tb.type} 车型</span>
|
<span className="text-gray-500">{type} 车型</span>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<span
|
<span
|
||||||
className="font-bold text-green-600 cursor-pointer"
|
className="font-bold text-green-600 cursor-pointer"
|
||||||
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', vehicleType, category: 'Operating' })}
|
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type, category: 'Operating', source: 'asset' })}
|
||||||
>
|
>
|
||||||
运:{tb.operating}
|
运:{Math.floor(typeTotal * 0.8)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="font-bold text-orange-600 cursor-pointer"
|
className="font-bold text-orange-600 cursor-pointer"
|
||||||
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', vehicleType, category: 'Pending' })}
|
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type, category: 'Pending', source: 'asset' })}
|
||||||
>
|
>
|
||||||
待:{tb.inventory}
|
待:{Math.floor(typeTotal * 0.05)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1645,11 +1712,11 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsCustomerFilterOpen(!isCustomerFilterOpen)}
|
onClick={() => setIsCustomerFilterOpen(!isCustomerFilterOpen)}
|
||||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
|
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 !== '')
|
isCustomerFilterOpen || Object.values(customerFilters).some(v => v !== '')
|
||||||
? 'bg-emerald-400 text-emerald-900 shadow-lg shadow-emerald-900/20'
|
? 'bg-emerald-400 text-emerald-900 shadow-lg shadow-emerald-900/20'
|
||||||
: 'bg-emerald-700/50 text-emerald-100 hover:bg-emerald-700'
|
: 'bg-emerald-700/50 text-emerald-100 hover:bg-emerald-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -1664,13 +1731,13 @@ export default function App() {
|
|||||||
{isCustomerFilterOpen && (
|
{isCustomerFilterOpen && (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop for closing */}
|
{/* Backdrop for closing */}
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-40"
|
className="fixed inset-0 z-40"
|
||||||
onClick={() => setIsCustomerFilterOpen(false)}
|
onClick={() => setIsCustomerFilterOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Popover Content */}
|
{/* Popover Content */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||||
@@ -1678,22 +1745,22 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-sm font-bold text-gray-900">数据筛选</h3>
|
<h3 className="text-sm font-bold text-gray-900">数据筛选</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCustomerFilters({ customer: '', brand: '', department: '', manager: '', region: '' })}
|
onClick={() => setCustomerFilters({ customer: '', brand: '', department: '', manager: '', region: '' })}
|
||||||
className="text-[10px] text-emerald-600 hover:text-emerald-700 font-medium"
|
className="text-[10px] text-emerald-600 hover:text-emerald-700 font-medium"
|
||||||
>
|
>
|
||||||
重置所有
|
重置所有
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">客户名称</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">客户名称</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} />
|
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="搜索客户..."
|
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"
|
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}
|
value={customerFilters.customer}
|
||||||
onChange={(e) => setCustomerFilters(prev => ({ ...prev, customer: e.target.value }))}
|
onChange={(e) => setCustomerFilters(prev => ({ ...prev, customer: e.target.value }))}
|
||||||
@@ -1705,9 +1772,9 @@ export default function App() {
|
|||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">业务负责人</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">业务负责人</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} />
|
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="搜索负责人..."
|
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"
|
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}
|
value={customerFilters.manager}
|
||||||
onChange={(e) => setCustomerFilters(prev => ({ ...prev, manager: e.target.value }))}
|
onChange={(e) => setCustomerFilters(prev => ({ ...prev, manager: e.target.value }))}
|
||||||
@@ -1718,7 +1785,7 @@ export default function App() {
|
|||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">品牌</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">品牌</label>
|
||||||
<select
|
<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"
|
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}
|
value={customerFilters.brand}
|
||||||
onChange={(e) => setCustomerFilters(prev => ({ ...prev, brand: e.target.value }))}
|
onChange={(e) => setCustomerFilters(prev => ({ ...prev, brand: e.target.value }))}
|
||||||
@@ -1729,7 +1796,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">部门</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">部门</label>
|
||||||
<select
|
<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"
|
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}
|
value={customerFilters.department}
|
||||||
onChange={(e) => setCustomerFilters(prev => ({ ...prev, department: e.target.value }))}
|
onChange={(e) => setCustomerFilters(prev => ({ ...prev, department: e.target.value }))}
|
||||||
@@ -1740,7 +1807,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">区域</label>
|
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider">区域</label>
|
||||||
<select
|
<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"
|
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}
|
value={customerFilters.region}
|
||||||
onChange={(e) => setCustomerFilters(prev => ({ ...prev, region: e.target.value }))}
|
onChange={(e) => setCustomerFilters(prev => ({ ...prev, region: e.target.value }))}
|
||||||
@@ -1753,7 +1820,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 pt-4 border-t border-gray-50">
|
<div className="mt-6 pt-4 border-t border-gray-50">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsCustomerFilterOpen(false)}
|
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"
|
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"
|
||||||
>
|
>
|
||||||
@@ -1790,7 +1857,7 @@ export default function App() {
|
|||||||
const isExpanded = expandedCustomers.has(cust.customer);
|
const isExpanded = expandedCustomers.has(cust.customer);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={cust.customer}>
|
<React.Fragment key={cust.customer}>
|
||||||
<tr
|
<tr
|
||||||
className={`cursor-pointer transition-all border-b border-gray-100 ${
|
className={`cursor-pointer transition-all border-b border-gray-100 ${
|
||||||
isExpanded ? 'bg-emerald-50/50' : 'hover:bg-gray-50'
|
isExpanded ? 'bg-emerald-50/50' : 'hover:bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
@@ -1804,17 +1871,17 @@ export default function App() {
|
|||||||
<span className="bg-gray-100 px-2 py-0.5 rounded text-[10px] font-medium">{cust.region}</span>
|
<span className="bg-gray-100 px-2 py-0.5 rounded text-[10px] font-medium">{cust.region}</span>
|
||||||
</td>
|
</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-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, vehicleType: '4.5T普货' }); }}>{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_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, vehicleType: '4.5T冷链' }); }}>{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: '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, vehicleType: '18T' }); }}>{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: '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, vehicleType: '49T' }); }}>{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: '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, isTrailer: true }); }}>{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.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, vehicleType: '其他' }); }}>{cust.other}</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 }); }}>{cust.total}</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>
|
</tr>
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<tr className="bg-gray-50/30">
|
<tr className="bg-gray-50/30">
|
||||||
<td colSpan={10} className="p-2">
|
<td colSpan={9} className="p-2">
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
<div className="bg-white p-2 rounded border border-gray-100 shadow-sm">
|
<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-[10px] text-gray-400 uppercase mb-1">客户详情</div>
|
||||||
@@ -1834,7 +1901,7 @@ export default function App() {
|
|||||||
<div className="bg-white p-2 rounded border border-gray-100 shadow-sm">
|
<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-[10px] text-gray-400 uppercase mb-1">资产占比</div>
|
||||||
<div className="text-sm font-bold text-gray-700">
|
<div className="text-sm font-bold text-gray-700">
|
||||||
{((cust.total / (customerData.reduce((s, c) => s + c.total, 0) || 1)) * 100).toFixed(1)}%
|
{((cust.total / deptData.reduce((s, d) => s + d.totalAssets, 0)) * 100).toFixed(1)}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1854,7 +1921,7 @@ export default function App() {
|
|||||||
const isExpanded = expandedCustomers.has(cust.customer);
|
const isExpanded = expandedCustomers.has(cust.customer);
|
||||||
return (
|
return (
|
||||||
<div key={cust.customer} className="bg-white rounded border border-gray-100 shadow-sm overflow-hidden">
|
<div key={cust.customer} className="bg-white rounded border border-gray-100 shadow-sm overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className={`p-2 flex justify-between items-center cursor-pointer transition-colors ${
|
className={`p-2 flex justify-between items-center cursor-pointer transition-colors ${
|
||||||
isExpanded ? 'bg-emerald-50' : 'bg-gray-50'
|
isExpanded ? 'bg-emerald-50' : 'bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
@@ -1894,7 +1961,7 @@ export default function App() {
|
|||||||
<div className="bg-gray-50 p-2 rounded border border-gray-100">
|
<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-[8px] text-gray-400 uppercase mb-1">资产占比</div>
|
||||||
<div className="text-xs font-bold text-gray-700">
|
<div className="text-xs font-bold text-gray-700">
|
||||||
{((cust.total / (customerData.reduce((s, c) => s + c.total, 0) || 1)) * 100).toFixed(1)}%
|
{((cust.total / deptData.reduce((s, d) => s + d.totalAssets, 0)) * 100).toFixed(1)}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1902,52 +1969,52 @@ export default function App() {
|
|||||||
<div className="border-t border-gray-50 pt-2">
|
<div className="border-t border-gray-50 pt-2">
|
||||||
<div className="text-[8px] text-gray-400 uppercase mb-2">车型分布</div>
|
<div className="text-[8px] text-gray-400 uppercase mb-2">车型分布</div>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<div
|
<div
|
||||||
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
|
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, vehicleType: '4.5T普货' })}
|
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-[8px] text-gray-400 uppercase">4.5T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{cust.t4_5}</div>
|
<div className="text-[10px] font-bold text-gray-600">{cust.t4_5}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
|
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, vehicleType: '4.5T冷链' })}
|
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-[8px] text-gray-400 uppercase">冷链</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{cust.t4_5c}</div>
|
<div className="text-[10px] font-bold text-gray-600">{cust.t4_5c}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
|
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, vehicleType: '18T' })}
|
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-[8px] text-gray-400 uppercase">18T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{cust.t18}</div>
|
<div className="text-[10px] font-bold text-gray-600">{cust.t18}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
|
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, vehicleType: '49T' })}
|
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-[8px] text-gray-400 uppercase">49T</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{cust.t49}</div>
|
<div className="text-[10px] font-bold text-gray-600">{cust.t49}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
|
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, isTrailer: true })}
|
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-[8px] text-gray-400 uppercase">挂车</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{cust.trailer}</div>
|
<div className="text-[10px] font-bold text-gray-600">{cust.trailer}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="text-center bg-gray-50 p-1 rounded cursor-pointer hover:bg-emerald-50 transition-colors"
|
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, vehicleType: '其他' })}
|
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-[8px] text-gray-400 uppercase">其他</div>
|
||||||
<div className="text-[10px] font-bold text-gray-600">{cust.other}</div>
|
<div className="text-[10px] font-bold text-gray-600">{cust.other}</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -1955,6 +2022,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
{/* Plate Number Modal */}
|
{/* Plate Number Modal */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showPlateNumbers && (
|
{showPlateNumbers && (
|
||||||
|
|||||||
Reference in New Issue
Block a user