diff --git a/vitallink/backend/server.py b/vitallink/backend/server.py index 69397b5..c71d646 100644 --- a/vitallink/backend/server.py +++ b/vitallink/backend/server.py @@ -3,6 +3,7 @@ VitalLink Backend API FastAPI server for managing patients, wristbands, and real-time data """ +import uuid import os import socket from fastapi import FastAPI, WebSocket, HTTPException @@ -228,7 +229,8 @@ async def check_in_patient(data: PatientCheckIn): if not available_bands: raise HTTPException(status_code=503, detail="No wristbands available") - patient_id = f"P{len(patients_db) + 100001}" + # patient_id = f"P{len(patients_db) + 100001}" + patient_id = f"P{int(time.time())}-{uuid.uuid4().hex[:4].upper()}" band_id = available_bands.pop(0) patient = Patient( diff --git a/vitallink/frontend/dashboard/src/App.jsx b/vitallink/frontend/dashboard/src/App.jsx index 3713c07..716fbfe 100644 --- a/vitallink/frontend/dashboard/src/App.jsx +++ b/vitallink/frontend/dashboard/src/App.jsx @@ -1,9 +1,10 @@ import React, { useState, useEffect } from 'react'; import * as LucideIcons from 'lucide-react'; -import PatientDetailModal from './PatientDetailModal'; // ADD THIS IMPORT +import PatientDetailModal from './PatientDetailModal'; const { Activity, AlertCircle, Clock, Users, Bell, Heart, Thermometer, Wind, CheckCircle, UserX } = LucideIcons; +// Dynamic API URL based on where the frontend is loaded const API_BASE = `http://${window.location.hostname}:8000`; function App() { @@ -18,7 +19,7 @@ function App() { const [activeTab, setActiveTab] = useState('patients'); const [wristbands, setWristbands] = useState([]); const [selectedWristband, setSelectedWristband] = useState(null); - const [selectedPatient, setSelectedPatient] = useState(null); // ADD THIS STATE + const [selectedPatient, setSelectedPatient] = useState(null); useEffect(() => { const fetchData = async () => { @@ -48,7 +49,6 @@ function App() { setStats(statsData); setWristbands(wristbandData.wristbands || []); - console.log(`✓ Fetched ${queueData.length} patients and ${wristbandData.wristbands?.length || 0} wristbands`); } catch (error) { console.error('Failed to fetch from backend:', error); } @@ -104,10 +104,9 @@ function App() { }); console.log(`✓ Discharged patient ${patientId}`); setPatients(prev => prev.filter(p => p.patient_id !== patientId)); - setSelectedPatient(null); // Close modal if open + setSelectedPatient(null); } catch (error) { console.error('Failed to discharge patient:', error); - setPatients(prev => prev.filter(p => p.patient_id !== patientId)); } }; @@ -117,6 +116,7 @@ function App() { return (
+ {/* HEADER SECTION */}
@@ -125,70 +125,47 @@ function App() {

Emergency Department Patient Monitoring

-
-

Last Update

-

{new Date().toLocaleTimeString()}

+
+

Last Update

+

{new Date().toLocaleTimeString()}

+ {/* STATS BAR */}
-
- -
-
-

Active Patients

-

{stats.active_patients}

-
+
+

Active Patients

{stats.active_patients}

-
-
- -
-
-

Emergency

-

{stats.tier_breakdown.EMERGENCY}

-
+
+

Emergency

{stats.tier_breakdown.EMERGENCY}

-
-
- -
-
-

Alert

-

{stats.tier_breakdown.ALERT}

-
+
+

Alert

{stats.tier_breakdown.ALERT}

-
-
- -
-
-

Avg Wait Time

-

{stats.average_wait_minutes} min

-
+
+

Avg Wait Time

{stats.average_wait_minutes} min

+ {/* TABS */}
+ {/* MAIN CONTENT AREA */}
{activeTab === 'patients' ? ( + /* ==================== PATIENTS TAB ==================== */ <>
- - - - + + + +
@@ -259,15 +200,13 @@ function App() { {filteredPatients.map((patient, index) => (
setSelectedPatient(patient)} /* ADD CLICK HANDLER */ - className="bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow cursor-pointer" /* ADD cursor-pointer */ + onClick={() => setSelectedPatient(patient)} + className="bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow cursor-pointer" >
-
- #{index + 1} -
+
#{index + 1}

{patient.name}

@@ -275,13 +214,11 @@ function App() { {patient.band_id}
-

Click for detailed history

{/* ADD THIS */} +

Click for detailed history

{patient.symptoms && patient.symptoms.length > 0 && (
{patient.symptoms.map(symptom => ( - - {symptom} - + {symptom} ))}
)} @@ -294,8 +231,8 @@ function App() { {patient.tier}
))}
- + {filteredPatients.length === 0 && (

No patients in this category

-

Patients will appear here as they check in

)} ) : ( + /* ==================== WRISTBANDS TAB ==================== */

Wristband Inventory

-
{wristbands.map(band => (
{band.status.toUpperCase().replace('_', ' ')}
- {band.patient_id && ( -
- Patient: {band.patient_id} -
+
Patient: {band.patient_id}
)} -
Packets: {band.packet_count} - {band.is_monitoring && ( - ● LIVE - )} + {band.is_monitoring && ● LIVE}
))}
- - {wristbands.length === 0 && ( -

No wristbands configured

- )} + {wristbands.length === 0 &&

No wristbands configured

}
- {selectedWristband && selectedWristband.last_raw_packet && ( -
-
-

- Packet Details: {selectedWristband.band_id} - {selectedWristband.type === 'simulated' && ( - MOCK - )} -

- -
- -
-

Raw Packet (16 bytes, Hex):

-
- {selectedWristband.last_raw_packet.hex.toUpperCase().match(/.{1,2}/g).join(' ')} + {/* --- WRISTBAND DETAILS SECTION --- */} + {selectedWristband && ( +
+ {/* Header */} +
+
+

+ Wristband Details: {selectedWristband.band_id} + {selectedWristband.type === 'simulated' && MOCK} + {selectedWristband.type === 'real' && BLE HARDWARE} +

+

+ Status: {selectedWristband.status.toUpperCase().replace('_', ' ')} + {selectedWristband.patient_id && • Assigned to: {selectedWristband.patient_id}} +

-

- Format: [ver][seq][timestamp][flags][hr][spo2][temp_x100][activity_x100][checksum][rfu] -

+
- {selectedWristband.last_raw_packet.decoded && ( + {/* Conditional Body */} + {selectedWristband.last_raw_packet ? ( + /* CASE A: HAVE DATA */ <> -
-

Decoded Fields:

-
-
-
Version
-
0x{selectedWristband.last_raw_packet.decoded.version.toString(16).padStart(2, '0')}
-
-
-
Sequence #
-
{selectedWristband.last_raw_packet.decoded.sequence}
-
-
-
Timestamp (ms)
-
{selectedWristband.last_raw_packet.decoded.timestamp_ms.toLocaleString()}
-
-
-
Heart Rate
-
{selectedWristband.last_raw_packet.decoded.hr_bpm}
-
bpm
-
-
-
SpO₂
-
{selectedWristband.last_raw_packet.decoded.spo2}
-
%
-
-
-
Temperature
-
{selectedWristband.last_raw_packet.decoded.temperature_c.toFixed(2)}
-
°C
-
-
-
Activity
-
{selectedWristband.last_raw_packet.decoded.activity.toFixed(2)}
-
RMS
-
-
-
Checksum
-
{selectedWristband.last_raw_packet.decoded.checksum}
-
-
-
Flags (raw)
-
0x{selectedWristband.last_raw_packet.decoded.flags.raw.toString(16).padStart(2, '0')}
-
+
+
+

Latest Packet (Raw Hex)

+ 16 Bytes
+
+ {selectedWristband.last_raw_packet.hex ? selectedWristband.last_raw_packet.hex.toUpperCase().match(/.{1,2}/g).join(' ') : "ERROR"} +
+

Structure: [Ver][Seq][Time....][Flags][HR][SpO2][Temp][Act][Chk][RFU]

- {selectedWristband.last_raw_packet.decoded.flags && ( -
-

Status Flags:

-
- {selectedWristband.last_raw_packet.decoded.flags.emergency && ( - - 🚨 Bit 4: Emergency - - )} - {selectedWristband.last_raw_packet.decoded.flags.alert && ( - - ⚠️ Bit 3: Alert - - )} - {selectedWristband.last_raw_packet.decoded.flags.sensor_fault && ( - - ⚙️ Bit 2: Sensor Fault - - )} - {selectedWristband.last_raw_packet.decoded.flags.low_battery && ( - - 🔋 Bit 1: Low Battery - - )} - {selectedWristband.last_raw_packet.decoded.flags.motion_artifact && ( - - 👋 Bit 0: Motion Artifact - - )} - {!Object.values(selectedWristband.last_raw_packet.decoded.flags).some(v => v === true) && ( - - ✓ No flags set (all normal) - - )} + {selectedWristband.last_raw_packet.decoded && ( +
+

Decoded Telemetry

+ +
+
Heart Rate
+
{selectedWristband.last_raw_packet.decoded.hr_bpm}bpm
+
+
SpO₂
+
{selectedWristband.last_raw_packet.decoded.spo2}%
+
+
+
Temp
+
{selectedWristband.last_raw_packet.decoded.temperature_c.toFixed(1)}°C
+
+
+
Sequence
+
{selectedWristband.last_raw_packet.decoded.sequence}#
+
+ + {selectedWristband.last_raw_packet.decoded.flags && ( +
+
System Flags
+
+ {!Object.values(selectedWristband.last_raw_packet.decoded.flags).some(v => v === true) + ? ✓ Normal + : ! Flags Set + } +
+
+ )}
)} + ) : ( + /* CASE B: NO DATA */ +
+
+

Waiting for Data...

+

+ {selectedWristband.status === 'available' + ? "Wristband available. Assign a patient to start." + : "Connected. Waiting for first packet..."} +

+
)}
)} @@ -534,7 +404,7 @@ function App() { )}
- {/* Patient Detail Modal */} + {/* MODALS */} {selectedPatient && ( vite - VITE v7.1.10 ready in 220 ms + VITE v7.1.10 ready in 262 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose diff --git a/vitallink/logs/kiosk.log b/vitallink/logs/kiosk.log index 07e5e2b..1be72c0 100644 --- a/vitallink/logs/kiosk.log +++ b/vitallink/logs/kiosk.log @@ -4,7 +4,7 @@ Port 5173 is in use, trying another one... - VITE v7.1.10 ready in 207 ms + VITE v7.1.10 ready in 236 ms ➜ Local: http://localhost:5174/ ➜ Network: use --host to expose diff --git a/vitallink/vitallink.db b/vitallink/vitallink.db index e149dad..3032ea2 100644 Binary files a/vitallink/vitallink.db and b/vitallink/vitallink.db differ diff --git a/vitallink/wristband_config.yaml b/vitallink/wristband_config.yaml index 5fae9bc..730202d 100644 --- a/vitallink/wristband_config.yaml +++ b/vitallink/wristband_config.yaml @@ -23,6 +23,31 @@ simulated_bands: - band_id: "VitalLink-SIM4" profile: "sepsis" + + - band_id: "VitalLink-SIM5" + profile: "stable" + + - band_id: "VitalLink-SIM6" + profile: "stable" + + - band_id: "VitalLink-SIM7" + profile: "stable" + + - band_id: "VitalLink-SIM8" + profile: "stable" + + - band_id: "VitalLink-SIM9" + profile: "stable" + + - band_id: "VitalLink-SIM10" + profile: "stable" + + - band_id: "VitalLink-SIM11" + profile: "stable" + + - band_id: "VitalLink-SIM12" + profile: "stable" + # Real Wristbands (Hardware) # Add BLE addresses of your physical wristbands # You can find these by running: python -m wristband_manager --scan