403 lines
15 KiB
JavaScript
403 lines
15 KiB
JavaScript
// 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') || 'OWNER';
|
|
}
|
|
|
|
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 userRole = 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;
|
|
|
|
if (userRole === 'DOCTOR') {
|
|
document.body.classList.add('role-doctor');
|
|
}
|
|
|
|
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);
|
|
|
|
// AI Nudge Simulation Logic
|
|
const notesArea = document.getElementById("consultationNotes");
|
|
const nudgeText = document.getElementById("aiNudgeText");
|
|
const tagsContainer = document.querySelector(".suggested-tags");
|
|
|
|
if (notesArea) {
|
|
notesArea.addEventListener("input", (e) => {
|
|
const val = e.target.value.toLowerCase();
|
|
if (val.includes("fever") || val.includes("temperature")) {
|
|
nudgeText.innerText = "✨ AI suggests: Patient has elevated temp. Consider prescribing Paracetamol and checking for throat infection.";
|
|
tagsContainer.innerHTML = '<span>#ViralFever</span><span>#InfectionCheck</span>';
|
|
} else if (val.includes("cough") || val.includes("breath")) {
|
|
nudgeText.innerText = "✨ AI suggests: Respiratory symptoms detected. Check SpO2 and listen for chest congestion.";
|
|
tagsContainer.innerHTML = '<span>#AsthmaProtocol</span><span>#Bronchitis</span>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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;
|
|
document.getElementById("consultationNotes").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);
|
|
}
|
|
|
|
const target = resolveSectionTarget(sectionId);
|
|
const targetEl = document.getElementById(target);
|
|
if (targetEl) targetEl.classList.remove('hidden');
|
|
|
|
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) {
|
|
document.getElementById('sidebar')?.classList.remove('open');
|
|
}
|
|
|
|
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 = "🛑 Stop Scribe";
|
|
btn.style.background = "#EF4444";
|
|
btn.style.color = "white";
|
|
status.classList.remove("hidden");
|
|
} else {
|
|
btn.innerHTML = "🎙️ Scribe";
|
|
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) {
|
|
document.getElementById("consultationNotes").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 notes = document.getElementById("consultationNotes").value;
|
|
if (!notes) return;
|
|
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.";
|
|
|
|
document.getElementById("summaryText").innerText = summary;
|
|
document.getElementById("aiSummaryCard").classList.remove("hidden");
|
|
patientDataStore[currentPatientId] = { notes, summary };
|
|
}
|
|
|
|
window.switchPatientContext = (name, token, triage) => {
|
|
if (currentPatientId) patientDataStore[currentPatientId].notes = document.getElementById("consultationNotes").value;
|
|
isScribing = false; updateScribeUI();
|
|
currentPatientId = token;
|
|
|
|
const data = patientDataStore[token] || { notes: "", summary: "" };
|
|
document.getElementById("consultationNotes").value = data.notes;
|
|
document.getElementById("summaryText").innerText = data.summary;
|
|
if (data.summary) document.getElementById("aiSummaryCard").classList.remove("hidden");
|
|
else document.getElementById("aiSummaryCard").classList.add("hidden");
|
|
|
|
const patientHeading = document.querySelector(".patient-identity h1");
|
|
if (patientHeading) patientHeading.innerHTML = `${name} <small>(32y, Female)</small>`;
|
|
|
|
const metaPills = document.querySelectorAll(".patient-identity .meta-pills");
|
|
if (metaPills[1]) {
|
|
metaPills[1].innerHTML = `
|
|
<span class="pill triage">${token}</span>
|
|
<span class="pill time">${triage}</span>
|
|
`;
|
|
}
|
|
};
|
|
|
|
// 5. Global Helpers & Modal Handlers
|
|
window.openHistory = () => {
|
|
const heading = document.querySelector(".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 = () => {
|
|
document.getElementById('sidebar')?.classList.toggle('open');
|
|
};
|
|
|
|
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(); };
|