feat: add department operations statistics section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-03-27 18:22:00 +08:00
parent b4fdbd14a7
commit 4cac404f49

View File

@@ -860,6 +860,502 @@ export default function App() {
</div>
</div>
{/* Department Operations Statistics */}
<section className="bg-white rounded-sm border border-gray-100 shadow-sm 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) */}
<div className="m-2 bg-slate-800 rounded-xl p-3 text-white shadow-lg">
<div className="grid grid-cols-3 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>
</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>}
{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-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-24"></th>
<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 font-black text-gray-800 text-sm">
{dept.totalAssets}
</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={5} 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 });
}}
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, vehicleType: '4.5T普货' })}
>
<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, vehicleType: '4.5T冷链' })}
>
<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, vehicleType: '18T' })}
>
<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, vehicleType: '49T' })}
>
<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, isTrailer: true })}
>
<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, vehicleType: '其他' })}
>
<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 });
}}
>
{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 });
}}
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, vehicleType: '4.5T普货' })}>
<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, vehicleType: '4.5T冷链' })}>
<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, vehicleType: '18T' })}>
<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, vehicleType: '49T' })}>
<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, isTrailer: true })}>
<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, vehicleType: '其他' })}>
<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-sm 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>
</div>
<div className="grid grid-cols-3 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-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 });
}}
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, vehicleType: '4.5T普货' })}
>
<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, vehicleType: '4.5T冷链' })}
>
<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, vehicleType: '18T' })}
>
<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, vehicleType: '49T' })}
>
<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, isTrailer: true })}
>
<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, vehicleType: '其他' })}
>
<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-sm 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 });
}}
>
: {m.total}
</div>
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager });
}}
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, vehicleType: '4.5T普货' })}
>
<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, vehicleType: '4.5T冷链' })}
>
<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, vehicleType: '18T' })}
>
<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, vehicleType: '49T' })}
>
<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, isTrailer: true })}
>
<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, vehicleType: '其他' })}
>
<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>
{/* Plate Number Modal */}
<AnimatePresence>
{showPlateNumbers && (