Files
JML/src/app/admin/service-centers/[id]/page.tsx

802 lines
36 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';
import {
ArrowLeft, Building2, Star, Phone, Mail, MapPin, Activity, Wrench,
Battery, Bike, DollarSign, CheckCircle2, Clock, AlertTriangle, Search,
SlidersHorizontal, ArrowUpDown, User, Calendar, Shield, Tag, Plus, Eye,
BarChart3, Percent, ChevronRight, ExternalLink
} from 'lucide-react';
import Link from 'next/link';
import { ServiceCenter } from '../page';
// Interface for Maintenance History Record
interface HistoryRecord {
id: string;
date: string;
assetId: string;
assetType: 'EV Bike' | 'Battery';
serviceType: 'Damage' | 'Repair' | 'Service' | 'Battery Swap' | 'Inspection';
description: string;
severity: 'critical' | 'major' | 'minor' | 'cosmetic';
status: 'completed' | 'in_progress' | 'parts_ordered';
estimatedCost: number;
actualCost: number;
partsUsed: { name: string; qty: number; price: number }[];
laborCost: number;
technician: string;
}
// Generate realistic mock history data based on Center ID/Name
const getMockHistoryData = (centerName: string): HistoryRecord[] => {
const baseHistory: HistoryRecord[] = [
{
id: 'MNT-101',
date: '2024-03-21',
assetId: 'EV-004',
assetType: 'EV Bike',
serviceType: 'Damage',
description: 'Front fender shattered in traffic collision. Replaced brackets and front wheel.',
severity: 'major',
status: 'in_progress',
estimatedCost: 3500,
actualCost: 3200,
partsUsed: [
{ name: 'Front fender', qty: 1, price: 1500 },
{ name: 'Mounting brackets', qty: 2, price: 800 },
{ name: 'Brake pads', qty: 1, price: 600 }
],
laborCost: 1200,
technician: 'Sabbir Ahmed'
},
{
id: 'MNT-102',
date: '2024-03-18',
assetId: 'BAT-044',
assetType: 'Battery',
serviceType: 'Battery Swap',
description: 'Internal diagnostic showing rapid voltage degradation. Replaced cells and recalibrated BMS.',
severity: 'critical',
status: 'completed',
estimatedCost: 12000,
actualCost: 11500,
partsUsed: [
{ name: 'Battery 60V cell pack', qty: 1, price: 9500 },
{ name: 'BMS Controller Board', qty: 1, price: 2000 }
],
laborCost: 2500,
technician: 'Kamrul Hasan'
},
{
id: 'MNT-103',
date: '2024-03-15',
assetId: 'EV-012',
assetType: 'EV Bike',
serviceType: 'Service',
description: 'Routine 5,000km periodic maintenance. Calibrated drum brakes and greased chassis bearings.',
severity: 'minor',
status: 'completed',
estimatedCost: 1500,
actualCost: 1450,
partsUsed: [
{ name: 'Brake Cable', qty: 1, price: 250 },
{ name: 'Sprocket kit', qty: 1, price: 450 }
],
laborCost: 750,
technician: 'Sabbir Ahmed'
},
{
id: 'MNT-104',
date: '2024-03-10',
assetId: 'BAT-021',
assetType: 'Battery',
serviceType: 'Inspection',
description: 'Thermal warning flag during hyper-charging cycle. Terminals cleaned and thermal gel reapplied.',
severity: 'cosmetic',
status: 'completed',
estimatedCost: 500,
actualCost: 400,
partsUsed: [
{ name: 'Thermal paste', qty: 1, price: 150 }
],
laborCost: 250,
technician: 'Kamrul Hasan'
},
{
id: 'MNT-105',
date: '2024-03-05',
assetId: 'EV-009',
assetType: 'EV Bike',
serviceType: 'Repair',
description: 'Throttle failure reported by delivery driver. Replaced magnetic sensor assembly.',
severity: 'major',
status: 'completed',
estimatedCost: 1800,
actualCost: 2100,
partsUsed: [
{ name: 'Throttle control assembly', qty: 1, price: 800 },
{ name: 'Wiring loom adapter', qty: 1, price: 450 }
],
laborCost: 850,
technician: 'Rafiqul Islam'
},
{
id: 'MNT-106',
date: '2024-02-28',
assetId: 'EV-017',
assetType: 'EV Bike',
serviceType: 'Damage',
description: 'Rear tire blowout due to road debris. Replacement and alignment completed.',
severity: 'minor',
status: 'completed',
estimatedCost: 2800,
actualCost: 2750,
partsUsed: [
{ name: 'Rear Tire tubeless', qty: 1, price: 2200 },
{ name: 'Chain replacement', qty: 1, price: 400 }
],
laborCost: 500,
technician: 'Sabbir Ahmed'
},
{
id: 'MNT-107',
date: '2024-02-20',
assetId: 'BAT-089',
assetType: 'Battery',
serviceType: 'Battery Swap',
description: 'Dead module replacement under premium warranty. Replaced sub-assemblies.',
severity: 'critical',
status: 'parts_ordered',
estimatedCost: 15000,
actualCost: 0,
partsUsed: [
{ name: 'Battery 48V cell pack', qty: 1, price: 8000 }
],
laborCost: 1500,
technician: 'Kamrul Hasan'
}
];
// Variations in records based on Center's specialty & size to make data dynamic
if (centerName.includes('Gulshan') || centerName.includes('Center A')) {
return baseHistory;
} else if (centerName.includes('Banani') || centerName.includes('Center B')) {
return baseHistory.filter(h => h.serviceType === 'Battery Swap' || h.serviceType === 'Service' || h.serviceType === 'Inspection').map(h => ({
...h,
id: h.id.replace('10', '20'),
technician: 'Tanvir Rahman'
}));
} else {
// Uttara / Authorized
return baseHistory.filter(h => h.serviceType === 'Inspection' || h.serviceType === 'Repair').map(h => ({
...h,
id: h.id.replace('10', '30'),
technician: 'Arif Chowdhury'
}));
}
};
export default function ServiceCenterDetailsPage() {
const params = useParams();
const router = useRouter();
const id = params.id as string;
const [isMounted, setIsMounted] = useState(false);
const [center, setCenter] = useState<ServiceCenter | null>(null);
const [history, setHistory] = useState<HistoryRecord[]>([]);
// Filtering / Sorting / Search states for history
const [searchQuery, setSearchQuery] = useState('');
const [assetFilter, setAssetFilter] = useState('all');
const [typeFilter, setTypeFilter] = useState('all');
const [severityFilter, setSeverityFilter] = useState('all');
const [statusFilter, setStatusFilter] = useState('all');
const [sortBy, setSortBy] = useState<'date-desc' | 'date-asc' | 'cost-desc' | 'cost-asc' | 'urgency-desc' | 'urgency-asc'>('date-desc');
// Map Interactive detail popup state
const [mapPopup, setMapPopup] = useState<string | null>(null);
useEffect(() => {
setIsMounted(true);
// Load Service Centers from localStorage
const stored = localStorage.getItem('jaiben_service_centers');
let foundCenter: ServiceCenter | null = null;
if (stored) {
try {
const centers: ServiceCenter[] = JSON.parse(stored);
foundCenter = centers.find(c => c.id === id) || null;
} catch (e) {}
}
if (foundCenter) {
setCenter(foundCenter);
setHistory(getMockHistoryData(foundCenter.name));
} else {
router.push('/admin/service-centers');
}
}, [id, router]);
if (!isMounted || !center) return null;
// Filter History records
const filteredHistory = history.filter(h => {
const matchesSearch = h.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
h.assetId.toLowerCase().includes(searchQuery.toLowerCase()) ||
h.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
h.technician.toLowerCase().includes(searchQuery.toLowerCase());
const matchesAsset = assetFilter === 'all' || h.assetType === assetFilter;
const matchesType = typeFilter === 'all' || h.serviceType === typeFilter;
const matchesSeverity = severityFilter === 'all' || h.severity === severityFilter;
const matchesStatus = statusFilter === 'all' || h.status === statusFilter;
return matchesSearch && matchesAsset && matchesType && matchesSeverity && matchesStatus;
});
// Sort History records
const severityWeights = { cosmetic: 1, minor: 2, major: 3, critical: 4 };
const sortedHistory = [...filteredHistory].sort((a, b) => {
switch (sortBy) {
case 'date-desc':
return new Date(b.date).getTime() - new Date(a.date).getTime();
case 'date-asc':
return new Date(a.date).getTime() - new Date(b.date).getTime();
case 'cost-desc':
return (b.actualCost || b.estimatedCost) - (a.actualCost || a.estimatedCost);
case 'cost-asc':
return (a.actualCost || a.estimatedCost) - (b.actualCost || b.estimatedCost);
case 'urgency-desc':
return severityWeights[b.severity] - severityWeights[a.severity];
case 'urgency-asc':
return severityWeights[a.severity] - severityWeights[b.severity];
default:
return 0;
}
});
// Financial & Aggregate calculations
const totalRepairs = history.length;
const completedRepairs = history.filter(h => h.status === 'completed');
const totalEstimatedCost = completedRepairs.reduce((sum, h) => sum + h.estimatedCost, 0);
const totalActualCost = completedRepairs.reduce((sum, h) => sum + h.actualCost, 0);
const costVariance = totalActualCost - totalEstimatedCost;
const totalPartsCost = completedRepairs.reduce((sum, h) => sum + h.partsUsed.reduce((s, p) => s + (p.price * p.qty), 0), 0);
const totalLaborCost = completedRepairs.reduce((sum, h) => sum + h.laborCost, 0);
const totalSpend = totalPartsCost + totalLaborCost;
// Aggregate Parts Utilized Log
const partsAggregated: { name: string; totalQty: number; totalCost: number }[] = [];
history.forEach(h => {
h.partsUsed.forEach(part => {
const existing = partsAggregated.find(p => p.name === part.name);
if (existing) {
existing.totalQty += part.qty;
existing.totalCost += part.price * part.qty;
} else {
partsAggregated.push({
name: part.name,
totalQty: part.qty,
totalCost: part.price * part.qty
});
}
});
});
const topParts = partsAggregated.sort((a, b) => b.totalQty - a.totalQty).slice(0, 5);
const statusColors = {
active: 'bg-emerald-100 text-emerald-700',
busy: 'bg-amber-100 text-amber-700',
inactive: 'bg-slate-100 text-slate-700'
};
const severityColors = {
critical: 'bg-red-100 text-red-700',
major: 'bg-orange-100 text-orange-700',
minor: 'bg-amber-100 text-amber-700',
cosmetic: 'bg-slate-100 text-slate-700'
};
const statusHistoryColors = {
completed: 'bg-emerald-100 text-emerald-700',
in_progress: 'bg-blue-100 text-blue-700',
parts_ordered: 'bg-purple-100 text-purple-700'
};
return (
<div className="p-4 lg:p-6 mb-6 lg:mb-0 space-y-6 max-w-8xl mx-auto">
{/* Navigation Top - standard layout of other detail profiles */}
<div className="flex items-center justify-between border-b border-slate-100 pb-4 mb-4">
<button
onClick={() => router.push('/admin/service-centers')}
className="flex items-center gap-2 text-slate-600 hover:text-slate-900 text-sm font-semibold transition-colors duration-200"
>
<ArrowLeft className="w-4 h-4 text-slate-400" /> Back to Service Centers
</button>
<span className="text-xs font-bold text-slate-400">
Node Registry: {center.id}
</span>
</div>
{/* Main Profile Info Header - rounded-xl alignment matching maintenance page */}
<div className="bg-white rounded-xl p-6 border border-slate-100 shadow-sm space-y-6 relative overflow-hidden">
{/* Glow effect decorative */}
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/5 rounded-full blur-3xl -mr-16 -mt-16 pointer-events-none" />
<div className="flex flex-col lg:flex-row lg:items-start justify-between gap-6 relative z-10">
<div className="space-y-4 flex-1">
<div className="flex flex-wrap items-center gap-3">
<div className="w-14 h-14 rounded-xl bg-slate-50 border border-slate-100 flex items-center justify-center flex-shrink-0">
<Building2 className="w-7 h-7 text-slate-600" />
</div>
<div>
<h1 className="text-2xl lg:text-3xl font-extrabold text-slate-800 leading-tight">{center.name}</h1>
<div className="flex flex-wrap items-center gap-2 mt-1">
<span className={`px-2.5 py-0.5 text-xs font-semibold rounded-full border border-transparent ${statusColors[center.status]}`}>
{center.status}
</span>
<div className="flex items-center gap-1 text-yellow-500 font-bold text-sm bg-yellow-50 px-2 py-0.5 rounded border border-yellow-100">
<Star className="w-3.5 h-3.5 fill-yellow-500" />
<span>{center.rating.toFixed(1)}</span>
</div>
</div>
</div>
</div>
{/* Profile items - clean, consistent spacing */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-y-3 gap-x-6 text-sm text-slate-600 pt-2">
<div className="flex items-center gap-2">
<MapPin className="w-4 h-4 text-slate-400" />
<div className="flex flex-wrap items-center gap-x-2">
<span>{center.address}</span>
{center.googleMapLink && (
<a
href={center.googleMapLink}
target="_blank"
rel="noopener noreferrer"
className="text-accent hover:underline flex items-center gap-0.5 font-bold"
>
Map Link <ExternalLink className="w-3 h-3" />
</a>
)}
</div>
</div>
<div className="flex items-center gap-2">
<Phone className="w-4 h-4 text-slate-400" />
<span>{center.phone}</span>
</div>
<div className="flex items-center gap-2">
<Mail className="w-4 h-4 text-slate-400" />
<span>{center.email}</span>
</div>
<div className="flex items-center gap-2">
<Shield className="w-4 h-4 text-slate-400" />
<span>Staff: <strong className="font-semibold text-slate-800">{center.staffCount} technicians</strong></span>
</div>
<div className="flex items-center gap-2">
<Wrench className="w-4 h-4 text-slate-400" />
<span>Capacity: <strong className="font-semibold text-slate-800">{center.capacity}</strong> total slots</span>
</div>
</div>
{/* Specialization List Header */}
<div className="space-y-1.5 pt-2">
<span className="text-[10px] font-extrabold uppercase text-slate-400 tracking-wider">Node Specializations</span>
<div className="flex flex-wrap gap-1.5">
{center.specialization.map(spec => (
<span key={spec} className="px-2.5 py-1 bg-slate-50 border border-slate-100 rounded-lg text-xs font-bold text-slate-600">
{spec}
</span>
))}
</div>
</div>
</div>
{/* Quick Stats Header Summary - simplified matching maintenance specs, occupancy not needed */}
<div className="grid grid-cols-2 gap-4 bg-slate-50 p-5 rounded-xl border border-slate-100 w-full lg:max-w-xs flex-shrink-0">
<div className="space-y-1">
<p className="text-xs font-bold text-slate-400 uppercase tracking-wide">Repairs Logs</p>
<p className="text-2xl font-extrabold text-slate-800">{totalRepairs}</p>
<p className="text-[10px] text-slate-500">{completedRepairs.length} completed</p>
</div>
<div className="space-y-1">
<p className="text-xs font-bold text-slate-400 uppercase tracking-wide">Capacity</p>
<p className="text-2xl font-extrabold text-slate-800">{center.capacity}</p>
<p className="text-[10px] text-slate-500">Service slots registered</p>
</div>
</div>
</div>
</div>
{/* Analytics: Map Mockup & Cost Breakdown Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* INTERACTIVE STYLIZED MAP CONTAINER - aligned clean white rounded-xl styles */}
<div className="bg-white rounded-xl p-6 border border-slate-100 shadow-sm space-y-4 flex flex-col justify-between lg:col-span-1 min-h-[380px]">
<div className="space-y-1">
<h3 className="font-extrabold text-slate-800 text-lg">Node Location Map</h3>
<p className="text-xs text-slate-400">Dhaka city arterial coverage grid mockup</p>
</div>
{/* Map canvas container */}
<div className="flex-1 bg-slate-900 rounded-xl relative overflow-hidden border border-slate-850 shadow-inner flex items-center justify-center min-h-[220px]">
{/* Pulsating target coordinate representing the Center */}
<div
className="absolute w-8 h-8 flex items-center justify-center cursor-pointer group z-20"
style={{ top: '45%', left: '50%', transform: 'translate(-50%, -50%)' }}
onClick={() => setMapPopup(center.name)}
>
<span className="absolute inline-flex h-full w-full rounded-full bg-accent opacity-75 animate-ping" />
<div className="relative w-4 h-4 bg-accent border-2 border-white rounded-full flex items-center justify-center shadow-lg group-hover:scale-125 transition-transform">
<div className="w-1.5 h-1.5 bg-white rounded-full" />
</div>
</div>
{/* Stylized Dhaka grids & landmarks using SVGs */}
<svg className="w-full h-full absolute inset-0 opacity-40 select-none pointer-events-none" viewBox="0 0 100 100" preserveAspectRatio="none">
{/* Arterial Highways */}
<line x1="20" y1="0" x2="20" y2="100" stroke="#475569" strokeWidth="0.75" strokeDasharray="2" />
<line x1="50" y1="0" x2="50" y2="100" stroke="#475569" strokeWidth="1.5" />
<line x1="80" y1="0" x2="80" y2="100" stroke="#475569" strokeWidth="0.75" />
<line x1="0" y1="30" x2="100" y2="30" stroke="#475569" strokeWidth="0.75" />
<line x1="0" y1="50" x2="100" y2="50" stroke="#475569" strokeWidth="1.5" />
<line x1="0" y1="80" x2="100" y2="80" stroke="#475569" strokeWidth="0.75" />
{/* Waterway (Gulshan Lake) */}
<path d="M 50,0 Q 52,25 48,50 T 54,100" fill="none" stroke="#1e3a8a" strokeWidth="3" opacity="0.3" />
<path d="M 47,40 Q 70,45 80,42" fill="none" stroke="#1e3a8a" strokeWidth="2.5" opacity="0.3" />
{/* Other hubs mockup dots */}
<circle cx="20" cy="30" r="1.5" fill="#4f46e5" />
<circle cx="80" cy="30" r="1.5" fill="#4f46e5" />
<circle cx="20" cy="80" r="1.5" fill="#4f46e5" />
<circle cx="80" cy="80" r="1.5" fill="#4f46e5" />
</svg>
{/* Scale watermark */}
<div className="absolute bottom-2 left-2 text-[9px] text-slate-500 font-bold bg-slate-950/75 px-1.5 py-0.5 rounded border border-slate-800">
GPS: {center.latitude.toFixed(4)}°N, {center.longitude.toFixed(4)}°E
</div>
{/* Stylized popup when clicked */}
{mapPopup && (
<div className="absolute top-2 right-2 left-2 bg-slate-950/90 border border-slate-800 rounded-lg p-2.5 text-xs text-white z-30 animate-fadeIn space-y-1">
<div className="flex items-center justify-between">
<span className="font-extrabold text-accent">{center.name}</span>
<button onClick={() => setMapPopup(null)} className="text-slate-400 hover:text-white font-bold">×</button>
</div>
<p className="text-[10px] text-slate-400">{center.address}</p>
<div className="flex justify-between pt-1 border-t border-slate-800 text-[9px] text-slate-400 font-bold">
<span>Capacity: {center.capacity} slots</span>
<span>Rating: {center.rating.toFixed(1)}</span>
</div>
</div>
)}
{/* Custom street labels */}
<div className="absolute top-4 left-[53%] text-[8px] font-bold text-slate-600 tracking-widest uppercase origin-center rotate-90 select-none">
Gulshan Lake Road
</div>
<div className="absolute top-[52%] left-4 text-[8px] font-bold text-slate-600 tracking-widest uppercase select-none">
Tejgaon-Gulshan Link Road
</div>
</div>
<div className="text-center text-[10px] font-bold text-slate-400 uppercase tracking-widest pt-1">
📍 Click GPS coordinate node for telemetry details
</div>
</div>
{/* FINANCIAL PERFORMANCE & EXPENSE TRACKING */}
<div className="bg-white rounded-xl p-6 border border-slate-100 shadow-sm space-y-6 lg:col-span-2">
<div className="space-y-1">
<h3 className="font-extrabold text-slate-800 text-lg">Financial Performance & Cost Margins</h3>
<p className="text-xs text-slate-400">Aggregated historical metrics from completed maintenance invoices</p>
</div>
{/* Financial details panel */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-slate-50 border border-slate-100 p-4 rounded-xl flex flex-col justify-between">
<span className="text-xs font-bold text-slate-400 uppercase tracking-wider block">Estimated Spend</span>
<div className="mt-2">
<p className="text-2xl font-extrabold text-slate-800">{totalEstimatedCost.toLocaleString()}</p>
<p className="text-[10px] text-slate-500 mt-0.5">Budgeted repairs cost</p>
</div>
</div>
<div className="bg-indigo-50/50 border border-indigo-100/50 p-4 rounded-xl flex flex-col justify-between">
<span className="text-xs font-bold text-slate-400 uppercase tracking-wider block">Actual Invoice Spend</span>
<div className="mt-2">
<p className="text-2xl font-extrabold text-indigo-700">{totalActualCost.toLocaleString()}</p>
<p className="text-[10px] text-indigo-500 mt-0.5">Billed repair totals</p>
</div>
</div>
<div className={`p-4 rounded-xl border flex flex-col justify-between ${costVariance > 0 ? 'bg-rose-50 border-rose-100' : 'bg-emerald-50 border-emerald-100'}`}>
<span className="text-xs font-bold text-slate-400 uppercase tracking-wider block">Cost Variance</span>
<div className="mt-2">
<p className={`text-2xl font-extrabold ${costVariance > 0 ? 'text-rose-700' : 'text-emerald-700'}`}>
{costVariance > 0 ? `+৳${costVariance.toLocaleString()}` : `-৳${Math.abs(costVariance).toLocaleString()}`}
</p>
<p className="text-[10px] text-slate-500 mt-0.5">
{costVariance > 0 ? 'Over budget invoices' : 'Under budget savings!'}
</p>
</div>
</div>
</div>
{/* Parts Used Aggregates & Labor Breakdown */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 pt-2">
{/* Margins */}
<div className="space-y-4">
<h4 className="text-xs font-extrabold uppercase text-slate-400 tracking-wider">Expense Margin Distribution</h4>
<div className="space-y-3">
{/* Parts Spend Bar */}
<div className="space-y-1">
<div className="flex items-center justify-between text-xs font-bold text-slate-600">
<span className="flex items-center gap-1"><Battery className="w-3.5 h-3.5 text-indigo-500" /> Spare Parts Cost</span>
<span>{totalPartsCost.toLocaleString()} ({totalSpend > 0 ? Math.round((totalPartsCost/totalSpend)*100) : 0}%)</span>
</div>
<div className="w-full bg-slate-100 rounded-full h-2">
<div
className="bg-indigo-650 h-full rounded-full"
style={{ width: `${totalSpend > 0 ? (totalPartsCost/totalSpend)*100 : 0}%` }}
/>
</div>
</div>
{/* Labor Spend Bar */}
<div className="space-y-1">
<div className="flex items-center justify-between text-xs font-bold text-slate-600">
<span className="flex items-center gap-1"><Wrench className="w-3.5 h-3.5 text-emerald-500" /> Labor Costs</span>
<span>{totalLaborCost.toLocaleString()} ({totalSpend > 0 ? Math.round((totalLaborCost/totalSpend)*100) : 0}%)</span>
</div>
<div className="w-full bg-slate-100 rounded-full h-2">
<div
className="bg-emerald-550 h-full rounded-full"
style={{ width: `${totalSpend > 0 ? (totalLaborCost/totalSpend)*100 : 0}%` }}
/>
</div>
</div>
</div>
{/* General Health Tip */}
<div className="p-3 bg-slate-50 border border-slate-100 rounded-lg text-xs text-slate-500 flex items-start gap-2">
<AlertTriangle className="w-4 h-4 text-amber-500 flex-shrink-0 mt-0.5" />
<span>
<strong>Cost Ratio Notice:</strong> This node maintains a healthy parts-to-labor ratio of {totalSpend > 0 ? Math.round((totalPartsCost/totalSpend)*100) : 0}:{totalSpend > 0 ? Math.round((totalLaborCost/totalSpend)*100) : 0}. Lower labor ratios reflect technician efficiency.
</span>
</div>
</div>
{/* Parts utilized list */}
<div className="space-y-3">
<h4 className="text-xs font-extrabold uppercase text-slate-400 tracking-wider">Top Spare Parts Log</h4>
<div className="divide-y divide-slate-100 border border-slate-100 rounded-xl overflow-hidden bg-slate-50/50">
{topParts.length === 0 ? (
<p className="text-xs text-slate-400 p-4 text-center">No spare parts recorded yet</p>
) : topParts.map(part => (
<div key={part.name} className="p-2.5 flex items-center justify-between text-xs text-slate-600">
<span className="font-semibold text-slate-700">{part.name}</span>
<div className="flex items-center gap-4 text-right">
<span className="font-bold text-slate-500">Qty: {part.totalQty}</span>
<span className="font-extrabold text-slate-800">{part.totalCost.toLocaleString()}</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
{/* Interactive History Log - aligned standard filters and table headers */}
<div className="bg-white rounded-xl p-6 border border-slate-100 shadow-sm space-y-6">
{/* Section title */}
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4 border-b border-slate-100 pb-4">
<div className="space-y-1">
<h3 className="font-extrabold text-slate-800 text-lg flex items-center gap-2">
<Activity className="w-5 h-5 text-indigo-500" />
<span>Serviced Fleet History Log</span>
</h3>
<p className="text-xs text-slate-400">Integrated audit list for EV bikes and Battery Swap maintenance nodes</p>
</div>
{/* Quick Counter */}
<span className="px-3 py-1 bg-indigo-50 border border-indigo-100/50 rounded-lg text-xs font-extrabold text-indigo-700 self-start lg:self-auto">
{sortedHistory.length} Matches Found
</span>
</div>
{/* Filter Controls Panel */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-3 bg-slate-50 p-4 rounded-lg border border-slate-100">
{/* Search bar */}
<div className="relative col-span-1 lg:col-span-2">
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
<input
type="text"
placeholder="Search by ID, tech, description..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-9 pr-3 py-2 border border-slate-200 rounded-lg text-xs focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent transition-all bg-white"
/>
</div>
{/* Filter 1: Asset Type */}
<select
value={assetFilter}
onChange={(e) => setAssetFilter(e.target.value)}
className="py-2 px-3 border border-slate-200 rounded-lg text-xs font-semibold text-slate-600 bg-white focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent transition-all"
>
<option value="all">All Asset Types</option>
<option value="EV Bike">EV Bike</option>
<option value="Battery">Battery</option>
</select>
{/* Filter 2: Service Type */}
<select
value={typeFilter}
onChange={(e) => setTypeFilter(e.target.value)}
className="py-2 px-3 border border-slate-200 rounded-lg text-xs font-semibold text-slate-600 bg-white focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent transition-all"
>
<option value="all">All Service Types</option>
<option value="Damage">Damage</option>
<option value="Repair">Repair</option>
<option value="Service">Service</option>
<option value="Battery Swap">Battery Swap</option>
<option value="Inspection">Inspection</option>
</select>
{/* Sorting Dropdown */}
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as any)}
className="py-2 px-3 border border-slate-200 rounded-lg text-xs font-bold text-indigo-600 bg-white focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent transition-all"
>
<option value="date-desc">📆 Date: Newest First</option>
<option value="date-asc">📆 Date: Oldest First</option>
<option value="cost-desc"> Cost: Highest First</option>
<option value="cost-asc"> Cost: Lowest First</option>
<option value="urgency-desc"> Severity: Critical First</option>
<option value="urgency-asc"> Severity: Cosmetic First</option>
</select>
</div>
{/* Table / List representation */}
{sortedHistory.length === 0 ? (
<div className="text-center py-12 bg-slate-50 rounded-lg border border-dashed border-slate-200">
<Activity className="w-12 h-12 text-slate-300 mx-auto mb-3" />
<p className="text-sm font-semibold text-slate-500">No matching service records</p>
<p className="text-xs text-slate-400 mt-0.5">Try clearing filters or search variables</p>
</div>
) : (
<div className="overflow-x-auto border border-slate-100 rounded-lg">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50 border-b border-slate-100 text-xs font-semibold uppercase tracking-wider text-slate-500">
<th className="py-3 px-4">Record ID</th>
<th className="py-3 px-4">Asset Code</th>
<th className="py-3 px-4">Service Type</th>
<th className="py-3 px-4">Description</th>
<th className="py-3 px-4 text-center">Severity</th>
<th className="py-3 px-4 text-center">Status</th>
<th className="py-3 px-4 text-right">Invoice cost</th>
<th className="py-3 px-4 text-right">Details</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 text-sm text-slate-700">
{sortedHistory.map(record => (
<tr key={record.id} className="hover:bg-slate-50/50 transition-colors">
<td className="py-4 px-4 font-bold text-slate-400">{record.id}</td>
<td className="py-4 px-4">
<div className="flex items-center gap-1.5">
{record.assetType === 'EV Bike' ? (
<Bike className="w-4 h-4 text-purple-600 flex-shrink-0" />
) : (
<Battery className="w-4 h-4 text-green-600 flex-shrink-0" />
)}
<div className="space-y-0.5">
<span className="font-extrabold text-slate-800">{record.assetId}</span>
<span className="text-[10px] text-slate-400 uppercase font-semibold block">{record.assetType}</span>
</div>
</div>
</td>
<td className="py-4 px-4 font-semibold text-slate-700">{record.serviceType}</td>
<td className="py-4 px-4 max-w-sm">
<div className="space-y-1">
<p className="text-xs text-slate-600 line-clamp-2 leading-relaxed">{record.description}</p>
<div className="flex items-center gap-3 text-[10px] text-slate-400 font-semibold">
<span className="flex items-center gap-0.5"><User className="w-3 h-3" /> Tech: {record.technician}</span>
<span className="flex items-center gap-0.5"><Calendar className="w-3 h-3" /> {record.date}</span>
</div>
</div>
</td>
<td className="py-4 px-4 text-center">
<span className={`px-2 py-0.5 text-xs font-semibold rounded-full border inline-block ${severityColors[record.severity]}`}>
{record.severity}
</span>
</td>
<td className="py-4 px-4 text-center">
<span className={`px-2.5 py-0.5 text-xs font-semibold rounded-full border inline-block ${statusHistoryColors[record.status]}`}>
{record.status.replace('_', ' ')}
</span>
</td>
<td className="py-4 px-4 text-right font-extrabold text-slate-800">
{record.actualCost > 0 ? (
<span>{record.actualCost.toLocaleString()}</span>
) : (
<span className="text-slate-400 font-normal italic text-xs">Pending invoice</span>
)}
</td>
<td className="py-4 px-4 text-right">
{/* Deep link details */}
<Link
href={`/admin/maintenance/${record.id}`}
className="p-1.5 hover:bg-slate-100 rounded-lg text-slate-400 hover:text-slate-700 transition-colors inline-block"
title="Open full maintenance record"
>
<Eye className="w-4 h-4" />
</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
);
}