import React, { useState, useEffect, useCallback } from 'react'; import { UserPlus, Search, ShieldCheck, ChevronLeft, ChevronRight, Loader2, AlertCircle, Phone, Mail, X, Eye, EyeOff } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; interface Staff { id: string; type?: string; status?: string; createdAt?: string; aadhaar_number?: string; professional_details?: { qualification?: string; certificate_expiry?: string; certificate_number?: string; certification_body?: string; license_expiry?: string; license_number?: string; license_category?: string; }; user?: { name?: string; phone?: string; email?: string | null; status?: string; isAvailable?: boolean; }; } const ROLE_COLORS: Record = { DRIVER: { color: '#06B6D4', bg: 'rgba(6,182,212,0.12)' }, EMT: { color: '#3B82F6', bg: 'rgba(59,130,246,0.12)' }, DOCTOR: { color: '#10B981', bg: 'rgba(16,185,129,0.12)' }, }; const DEF = { color: '#94A3B8', bg: 'rgba(148,163,184,0.1)' }; const STATUS_CFG: Record = { ON_DUTY: { label: 'On Duty', color: '#10B981' }, OFF_DUTY: { label: 'Off Duty', color: '#64748B' }, ON_LEAVE: { label: 'On Leave', color: '#F59E0B' }, ACTIVE: { label: 'Active', color: '#10B981' }, INACTIVE: { label: 'Inactive', color: '#EF4444' }, }; const FILTERS = ['ALL', 'DRIVER', 'EMT', 'DOCTOR'] as const; const PAGE_SIZE = 5; const th: React.CSSProperties = { padding: '14px 18px', fontSize: '0.68rem', color: '#64748B', textTransform: 'uppercase', letterSpacing: '1px', fontWeight: 700, textAlign: 'left', borderBottom: '1px solid rgba(255,255,255,0.06)', background: 'rgba(255,255,255,0.02)', whiteSpace: 'nowrap' }; const norm = (s: Staff) => { const pd = s.professional_details; return { id: s.id, name: s.user?.name || '—', role: (s.type || '').toUpperCase(), status: (s.status || s.user?.status || 'ACTIVE').toUpperCase(), phone: s.user?.phone || '—', email: s.user?.email || null, joinedDate: s.createdAt ? s.createdAt.substring(0, 10) : '', specialization: pd?.qualification || pd?.license_category || pd?.certification_body || '', certExpiry: pd?.certificate_expiry || pd?.license_expiry || '', isAvailable: s.user?.isAvailable ?? true, }; }; export const FleetPersonnel: React.FC = () => { const [staffList, setStaffList] = useState([]); const [loading, setLoading] = useState(false); const [fetchError, setFetchError] = useState(''); const [filter, setFilter] = useState('ALL'); const [search, setSearch] = useState(''); const [page, setPage] = useState(1); const [selectedRaw, setSelectedRaw] = useState(null); const [showModal, setShowModal] = useState(false); const [submitting, setSubmitting] = useState(false); const [submitErr, setSubmitErr] = useState(''); const [submitOk, setSubmitOk] = useState(''); const [showPw, setShowPw] = useState(false); const [staffType, setStaffType] = useState<'DRIVER'|'EMT'|'DOCTOR'>('DRIVER'); const [form, setForm] = useState({ name:'', phone:'', password:'', aadhaar_number:'', license_number:'', license_category:'', license_expiry:'', qualification:'', certification_body:'', certificate_number:'', certificate_expiry:'', medical_registration_number:'', specialization:'', teleconsult_available: false, }); const setF = (k: string, v: string | boolean) => setForm(p => ({ ...p, [k]: v })); const resetModal = () => { setForm({ name:'', phone:'', password:'', aadhaar_number:'', license_number:'', license_category:'', license_expiry:'', qualification:'', certification_body:'', certificate_number:'', certificate_expiry:'', medical_registration_number:'', specialization:'', teleconsult_available: false }); setStaffType('DRIVER'); setSubmitErr(''); setSubmitOk(''); }; const token = localStorage.getItem('teleems_token') || ''; const fetchStaff = useCallback(async () => { setLoading(true); setFetchError(''); try { const res = await fetch('https://teleems-api-gateway.onrender.com/v1/fleet/staff', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); const json = await res.json(); if (res.status === 401 || res.status === 403) { setFetchError('Session expired.'); return; } if (!res.ok) { setFetchError(json?.message || `Error ${res.status}`); return; } let list: Staff[] = []; if (json?.data?.data && Array.isArray(json.data.data)) list = json.data.data; else if (json?.data && Array.isArray(json.data)) list = json.data; else if (Array.isArray(json)) list = json; setStaffList(list); } catch (e) { setFetchError('Network error.'); } finally { setLoading(false); } }, [token]); const submitStaff = async () => { if (!form.name || !form.phone) { setSubmitErr('Name and phone are required.'); return; } setSubmitting(true); setSubmitErr(''); setSubmitOk(''); let professional_details: Record = {}; if (staffType === 'DRIVER') professional_details = { license_number: form.license_number, license_category: form.license_category, license_expiry: form.license_expiry }; if (staffType === 'EMT') professional_details = { qualification: form.qualification, certification_body: form.certification_body, certificate_number: form.certificate_number, certificate_expiry: form.certificate_expiry }; if (staffType === 'DOCTOR') professional_details = { medical_registration_number: form.medical_registration_number, specialization: form.specialization, qualification: form.qualification, teleconsult_available: form.teleconsult_available }; const body = { name: form.name, phone: form.phone, password: form.password, type: staffType, aadhaar_number: form.aadhaar_number, professional_details }; try { const res = await fetch('https://teleems-api-gateway.onrender.com/v1/fleet/staff', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const json = await res.json(); if (!res.ok) { setSubmitErr(json?.message || `Error ${res.status}`); return; } setSubmitOk(`${staffType} registered successfully!`); resetModal(); fetchStaff(); setTimeout(() => { setShowModal(false); setSubmitOk(''); }, 1500); } catch { setSubmitErr('Network error.'); } finally { setSubmitting(false); } }; useEffect(() => { fetchStaff(); }, [fetchStaff]); const normalized = staffList.map(norm); const filtered = normalized.filter(s => (filter === 'ALL' || s.role === filter) && (s.name.toLowerCase().includes(search.toLowerCase()) || s.role.toLowerCase().includes(search.toLowerCase()))); const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const safePage = Math.min(page, totalPages); const pageData = filtered.slice((safePage - 1) * PAGE_SIZE, safePage * PAGE_SIZE); const onDuty = normalized.filter(s => ['ON_DUTY', 'ACTIVE'].includes(s.status)).length; const offDuty = normalized.filter(s => s.status === 'OFF_DUTY').length; const onLeave = normalized.filter(s => s.status === 'ON_LEAVE').length; // ── DETAIL PAGE ── if (selectedRaw) { const s = norm(selectedRaw); const pd = selectedRaw.professional_details; const rc = ROLE_COLORS[s.role] || DEF; const sc = STATUS_CFG[s.status] || { label: s.status, color: '#94A3B8' }; const ini = s.name.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase(); const cw = s.certExpiry ? new Date(s.certExpiry) < new Date(Date.now() + 60 * 24 * 60 * 60 * 1000) : false; const row = (label: string, value: string, mono = false) => (
{label}
{value || '—'}
); return ( {/* Hero */}
{ini}

{s.name}

{s.role} {sc.label} {s.isAvailable && ✓ Available}
{/* Grid */}
{/* Contact */}
📞 Contact
Phone
{s.phone}
{s.email && (
Email
{s.email}
)}
{/* Identity */}
🪪 Identity
{row('Staff ID', s.id, true)} {selectedRaw.aadhaar_number && row('Aadhaar', selectedRaw.aadhaar_number, true)} {row('Joined Date', s.joinedDate)}
{/* Professional — full width */} {pd && (
🎓 Professional Details
{pd.qualification && row('Qualification', pd.qualification)} {pd.certification_body && row('Certification Body', pd.certification_body)} {(pd.certificate_number || pd.license_number) && row('Cert / License No.', pd.certificate_number || pd.license_number || '', true)} {pd.license_category && row('License Category', pd.license_category)} {s.specialization && row('Specialization', s.specialization)} {s.certExpiry && (
Cert Expiry
{s.certExpiry}
)}
)}
); } // ── TABLE VIEW ── return (
{/* ── Add Staff Modal ── */} {showModal && ( { if (e.target === e.currentTarget) setShowModal(false); }} > {/* Modal Header */}

Register New Staff

Fill details based on staff type

{/* Type Selector */}
{(['DRIVER','EMT','DOCTOR'] as const).map(t => { const rc = ROLE_COLORS[t]; return ( ); })}
{/* Common Fields — 2 col grid */}
{[{ k: 'name', label: 'Full Name', ph: 'e.g. Driver 2' }, { k: 'phone', label: 'Phone', ph: '9999999998' }, { k: 'aadhaar_number', label: 'Aadhaar Number', ph: '1234-5678-9001' }].map(f => (
{f.label}
)[f.k] as string} onChange={e => setF(f.k, e.target.value)} placeholder={f.ph} autoComplete="off" style={{ width: '100%', padding: '9px 12px', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 9, color: '#fff', fontSize: '0.85rem', outline: 'none', boxSizing: 'border-box' }} />
))} {/* Password — same row as Aadhaar */}
Password (optional)
setF('password', e.target.value)} placeholder="Min 8 chars" autoComplete="new-password" style={{ width: '100%', padding: '9px 36px 9px 12px', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 9, color: '#fff', fontSize: '0.85rem', outline: 'none', boxSizing: 'border-box' }} />
{/* DRIVER fields */} {staffType === 'DRIVER' && (
Driver Professional Details
{[{ k:'license_number', label:'License Number', ph:'DL-KA-2024-DR1' }, { k:'license_category', label:'License Category', ph:'MCWG/LMV' }, { k:'license_expiry', label:'License Expiry', ph:'2034-01-01' }].map(f => (
{f.label}
)[f.k] as string} onChange={e => setF(f.k, e.target.value)} placeholder={f.ph} style={{ width: '100%', padding: '8px 10px', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, color: '#fff', fontSize: '0.82rem', outline: 'none', boxSizing: 'border-box' }} />
))}
)} {/* EMT fields */} {staffType === 'EMT' && (
EMT Professional Details
{[{ k:'qualification', label:'Qualification', ph:'Diploma in EMT' }, { k:'certification_body', label:'Certification Body', ph:'Indian Resuscitation Council' }, { k:'certificate_number', label:'Certificate Number', ph:'EMT-CERT-9901' }, { k:'certificate_expiry', label:'Certificate Expiry', ph:'2029-08-15' }].map(f => (
{f.label}
)[f.k] as string} onChange={e => setF(f.k, e.target.value)} placeholder={f.ph} style={{ width: '100%', padding: '8px 10px', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, color: '#fff', fontSize: '0.82rem', outline: 'none', boxSizing: 'border-box' }} />
))}
)} {/* DOCTOR fields */} {staffType === 'DOCTOR' && (
Doctor Professional Details
{[{ k:'medical_registration_number', label:'Medical Reg. Number', ph:'MCI/DMC/2024/77' }, { k:'specialization', label:'Specialization', ph:'Emergency Medicine' }, { k:'qualification', label:'Qualification', ph:'MBBS, MD (Emergency)' }].map(f => (
{f.label}
)[f.k] as string} onChange={e => setF(f.k, e.target.value)} placeholder={f.ph} style={{ width: '100%', padding: '8px 10px', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, color: '#fff', fontSize: '0.82rem', outline: 'none', boxSizing: 'border-box' }} />
))}
)} {submitErr &&
{submitErr}
} {submitOk &&
✓ {submitOk}
}
)}
{/* Stats */}
{[{ label: 'Total Staff', value: normalized.length, color: '#06B6D4' }, { label: 'On Duty', value: onDuty, color: '#10B981' }, { label: 'Off Duty', value: offDuty, color: '#64748B' }, { label: 'On Leave', value: onLeave, color: '#F59E0B' }].map(s => (
{loading ? '—' : s.value}
{s.label}
))}
{/* Toolbar */}
{FILTERS.map(f => ( ))}
{ setSearch(e.target.value); setPage(1); }} placeholder="Search staff..." style={{ background: 'transparent', border: 'none', color: '#fff', fontSize: '0.85rem', outline: 'none', width: 170 }} />
{loading &&
Loading staff...
} {!loading && fetchError && (

{fetchError}

)} {!loading && !fetchError && (
{['#', 'Staff Member', 'Role', 'Status', 'Phone', 'Specialization', 'Joined', 'Cert Expiry'].map(h => )} {pageData.length === 0 ? ( ) : pageData.map((s, idx) => { const rc = ROLE_COLORS[s.role] || DEF; const sc = STATUS_CFG[s.status] || { label: s.status, color: '#94A3B8' }; const certSoon = s.certExpiry ? new Date(s.certExpiry) < new Date(Date.now() + 60 * 24 * 60 * 60 * 1000) : false; return ( { const raw = staffList.find(r => r.id === s.id); if (raw) setSelectedRaw(raw); }} style={{ borderBottom: '1px solid rgba(255,255,255,0.05)', cursor: 'pointer', transition: 'background 0.15s' }} onMouseEnter={e => (e.currentTarget.style.background = 'rgba(255,255,255,0.04)')} onMouseLeave={e => (e.currentTarget.style.background = 'transparent')} > ); })}
{h}
{staffList.length === 0 ? 'No staff registered yet.' : 'No results match your filter.'}
{(safePage - 1) * PAGE_SIZE + idx + 1}
{s.name.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase()}
{s.name}
{s.role || '—'}
{sc.label}
{s.phone}
{s.specialization || '—'} {s.joinedDate || '—'} {s.certExpiry ?
{s.certExpiry}
: }
{/* Pagination */}
Showing {filtered.length === 0 ? 0 : (safePage - 1) * PAGE_SIZE + 1}–{Math.min(safePage * PAGE_SIZE, filtered.length)} of {filtered.length} staff
{Array.from({ length: totalPages }, (_, i) => i + 1).map(p => ( ))}
)}
); };