implement TeleEMS platform architecture with centralized API client and master data management system
This commit is contained in:
207
src/pages/fleet/FleetAssets.tsx
Normal file
207
src/pages/fleet/FleetAssets.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
200
src/pages/fleet/FleetInventory.tsx
Normal file
200
src/pages/fleet/FleetInventory.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
205
src/pages/fleet/FleetPersonnel.tsx
Normal file
205
src/pages/fleet/FleetPersonnel.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
165
src/pages/fleet/FleetScheduling.tsx
Normal file
165
src/pages/fleet/FleetScheduling.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user