import React, { useState, useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Building2, Activity, Truck, FileText, Settings, CheckCircle2, Monitor, TrendingUp, Database, Video, Hospital, ChevronDown, } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { Card } from '../components/Common'; import { authApi } from '../api/auth'; import { hospitalApi } from '../api/hospital'; import { HospitalSelector } from './hospital/HospitalSelector'; import { EDMonitor } from './hospital/EDMonitor'; import { TripManagement } from './hospital/TripManagement'; import { SetupPanel } from './hospital/SetupPanel'; import { StaffModal } from './hospital/StaffModal'; import { DeptModal } from './hospital/DeptModal'; import { TeleLinkHub } from './hospital/TeleLinkHub'; import { FleetView } from './hospital/FleetView'; import { EPCRRecords } from './hospital/EPCRRecords'; import { PatientArchive } from './hospital/PatientArchive'; import { ReferralsSetup } from './hospital/ReferralsSetup'; import { HospitalAnalytics } from './hospital/HospitalAnalytics'; import './HospitalConsole.css'; type ConsoleModule = | 'ED_MONITOR' | 'BOOKINGS' | 'FLEET' | 'TELELINK' | 'EPCR' | 'HISTORY' | 'REPORTS' | 'SETUP' | 'REFERRALS'; // ─── Static data ───────────────────────────────────────────────────────────── import { incidentsApi } from '../api/incidents'; const INITIAL_TRIPS: any[] = []; const FLEET_DATA: any[] = []; const EPCR_DATA: any[] = []; const TELELINK_QUEUE: any[] = []; // ─── Component ─────────────────────────────────────────────────────────────── export const HospitalConsole: React.FC = () => { // ── Core state ── const [hospitals, setHospitals] = useState([]); const [isLoadingHospitals, setIsLoadingHospitals] = useState(true); const [selectedHospital, setSelectedHospital] = useState(null); const [searchParams, setSearchParams] = useSearchParams(); const activeModule = (searchParams.get('tab') as ConsoleModule) || 'ED_MONITOR'; const setActiveModule = (key: ConsoleModule) => { setSearchParams({ tab: key }); }; // activeModule defaults to ED_MONITOR when tab param exists // When no tab is set, we show the card landing page const [successMessage, setSuccessMessage] = useState(''); // ── Data state ── const [incomingPatients, setIncomingPatients] = useState([]); const [tripsData, setTripsData] = useState(INITIAL_TRIPS); const [departments, setDepartments] = useState([]); const [allUsers, setAllUsers] = useState([]); const [rolesList, setRolesList] = useState([]); const [user, setUser] = useState(null); // ── Modal state ── const [isBookingModalOpen, setIsBookingModalOpen] = useState(false); const [isStaffModalOpen, setIsStaffModalOpen] = useState(false); const [isEditingStaff, setIsEditingStaff] = useState(false); const [editingStaffId, setEditingStaffId] = useState(null); const [isDeptModalOpen, setIsDeptModalOpen] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const [setupSubTab, setSetupSubTab] = useState('PROFILE'); const [showPassword, setShowPassword] = useState(false); // ── Form state ── const [staffFormData, setStaffFormData] = useState({ name: '', email: '', phone: '', username: '', password: '', role: 'ED_DOCTOR', department: '', specialization: '', license_number: '', designation: '', shift: 'General', employee_id: '', org_id: '', assigned_floor: '', languages: '' }); const [deptFormData, setDeptFormData] = useState({ name: '', headOfDepartment: '', totalBedsCapacity: 0, contactPhone: '', isActive: true, }); // ── Load user from localStorage ── useEffect(() => { const userData = localStorage.getItem('teleems_user'); if (userData) setUser(JSON.parse(userData)); }, []); // ── Fetch roles ── useEffect(() => { const loadRoles = async () => { try { const token = localStorage.getItem('teleems_token') || ''; if (!token) return; const res = await authApi.getRoles(token); if (res.data?.data) setRolesList(res.data.data); } catch (err) { console.error('Failed to fetch roles:', err); } }; loadRoles(); }, []); // ── Fetch trips ── useEffect(() => { const loadTrips = async () => { try { const token = localStorage.getItem('teleems_token') || ''; if (!token) return; const res = await incidentsApi.getIncidents({}, token); if (res.data) { const formatted = (Array.isArray(res.data) ? res.data : []).map((inc: any) => ({ id: inc.id.substring(0, 8).toUpperCase(), patient: inc.patients?.[0]?.name || 'Unknown', mrn: `MRN-${inc.id.substring(8, 12).toUpperCase()}`, type: inc.category || 'EMERGENCY', origin: inc.address || 'Field Location', destination: selectedHospital?.name || 'Hospital', vehicle: 'Pending', crew: 'Pending', status: inc.status || 'PENDING', eta: 'TBD', urgency: inc.severity || 'YELLOW', step: Math.max(1, inc.status === 'COMPLETED' ? 4 : (inc.status === 'ACTIVE' ? 2 : 1)) })); setTripsData(formatted); } } catch (err) { console.error('Failed to fetch trips:', err); setTripsData([]); } }; loadTrips(); const interval = setInterval(loadTrips, 15000); // 15-sec poll return () => clearInterval(interval); }, [selectedHospital]); // ── Fetch incoming patients for ED Monitor ── useEffect(() => { const loadIncoming = async () => { try { const token = localStorage.getItem('teleems_token') || ''; if (!token) return; const orgId = user?.organisationId || user?.hospitalId || selectedHospital?.rawUser?.organisationId || selectedHospital?.id; const res = await hospitalApi.getIncomingOperations(token, orgId); console.log('[DEBUG] Incoming Operations Full Response:', res); let items = []; if (res.data?.data?.items && Array.isArray(res.data.data.items)) { items = res.data.data.items; } else if (res.data?.items && Array.isArray(res.data.items)) { items = res.data.items; } else if (Array.isArray(res.data?.data)) { items = res.data.data; } else if (Array.isArray(res.data)) { items = res.data; } else if (Array.isArray(res.items)) { items = res.items; } console.log('[DEBUG] Extracted Items:', items); if (items.length > 0) { const flattenedPatients: any[] = []; items.forEach((dispatch: any) => { const rawPatients = dispatch.patients || dispatch.patient_data; const patientsArr = Array.isArray(rawPatients) ? rawPatients : (rawPatients ? [rawPatients] : [{}]); patientsArr.forEach((patient: any, idx: number) => { const pId = patient.id || dispatch.dispatch_id || `idx-${idx}`; flattenedPatients.push({ id: `${pId}-${idx}`, originalId: pId, mrn: patient.mrn || String(pId).substring(0, 8).toUpperCase(), name: patient.name || dispatch.patient_name || 'Unknown Patient', age: patient.age || dispatch.patient_age || '--', gender: patient.gender || dispatch.patient_gender || '--', triage: patient.triage_level || dispatch.severity || 'GREEN', complaint: patient.chief_complaint || patient.symptoms?.[0]?.name || dispatch.category || 'Observation', eta: dispatch.eta_seconds ? Math.floor(dispatch.eta_seconds / 60) + 'm' : 'TBD', ambulanceId: (dispatch.dispatch_id || 'UNIT').substring(0, 8).toUpperCase(), status: dispatch.status || 'IN_TRANSIT', vitals: { hr: '--', spo2: '--', bp: '--/--' }, location: 'In Transit', gcs: patient.gcs || null, hpi: patient.hpi || null, avpu: patient.avpu || '--', pupils: patient.pupils || null, trauma: patient.trauma || null, allergies: patient.allergies || [], surgeries: patient.surgeries || [], conditions: patient.conditions || [], medications: patient.medications || [], informer: { name: patient.informer_name || '--', relation: patient.informer_relation || '--', phone: patient.informer_phone || '--' }, mlc: { is_mlc: patient.is_mlc || false, fir: patient.mlc_fir_number || '--', station: patient.mlc_police_station || '--', officer: patient.mlc_officer_contact || '--' } }); }); }); console.log('[DEBUG] Final Flattened Patients:', flattenedPatients); setIncomingPatients(flattenedPatients); } else { setIncomingPatients([]); } } catch (err) { console.error('Failed to fetch incoming patients:', err); } }; loadIncoming(); const interval = setInterval(loadIncoming, 15000); return () => clearInterval(interval); }, [selectedHospital]); // ── Fetch departments ── useEffect(() => { const fetchDepartments = async () => { const token = localStorage.getItem('teleems_token'); if (token && selectedHospital) { try { const res = await authApi.getDepartments(token); if (res.data) { const hospId = selectedHospital.rawUser?.hospitalId || selectedHospital.id; setDepartments(res.data.filter((d: any) => d.hospitalId === hospId)); } } catch (err) { console.error('Failed to fetch departments:', err); } } }; fetchDepartments(); }, [selectedHospital]); // ── Fetch hospitals ── useEffect(() => { const fetchHospitals = async () => { const token = localStorage.getItem('teleems_token'); if (!token) { setIsLoadingHospitals(false); return; } try { const response = await authApi.getUsers(token); if (response.status === 200 || response.status === 201) { const users = response.data || []; setAllUsers(users); const admins = users.filter((u: any) => { const roles = Array.isArray(u.roles) ? u.roles.map((r: any) => String(r).toUpperCase()) : []; return roles.includes('HOSPITAL ADMIN') || roles.includes('HOSPITAL_ADMIN'); }); const mappedHospitals = admins.map((u: any) => { const h = u.metadata?.hospital || {}; const org = u.metadata?.organization || {}; return { id: u.id, name: h.name || org.company_name || u.name || u.username || 'Unknown Hospital', type: h.specialization || h.type || 'General Care', address: h.city || org.city || 'Bangalore', floor: h.floor || 0, lat: h.lat || 13.0827, lon: h.lon || 80.2707, status: u.status === 'ACTIVE' ? 'Active' : 'Occupied', beds: h.beds || '12/50', icon: , specialty: h.specialization ? [h.specialization] : ['General'], admin: u.name, email: u.email, phone: u.phone, rawUser: u, }; }); setHospitals(mappedHospitals); // Auto-select first hospital if none selected if (!selectedHospital && mappedHospitals.length > 0) { setSelectedHospital(mappedHospitals[0]); } } } catch (error) { console.error('Failed to fetch hospitals:', error); setHospitals([]); } finally { setIsLoadingHospitals(false); } }; fetchHospitals(); }, []); // ── Fetch specific profile ── useEffect(() => { const fetchProfile = async () => { const token = localStorage.getItem('teleems_token'); if (!token) return; try { const res = await hospitalApi.getProfile(token); console.log("PROFILE API RESPONSE:", res); if (res.data) { const apiData = res.data.metadata?.hospital || res.data.profile || res.data; setSelectedHospital((prev: any) => ({ ...prev, ...apiData, name: apiData.hospitalName || apiData.name || prev?.name || '', contact_phone: apiData.contact_phone || apiData.phone || prev?.contact_phone || '', contact_email: apiData.contact_email || apiData.email || prev?.contact_email || '', address: apiData.address || prev?.address || '', emergency_phone: apiData.emergency_phone || prev?.emergency_phone || '', gps_lat: apiData.gps_lat || prev?.gps_lat || '', gps_lon: apiData.gps_lon || prev?.gps_lon || '', nabh_status: apiData.nabh_status || prev?.nabh_status || false, // Keep the raw user object around just in case rawUser: res.data })); } } catch (err) { console.error('Failed to fetch hospital profile:', err); } }; fetchProfile(); }, []); // ── Handlers ── const handleSaveProfile = async () => { if (!selectedHospital) return; setIsUpdating(true); try { const token = localStorage.getItem('teleems_token'); if (!token) throw new Error('Not authenticated'); const payload = { name: selectedHospital.name || '', address: selectedHospital.address || '', emergency_phone: selectedHospital.emergency_phone || '', contact_phone: selectedHospital.contact_phone || '', contact_email: selectedHospital.contact_email || '', gps_lat: parseFloat(selectedHospital.gps_lat) || 0, gps_lon: parseFloat(selectedHospital.gps_lon) || 0, nabh_status: selectedHospital.nabh_status || false }; const result = await hospitalApi.updateProfile(payload, token); console.log('PATCH RESPONSE:', result); if (result.status === 200 || result.status === 201 || !result.error) { setSuccessMessage('Configuration Updated!'); setTimeout(() => setSuccessMessage(''), 3000); } else { console.error('PATCH FAILED:', result); alert(result.message || 'Update failed'); } } catch (err: any) { alert(err.message); } finally { setIsUpdating(false); } }; const handleRegisterStaff = async () => { try { const token = localStorage.getItem('teleems_token'); const basePayload: any = { phone: staffFormData.phone, email: staffFormData.email, name: staffFormData.name, username: staffFormData.username, role: staffFormData.role, }; if (staffFormData.password) { basePayload.password = staffFormData.password; } let finalPayload: any = { ...basePayload }; if (staffFormData.role === 'ED_DOCTOR') { finalPayload.metadata = { specialization: staffFormData.specialization, license_number: staffFormData.license_number, department: staffFormData.department, designation: staffFormData.designation, shift: staffFormData.shift, }; } else if (staffFormData.role === 'Hospital Coordinator') { finalPayload.department = staffFormData.department; finalPayload.designation = staffFormData.designation; finalPayload.metadata = { shift: staffFormData.shift, languages: staffFormData.languages ? staffFormData.languages.split(',').map(s => s.trim()) : [], }; } else if (staffFormData.role === 'Hospital Nurse') { finalPayload.department = staffFormData.department; finalPayload.designation = staffFormData.designation; finalPayload.employee_id = staffFormData.employee_id; finalPayload.org_id = staffFormData.org_id || selectedHospital?.rawUser?.organisationId; finalPayload.metadata = { shift: staffFormData.shift, languages: staffFormData.languages ? staffFormData.languages.split(',').map(s => s.trim()) : [], assigned_floor: staffFormData.assigned_floor, }; } else { // Fallback for other roles finalPayload.org_id = selectedHospital?.rawUser?.organisationId; finalPayload.metadata = { department: staffFormData.department, designation: staffFormData.designation, shift: staffFormData.shift, }; } let res; if (isEditingStaff && editingStaffId) { // Backend forbids updating username and password via this endpoint delete finalPayload.username; delete finalPayload.password; res = await authApi.updateUser(editingStaffId, finalPayload, token || ''); } else { res = await hospitalApi.registerStaff(finalPayload, token || ''); } if (res.status === 201 || res.status === 200 || !res.error) { setSuccessMessage(`Staff ${isEditingStaff ? 'updated' : 'registered'} successfully.`); setIsStaffModalOpen(false); setIsEditingStaff(false); setEditingStaffId(null); // Refresh users list const usersRes = await authApi.getUsers(token || ''); if (usersRes.data) { setAllUsers(usersRes.data); } } else { alert(res.message || 'Operation failed'); } } catch (e: any) { alert(e.message || 'Failed'); } }; const handleEditStaff = (user: any) => { setIsEditingStaff(true); setEditingStaffId(user.id); // Parse role-specific metadata const role = user.roles?.[0] || 'ED_DOCTOR'; const metadata = user.metadata || {}; setStaffFormData({ name: user.name || '', email: user.email || '', phone: user.phone || '', username: user.username || '', password: '', // Leave blank so it doesn't update unless typed role: role, department: user.department || metadata.department || '', designation: user.designation || metadata.designation || '', specialization: metadata.specialization || '', license_number: metadata.license_number || '', shift: metadata.shift || '', employee_id: user.employee_id || user.employeeId || '', org_id: user.org_id || user.organisationId || '', assigned_floor: metadata.assigned_floor || '', languages: metadata.languages ? metadata.languages.join(', ') : '', }); setIsStaffModalOpen(true); }; const handleCreateDepartment = async () => { try { const token = localStorage.getItem('teleems_token'); const payload = { name: deptFormData.name, headOfDepartment: deptFormData.headOfDepartment, totalBedsCapacity: deptFormData.totalBedsCapacity, contactPhone: deptFormData.contactPhone, hospitalId: selectedHospital?.rawUser?.hospitalId || selectedHospital?.id, isActive: true, }; const res = await hospitalApi.createDepartment(payload, token || ''); if (res.status === 200 || res.status === 201) { setSuccessMessage('Department verified and registered.'); setIsDeptModalOpen(false); const updatedRes = await authApi.getDepartments(token || ''); if (updatedRes.data) { const hospId = selectedHospital?.rawUser?.hospitalId || selectedHospital?.id; setDepartments(updatedRes.data.filter((d: any) => d.hospitalId === hospId)); } } else { alert('Failed to register department. Code: ' + res.status); } } catch (e) { alert('Exception: ' + e); } }; // ── Module renderer ── const renderModule = () => { switch (activeModule) { case 'ED_MONITOR': return ( { // Trigger reload in console window.dispatchEvent(new CustomEvent('refresh_incoming')); }} onOpenBooking={() => setIsBookingModalOpen(true)} onOpenTelelink={() => setActiveModule('TELELINK')} /> ); case 'BOOKINGS': return ( setTripsData(tripsData.filter((t) => t.id !== id))} onOpenBooking={() => setIsBookingModalOpen(true)} /> ); case 'FLEET': return ; case 'TELELINK': return ; case 'EPCR': return ; case 'HISTORY': return ; case 'REPORTS': return ; case 'SETUP': return ( { setIsEditingStaff(false); setEditingStaffId(null); setStaffFormData({ name: '', email: '', phone: '', username: '', password: '', role: 'ED_DOCTOR', department: '', specialization: '', license_number: '', designation: '', shift: 'General', employee_id: '', org_id: '', assigned_floor: '', languages: '' }); setIsStaffModalOpen(true); }} onEditStaff={handleEditStaff} onOpenDeptModal={() => setIsDeptModalOpen(true)} /> ); case 'REFERRALS': return ; default: return
Module Under Development
; } }; // Hospital selection is now handled inline — no full-screen overlay // ── Main console ── // Module card definitions with descriptions and color accents const MODULE_CARDS: { key: ConsoleModule; icon: any; label: string; description: string; accent: string; accentBg: string }[] = [ { key: 'ED_MONITOR', icon: Monitor, label: 'ED Monitor', description: 'Live triage stream, incoming patients & emergency broadcasts', accent: 'hsl(0, 84%, 60%)', accentBg: 'hsla(0, 84%, 60%, 0.08)' }, { key: 'BOOKINGS', icon: Activity, label: 'Trip Management', description: 'Ambulance bookings, dispatches & trip tracking', accent: 'hsl(199, 89%, 48%)', accentBg: 'hsla(199, 89%, 48%, 0.08)' }, { key: 'FLEET', icon: Truck, label: 'Fleet Visibility', description: 'Real-time fleet map, vehicle status & route monitoring', accent: 'hsl(262, 83%, 58%)', accentBg: 'hsla(262, 83%, 58%, 0.08)' }, { key: 'TELELINK', icon: Video, label: 'TeleLink Hub', description: 'Video consults, physician console & remote triage', accent: 'hsl(152, 69%, 40%)', accentBg: 'hsla(152, 69%, 40%, 0.08)' }, { key: 'EPCR', icon: FileText, label: 'ePCR Records', description: 'Electronic patient care reports & documentation', accent: 'hsl(38, 92%, 50%)', accentBg: 'hsla(38, 92%, 50%, 0.08)' }, { key: 'HISTORY', icon: Database, label: 'Patient Archive', description: 'Historical patient data, discharge records & search', accent: 'hsl(215, 60%, 50%)', accentBg: 'hsla(215, 60%, 50%, 0.08)' }, { key: 'REPORTS', icon: TrendingUp, label: 'Analytics', description: 'Performance metrics, KPIs & operational insights', accent: 'hsl(280, 65%, 55%)', accentBg: 'hsla(280, 65%, 55%, 0.08)' }, { key: 'REFERRALS', icon: Hospital, label: 'Referral Hub', description: 'Inter-hospital referrals, network & transfer protocols', accent: 'hsl(170, 60%, 42%)', accentBg: 'hsla(170, 60%, 42%, 0.08)' }, { key: 'SETUP', icon: Settings, label: 'Account & Setup', description: 'Hospital profile, departments, staff & configuration', accent: 'hsl(220, 15%, 50%)', accentBg: 'hsla(220, 15%, 50%, 0.08)' }, ]; return (
{successMessage && (
{successMessage}
)} {isLoadingHospitals ? (
Loading infrastructure... ) : (
c.key === activeModule)?.accent || 'var(--accent-cyan)' }} /> {MODULE_CARDS.find(c => c.key === activeModule)?.label || 'Hospital Ops'} {activeModule === 'ED_MONITOR' && incomingPatients.length > 0 && ( {incomingPatients.length} )}
{hospitals.length > 0 && (
)}
{renderModule()} )} setIsStaffModalOpen(false)} staffFormData={staffFormData} setStaffFormData={setStaffFormData} rolesList={rolesList} showPassword={showPassword} setShowPassword={setShowPassword} onSubmit={handleRegisterStaff} isEditing={isEditingStaff} /> setIsDeptModalOpen(false)} deptFormData={deptFormData} setDeptFormData={setDeptFormData} allUsers={allUsers} selectedHospital={selectedHospital} onSubmit={handleCreateDepartment} />
); }; export default HospitalConsole;