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 ==================== */
<>
- setFilter('all')}
- className={`px-6 py-2 rounded-lg font-semibold transition-colors ${
- filter === 'all'
- ? 'bg-blue-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- All Patients ({patients.length})
-
- setFilter('EMERGENCY')}
- className={`px-6 py-2 rounded-lg font-semibold transition-colors ${
- filter === 'EMERGENCY'
- ? 'bg-red-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- Emergency ({stats.tier_breakdown.EMERGENCY})
-
- setFilter('ALERT')}
- className={`px-6 py-2 rounded-lg font-semibold transition-colors ${
- filter === 'ALERT'
- ? 'bg-yellow-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- Alert ({stats.tier_breakdown.ALERT})
-
- setFilter('NORMAL')}
- className={`px-6 py-2 rounded-lg font-semibold transition-colors ${
- filter === 'NORMAL'
- ? 'bg-green-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- Stable ({stats.tier_breakdown.NORMAL})
-
+ setFilter('all')} className={`px-6 py-2 rounded-lg font-semibold transition-colors ${filter === 'all' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}>All Patients ({patients.length})
+ setFilter('EMERGENCY')} className={`px-6 py-2 rounded-lg font-semibold transition-colors ${filter === 'EMERGENCY' ? 'bg-red-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}>Emergency ({stats.tier_breakdown.EMERGENCY})
+ setFilter('ALERT')} className={`px-6 py-2 rounded-lg font-semibold transition-colors ${filter === 'ALERT' ? 'bg-yellow-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}>Alert ({stats.tier_breakdown.ALERT})
+ setFilter('NORMAL')} className={`px-6 py-2 rounded-lg font-semibold transition-colors ${filter === 'NORMAL' ? 'bg-green-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}>Stable ({stats.tier_breakdown.NORMAL})
@@ -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}
{ /* UPDATE DISCHARGE HANDLER */
- e.stopPropagation(); // Prevent opening modal
+ onClick={(e) => {
+ e.stopPropagation();
handleDischarge(patient.patient_id);
}}
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-2 font-semibold"
@@ -308,46 +245,23 @@ function App() {
-
-
- Heart Rate
-
-
- {patient.last_hr}
-
+
Heart Rate
+
{patient.last_hr}
bpm
-
-
-
- SpO₂
-
-
- {patient.last_spo2}
-
+
SpO₂
+
{patient.last_spo2}
%
-
-
-
- Temperature
-
-
- {patient.last_temp.toFixed(1)}
-
+
Temperature
+
{patient.last_temp.toFixed(1)}
°C
-
-
-
- Wait Time
-
-
- {patient.wait_time_minutes}
-
+
Wait Time
+
{patient.wait_time_minutes}
minutes
@@ -355,20 +269,19 @@ function App() {
))}
-
+
{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
- )}
-
- setSelectedWristband(null)}
- className="text-gray-500 hover:text-gray-700 text-2xl font-bold"
- >
- ✕
-
-
-
-
-
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]
-
+
setSelectedWristband(null)} className="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 text-gray-500 hover:text-gray-700 transition-colors">✕
- {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 && (
+
+
+
+
+
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