implement TeleEMS platform architecture with centralized API client and master data management system

This commit is contained in:
2026-05-04 15:27:35 +05:30
parent e165269e92
commit 8dc773d205
61 changed files with 22829 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
import React, { useState } from 'react';
import {
Plus,
Search,
Filter,
Truck,
FileText,
Wrench,
Calendar,
AlertTriangle,
ExternalLink,
ChevronRight,
ShieldCheck,
Fuel,
Gauge
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Card } from '../../components/Common';
interface Vehicle {
id: string;
number: string;
type: 'ALS' | 'BLS' | 'TRANSPORT';
model: string;
station: string;
status: 'ACTIVE' | 'MAINTENANCE' | 'BREAKDOWN' | 'OFF_DUTY';
docs: {
rc: string;
fc: string;
insurance: string;
permit: string;
};
lastService: string;
nextService: string;
fuel: number;
}
const MOCK_FLEET: Vehicle[] = [
{ id: 'V-001', number: 'KA 01 MG 2341', type: 'ALS', model: 'Force Traveller 2024', station: 'ALPHA-NODE-01', status: 'ACTIVE', docs: { rc: 'VALID', fc: 'EXPIRING_SOON', insurance: 'VALID', permit: 'VALID' }, lastService: '2026-04-10', nextService: '2026-07-10', fuel: 85 },
{ id: 'V-002', number: 'KA 51 BH 9921', type: 'BLS', model: 'Tata Winger 2023', station: 'BETA-HUB-04', status: 'MAINTENANCE', docs: { rc: 'VALID', fc: 'VALID', insurance: 'VALID', permit: 'VALID' }, lastService: '2026-05-01', nextService: '2026-08-01', fuel: 42 },
{ id: 'V-003', number: 'KA 03 AA 1122', type: 'ALS', model: 'Force Traveller 2023', station: 'ALPHA-NODE-01', status: 'BREAKDOWN', docs: { rc: 'VALID', fc: 'VALID', insurance: 'EXPIRING_SOON', permit: 'VALID' }, lastService: '2026-02-15', nextService: '2026-05-15', fuel: 0 },
{ id: 'V-004', number: 'KA 05 MN 5678', type: 'TRANSPORT', model: 'Maruti Eeco 2022', station: 'GAMMA-STATION-02', status: 'ACTIVE', docs: { rc: 'VALID', fc: 'VALID', insurance: 'VALID', permit: 'VALID' }, lastService: '2026-03-20', nextService: '2026-06-20', fuel: 92 },
];
export const FleetAssets: React.FC = () => {
const [searchQuery, setSearchQuery] = useState('');
const [selectedVehicle, setSelectedVehicle] = useState<Vehicle | null>(null);
return (
<div className="fleet-assets animate-in fade-in duration-500">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
<div style={{ display: 'flex', gap: '12px' }}>
<div className="glass" style={{ padding: '4px 16px', borderRadius: '12px', display: 'flex', alignItems: 'center', gap: '8px', border: '1px solid rgba(255,255,255,0.1)' }}>
<Search size={16} style={{ opacity: 0.5 }} />
<input
type="text"
placeholder="Search by vehicle number or model..."
style={{ background: 'transparent', border: 'none', color: '#fff', fontSize: '0.875rem', padding: '8px 0', width: '300px', outline: 'none' }}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<button className="btn-icon glass"><Filter size={18} /></button>
</div>
<button className="btn-primary" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Plus size={18} /> REGISTER NEW VEHICLE
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: '24px' }}>
{/* Fleet Inventory Grid */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: '16px' }}>
{MOCK_FLEET.map((v) => (
<Card
key={v.id}
onClick={() => setSelectedVehicle(v)}
style={{
cursor: 'pointer',
border: selectedVehicle?.id === v.id ? '2px solid var(--accent-cyan)' : '1px solid var(--card-border)',
background: selectedVehicle?.id === v.id ? 'rgba(59, 130, 246, 0.05)' : 'rgba(15, 23, 42, 0.4)'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '16px' }}>
<div>
<div style={{ fontSize: '0.65rem', fontWeight: 900, color: 'var(--accent-cyan)', marginBottom: '4px' }}>{v.type} UNIT {v.id}</div>
<h3 style={{ fontSize: '1.125rem', fontWeight: 800 }}>{v.number}</h3>
<div style={{ fontSize: '0.75rem', opacity: 0.5 }}>{v.model}</div>
</div>
<div style={{
padding: '4px 8px',
borderRadius: '4px',
fontSize: '0.65rem',
fontWeight: 900,
background: v.status === 'ACTIVE' ? 'rgba(34, 197, 94, 0.1)' : v.status === 'BREAKDOWN' ? 'rgba(239, 68, 68, 0.1)' : 'rgba(245, 158, 11, 0.1)',
color: v.status === 'ACTIVE' ? '#22C55E' : v.status === 'BREAKDOWN' ? '#EF4444' : '#F59E0B',
border: `1px solid ${v.status === 'ACTIVE' ? 'rgba(34, 197, 94, 0.2)' : v.status === 'BREAKDOWN' ? 'rgba(239, 68, 68, 0.2)' : 'rgba(245, 158, 11, 0.2)'}`
}}>
{v.status}
</div>
</div>
<div style={{ display: 'flex', gap: '20px', marginBottom: '16px' }}>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '0.6rem', opacity: 0.5, textTransform: 'uppercase', marginBottom: '4px' }}>Fuel Level</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{ flex: 1, height: '4px', background: 'rgba(255,255,255,0.1)', borderRadius: '2px', overflow: 'hidden' }}>
<div style={{ width: `${v.fuel}%`, height: '100%', background: v.fuel < 25 ? '#EF4444' : '#22C55E' }}></div>
</div>
<span style={{ fontSize: '0.75rem', fontWeight: 700 }}>{v.fuel}%</span>
</div>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingTop: '16px', borderTop: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', gap: '8px' }}>
<div className={v.docs.fc === 'VALID' ? 'status-dot-green' : 'status-pulse-amber'} title="Fitness Certificate"></div>
<div className={v.docs.insurance === 'VALID' ? 'status-dot-green' : 'status-pulse-amber'} title="Insurance"></div>
<div className={v.docs.permit === 'VALID' ? 'status-dot-green' : 'status-pulse-amber'} title="Ambulance Permit"></div>
</div>
<span style={{ fontSize: '0.7rem', opacity: 0.5 }}>{v.station}</span>
</div>
</Card>
))}
</div>
</div>
{/* Detailed Inspector Panel */}
<div style={{ position: 'sticky', top: '0' }}>
{selectedVehicle ? (
<AnimatePresence mode="wait">
<motion.div
key={selectedVehicle.id}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
>
<Card title="Asset Intelligence" subtitle={`Detailed diagnostics for ${selectedVehicle.number}`}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
{/* Critical Document Status */}
<div>
<h4 style={{ fontSize: '0.75rem', fontWeight: 800, textTransform: 'uppercase', color: 'var(--accent-cyan)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
<FileText size={14} /> Document Vault
</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{[
{ label: 'Registration (RC)', status: selectedVehicle.docs.rc, expiry: '2030-12-15' },
{ label: 'Fitness (FC)', status: selectedVehicle.docs.fc, expiry: '2026-06-01' },
{ label: 'Insurance Policy', status: selectedVehicle.docs.insurance, expiry: '2026-05-15' },
{ label: 'Ambulance Permit', status: selectedVehicle.docs.permit, expiry: '2026-09-20' },
].map((doc, idx) => (
<div key={idx} style={{ padding: '12px', borderRadius: '8px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<div style={{ fontSize: '0.8125rem', fontWeight: 600 }}>{doc.label}</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5 }}>Expires: {doc.expiry}</div>
</div>
<div style={{ color: doc.status === 'VALID' ? '#22C55E' : '#F59E0B', display: 'flex', alignItems: 'center', gap: '6px' }}>
{doc.status === 'VALID' ? <ShieldCheck size={16} /> : <AlertTriangle size={16} />}
<span style={{ fontSize: '0.65rem', fontWeight: 900 }}>{doc.status}</span>
</div>
</div>
))}
</div>
</div>
{/* Maintenance History */}
<div>
<h4 style={{ fontSize: '0.75rem', fontWeight: 800, textTransform: 'uppercase', color: 'var(--accent-cyan)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
<Wrench size={14} /> Service Records
</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{ padding: '16px', borderRadius: '12px', background: 'rgba(59, 130, 246, 0.05)', border: '1px solid rgba(59, 130, 246, 0.1)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style={{ fontSize: '0.75rem', fontWeight: 700 }}>Upcoming Service</span>
<span style={{ fontSize: '0.75rem', color: 'var(--accent-cyan)' }}>{selectedVehicle.nextService}</span>
</div>
<div style={{ fontSize: '0.7rem', opacity: 0.6 }}>Scheduled for: Engine Oil change, Brake pad inspection, and AC filter cleaning.</div>
</div>
<div style={{ padding: '12px', borderRadius: '8px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between' }}>
<div style={{ fontSize: '0.75rem' }}>Last Major Service</div>
<div style={{ fontSize: '0.75rem', fontWeight: 700 }}>{selectedVehicle.lastService}</div>
</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
<button className="btn-ghost" style={{ width: '100%', fontSize: '0.75rem' }}>VIEW ALL RECORDS</button>
<button className="btn-primary" style={{ width: '100%', fontSize: '0.75rem' }}>LOG MAINTENANCE</button>
</div>
</div>
</Card>
</motion.div>
</AnimatePresence>
) : (
<div className="glass" style={{ padding: '40px', borderRadius: '16px', textAlign: 'center', border: '1px dashed rgba(255,255,255,0.1)' }}>
<div style={{ width: '48px', height: '48px', borderRadius: '50%', background: 'rgba(255,255,255,0.05)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 16px', color: 'var(--accent-cyan)' }}>
<Truck size={24} />
</div>
<h3 style={{ fontSize: '1rem', fontWeight: 700, marginBottom: '8px' }}>No Asset Selected</h3>
<p style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>Select a vehicle from the fleet inventory to view tactical diagnostics, documents, and service history.</p>
</div>
)}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,200 @@
import React, { useState } from 'react';
import {
Plus,
Search,
Filter,
ShoppingCart,
Package,
AlertTriangle,
ArrowUpRight,
ArrowDownLeft,
ChevronRight,
Database,
Truck,
Activity,
Archive,
BarChart3
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Card } from '../../components/Common';
interface InventoryItem {
id: string;
name: string;
category: 'MEDICINE' | 'CONSUMABLE' | 'EQUIPMENT';
totalStock: number;
minStock: number;
unit: string;
expiringSoon: number;
vehicles: { vehicleId: string; stock: number }[];
}
const MOCK_INVENTORY: InventoryItem[] = [
{ id: 'ITM-001', name: 'Adrenaline Injection 1mg', category: 'MEDICINE', totalStock: 450, minStock: 100, unit: 'AMPULES', expiringSoon: 24, vehicles: [{ vehicleId: 'V-001', stock: 10 }, { vehicleId: 'V-002', stock: 8 }] },
{ id: 'ITM-002', name: 'Oxygen Cylinder (D-Type)', category: 'EQUIPMENT', totalStock: 32, minStock: 10, unit: 'UNITS', expiringSoon: 0, vehicles: [{ vehicleId: 'V-001', stock: 2 }, { vehicleId: 'V-002', stock: 1 }] },
{ id: 'ITM-003', name: 'Surgical Gloves (Size 7)', category: 'CONSUMABLE', totalStock: 1200, minStock: 500, unit: 'PAIRS', expiringSoon: 0, vehicles: [{ vehicleId: 'V-001', stock: 50 }, { vehicleId: 'V-002', stock: 40 }] },
{ id: 'ITM-004', name: 'IV Fluids (NS 500ml)', category: 'MEDICINE', totalStock: 85, minStock: 150, unit: 'BOTTLES', expiringSoon: 12, vehicles: [{ vehicleId: 'V-001', stock: 5 }, { vehicleId: 'V-002', stock: 3 }] },
];
export const FleetInventory: React.FC = () => {
const [selectedItem, setSelectedItem] = useState<InventoryItem | null>(null);
return (
<div className="fleet-inventory animate-in fade-in duration-500">
{/* Top Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px', marginBottom: '24px' }}>
<div className="glass" style={{ padding: '20px', borderRadius: '16px', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: 'var(--accent-cyan)' }}><Archive size={20} /></div>
<span style={{ fontSize: '0.65rem', fontWeight: 900, color: 'var(--accent-green)' }}>+12%</span>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900 }}>1,248</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>TOTAL SKUs</div>
</div>
<div className="glass" style={{ padding: '20px', borderRadius: '16px', border: '1px solid rgba(239, 68, 68, 0.2)', background: 'rgba(239, 68, 68, 0.02)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: '#EF4444' }}><AlertTriangle size={20} /></div>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900, color: '#EF4444' }}>14</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>LOW STOCK ITEMS</div>
</div>
<div className="glass" style={{ padding: '20px', borderRadius: '16px', border: '1px solid rgba(245, 158, 11, 0.2)', background: 'rgba(245, 158, 11, 0.02)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: '#F59E0B' }}><Clock size={20} /></div>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900, color: '#F59E0B' }}>8</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>EXPIRING (30D)</div>
</div>
<div className="glass" style={{ padding: '20px', borderRadius: '16px', border: '1px solid rgba(59, 130, 246, 0.2)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: 'var(--accent-cyan)' }}><BarChart3 size={20} /></div>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900 }}>42.5k</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>CONSUMPTION (MTD)</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: '24px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<Card title="Tactical Inventory Ledger">
<div style={{ marginBottom: '16px', display: 'flex', gap: '12px' }}>
<div className="glass" style={{ flex: 1, padding: '8px 16px', borderRadius: '12px', display: 'flex', alignItems: 'center', gap: '8px', border: '1px solid rgba(255,255,255,0.1)' }}>
<Search size={16} style={{ opacity: 0.5 }} />
<input
type="text"
placeholder="Filter item master by name, category, or batch..."
style={{ background: 'transparent', border: 'none', color: '#fff', fontSize: '0.8125rem', width: '100%', outline: 'none' }}
/>
</div>
<button className="btn-primary" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Plus size={16} /> ADD STOCK
</button>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ textAlign: 'left', opacity: 0.5, fontSize: '0.65rem', textTransform: 'uppercase', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
<th style={{ padding: '12px' }}>Item Details</th>
<th style={{ padding: '12px' }}>Category</th>
<th style={{ padding: '12px' }}>Current Stock</th>
<th style={{ padding: '12px' }}>Status</th>
</tr>
</thead>
<tbody>
{MOCK_INVENTORY.map(item => (
<tr
key={item.id}
onClick={() => setSelectedItem(item)}
style={{
borderBottom: '1px solid rgba(255,255,255,0.05)',
cursor: 'pointer',
background: selectedItem?.id === item.id ? 'rgba(59, 130, 246, 0.05)' : 'transparent'
}}
className="hover-glow"
>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontWeight: 700, fontSize: '0.875rem' }}>{item.name}</div>
<div style={{ fontSize: '0.65rem', opacity: 0.5 }}>SKU: {item.id}</div>
</td>
<td style={{ padding: '16px 12px' }}>
<span style={{ fontSize: '0.7rem', fontWeight: 600, opacity: 0.7 }}>{item.category}</span>
</td>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontWeight: 800 }}>{item.totalStock} <span style={{ fontSize: '0.65rem', fontWeight: 500, opacity: 0.5 }}>{item.unit}</span></div>
</td>
<td style={{ padding: '16px 12px' }}>
{item.totalStock < item.minStock ? (
<div style={{ color: '#EF4444', fontSize: '0.65rem', fontWeight: 900, display: 'flex', alignItems: 'center', gap: '4px' }}>
<AlertTriangle size={12} /> CRITICAL
</div>
) : (
<div style={{ color: '#22C55E', fontSize: '0.65rem', fontWeight: 900 }}>OPTIMAL</div>
)}
</td>
</tr>
))}
</tbody>
</table>
</Card>
</div>
<div>
{selectedItem ? (
<AnimatePresence mode="wait">
<motion.div
key={selectedItem.id}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
>
<Card title="Supply Intelligence" subtitle={`Ambulance-wise distribution for ${selectedItem.name}`}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
<div style={{ padding: '16px', borderRadius: '12px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ fontSize: '0.65rem', opacity: 0.5, marginBottom: '4px' }}>STOCK GAP</div>
<div style={{ fontSize: '1.25rem', fontWeight: 900, color: selectedItem.totalStock < selectedItem.minStock ? '#EF4444' : '#22C55E' }}>
{selectedItem.totalStock - selectedItem.minStock}
</div>
</div>
<div style={{ padding: '16px', borderRadius: '12px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ fontSize: '0.65rem', opacity: 0.5, marginBottom: '4px' }}>REORDER POINT</div>
<div style={{ fontSize: '1.25rem', fontWeight: 900 }}>{selectedItem.minStock}</div>
</div>
</div>
<div>
<h4 style={{ fontSize: '0.75rem', fontWeight: 800, textTransform: 'uppercase', color: 'var(--accent-cyan)', marginBottom: '12px' }}>Ambulance Stock Distribution</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{selectedItem.vehicles.map((v, i) => (
<div key={i} style={{ padding: '12px', borderRadius: '12px', background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<Truck size={14} style={{ opacity: 0.5 }} />
<span style={{ fontSize: '0.8125rem', fontWeight: 700 }}>{v.vehicleId}</span>
</div>
<div style={{ fontWeight: 800, color: v.stock < 5 ? '#EF4444' : 'inherit' }}>
{v.stock} {selectedItem.unit}
</div>
</div>
))}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
<button className="btn-ghost" style={{ width: '100%', fontSize: '0.75rem' }}>AUDIT LOG</button>
<button className="btn-primary" style={{ width: '100%', fontSize: '0.75rem' }}>RESTOCK REQUEST</button>
</div>
</div>
</Card>
</motion.div>
</AnimatePresence>
) : (
<div className="glass" style={{ padding: '40px', borderRadius: '16px', textAlign: 'center', border: '1px dashed rgba(255,255,255,0.1)' }}>
<Package size={32} style={{ opacity: 0.2, margin: '0 auto 16px' }} />
<h3 style={{ fontSize: '1rem', fontWeight: 700, marginBottom: '8px' }}>Select Supply Item</h3>
<p style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>Inspect real-time stock levels across the fleet, track expiries, and manage replenishment requests.</p>
</div>
)}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,205 @@
import React, { useState } from 'react';
import {
UserPlus,
Search,
Filter,
Users,
Medal,
Clock,
ShieldCheck,
AlertTriangle,
Mail,
Phone,
Calendar,
CheckCircle2,
XCircle,
MoreVertical
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Card } from '../../components/Common';
interface Staff {
id: string;
name: string;
role: 'DRIVER' | 'EMT' | 'DOCTOR' | 'PARAMEDIC';
status: 'ON_DUTY' | 'OFF_DUTY' | 'ON_LEAVE';
specialization?: string;
phone: string;
email: string;
joinedDate: string;
tripsCompleted: number;
rating: number;
certExpiry: string;
}
const MOCK_STAFF: Staff[] = [
{ id: 'S-101', name: 'Vikram Singh', role: 'DRIVER', status: 'ON_DUTY', phone: '+91 98765 43210', email: 'v.singh@teleems.com', joinedDate: '2023-01-15', tripsCompleted: 452, rating: 4.8, certExpiry: '2026-12-01' },
{ id: 'S-102', name: 'Dr. Ananya Iyer', role: 'DOCTOR', specialization: 'Critical Care', status: 'ON_DUTY', phone: '+91 98765 43211', email: 'a.iyer@teleems.com', joinedDate: '2023-06-20', tripsCompleted: 128, rating: 4.9, certExpiry: '2026-05-15' },
{ id: 'S-103', name: 'Rahul Verma', role: 'EMT', status: 'ON_LEAVE', phone: '+91 98765 43212', email: 'r.verma@teleems.com', joinedDate: '2024-02-10', tripsCompleted: 215, rating: 4.7, certExpiry: '2026-06-01' },
{ id: 'S-104', name: 'Suresh Kumar', role: 'DRIVER', status: 'OFF_DUTY', phone: '+91 98765 43213', email: 's.kumar@teleems.com', joinedDate: '2022-11-05', tripsCompleted: 890, rating: 4.6, certExpiry: '2026-08-20' },
];
export const FleetPersonnel: React.FC = () => {
const [activeTab, setActiveTab] = useState<'ALL' | 'DRIVER' | 'EMT' | 'DOCTOR'>('ALL');
const [selectedStaff, setSelectedStaff] = useState<Staff | null>(null);
const filteredStaff = MOCK_STAFF.filter(s => activeTab === 'ALL' || s.role === activeTab);
return (
<div className="fleet-personnel animate-in fade-in duration-500">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
<div style={{ display: 'flex', gap: '8px' }}>
{['ALL', 'DRIVER', 'EMT', 'DOCTOR'].map(t => (
<button
key={t}
onClick={() => setActiveTab(t as any)}
style={{
padding: '8px 16px',
borderRadius: '8px',
border: '1px solid rgba(255,255,255,0.1)',
background: activeTab === t ? 'var(--accent-cyan)' : 'rgba(255,255,255,0.05)',
color: activeTab === t ? '#000' : 'var(--text-secondary)',
fontSize: '0.75rem',
fontWeight: 700,
cursor: 'pointer',
transition: 'all 0.2s'
}}
>
{t}S
</button>
))}
</div>
<button className="btn-primary" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<UserPlus size={18} /> REGISTER PERSONNEL
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: '24px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
<Card style={{ padding: '0', overflow: 'hidden' }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,0.1)', textAlign: 'left', background: 'rgba(255,255,255,0.02)' }}>
<th style={{ padding: '16px', fontSize: '0.75rem', textTransform: 'uppercase', opacity: 0.5 }}>Personnel</th>
<th style={{ padding: '16px', fontSize: '0.75rem', textTransform: 'uppercase', opacity: 0.5 }}>Role / Specialization</th>
<th style={{ padding: '16px', fontSize: '0.75rem', textTransform: 'uppercase', opacity: 0.5 }}>Status</th>
<th style={{ padding: '16px', fontSize: '0.75rem', textTransform: 'uppercase', opacity: 0.5 }}>Trips</th>
<th style={{ padding: '16px', fontSize: '0.75rem', textTransform: 'uppercase', opacity: 0.5 }}>Actions</th>
</tr>
</thead>
<tbody>
{filteredStaff.map(s => (
<tr
key={s.id}
onClick={() => setSelectedStaff(s)}
style={{
borderBottom: '1px solid rgba(255,255,255,0.05)',
cursor: 'pointer',
background: selectedStaff?.id === s.id ? 'rgba(59, 130, 246, 0.05)' : 'transparent'
}}
className="hover-glow"
>
<td style={{ padding: '16px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style={{ width: '36px', height: '36px', borderRadius: '50%', background: 'rgba(59, 130, 246, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--accent-cyan)', fontWeight: 700, fontSize: '0.875rem', border: '1px solid rgba(59, 130, 246, 0.2)' }}>
{s.name.charAt(0)}
</div>
<div>
<div style={{ fontWeight: 700 }}>{s.name}</div>
<div style={{ fontSize: '0.65rem', opacity: 0.5 }}>ID: {s.id}</div>
</div>
</div>
</td>
<td style={{ padding: '16px' }}>
<div style={{ fontSize: '0.8125rem', fontWeight: 600 }}>{s.role}</div>
{s.specialization && <div style={{ fontSize: '0.65rem', opacity: 0.5 }}>{s.specialization}</div>}
</td>
<td style={{ padding: '16px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<div style={{ width: '6px', height: '6px', borderRadius: '50%', background: s.status === 'ON_DUTY' ? '#22C55E' : s.status === 'ON_LEAVE' ? '#EF4444' : '#94A3B8' }}></div>
<span style={{ fontSize: '0.7rem', fontWeight: 700, opacity: 0.8 }}>{s.status.replace('_', ' ')}</span>
</div>
</td>
<td style={{ padding: '16px' }}>
<div style={{ fontWeight: 800 }}>{s.tripsCompleted}</div>
</td>
<td style={{ padding: '16px' }}>
<button className="btn-ghost-sm"><MoreVertical size={14} /></button>
</td>
</tr>
))}
</tbody>
</table>
</Card>
</div>
<div style={{ position: 'sticky', top: '0' }}>
{selectedStaff ? (
<AnimatePresence mode="wait">
<motion.div
key={selectedStaff.id}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
>
<Card>
<div style={{ textAlign: 'center', marginBottom: '24px' }}>
<div style={{ width: '80px', height: '80px', borderRadius: '50%', background: 'rgba(59, 130, 246, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--accent-cyan)', fontSize: '1.5rem', fontWeight: 900, border: '2px solid var(--accent-cyan)', margin: '0 auto 16px', boxShadow: '0 0 20px rgba(59, 130, 246, 0.2)' }}>
{selectedStaff.name.charAt(0)}
</div>
<h2 style={{ fontSize: '1.25rem', fontWeight: 800 }}>{selectedStaff.name}</h2>
<div style={{ fontSize: '0.75rem', color: 'var(--accent-cyan)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em' }}>{selectedStaff.role}</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginBottom: '24px' }}>
<div style={{ padding: '12px', borderRadius: '12px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ fontSize: '0.65rem', opacity: 0.5, textTransform: 'uppercase', marginBottom: '4px' }}>Trips Rate</div>
<div style={{ fontSize: '1.125rem', fontWeight: 800, color: 'var(--accent-green)' }}>{selectedStaff.rating}/5.0</div>
</div>
<div style={{ padding: '12px', borderRadius: '12px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ fontSize: '0.65rem', opacity: 0.5, textTransform: 'uppercase', marginBottom: '4px' }}>SLA Compliance</div>
<div style={{ fontSize: '1.125rem', fontWeight: 800 }}>98.4%</div>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div>
<h4 style={{ fontSize: '0.7rem', fontWeight: 800, textTransform: 'uppercase', opacity: 0.5, marginBottom: '8px' }}>Contact Information</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', fontSize: '0.8125rem' }}>
<Phone size={14} style={{ opacity: 0.5 }} /> {selectedStaff.phone}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', fontSize: '0.8125rem' }}>
<Mail size={14} style={{ opacity: 0.5 }} /> {selectedStaff.email}
</div>
</div>
</div>
<div>
<h4 style={{ fontSize: '0.7rem', fontWeight: 800, textTransform: 'uppercase', opacity: 0.5, marginBottom: '8px' }}>Certifications</h4>
<div style={{ padding: '12px', borderRadius: '12px', background: 'rgba(245, 158, 11, 0.05)', border: '1px solid rgba(245, 158, 11, 0.1)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<div style={{ fontSize: '0.75rem', fontWeight: 700 }}>Professional License</div>
<div style={{ fontSize: '0.65rem', opacity: 0.6 }}>Expiry: {selectedStaff.certExpiry}</div>
</div>
<AlertTriangle size={16} color="#F59E0B" />
</div>
</div>
</div>
<button className="btn-primary" style={{ width: '100%', marginTop: '24px' }}>MANAGE SHIFT SCHEDULE</button>
</Card>
</motion.div>
</AnimatePresence>
) : (
<div className="glass" style={{ padding: '40px', borderRadius: '16px', textAlign: 'center', border: '1px dashed rgba(255,255,255,0.1)' }}>
<Users size={32} style={{ opacity: 0.2, margin: '0 auto 16px' }} />
<h3 style={{ fontSize: '1rem', fontWeight: 700, marginBottom: '8px' }}>Select Personnel</h3>
<p style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>View detailed performance metrics, licensing status, and shift history for your fleet crew.</p>
</div>
)}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,165 @@
import React, { useState } from 'react';
import {
Calendar,
Clock,
Users,
Truck,
AlertTriangle,
CheckCircle2,
Plus,
ChevronLeft,
ChevronRight,
MoreVertical,
Navigation,
ShieldAlert
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Card } from '../../components/Common';
interface Assignment {
id: string;
vehicleId: string;
shift: 'MORNING' | 'EVENING' | 'NIGHT';
driver: string;
emt: string;
doctor?: string;
status: 'SCHEDULED' | 'ON_DUTY' | 'HANDOVER_PENDING';
startTime: string;
endTime: string;
}
const MOCK_ASSIGNMENTS: Assignment[] = [
{ id: 'AS-1001', vehicleId: 'V-001 (ALS)', shift: 'MORNING', driver: 'Vikram Singh', emt: 'Rahul Verma', doctor: 'Dr. Ananya Iyer', status: 'ON_DUTY', startTime: '06:00', endTime: '14:00' },
{ id: 'AS-1002', vehicleId: 'V-002 (BLS)', shift: 'MORNING', driver: 'Suresh Kumar', emt: 'Amit Roy', status: 'ON_DUTY', startTime: '06:00', endTime: '14:00' },
{ id: 'AS-1003', vehicleId: 'V-003 (ALS)', shift: 'EVENING', driver: 'Karan Mehra', emt: 'Priya Das', doctor: 'Dr. Sameer Gupta', status: 'SCHEDULED', startTime: '14:00', endTime: '22:00' },
{ id: 'AS-1004', vehicleId: 'V-004 (TRANS)', shift: 'MORNING', driver: 'Ravi Teja', emt: 'Sneha Rao', status: 'HANDOVER_PENDING', startTime: '06:00', endTime: '14:00' },
];
export const FleetScheduling: React.FC = () => {
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
return (
<div className="fleet-scheduling animate-in fade-in duration-500">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<div className="glass" style={{ padding: '8px 16px', borderRadius: '12px', display: 'flex', alignItems: 'center', gap: '12px', border: '1px solid rgba(255,255,255,0.1)' }}>
<button className="btn-ghost-sm" style={{ padding: '4px' }}><ChevronLeft size={16} /></button>
<span style={{ fontWeight: 800, fontSize: '0.875rem' }}>{selectedDate}</span>
<button className="btn-ghost-sm" style={{ padding: '4px' }}><ChevronRight size={16} /></button>
</div>
<div style={{ display: 'flex', gap: '8px' }}>
{['DAY', 'WEEK', 'MONTH'].map(v => (
<button key={v} style={{ fontSize: '0.65rem', fontWeight: 900, padding: '6px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.05)', background: v === 'DAY' ? 'var(--accent-cyan)' : 'transparent', color: v === 'DAY' ? '#000' : 'var(--text-secondary)' }}>{v}</button>
))}
</div>
</div>
<button className="btn-primary" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Plus size={18} /> CREATE NEW ASSIGNMENT
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '24px' }}>
{/* Mission Roster Grid */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<Card title="Shift Roster Matrix">
<div className="table-container">
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ textAlign: 'left', opacity: 0.5, fontSize: '0.65rem', textTransform: 'uppercase', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
<th style={{ padding: '12px' }}>Time Slot</th>
<th style={{ padding: '12px' }}>Vehicle</th>
<th style={{ padding: '12px' }}>Assigned Crew</th>
<th style={{ padding: '12px' }}>Status</th>
<th style={{ padding: '12px' }}>Actions</th>
</tr>
</thead>
<tbody>
{MOCK_ASSIGNMENTS.map(as => (
<tr key={as.id} style={{ borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontWeight: 800, fontSize: '0.875rem', color: 'var(--accent-cyan)' }}>{as.startTime} - {as.endTime}</div>
<div style={{ fontSize: '0.65rem', opacity: 0.5 }}>{as.shift} SHIFT</div>
</td>
<td style={{ padding: '16px 12px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Truck size={14} style={{ opacity: 0.5 }} />
<span style={{ fontWeight: 700 }}>{as.vehicleId}</span>
</div>
</td>
<td style={{ padding: '16px 12px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div style={{ fontSize: '0.75rem', fontWeight: 600 }}>P: {as.driver}</div>
<div style={{ fontSize: '0.75rem', opacity: 0.8 }}>E: {as.emt}</div>
{as.doctor && <div style={{ fontSize: '0.75rem', color: 'var(--accent-green)' }}>D: {as.doctor}</div>}
</div>
</td>
<td style={{ padding: '16px 12px' }}>
<span style={{
fontSize: '0.6rem',
fontWeight: 900,
padding: '4px 8px',
borderRadius: '4px',
background: as.status === 'ON_DUTY' ? 'rgba(34, 197, 94, 0.1)' : as.status === 'HANDOVER_PENDING' ? 'rgba(245, 158, 11, 0.1)' : 'rgba(148, 163, 184, 0.1)',
color: as.status === 'ON_DUTY' ? '#22C55E' : as.status === 'HANDOVER_PENDING' ? '#F59E0B' : '#94A3B8',
border: `1px solid ${as.status === 'ON_DUTY' ? 'rgba(34, 197, 94, 0.2)' : as.status === 'HANDOVER_PENDING' ? 'rgba(245, 158, 11, 0.2)' : 'rgba(148, 163, 184, 0.2)'}`
}}>{as.status.replace('_', ' ')}</span>
</td>
<td style={{ padding: '16px 12px' }}>
<button className="btn-ghost-sm"><MoreVertical size={14} /></button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
</div>
{/* Conflict & Handover Panel */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
<Card title="Conflict Engine" glowColor="amber">
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
<div style={{ padding: '12px', borderRadius: '12px', background: 'rgba(245, 158, 11, 0.05)', border: '1px solid rgba(245, 158, 11, 0.1)', display: 'flex', gap: '12px' }}>
<ShieldAlert size={20} color="#F59E0B" />
<div>
<div style={{ fontSize: '0.75rem', fontWeight: 800 }}>DOUBLE BOOKING DETECTED</div>
<p style={{ fontSize: '0.65rem', opacity: 0.7, marginTop: '4px' }}>Amit Roy (EMT) assigned to V-002 and V-005 in Evening Shift.</p>
</div>
</div>
<div style={{ padding: '12px', borderRadius: '12px', background: 'rgba(239, 68, 68, 0.05)', border: '1px solid rgba(239, 68, 68, 0.1)', display: 'flex', gap: '12px' }}>
<AlertTriangle size={20} color="#EF4444" />
<div>
<div style={{ fontSize: '0.75rem', fontWeight: 800 }}>CERTIFICATION EXPIRED</div>
<p style={{ fontSize: '0.65rem', opacity: 0.7, marginTop: '4px' }}>Dr. Sameer Gupta license expired on 2026-05-01.</p>
</div>
</div>
</div>
</Card>
<Card title="Shift Handover Tracker">
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
<div style={{ padding: '16px', borderRadius: '12px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<span style={{ fontSize: '0.75rem', fontWeight: 700 }}>V-004 Handover Checklist</span>
<span style={{ fontSize: '0.65rem', color: '#F59E0B', fontWeight: 800 }}>4/6 TASKS</span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.7rem', opacity: 0.8 }}>
<CheckCircle2 size={12} color="#22C55E" /> Fuel Tank Checked (100%)
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.7rem', opacity: 0.8 }}>
<CheckCircle2 size={12} color="#22C55E" /> Oxygen Level Verified
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.7rem', opacity: 0.5 }}>
<Clock size={12} /> Narcotics Inventory Counter-sign
</div>
</div>
</div>
</div>
<button className="btn-primary" style={{ width: '100%', marginTop: '16px', fontSize: '0.75rem' }}>RESOLVE HANDOVERS</button>
</Card>
</div>
</div>
</div>
);
};