// Core UI & Modal Elements const modal = document.getElementById("consultationModal"); const addMedBtn = document.getElementById("addMed"); const medList = document.getElementById("medicineList"); const sendRxBtn = document.getElementById("sendRx"); const closeModal = document.querySelector(".rx-modal .close-modal"); const cancelRxBtn = document.getElementById("cancelRx"); const startRecordBtn = document.getElementById("startRecording"); const recordingStatus = document.getElementById("recordingStatus"); const aiInsightsBox = document.getElementById("aiInsights"); const insightText = document.getElementById("insightText"); const queueDateInput = document.getElementById("queueDate"); const newWalkinBtn = document.getElementById("newWalkinBtn"); const labList = document.getElementById("labTestList"); const addLabBtn = document.getElementById("addLabTest"); // 1. App State & Stores let isScribing = false; let currentPatientId = "#012"; let recognition; let mediaRecorder; let audioChunks = []; const patientDataStore = { "#012": { notes: "", summary: "" }, "#013": { notes: "", summary: "" }, "#014": { notes: "", summary: "" } }; const SECTION_META = { overview: { target: 'overview-section', title: 'Clinic Overview', subtitle: 'Live queue, stats, and AI insights for today.' }, queue: { target: 'overview-section', title: 'Live Queue', subtitle: 'Patients waiting and in consultation.' }, patients: { target: 'patients-section', title: 'Patient Records', subtitle: 'Search history, visits, and contact details.' }, pharmacy: { target: 'pharmacy-section', title: 'Pharmacy', subtitle: 'Inventory, prescriptions, and stock alerts.' }, lab: { target: 'lab-section', title: 'Laboratory', subtitle: 'Orders, sample collection, and results.' }, zen: { target: 'doctor-zen-section', title: 'Clinical Workspace', subtitle: 'Document visits, prescribe, and review vitals.' }, settings: { target: 'settings-section', title: 'Preferences', subtitle: 'Manage your profile and clinical tool settings.' } }; const ROLE_CONFIG = { DOCTOR: { hideSections: ['overview', 'billing', 'staff'], defaultSection: 'zen', displayName: 'Dr. Sharma' }, OWNER: { hideSections: [], defaultSection: 'overview', displayName: null }, RECEPTIONIST: { hideSections: ['pharmacy', 'lab'], defaultSection: 'queue', displayName: 'Reception' } }; function getUserRole() { return localStorage.getItem('userRole'); } function resolveSectionTarget(sectionId) { const role = getUserRole(); const config = ROLE_CONFIG[role] || ROLE_CONFIG.OWNER; if (sectionId === 'queue' && role === 'DOCTOR') return 'doctor-zen-section'; if (sectionId === 'zen') return 'doctor-zen-section'; const meta = SECTION_META[sectionId]; return meta ? meta.target : 'overview-section'; } function updateSectionPageHeader(sectionId) { const header = document.getElementById('section-page-header'); const titleEl = document.getElementById('sectionPageTitle'); const subtitleEl = document.getElementById('sectionPageSubtitle'); if (!header || !titleEl || !subtitleEl) return; const isDoctor = getUserRole() === 'DOCTOR'; const isClinicalView = sectionId === 'zen' || sectionId === 'queue'; const meta = SECTION_META[sectionId]; if (isDoctor && !isClinicalView && meta) { titleEl.textContent = meta.title; subtitleEl.textContent = meta.subtitle; header.classList.remove('hidden'); } else { header.classList.add('hidden'); } } function setActiveNavLink(sectionId) { const isDoctor = getUserRole() === 'DOCTOR'; const activeKey = (sectionId === 'zen' || (sectionId === 'queue' && isDoctor)) ? 'queue' : sectionId; document.querySelectorAll('.side-nav a[data-section]').forEach(link => { link.classList.toggle('active', link.dataset.section === activeKey); }); } function applyRoleNav() { const role = getUserRole(); const config = ROLE_CONFIG[role] || ROLE_CONFIG.OWNER; document.querySelectorAll('.side-nav a[data-section]').forEach(link => { const hidden = config.hideSections.includes(link.dataset.section); link.style.display = hidden ? 'none' : ''; }); } // 2. Initialization & Speech Recognition Setup document.addEventListener("DOMContentLoaded", () => { const payload = requireLogin(['OWNER', 'DOCTOR', 'RECEPTIONIST']); if (!payload) return; const userRole = payload.role || getUserRole(); const userEmail = localStorage.getItem('userEmail') || 'admin@curio.app'; const config = ROLE_CONFIG[userRole] || ROLE_CONFIG.OWNER; const roleDisplay = document.querySelector(".user-profile .info strong"); const emailDisplay = document.querySelector(".user-profile .info span"); if (roleDisplay) { roleDisplay.innerText = config.displayName || (userRole.charAt(0) + userRole.slice(1).toLowerCase()); } if (emailDisplay) emailDisplay.innerText = userEmail; const sidebar = document.getElementById('sidebar'); if (userRole === 'DOCTOR') { document.body.classList.add('role-doctor'); sidebar?.classList.add('compact-rail'); } else { document.body.classList.remove('role-doctor'); sidebar?.classList.remove('compact-rail'); } applyRoleNav(); document.getElementById('sideNav')?.addEventListener('click', (e) => { const link = e.target.closest('a[data-section]'); if (!link || link.hasAttribute('data-external')) return; e.preventDefault(); showSection(link.dataset.section); }); showSection(config.defaultSection === 'zen' ? 'zen' : config.defaultSection); // Zen 4.0 Macros & Nudge Logic const MACROS = { ".fever": "Patient presents with elevated body temperature. Antipyretics advised. Rest and hydration recommended.", ".cough": "Patient reports persistent cough. Chest sounds clear bilaterally. Recommended symptomatic relief.", ".cold": "Patient reports nasal congestion and rhinorrhea. Viral etiology suspected. Supportive care advised." }; const nudgeText = document.getElementById("aiNudgeText"); const tagsContainer = document.querySelector(".suggested-tags"); document.addEventListener("input", (e) => { if (e.target && e.target.tagName === 'TEXTAREA' && e.target.id.startsWith('soap-')) { let val = e.target.value; let modified = false; for (const [macro, text] of Object.entries(MACROS)) { if (val.includes(macro)) { val = val.replace(macro, text); modified = true; } } if (modified) { e.target.value = val; // Dispatch event so other listeners (if any) know the value changed e.target.dispatchEvent(new Event('input', { bubbles: true })); return; // Prevent recursive trigger on this pass } // Nudge Logic update if (nudgeText && tagsContainer) { const lowerVal = val.toLowerCase(); if (lowerVal.includes("fever") || lowerVal.includes("temperature")) { nudgeText.innerText = "✨ AI suggests: Patient has elevated temp. Consider prescribing Paracetamol and checking for throat infection."; tagsContainer.innerHTML = '#ViralFever#InfectionCheck'; } else if (lowerVal.includes("cough") || lowerVal.includes("breath")) { nudgeText.innerText = "✨ AI suggests: Respiratory symptoms detected. Check SpO2 and listen for chest congestion."; tagsContainer.innerHTML = '#AsthmaProtocol#Bronchitis'; } } } }); // Zen 4.0 Global Hotkeys document.addEventListener("keydown", (e) => { // Ctrl + Q to toggle queue sidebar if (e.ctrlKey && e.key.toLowerCase() === 'q') { e.preventDefault(); const layout = document.querySelector('.zen-layout-3col'); const pipeline = document.getElementById('zenPipeline'); if (layout && pipeline) { layout.classList.toggle('collapsed-queue'); pipeline.classList.toggle('collapsed'); } } // Ctrl + Shift + S to toggle Scribe if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 's') { e.preventDefault(); window.toggleZenScribe(); } }); // Initialize Recognition if ('webkitSpeechRecognition' in window) { recognition = new webkitSpeechRecognition(); recognition.continuous = true; recognition.interimResults = true; recognition.maxAlternatives = 3; recognition.onresult = (event) => { for (let i = event.resultIndex; i < event.results.length; ++i) { if (event.results[i].isFinal) { const transcript = event.results[i][0].transcript; // Default streaming to Subjective section or active field const activeField = document.activeElement; if (activeField && activeField.tagName === 'TEXTAREA' && activeField.id.startsWith('soap-')) { activeField.value += transcript + '. '; } else { const defaultField = document.getElementById("soap-s"); if (defaultField) defaultField.value += transcript + '. '; } } } }; recognition.onerror = () => { isScribing = false; updateScribeUI(); }; } }); // 3. Section Switching Logic window.showSection = (sectionId) => { const sections = ['overview-section', 'patients-section', 'pharmacy-section', 'lab-section', 'doctor-zen-section', 'settings-section']; sections.forEach(id => { const el = document.getElementById(id); if (el) el.classList.add('hidden'); }); const role = getUserRole(); if (sectionId === 'settings') { const isDoctor = role === 'DOCTOR'; document.querySelector('#settings-section .doctor-only')?.classList.toggle('hidden', !isDoctor); document.querySelector('#settings-section .admin-only')?.classList.toggle('hidden', isDoctor); if (typeof loadSettings === 'function') loadSettings(); } const target = resolveSectionTarget(sectionId); const targetEl = document.getElementById(target); if (targetEl) targetEl.classList.remove('hidden'); if (target === 'doctor-zen-section') { document.body.classList.add('zen-mode-active'); } else { document.body.classList.remove('zen-mode-active'); } if (sectionId === 'queue' && target === 'overview-section') { document.getElementById('queue-section')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); } const headerSection = (sectionId === 'queue' && getUserRole() === 'DOCTOR') ? 'zen' : sectionId; updateSectionPageHeader(headerSection); setActiveNavLink(sectionId); if (window.innerWidth <= 768) { const sidebar = document.getElementById('sidebar'); sidebar?.classList.remove('open'); document.getElementById('sidebarBackdrop')?.classList.remove('visible'); document.getElementById('sidebarMenuBtn')?.setAttribute('aria-expanded', 'false'); } return false; }; // 4. Clinical Context & Scribe Logic window.toggleZenScribe = async () => { if (!recognition) { alert("Speech Recognition not supported."); return; } isScribing = !isScribing; updateScribeUI(); if (isScribing) { recognition.lang = document.getElementById("scribeLang")?.value || 'en-US'; recognition.start(); startDeepCapture(); } else { recognition.stop(); if (mediaRecorder) mediaRecorder.stop(); } }; function updateScribeUI() { const btn = document.getElementById("zenScribeBtn"); const status = document.getElementById("scribeStatus"); if (!btn || !status) return; if (isScribing) { btn.innerHTML = "⏸️ Pause Scribe"; btn.style.background = "#EF4444"; btn.style.color = "white"; status.classList.remove("hidden"); } else { btn.innerHTML = "πŸ”΄ Start Recording"; btn.style.background = ""; // Resets to CSS default btn.style.color = ""; status.classList.add("hidden"); } } async function startDeepCapture() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = (e) => audioChunks.push(e.data); mediaRecorder.onstop = () => { const blob = new Blob(audioChunks, { type: 'audio/wav' }); processWithWhisper(blob); }; mediaRecorder.start(); } catch (err) { console.warn("Mic access denied for Deep Scribe"); } } async function processWithWhisper(blob) { const formData = new FormData(); formData.append("file", blob, "audio.wav"); formData.append("model", "tiny"); // Explicitly tell the server which model to use try { const res = await fetch("https://curio.applaude.net/api/whisper/v1/audio/transcriptions", { method: "POST", body: formData }); const data = await res.json(); if (data.text) { const activeField = document.activeElement; if (activeField && activeField.tagName === 'TEXTAREA' && activeField.id.startsWith('soap-')) { activeField.value += "\n[DEEP SCRIBE]: " + data.text; } else { const defaultField = document.getElementById("soap-s"); if (defaultField) defaultField.value += "\n[DEEP SCRIBE]: " + data.text; } generateSummary(true); } } catch (e) { console.warn("Deep Scribe error, using fallback summary.", e); generateSummary(document.getElementById("scribeLang")?.value === 'te-IN'); } } function generateSummary(isTelugu = false) { const soapS = document.getElementById("soap-s")?.value || ""; const soapO = document.getElementById("soap-o")?.value || ""; const soapA = document.getElementById("soap-a")?.value || ""; const soapP = document.getElementById("soap-p")?.value || ""; const combinedNotes = [soapS, soapO, soapA, soapP].filter(Boolean).join("\n"); const notesEl = document.getElementById("consultationNotes"); if (notesEl) notesEl.value = combinedNotes; const summary = isTelugu ? "πŸ“ [TRANSLATED] CLINICAL SUMMARY: Patient presents in Telugu. Dry cough and fatigue noted. Recommended fluids and rest." : "πŸ“‹ CLINICAL SUMMARY: Patient presents with persistent dry cough. Recommended hydration and follow-up."; const summaryTextEl = document.getElementById("summaryText"); if (summaryTextEl) summaryTextEl.innerText = summary; const summaryCard = document.getElementById("aiSummaryCard"); if (summaryCard) summaryCard.classList.remove("hidden"); if (currentPatientId) { patientDataStore[currentPatientId] = { notes: combinedNotes, soapS, soapO, soapA, soapP, summary }; } } window.switchPatientContext = (name, token, triage) => { if (currentPatientId) { const notesVal = document.getElementById("consultationNotes")?.value || ""; const soapS = document.getElementById("soap-s")?.value || ""; const soapO = document.getElementById("soap-o")?.value || ""; const soapA = document.getElementById("soap-a")?.value || ""; const soapP = document.getElementById("soap-p")?.value || ""; const summary = document.getElementById("summaryText")?.innerText || ""; patientDataStore[currentPatientId] = { notes: notesVal, soapS, soapO, soapA, soapP, summary }; } isScribing = false; updateScribeUI(); currentPatientId = token; const data = patientDataStore[token] || { notes: "", soapS: "", soapO: "", soapA: "", soapP: "", summary: "" }; const notesEl = document.getElementById("consultationNotes"); if (notesEl) notesEl.value = data.notes || ""; const sField = document.getElementById("soap-s"); const oField = document.getElementById("soap-o"); const aField = document.getElementById("soap-a"); const pField = document.getElementById("soap-p"); if (sField) sField.value = data.soapS || ""; if (oField) oField.value = data.soapO || ""; if (aField) aField.value = data.soapA || ""; if (pField) pField.value = data.soapP || ""; const summaryTextEl = document.getElementById("summaryText"); if (summaryTextEl) summaryTextEl.innerText = data.summary || ""; const summaryCard = document.getElementById("aiSummaryCard"); if (summaryCard) { if (data.summary) summaryCard.classList.remove("hidden"); else summaryCard.classList.add("hidden"); } const patientHeading = document.querySelector(".patient-identity-row h1"); if (patientHeading) patientHeading.innerHTML = `${name} (32y, Female)`; document.querySelectorAll('.pipeline-item').forEach(item => { const itemToken = item.querySelector('.p-token')?.textContent?.trim(); item.classList.toggle('next', itemToken === token); item.classList.toggle('active', itemToken === token); }); }; // 5. Global Helpers & Modal Handlers window.openHistory = () => { const heading = document.querySelector(".patient-identity-row h1, .patient-identity h1"); const name = heading ? heading.innerText.split(' (')[0] : 'Patient'; alert(`πŸ“‚ Loading History for ${name}...\n\n- Visit (05/12/26): Chronic Gastritis`); }; window.openConsultation = () => { const triagePills = document.querySelectorAll(".patient-identity .meta-pills .pill.time"); const triage = triagePills[triagePills.length - 1]; const rxField = document.getElementById("rxDiagnosis"); if (rxField && triage) rxField.value = triage.innerText; if (modal) modal.classList.add('open'); }; window.shareSummary = () => alert("πŸ“€ Summary sent to Patient's WhatsApp."); window.printSummary = () => alert("πŸ–¨οΈ Printing Handover Note..."); window.toggleSidebar = () => { const sidebar = document.getElementById('sidebar'); const backdrop = document.getElementById('sidebarBackdrop'); const menuBtn = document.getElementById('sidebarMenuBtn'); const isOpen = sidebar?.classList.toggle('open'); backdrop?.classList.toggle('visible', !!isOpen); if (menuBtn) menuBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); }; function closeRxModal() { if (modal) modal.classList.remove('open'); } if (addMedBtn && medList) { addMedBtn.addEventListener('click', () => { const row = medList.querySelector('.med-row'); if (row) medList.appendChild(row.cloneNode(true)); }); } if (addLabBtn && labList) { addLabBtn.addEventListener('click', () => { const row = labList.querySelector('.lab-row'); if (row) { const clone = row.cloneNode(true); const input = clone.querySelector('input'); if (input) input.value = ''; labList.appendChild(clone); } }); } window.shareTip = (type) => { const tips = { diabetes: 'Maintain a balanced diet low in refined sugars.', hypertension: 'Reduce salt intake and practice daily breathing exercises.', viral: 'Stay hydrated and get adequate rest.' }; alert(`πŸ“€ Health tip sent: ${tips[type] || 'General wellness advice.'}`); }; window.printTips = () => alert('πŸ–¨οΈ Printing patient education handout…'); // Global Listeners if (closeModal) closeModal.onclick = closeRxModal; if (cancelRxBtn) cancelRxBtn.onclick = closeRxModal; window.onclick = (e) => { if (e.target === modal) closeRxModal(); }; // AI Scribe Polling Logic const submitAiBtn = document.getElementById("submitAiScribeBtn"); if (submitAiBtn) { submitAiBtn.addEventListener("click", async () => { const dictationInput = document.getElementById("aiDictationInput"); const statusDiv = document.getElementById("aiScribeStatus"); const statusText = document.getElementById("aiScribeStatusText"); if (!dictationInput || !dictationInput.value.trim()) { alert("Please enter some dictation first."); return; } // UI Reset submitAiBtn.disabled = true; statusDiv.classList.remove("hidden"); statusText.innerText = "Submitting to CuraFlow AI Engine..."; try { // Start Job const res = await fetch("/api/ai/scribe/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ dictation: dictationInput.value }) }); const data = await res.json(); if (!data.success) throw new Error(data.error); const jobId = data.jobId; statusText.innerText = "AI Scribe analyzing dictation (this may take a minute)..."; // Poll Status const pollInterval = setInterval(async () => { try { const statusRes = await fetch(`/api/ai/scribe/status/${jobId}`); const statusData = await statusRes.json(); if (statusData.status === 'COMPLETED') { clearInterval(pollInterval); statusText.innerText = "Analysis Complete! Populating fields..."; // Populate Fields from AI Output const result = statusData.result; if (result && result.clinical_note) { const rxDiagnosis = document.getElementById("rxDiagnosis"); if (rxDiagnosis && result.clinical_note.assessment) { rxDiagnosis.value = result.clinical_note.assessment; } // Log full output to insights box const insightsBox = document.getElementById("aiInsights"); const insightText = document.getElementById("insightText"); if (insightsBox && insightText) { insightsBox.classList.remove("hidden"); insightText.innerHTML = ` Symptoms: ${result.clinical_note.chief_complaint?.symptoms?.join(', ')}
Billing Codes: ${result.billing_codes?.icd_10_codes?.map(c => c.code).join(', ') || 'None'}
(Check console for full JSON structure) `; console.log("AI Scribe Full Output:", result); } } setTimeout(() => { statusDiv.classList.add("hidden"); submitAiBtn.disabled = false; }, 2000); } else if (statusData.status === 'FAILED') { clearInterval(pollInterval); statusText.innerText = "AI Analysis Failed. Please try again."; submitAiBtn.disabled = false; } } catch (err) { console.error("Polling error", err); } }, 5000); // Poll every 5 seconds } catch (err) { console.error(err); statusText.innerText = "Error submitting dictation."; submitAiBtn.disabled = false; } }); } // ========================================== // AI Lab Anomaly Watcher Logic // ========================================== const startWatcherBtn = document.getElementById("startWatcherBtn"); const submitLabBtn = document.getElementById("submitLabBtn"); const labWatcherStatusBadge = document.getElementById("labWatcherStatusBadge"); const labResultInputSection = document.getElementById("labResultInputSection"); const aiAlertsContainer = document.getElementById("aiAlertsContainer"); let activeWatcherPatientId = null; let alertPollingInterval = null; if (startWatcherBtn) { startWatcherBtn.addEventListener("click", async () => { const patientId = document.getElementById("labPatientId").value.trim(); const baseline = document.getElementById("labBaseline").value.trim(); if (!patientId) return alert("Please enter a Patient ID"); startWatcherBtn.disabled = true; startWatcherBtn.innerText = "Starting..."; try { const res = await fetch("/api/ai/lab/watch/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ patientId: patientId, baselineData: { baseline: baseline || "Patient is healthy", medications: [] } }) }); const data = await res.json(); if (data.success) { activeWatcherPatientId = patientId; labWatcherStatusBadge.innerText = "Active (Monitoring)"; labWatcherStatusBadge.classList.add('badge-active'); labResultInputSection.classList.remove("hidden"); startWatcherBtn.innerText = "Watcher Running"; // Start polling for alerts if (alertPollingInterval) clearInterval(alertPollingInterval); alertPollingInterval = setInterval(pollLabAlerts, 5000); } else { alert("Failed: " + data.error); startWatcherBtn.disabled = false; startWatcherBtn.innerText = "Start Watcher"; } } catch (err) { console.error(err); alert("Error starting watcher"); startWatcherBtn.disabled = false; startWatcherBtn.innerText = "Start Watcher"; } }); } if (submitLabBtn) { submitLabBtn.addEventListener("click", async () => { if (!activeWatcherPatientId) return alert("Start watcher first"); const resultInput = document.getElementById("newLabResult"); const result = resultInput.value.trim(); if (!result) return alert("Enter a lab result"); submitLabBtn.disabled = true; submitLabBtn.innerText = "Sending..."; try { const res = await fetch(`/api/ai/lab/result/${activeWatcherPatientId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ result }) }); const data = await res.json(); if (data.success) { resultInput.value = ""; setTimeout(() => { submitLabBtn.innerText = "Send to AI Watcher"; submitLabBtn.disabled = false; }, 1000); } else { alert("Error: " + data.error); submitLabBtn.disabled = false; submitLabBtn.innerText = "Send to AI Watcher"; } } catch (err) { console.error(err); alert("Failed to submit result"); submitLabBtn.disabled = false; submitLabBtn.innerText = "Send to AI Watcher"; } }); } async function pollLabAlerts() { if (!activeWatcherPatientId) return; try { const res = await fetch(`/api/ai/lab/alerts/${activeWatcherPatientId}`); const data = await res.json(); if (data.success && data.alerts && data.alerts.length > 0) { renderAlerts(data.alerts); } } catch (err) { console.error("Error polling alerts:", err); } } function renderAlerts(alerts) { if (!aiAlertsContainer) return; let html = `

⚠️ Critical AI Alerts (${alerts.length})

`; alerts.forEach((alert, index) => { if (alert.is_dangerous) { html += `
Alert #${index + 1}

${alert.danger_explanation}

Action: ${alert.recommended_action}

`; } }); aiAlertsContainer.innerHTML = html; } // ========================================== // Zen 4.0 Vitals Sync // ========================================== window.syncVitals = () => { const temp = document.getElementById("vital-temp")?.innerText || ""; const bp = document.getElementById("vital-bp")?.innerText || ""; const spo2 = document.getElementById("vital-spo2")?.innerText || ""; const objectiveField = document.getElementById("soap-o"); if (objectiveField) { objectiveField.value += `\n[Vitals Sync] Temp: ${temp}, BP: ${bp}, SpO2: ${spo2}\n`; // Trigger input event to resize textarea or run macros if needed objectiveField.dispatchEvent(new Event('input', { bubbles: true })); } }; // ========================================== // Settings UI Logic // ========================================== async function loadSettings() { // 1. Load local preferences (Doctor profile & Scribe prefs) const nameEl = document.getElementById("settingsName"); const specialtyEl = document.getElementById("settingsSpecialty"); const scribeLangEl = document.getElementById("settingsScribeLang"); const autoSummaryEl = document.getElementById("settingsAutoSummary"); if (nameEl) nameEl.value = localStorage.getItem("userName") || localStorage.getItem("userEmail") || ""; if (specialtyEl) specialtyEl.value = localStorage.getItem("userSpecialty") || ""; if (scribeLangEl) scribeLangEl.value = localStorage.getItem("scribeLangPref") || "en-US"; if (autoSummaryEl) autoSummaryEl.value = localStorage.getItem("autoSummaryPref") || "enabled"; // 2. Fetch remote clinic config (Admin config) const tenantId = localStorage.getItem("Curio_tenant") || "default"; try { const res = await fetch(`/api/config/${tenantId}`); if (res.ok) { const config = await res.json(); const feeEl = document.getElementById("settingsConsultationFee"); if (feeEl && config.consultationFee) { feeEl.value = config.consultationFee; } if (specialtyEl && !specialtyEl.value && config.specialty) { specialtyEl.value = config.specialty; } } } catch (err) { console.error("Failed to load clinic settings from server:", err); } } window.saveSettings = async () => { const btn = document.getElementById("saveSettingsBtn"); const status = document.getElementById("settingsSaveStatus"); if (btn) { btn.disabled = true; btn.textContent = "Saving..."; } if (status) { status.textContent = ""; status.style.color = "inherit"; } try { // 1. Save local preferences const nameVal = document.getElementById("settingsName")?.value.trim(); const specVal = document.getElementById("settingsSpecialty")?.value.trim(); const scribeLangVal = document.getElementById("settingsScribeLang")?.value; const autoSumVal = document.getElementById("settingsAutoSummary")?.value; if (nameVal) localStorage.setItem("userName", nameVal); if (specVal) localStorage.setItem("userSpecialty", specVal); if (scribeLangVal) localStorage.setItem("scribeLangPref", scribeLangVal); if (autoSumVal) localStorage.setItem("autoSummaryPref", autoSumVal); // 2. Save remote config if user is OWNER const role = getUserRole(); if (role === "OWNER") { const feeVal = document.getElementById("settingsConsultationFee")?.value; const payload = {}; if (feeVal) payload.consultationFee = parseInt(feeVal); const token = localStorage.getItem("curio_token"); if (token) { const res = await fetch('/api/clinic/settings', { method: 'PATCH', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || "Failed to update clinic settings"); } } } if (status) { status.textContent = "βœ… Settings saved successfully!"; status.style.color = "#10B981"; // Success green } // Apply any real-time UI updates const roleDisplay = document.querySelector(".user-profile .info strong"); if (roleDisplay && nameVal && role === "DOCTOR") { roleDisplay.innerText = nameVal; } } catch (err) { console.error("Error saving settings:", err); if (status) { status.textContent = "❌ " + err.message; status.style.color = "#EF4444"; // Error red } } finally { if (btn) { btn.disabled = false; btn.textContent = "Save Preferences"; } // Hide status message after 3 seconds setTimeout(() => { if (status) status.textContent = ""; }, 3000); } }; // --- Prescription Builder Logic --- function addMedicineRow() { const container = document.getElementById('medicines-container'); if (!container) return; const row = document.createElement('div'); row.className = 'medicine-row'; row.innerHTML = ` `; container.appendChild(row); } // Initial setup when opening a patient const originalOpenPatient = window.openPatient; window.openPatient = function(name, id) { if (originalOpenPatient) originalOpenPatient(name, id); // Fill print details const printName = document.getElementById('print-patient-name'); const printId = document.getElementById('print-patient-id'); const printDate = document.getElementById('print-date'); if (printName) printName.innerText = name; if (printId) printId.innerText = id; if (printDate) printDate.innerText = new Date().toLocaleDateString(); // Reset medicines const medContainer = document.getElementById('medicines-container'); if (medContainer) { medContainer.innerHTML = ''; addMedicineRow(); } }; // If there was no original openPatient (it was inline or something), let's hook into the global DOM elements instead: document.addEventListener('DOMContentLoaded', () => { // If medicines container exists, add the first row const medContainer = document.getElementById('medicines-container'); if (medContainer && medContainer.children.length === 0) { addMedicineRow(); } }); async function submitPrescription() { // Attempt to pull patient name from the main dashboard header (if openPatient didn't set it) let patientName = document.getElementById('print-patient-name').innerText; let patientId = document.getElementById('print-patient-id').innerText; // Fallback: pull from the h1 element in the clinical workspace if it exists const titleEl = document.querySelector('.patient-title-bar h1'); if (titleEl && (!patientName || patientName === 'Rahul Verma')) { // 'Rahul Verma' is the default template patientName = titleEl.childNodes[0].textContent.trim(); patientId = 'N/A'; // We might not have it displayed easily } const diagnosis = document.getElementById('soap-a').value; const medicines = []; document.querySelectorAll('.medicine-row').forEach(row => { const name = row.querySelector('.med-name').value; if (name) { medicines.push({ name: name, dosage: row.querySelector('.med-dosage').value, frequency: row.querySelector('.med-freq').value, duration: row.querySelector('.med-dur').value }); } }); if (!patientName || medicines.length === 0) { alert("Please fill in at least one medicine and ensure patient name is loaded."); return; } try { const res = await apiCall('/api/prescriptions', 'POST', { patientId, patientName, diagnosis, medicines, labTests: [] }); if (res.success) { alert('Prescription created and sent to pharmacy! ID: ' + res.prescriptionId); // Optionally print right away // window.print(); } else { alert('Failed to create prescription.'); } } catch (err) { console.error(err); alert('An error occurred while creating the prescription.'); } } // ========================================== // Live Queue & Walk-in UI Logic // ========================================== let liveQueuePollingInterval = null; async function fetchLiveQueue() { const token = localStorage.getItem("curio_token"); if (!token) return; try { const res = await fetch('/api/clinic/queue', { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const queue = await res.json(); renderLiveQueueTable(queue); renderZenPipeline(queue); updateQueueStats(queue); } } catch (err) { console.error("Failed to fetch live queue:", err); } } function updateQueueStats(queue) { const counterBadge = document.querySelector('.counter-badge'); if (counterBadge) counterBadge.innerText = `Waiting: ${queue.filter(q => q.status === 'waiting').length}`; const waitingSpan = document.querySelector('.pipeline-header .badge'); if (waitingSpan) waitingSpan.innerText = `${queue.filter(q => q.status === 'waiting').length} Waiting`; } function renderLiveQueueTable(queue) { const tbody = document.getElementById("liveQueueTbody"); if (!tbody) return; if (queue.length === 0) { tbody.innerHTML = `Queue is empty`; return; } tbody.innerHTML = queue.map(visit => { const time = new Date(visit.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const sourceClass = visit.type === 'online' ? 'online' : 'walkin'; const sourceText = visit.type === 'online' ? 'Online' : 'Walk-in'; let statusHtml = ''; if (visit.status === 'in-room') { statusHtml = `In Room`; } else if (visit.payment_status === 'unpaid') { statusHtml = `Unpaid (β‚Ή${visit.fee})`; } else { statusHtml = `Paid (Waiting)`; } let actionHtml = ''; if (visit.status === 'in-room') { actionHtml = ``; } else if (visit.payment_status === 'unpaid') { actionHtml = ``; } else { actionHtml = ``; } return ` ${visit.token_number} ${visit.patient_name}
${visit.display_id} ${sourceText} EN ${visit.triage_category || 'Routine'} ${statusHtml} ${time} ${actionHtml} `; }).join(''); } function renderZenPipeline(queue) { const pipelineList = document.getElementById("livePipelineList"); if (!pipelineList) return; if (queue.length === 0) { pipelineList.innerHTML = `
No patients waiting
`; return; } pipelineList.innerHTML = queue.map((visit, index) => { const isNext = index === 0 && visit.status !== 'in-room'; const isActive = visit.status === 'in-room'; const itemClass = isActive ? 'active' : (isNext ? 'next' : ''); const triageClass = visit.triage_category === 'Urgent' ? 'urgent' : 'routine'; return `
${visit.token_number}
${visit.patient_name} ${visit.triage_category || 'Routine'}
`; }).join(''); } // Walk-in Modal Handlers window.openWalkinModal = () => { document.getElementById('walkInModal')?.classList.add('open'); }; window.closeWalkinModal = () => { document.getElementById('walkInModal')?.classList.remove('open'); document.getElementById('walkInForm')?.reset(); }; window.submitWalkIn = async () => { const btn = document.getElementById("submitWalkInBtn"); const name = document.getElementById("walkInName").value.trim(); const phone = document.getElementById("walkInPhone").value.trim(); const age = document.getElementById("walkInAge").value; const gender = document.getElementById("walkInGender").value; const fee = document.getElementById("walkInFee").value; if (!name || !phone) { alert("Name and WhatsApp Number are required."); return; } btn.disabled = true; btn.innerText = "Registering..."; try { const token = localStorage.getItem("curio_token"); const res = await fetch('/api/clinic/walk-in', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, whatsappNumber: phone, age, gender, fee: parseInt(fee) || 0 }) }); const data = await res.json(); if (data.success) { closeWalkinModal(); fetchLiveQueue(); // Instantly refresh alert(`Token ${data.token} generated for ${name}!`); } else { alert("Error: " + data.error); } } catch (err) { console.error("Walk-in error:", err); alert("Failed to register walk-in."); } finally { btn.disabled = false; btn.innerText = "Register & Print Token"; } }; // Start Polling when DOM loads document.addEventListener('DOMContentLoaded', () => { fetchLiveQueue(); liveQueuePollingInterval = setInterval(fetchLiveQueue, 10000); // Poll every 10s });