devsofttech_hms_ci4/public/App/Nursing/index.html

736 lines
42 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Nursing Care PWA App</title>
<link rel="manifest" href="/App/Nursing/manifest.json">
<meta name="theme-color" content="#2563eb">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" href="https://cdn-icons-png.flaticon.com/512/3304/3304567.png">
<!-- Bootstrap 5 CSS & Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<!-- React 18 & ReactDOM UMD -->
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js" crossorigin></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js"></script>
<style>
:root {
--primary-bg: #0f172a;
--card-bg: #1e293b;
--accent-blue: #2563eb;
--accent-green: #10b981;
--accent-amber: #f59e0b;
--accent-red: #ef4444;
}
body {
background-color: #f8fafc;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
margin: 0;
padding: 0;
user-select: none;
-webkit-user-select: none;
}
.nursing-header {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
color: #ffffff;
padding: 12px 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
position: sticky;
top: 0;
z-index: 1000;
}
.bed-card {
border: 1px solid #e2e8f0;
border-radius: 12px;
background: #ffffff;
transition: all 0.2s ease-in-out;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,0.02);
}
.bed-card:hover, .bed-card:active {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(37,99,235,0.12);
border-color: #2563eb;
}
.bed-status-available { border-top: 4px solid var(--accent-green); }
.bed-status-occupied { border-top: 4px solid var(--accent-blue); }
.bed-status-cleaning { border-top: 4px solid var(--accent-amber); }
.bed-status-maintenance { border-top: 4px solid var(--accent-red); }
.badge-soft-green { background: #d1fae5; color: #065f46; }
.badge-soft-blue { background: #dbeafe; color: #1e40af; }
.badge-soft-amber { background: #fef3c7; color: #92400e; }
.badge-soft-red { background: #fee2e2; color: #991b1b; }
.nav-pills .nav-link {
border-radius: 8px;
font-weight: 500;
color: #64748b;
}
.nav-pills .nav-link.active {
background-color: var(--accent-blue);
color: #ffffff;
}
.install-pwa-banner {
background: linear-gradient(90deg, #2563eb, #3b82f6);
color: white;
padding: 10px 16px;
font-size: 14px;
}
.equipment-chip {
font-size: 11px;
padding: 2px 6px;
border-radius: 4px;
background: #f1f5f9;
color: #475569;
}
/* Lock Screen Styling */
.lock-screen-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.lock-card {
background: #ffffff;
border-radius: 16px;
width: 100%;
max-width: 400px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.pin-pad-btn {
width: 60px;
height: 60px;
border-radius: 50%;
font-size: 20px;
font-weight: 600;
margin: 6px;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function NursingApp() {
const [beds, setBeds] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedWard, setSelectedWard] = useState('all');
const [searchQuery, setSearchQuery] = useState('');
const [activeBed, setActiveBed] = useState(null);
const [activeTab, setActiveTab] = useState('vitals');
const [workspaceData, setWorkspaceData] = useState([]);
const [installPrompt, setInstallPrompt] = useState(null);
const [nurses, setNurses] = useState([]);
// Authentication & Security State
const [authenticatedNurse, setAuthenticatedNurse] = useState(null);
const [authNurseId, setAuthNurseId] = useState(0);
const [pinInput, setPinInput] = useState('');
const [pinError, setPinError] = useState('');
const [isSettingNewPin, setIsSettingNewPin] = useState(false);
const [newPin, setNewPin] = useState('');
const [confirmPin, setConfirmPin] = useState('');
const [showChangePinModal, setShowChangePinModal] = useState(false);
// Form state for workspace
const [vitalsForm, setVitalsForm] = useState({
temperature_f: '', pulse_rate: '', resp_rate: '',
bp_systolic: '', bp_diastolic: '', spo2: '', weight_kg: '', general_note: ''
});
const [fluidForm, setFluidForm] = useState({
fluid_direction: 'intake', fluid_route: 'IV (Intravenous)',
fluid_amount_ml: '', general_note: ''
});
const [treatmentForm, setTreatmentForm] = useState({
treatment_text: '', general_note: ''
});
// Fetch live bed list from CI4 API
const fetchBeds = async () => {
try {
setLoading(true);
const res = await fetch('/api/v1/nursing/beds');
const json = await res.json();
if (json.status === 1 && json.data) {
setBeds(json.data.beds || []);
}
} catch (err) {
console.error('Failed to load beds', err);
} finally {
setLoading(false);
}
};
// Fetch active nurses
const fetchNurses = async () => {
try {
const res = await fetch('/api/v1/nursing/nurses');
const json = await res.json();
if (json.status === 1 && json.data) {
setNurses(json.data);
// Check URL query parameters for nurse_code (QR Code scan)
const params = new URLSearchParams(window.location.search);
const codeParam = params.get('nurse_code');
if (codeParam) {
const matched = json.data.find(n => n.nurse_code === codeParam);
if (matched) {
setAuthNurseId(matched.id);
}
}
}
} catch (err) {
console.error('Failed to load nurses', err);
}
};
// Restore saved authentication from sessionStorage
useEffect(() => {
const saved = sessionStorage.getItem('nursing_auth_nurse');
if (saved) {
try {
const parsed = JSON.parse(saved);
if (parsed && parsed.id) {
setAuthenticatedNurse(parsed);
}
} catch (e) {}
}
fetchBeds();
fetchNurses();
// PWA Install Prompt Listener
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
setInstallPrompt(e);
});
// Service worker registration
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/App/Nursing/sw.js')
.then(() => console.log('Nursing PWA Service Worker Registered'))
.catch(err => console.error('SW failed', err));
}
}, []);
// Authenticate Nurse with Security PIN
const handleVerifyPin = async () => {
if (!authNurseId || authNurseId === '0') {
setPinError('Please select your Nursing Staff ID / Profile.');
return;
}
if (!pinInput || pinInput.length < 4) {
setPinError('Enter your 4 to 6 digit Security PIN.');
return;
}
setPinError('');
try {
const body = new URLSearchParams();
body.append('nurse_id', authNurseId);
body.append('pin', pinInput);
const res = await fetch('/api/v1/nursing/auth/verify-pin', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
const json = await res.json();
if (json.status === 1 && json.nurse) {
setAuthenticatedNurse(json.nurse);
sessionStorage.setItem('nursing_auth_nurse', JSON.stringify(json.nurse));
setPinInput('');
} else if (json.status === 2) {
// PIN not set yet for this nurse
setIsSettingNewPin(true);
} else {
setPinError(json.message || 'Invalid Security PIN');
}
} catch (err) {
setPinError('Connection error verifying PIN.');
}
};
// Set Initial PIN or Change PIN
const handleSaveNewPin = async () => {
if (!newPin || newPin.length < 4) {
setPinError('PIN must be 4 to 6 numeric digits.');
return;
}
if (newPin !== confirmPin) {
setPinError('New PIN and Confirm PIN do not match.');
return;
}
try {
const body = new URLSearchParams();
body.append('nurse_id', authNurseId || (authenticatedNurse ? authenticatedNurse.id : 0));
body.append('new_pin', newPin);
const res = await fetch('/api/v1/nursing/auth/set-pin', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
const json = await res.json();
if (json.status === 1) {
alert('Security PIN updated! You can now login with your PIN.');
setIsSettingNewPin(false);
setShowChangePinModal(false);
setNewPin('');
setConfirmPin('');
setPinInput('');
} else {
setPinError(json.message || 'Failed to update PIN');
}
} catch (err) {
setPinError('Error setting PIN.');
}
};
const handleLogout = () => {
setAuthenticatedNurse(null);
sessionStorage.removeItem('nursing_auth_nurse');
setPinInput('');
setActiveBed(null);
};
// Fetch patient workspace entries
const fetchWorkspace = async (ipdId) => {
try {
const res = await fetch(`/api/v1/nursing/workspace/${ipdId}`);
const json = await res.json();
if (json.status === 1) {
setWorkspaceData(json.entries || []);
}
} catch (err) {
console.error('Failed to load workspace data', err);
}
};
const openBedWorkspace = (bed) => {
setActiveBed(bed);
if (bed.current_ipd_id) {
fetchWorkspace(bed.current_ipd_id);
} else {
setWorkspaceData([]);
}
};
const handleSaveEntry = async (entryType, formData) => {
if (!activeBed || !activeBed.current_ipd_id) {
alert('Please select an occupied bed to save entries.');
return;
}
const body = new URLSearchParams();
body.append('entry_type', entryType);
body.append('recorded_by_id', authenticatedNurse ? authenticatedNurse.id : 0);
body.append('recorded_by', authenticatedNurse ? `[${authenticatedNurse.nurse_code}] ${authenticatedNurse.name}` : 'Staff');
Object.keys(formData).forEach(key => body.append(key, formData[key]));
try {
const res = await fetch(`/api/v1/nursing/entry/save/${activeBed.current_ipd_id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
const json = await res.json();
if (json.status === 1) {
alert(json.message || 'Saved successfully');
fetchWorkspace(activeBed.current_ipd_id);
} else {
alert(json.message || 'Failed to save');
}
} catch (err) {
alert('Error submitting entry');
}
};
const uniqueWards = ['all', ...new Set(beds.map(b => b.ward_name).filter(Boolean))];
const filteredBeds = beds.filter(bed => {
const matchesWard = selectedWard === 'all' || bed.ward_name === selectedWard;
const matchesSearch = searchQuery === '' ||
(bed.bed_code && bed.bed_code.toLowerCase().includes(searchQuery.toLowerCase())) ||
(bed.ward_name && bed.ward_name.toLowerCase().includes(searchQuery.toLowerCase()));
return matchesWard && matchesSearch;
});
// -------------------------------------------------------------
// RENDER: LOCK SCREEN (PIN AUTHENTICATION)
// -------------------------------------------------------------
if (!authenticatedNurse) {
return (
<div className="lock-screen-overlay">
<div className="lock-card">
<div className="p-4 text-center bg-primary text-white">
<i className="bi bi-shield-lock-fill display-4 d-block mb-2"></i>
<h5 className="fw-bold mb-0">Nursing Care PWA App</h5>
<small className="opacity-75">Staff PIN Authentication Security</small>
</div>
<div className="p-4">
{pinError && <div className="alert alert-danger py-2 px-3 small mb-3">{pinError}</div>}
{!isSettingNewPin ? (
<div>
<div className="mb-3">
<label className="form-label fw-bold small">Select Nursing Staff</label>
<select
className="form-select"
value={authNurseId}
onChange={(e) => setAuthNurseId(e.target.value)}
>
<option value="0">-- Choose Nursing Staff --</option>
{nurses.map(n => (
<option key={n.id} value={n.id}>[{n.nurse_code}] {n.name}</option>
))}
</select>
</div>
<div className="mb-4">
<label className="form-label fw-bold small">Enter App Security PIN</label>
<input
type="password"
maxLength="6"
className="form-control text-center fs-4 tracking-widest"
placeholder="••••"
value={pinInput}
onChange={(e) => setPinInput(e.target.value)}
onKeyUp={(e) => e.key === 'Enter' && handleVerifyPin()}
/>
</div>
<button className="btn btn-primary w-100 py-2 fw-bold" onClick={handleVerifyPin}>
<i className="bi bi-unlock-fill me-1"></i> Unlock App Access
</button>
</div>
) : (
<div>
<div className="alert alert-info py-2 small mb-3">
Initial PIN Setup for selected nurse. Set a 4-6 digit Security PIN.
</div>
<div className="mb-3">
<label className="form-label fw-bold small">New Security PIN</label>
<input
type="password"
maxLength="6"
className="form-control text-center fs-4"
placeholder="••••"
value={newPin}
onChange={(e) => setNewPin(e.target.value)}
/>
</div>
<div className="mb-3">
<label className="form-label fw-bold small">Confirm New PIN</label>
<input
type="password"
maxLength="6"
className="form-control text-center fs-4"
placeholder="••••"
value={confirmPin}
onChange={(e) => setConfirmPin(e.target.value)}
/>
</div>
<button className="btn btn-success w-100 py-2 fw-bold" onClick={handleSaveNewPin}>
<i className="bi bi-check-circle-fill me-1"></i> Save PIN &amp; Unlock
</button>
</div>
)}
</div>
</div>
</div>
);
}
// -------------------------------------------------------------
// RENDER: UNLOCKED PWA APP DASHBOARD
// -------------------------------------------------------------
return (
<div>
{/* PWA Install Banner */}
{installPrompt && (
<div className="install-pwa-banner d-flex justify-content-between align-items-center">
<span><i className="bi bi-download me-2"></i> Install Nursing App on your device home screen</span>
<button className="btn btn-sm btn-light text-primary fw-bold" onClick={() => installPrompt.prompt()}>Install App</button>
</div>
)}
{/* Top Nursing Header */}
<header className="nursing-header d-flex justify-content-between align-items-center">
<div className="d-flex align-items-center">
<i className="bi bi-heart-pulse-fill text-danger fs-4 me-2"></i>
<div>
<h6 className="mb-0 fw-bold">NursingCare PWA</h6>
<small className="text-light opacity-75">Staff: <strong>[{authenticatedNurse.nurse_code}] {authenticatedNurse.name}</strong></small>
</div>
</div>
<div className="d-flex align-items-center gap-2">
<button className="btn btn-sm btn-outline-light" onClick={fetchBeds} title="Refresh Beds">
<i className="bi bi-arrow-clockwise"></i>
</button>
<button className="btn btn-sm btn-danger" onClick={handleLogout} title="Lock App Device Access">
<i className="bi bi-lock-fill me-1"></i> Lock
</button>
</div>
</header>
{/* Main Container */}
<main className="container-fluid py-3">
{!activeBed ? (
<div>
{/* Filter Bar */}
<div className="row g-2 mb-3 align-items-center">
<div className="col-md-4 col-12">
<div className="input-group">
<span className="input-group-text bg-white"><i className="bi bi-search"></i></span>
<input
type="text"
className="form-control"
placeholder="Search Bed Code or Ward..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
<div className="col-md-8 col-12 d-flex gap-2 overflow-auto">
{uniqueWards.map(ward => (
<button
key={ward}
className={`btn btn-sm text-nowrap ${selectedWard === ward ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => setSelectedWard(ward)}
>
{ward === 'all' ? 'All Wards' : ward}
</button>
))}
</div>
</div>
{/* Bed Matrix */}
{loading ? (
<div className="text-center py-5">
<div className="spinner-border text-primary" role="status"></div>
<p className="mt-2 text-muted">Loading Hospital Bed Matrix...</p>
</div>
) : (
<div className="row g-3">
{filteredBeds.map(bed => {
const statusClass = `bed-status-${bed.bed_status || 'available'}`;
const badgeClass = bed.bed_status === 'occupied' ? 'badge-soft-blue'
: bed.bed_status === 'available' ? 'badge-soft-green'
: bed.bed_status === 'cleaning' ? 'badge-soft-amber' : 'badge-soft-red';
return (
<div key={bed.id} className="col-lg-3 col-md-4 col-sm-6 col-12">
<div className={`card p-3 bed-card ${statusClass}`} onClick={() => openBedWorkspace(bed)}>
<div className="d-flex justify-content-between align-items-start mb-2">
<div>
<h5 className="fw-bold mb-0 text-dark">{bed.bed_code || bed.bed_number}</h5>
<small className="text-muted">{bed.ward_name || 'General Ward'}</small>
</div>
<span className={`badge ${badgeClass} text-uppercase`}>
{bed.bed_status || 'Available'}
</span>
</div>
{bed.current_ipd_id ? (
<div className="mt-2 pt-2 border-top">
<span className="badge bg-primary me-1">IPD #{bed.current_ipd_id}</span>
<small className="text-muted">Patient Admitted</small>
</div>
) : (
<small className="text-success mt-2 d-block"><i className="bi bi-check-circle-fill me-1"></i> Ready for Admission</small>
)}
<div className="d-flex gap-1 mt-2 flex-wrap">
{bed.has_oxygen == 1 && <span className="equipment-chip"><i className="bi bi-wind text-info"></i> O2</span>}
{bed.has_suction == 1 && <span className="equipment-chip"><i className="bi bi-funnel text-primary"></i> Suction</span>}
{bed.has_monitor == 1 && <span className="equipment-chip"><i className="bi bi-activity text-danger"></i> Monitor</span>}
</div>
</div>
</div>
);
})}
</div>
)}
</div>
) : (
/* Bedside Patient Workspace */
<div>
<button className="btn btn-sm btn-outline-dark mb-3" onClick={() => setActiveBed(null)}>
<i className="bi bi-arrow-left me-1"></i> Back to Bed Matrix
</button>
<div className="card shadow-sm mb-3 border-0">
<div className="card-body bg-light rounded d-flex justify-content-between align-items-center flex-wrap gap-2">
<div>
<h5 className="fw-bold mb-1 text-primary">Bed: {activeBed.bed_code}</h5>
<span className="badge bg-secondary me-2">{activeBed.ward_name}</span>
{activeBed.current_ipd_id ? (
<span className="badge bg-primary">IPD ID: #{activeBed.current_ipd_id}</span>
) : (
<span className="badge bg-success">Unoccupied Bed</span>
)}
</div>
</div>
</div>
{/* Tabs */}
<ul className="nav nav-pills mb-3 gap-2">
<li className="nav-item">
<button className={`nav-link ${activeTab === 'vitals' ? 'active' : ''}`} onClick={() => setActiveTab('vitals')}>
<i className="bi bi-activity me-1"></i> Vitals Entry
</button>
</li>
<li className="nav-item">
<button className={`nav-link ${activeTab === 'fluid' ? 'active' : ''}`} onClick={() => setActiveTab('fluid')}>
<i className="bi bi-droplet me-1"></i> Fluid Intake / Output
</button>
</li>
<li className="nav-item">
<button className={`nav-link ${activeTab === 'treatment' ? 'active' : ''}`} onClick={() => setActiveTab('treatment')}>
<i className="bi bi-prescription2 me-1"></i> Treatment Note
</button>
</li>
</ul>
{/* Tab Content */}
<div className="card p-3 shadow-sm border-0">
{activeTab === 'vitals' && (
<div>
<h6 className="fw-bold mb-3"><i className="bi bi-heart-pulse text-danger me-1"></i> Log Patient Vitals</h6>
<div className="row g-2">
<div className="col-md-2 col-6"><label className="form-label small">Temp °F</label><input type="number" step="0.1" className="form-control" value={vitalsForm.temperature_f} onChange={e => setVitalsForm({...vitalsForm, temperature_f: e.target.value})} /></div>
<div className="col-md-2 col-6"><label className="form-label small">Pulse</label><input type="number" className="form-control" value={vitalsForm.pulse_rate} onChange={e => setVitalsForm({...vitalsForm, pulse_rate: e.target.value})} /></div>
<div className="col-md-2 col-6"><label className="form-label small">Resp</label><input type="number" className="form-control" value={vitalsForm.resp_rate} onChange={e => setVitalsForm({...vitalsForm, resp_rate: e.target.value})} /></div>
<div className="col-md-2 col-6"><label className="form-label small">SBP</label><input type="number" className="form-control" value={vitalsForm.bp_systolic} onChange={e => setVitalsForm({...vitalsForm, bp_systolic: e.target.value})} /></div>
<div className="col-md-2 col-6"><label className="form-label small">DBP</label><input type="number" className="form-control" value={vitalsForm.bp_diastolic} onChange={e => setVitalsForm({...vitalsForm, bp_diastolic: e.target.value})} /></div>
<div className="col-md-2 col-6"><label className="form-label small">SpO2 %</label><input type="number" className="form-control" value={vitalsForm.spo2} onChange={e => setVitalsForm({...vitalsForm, spo2: e.target.value})} /></div>
<div className="col-12"><label className="form-label small">Nursing Observation</label><textarea className="form-control" rows="2" value={vitalsForm.general_note} onChange={e => setVitalsForm({...vitalsForm, general_note: e.target.value})}></textarea></div>
<div className="col-12 mt-3">
<button className="btn btn-primary" onClick={() => handleSaveEntry('vitals', vitalsForm)}>Save Vitals Entry</button>
</div>
</div>
</div>
)}
{activeTab === 'fluid' && (
<div>
<h6 className="fw-bold mb-3"><i className="bi bi-droplet text-primary me-1"></i> Log Fluid Intake / Output</h6>
<div className="row g-2">
<div className="col-md-3 col-6">
<label className="form-label small">Direction</label>
<select className="form-select" value={fluidForm.fluid_direction} onChange={e => setFluidForm({...fluidForm, fluid_direction: e.target.value})}>
<option value="intake">Intake (In)</option>
<option value="output">Output (Out)</option>
</select>
</div>
<div className="col-md-4 col-6">
<label className="form-label small">Route</label>
<input type="text" className="form-control" value={fluidForm.fluid_route} onChange={e => setFluidForm({...fluidForm, fluid_route: e.target.value})} />
</div>
<div className="col-md-3 col-6">
<label className="form-label small">Amount (ml)</label>
<input type="number" className="form-control" value={fluidForm.fluid_amount_ml} onChange={e => setFluidForm({...fluidForm, fluid_amount_ml: e.target.value})} />
</div>
<div className="col-12"><label className="form-label small">Observation</label><textarea className="form-control" rows="2" value={fluidForm.general_note} onChange={e => setFluidForm({...fluidForm, general_note: e.target.value})}></textarea></div>
<div className="col-12 mt-3">
<button className="btn btn-primary" onClick={() => handleSaveEntry('fluid', fluidForm)}>Save Fluid Entry</button>
</div>
</div>
</div>
)}
{activeTab === 'treatment' && (
<div>
<h6 className="fw-bold mb-3"><i className="bi bi-prescription2 text-warning me-1"></i> Log Treatment / Procedure Note</h6>
<div className="row g-2">
<div className="col-12"><label className="form-label small">Treatment Given</label><textarea className="form-control" rows="3" value={treatmentForm.treatment_text} onChange={e => setTreatmentForm({...treatmentForm, treatment_text: e.target.value})}></textarea></div>
<div className="col-12"><label className="form-label small">Additional Note</label><textarea className="form-control" rows="2" value={treatmentForm.general_note} onChange={e => setTreatmentForm({...treatmentForm, general_note: e.target.value})}></textarea></div>
<div className="col-12 mt-3">
<button className="btn btn-primary" onClick={() => handleSaveEntry('treatment', treatmentForm)}>Save Treatment Note</button>
</div>
</div>
</div>
)}
{/* Workspace Entries Table */}
<div className="mt-4 border-top pt-3">
<h6 className="fw-bold mb-2">Live Entries History ({workspaceData.length})</h6>
<div className="table-responsive">
<table className="table table-sm table-striped">
<thead>
<tr>
<th>Recorded At</th>
<th>Type</th>
<th>Staff</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{workspaceData.length === 0 ? (
<tr><td colSpan="4" className="text-center text-muted py-3">No nursing entries found for this admission.</td></tr>
) : (
workspaceData.map(entry => (
<tr key={entry.id}>
<td><small>{entry.recorded_at}</small></td>
<td><span className="badge bg-secondary text-uppercase">{entry.entry_type}</span></td>
<td><small>{entry.recorded_by || 'Staff'}</small></td>
<td>
<small>
{entry.entry_type === 'vitals' && `T: ${entry.temperature_c ? ((entry.temperature_c * 9/5) + 32).toFixed(1) + '°F' : '-'}, P: ${entry.pulse_rate || '-'}, BP: ${entry.bp_systolic || '-'}/${entry.bp_diastolic || '-'}, SpO2: ${entry.spo2 || '-'}`}
{entry.entry_type === 'fluid' && `${entry.fluid_direction?.toUpperCase()} - ${entry.fluid_route}: ${entry.fluid_amount_ml}ml`}
{entry.entry_type === 'treatment' && entry.treatment_text}
{entry.general_note && ` (${entry.general_note})`}
</small>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
)}
</main>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<NursingApp />);
</script>
</body>
</html>