feat: add category-based filtering and enhanced dashboard stats for damage and maintenance records

This commit is contained in:
sazzadulalambd
2026-05-17 15:19:31 +06:00
parent fb1eff4931
commit 440a87f0b5

View File

@@ -245,7 +245,8 @@ const typeIcons: Record<string, any> = {
};
export default function MaintenancePage() {
const [activeTab, setActiveTab] = useState<'all' | MaintenanceType>('all');
const [mainCategory, setMainCategory] = useState<'damage' | 'maintenance'>('damage');
const [targetType, setTargetType] = useState<'all' | 'battery' | 'fleet'>('all');
const [records, setRecords] = useState<MaintenanceRecord[]>(mockMaintenance);
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
@@ -259,20 +260,36 @@ export default function MaintenancePage() {
const [expandedNotes, setExpandedNotes] = useState<string[]>([]);
const [newNoteText, setNewNoteText] = useState('');
const [editForm, setEditForm] = useState<Partial<MaintenanceRecord>>({});
const [reportType, setReportType] = useState<'damage' | 'maintenance'>('damage');
const filteredRecords = records.filter(r => {
const matchesTab = activeTab === 'all' || r.type === activeTab;
const isDamage = r.type === 'damage';
const matchesCategory = mainCategory === 'damage' ? isDamage : !isDamage;
const matchesTarget = targetType === 'all' ||
(targetType === 'battery' && r.batteryId) ||
(targetType === 'fleet' && r.bikeId && !r.batteryId);
const matchesSearch = !searchQuery ||
r.bikeId.toLowerCase().includes(searchQuery.toLowerCase()) ||
r.bikeModel.toLowerCase().includes(searchQuery.toLowerCase()) ||
r.bikePlate.toLowerCase().includes(searchQuery.toLowerCase()) ||
r.reporterName.toLowerCase().includes(searchQuery.toLowerCase()) ||
r.id.toLowerCase().includes(searchQuery.toLowerCase());
r.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
(r.batteryId && r.batteryId.toLowerCase().includes(searchQuery.toLowerCase()));
const matchesStatus = statusFilter === 'all' || r.status === statusFilter;
return matchesTab && matchesSearch && matchesStatus;
return matchesCategory && matchesTarget && matchesSearch && matchesStatus;
});
const damageRecords = records.filter(r => r.type === 'damage');
const maintenanceRecords = records.filter(r => r.type !== 'damage');
const stats = {
damageCount: damageRecords.length,
maintenanceCount: maintenanceRecords.length,
batteryDamage: damageRecords.filter(r => r.batteryId).length,
fleetDamage: damageRecords.filter(r => r.bikeId && !r.batteryId).length,
batteryMaintenance: maintenanceRecords.filter(r => r.batteryId).length,
fleetMaintenance: maintenanceRecords.filter(r => r.bikeId && !r.batteryId).length,
pendingMaintenance: maintenanceRecords.filter(r => r.status === 'reported' || r.status === 'in_progress').length,
completedMaintenance: maintenanceRecords.filter(r => r.status === 'completed').length,
critical: records.filter(r => r.severity === 'critical' && r.status !== 'completed').length,
inProgress: records.filter(r => r.status === 'in_progress' || r.status === 'parts_ordered').length,
completed: records.filter(r => r.status === 'completed').length,
@@ -413,26 +430,26 @@ export default function MaintenancePage() {
</div>
</div>
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4 mb-6">
<div className="bg-white rounded-xl p-5 shadow-sm border border-slate-100">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="bg-white rounded-xl p-5 shadow-sm border border-red-100">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-red-50 flex items-center justify-center">
<AlertTriangle className="w-6 h-6 text-red-600" />
</div>
<div>
<p className="text-2xl font-extrabold text-slate-800">{stats.critical}</p>
<p className="text-sm text-slate-500">Critical</p>
<p className="text-2xl font-extrabold text-slate-800">{stats.damageCount}</p>
<p className="text-sm text-slate-500">Total Damage</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl p-5 shadow-sm border border-slate-100">
<div className="bg-white rounded-xl p-5 shadow-sm border border-blue-100">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center">
<Wrench className="w-6 h-6 text-blue-600" />
</div>
<div>
<p className="text-2xl font-extrabold text-slate-800">{stats.inProgress}</p>
<p className="text-sm text-slate-500">In Progress</p>
<p className="text-2xl font-extrabold text-slate-800">{stats.maintenanceCount}</p>
<p className="text-sm text-slate-500">Total Maintenance</p>
</div>
</div>
</div>
@@ -458,7 +475,7 @@ export default function MaintenancePage() {
</div>
</div>
</div>
<div className="bg-white rounded-xl p-5 shadow-sm border border-slate-100">
{/* <div className="bg-white rounded-xl p-5 shadow-sm border border-slate-100">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-purple-50 flex items-center justify-center">
<DollarSign className="w-6 h-6 text-purple-600" />
@@ -468,43 +485,145 @@ export default function MaintenancePage() {
<p className="text-sm text-slate-500">Total Cost</p>
</div>
</div>
</div> */}
</div>
{mainCategory === 'damage' && (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="bg-red-50 rounded-xl p-4 border border-red-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center">
<AlertTriangle className="w-5 h-5 text-red-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.damageCount}</p>
<p className="text-sm text-slate-500">Total Damage</p>
</div>
</div>
</div>
<div className="bg-green-50 rounded-xl p-4 border border-green-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<Battery className="w-5 h-5 text-green-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.batteryDamage}</p>
<p className="text-sm text-slate-500">Battery Damage</p>
</div>
</div>
</div>
<div className="bg-purple-50 rounded-xl p-4 border border-purple-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
<Bike className="w-5 h-5 text-purple-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.fleetDamage}</p>
<p className="text-sm text-slate-500">Fleet Damage</p>
</div>
</div>
</div>
<div className="bg-orange-50 rounded-xl p-4 border border-orange-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center">
<Clock className="w-5 h-5 text-orange-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.critical}</p>
<p className="text-sm text-slate-500">Critical Damage</p>
</div>
</div>
</div>
</div>
)}
{mainCategory === 'maintenance' && (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="bg-blue-50 rounded-xl p-4 border border-blue-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<Wrench className="w-5 h-5 text-blue-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.maintenanceCount}</p>
<p className="text-sm text-slate-500">Total Maintenance</p>
</div>
</div>
</div>
<div className="bg-green-50 rounded-xl p-4 border border-green-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<Battery className="w-5 h-5 text-green-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.batteryMaintenance}</p>
<p className="text-sm text-slate-500">Battery Maintenance</p>
</div>
</div>
</div>
<div className="bg-purple-50 rounded-xl p-4 border border-purple-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
<Bike className="w-5 h-5 text-purple-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.fleetMaintenance}</p>
<p className="text-sm text-slate-500">Fleet Maintenance</p>
</div>
</div>
</div>
<div className="bg-orange-50 rounded-xl p-4 border border-orange-100 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-orange-100 flex items-center justify-center">
<Clock className="w-5 h-5 text-orange-600" />
</div>
<div>
<p className="text-lg font-bold text-slate-800">{stats.pendingMaintenance}</p>
<p className="text-sm text-slate-500">Pending Maintenance</p>
</div>
</div>
</div>
</div>
)}
<div className="bg-white rounded-xl shadow-sm border border-slate-100 mb-6">
<div className="p-4 border-b border-slate-100">
<div className="flex flex-col lg:flex-row lg:items-center gap-4">
<div className="flex items-center gap-2 flex-wrap">
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-3">
<div className="flex items-center gap-1 bg-slate-100 p-1 rounded-lg">
<button
onClick={() => setActiveTab('all')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium ${activeTab === 'all' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
>
All
</button>
<button
onClick={() => setActiveTab('damage')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'damage' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
onClick={() => setMainCategory('damage')}
className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 ${mainCategory === 'damage' ? 'bg-red-600 text-white shadow-sm' : 'text-slate-600 hover:bg-white hover:shadow-sm'}`}
>
<AlertTriangle className="w-4 h-4" /> Damage
</button>
<button
onClick={() => setActiveTab('repair')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'repair' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
onClick={() => setMainCategory('maintenance')}
className={`px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 ${mainCategory === 'maintenance' ? 'bg-blue-600 text-white shadow-sm' : 'text-slate-600 hover:bg-white hover:shadow-sm'}`}
>
<Wrench className="w-4 h-4" /> Repair
<Wrench className="w-4 h-4" /> Maintenance
</button>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => setTargetType('all')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium ${targetType === 'all' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
>
All
</button>
<button
onClick={() => setActiveTab('service')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'service' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
>
<Wrench className="w-4 h-4" /> Service
</button>
<button
onClick={() => setActiveTab('battery_swap')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'battery_swap' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
onClick={() => setTargetType('battery')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${targetType === 'battery' ? 'bg-green-600 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
>
<Battery className="w-4 h-4" /> Battery
</button>
<button
onClick={() => setTargetType('fleet')}
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${targetType === 'fleet' ? 'bg-purple-600 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
>
<Bike className="w-4 h-4" /> Fleet
</button>
</div>
</div>
<div className="flex-1">
<div className="relative">
@@ -546,6 +665,16 @@ export default function MaintenancePage() {
<div>
<div className="flex items-center gap-2">
<p className="font-semibold text-slate-800">{record.id}</p>
{record.batteryId && (
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-green-100 text-green-700">
<Battery className="w-3 h-3" /> Battery
</span>
)}
{!record.batteryId && record.bikeId && (
<span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full bg-purple-100 text-purple-700">
<Bike className="w-3 h-3" /> Fleet
</span>
)}
<span className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${severityColors[record.severity]}`}>
{record.severity}
</span>
@@ -794,10 +923,32 @@ export default function MaintenancePage() {
</button>
</div>
<div className="p-4 overflow-y-auto max-h-[70vh] space-y-4">
<div>
<label className="text-sm font-semibold text-slate-700 mb-2 block">Select Target *</label>
<div className="grid grid-cols-2 gap-3 mb-4">
<button
type="button"
className="p-4 border-2 rounded-xl flex flex-col items-center gap-2 hover:border-green-500 hover:bg-green-50 transition-colors"
>
<Battery className="w-8 h-8 text-green-600" />
<span className="font-medium text-slate-700">Battery</span>
<span className="text-xs text-slate-500">For battery issues</span>
</button>
<button
type="button"
className="p-4 border-2 rounded-xl flex flex-col items-center gap-2 hover:border-purple-500 hover:bg-purple-50 transition-colors"
>
<Bike className="w-8 h-8 text-purple-600" />
<span className="font-medium text-slate-700">Fleet (Bike)</span>
<span className="text-xs text-slate-500">For bike issues</span>
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Issue Type *</label>
<label className="text-xs font-medium text-slate-600 mb-1 block">Category *</label>
<select className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm">
<option value="">Select Category</option>
<option value="damage">Damage</option>
<option value="repair">Repair</option>
<option value="service">Service</option>
@@ -815,32 +966,45 @@ export default function MaintenancePage() {
<option value="cosmetic">Cosmetic</option>
</select>
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Bike ID *</label>
<input type="text" placeholder="EV-XXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Battery ID</label>
<input type="text" placeholder="BAT-XXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Bike ID</label>
<input type="text" placeholder="EV-XXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Reporter Name *</label>
<input type="text" placeholder="Enter name" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Reporter Phone *</label>
<input type="tel" placeholder="01XXXXXXXXX" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Description *</label>
<textarea rows={3} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Describe the issue..." />
<textarea rows={3} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Describe the issue in detail..." />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Location / Hub *</label>
<input type="text" placeholder="e.g., Gulshan Hub" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Location *</label>
<input type="text" placeholder="Where did the issue occur?" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Estimated Cost</label>
<label className="text-xs font-medium text-slate-600 mb-1 block">Estimated Cost ()</label>
<input type="number" placeholder="0" className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
</div>
</div>
<div>
<label className="text-xs font-medium text-slate-600 mb-1 block">Upload Images</label>
<div className="border-2 border-dashed border-slate-200 rounded-lg p-6 text-center">
<div className="border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-accent cursor-pointer">
<Image className="w-8 h-8 text-slate-300 mx-auto mb-2" />
<p className="text-sm text-slate-500">Drag and drop or click to upload</p>
<p className="text-sm text-slate-500">Click to upload images</p>
<p className="text-xs text-slate-400">JPG, PNG up to 5MB</p>
</div>
</div>
</div>