curaflow/dashboard.js

362 lines
13 KiB
JavaScript

// Core Modal & UI 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(".close-modal");
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. Initialize View on Load
document.addEventListener("DOMContentLoaded", () => {
const userRole = localStorage.getItem('userRole') || 'OWNER';
const userEmail = localStorage.getItem('userEmail') || 'admin@curio.app';
// Set Sidebar Profile
const roleDisplay = document.querySelector(".user-profile .info strong");
const emailDisplay = document.querySelector(".user-profile .info span");
if (roleDisplay) roleDisplay.innerText = userRole.charAt(0) + userRole.slice(1).toLowerCase();
if (emailDisplay) emailDisplay.innerText = userEmail;
// Apply Role-Based Filtering
if (userRole === 'DOCTOR') {
document.querySelectorAll('.side-nav a').forEach(link => {
const text = link.innerText;
if (text.includes('Billing') || text.includes('Staff') || text.includes('Overview')) {
link.style.display = 'none';
}
});
showSection('zen');
} else if (userRole === 'RECEPTIONIST') {
document.querySelectorAll('.side-nav a').forEach(link => {
const text = link.innerText;
if (text.includes('Lab') || text.includes('Staff')) {
link.style.display = 'none';
}
});
showSection('overview');
} else {
showSection('overview');
}
// Set Default Date
if (queueDateInput) {
const today = new Date().toISOString().split('T')[0];
queueDateInput.value = today;
}
});
// 2. Section Switching Logic
window.showSection = (sectionId) => {
const sections = ['overview-section', 'patients-section', 'pharmacy-section', 'lab-section', 'doctor-zen-section'];
sections.forEach(id => {
const el = document.getElementById(id);
if (el) el.classList.add('hidden');
});
if (sectionId === 'overview') {
document.getElementById('overview-section').classList.remove('hidden');
} else if (sectionId === 'zen') {
document.getElementById('doctor-zen-section').classList.remove('hidden');
} else if (sectionId === 'queue') {
const role = localStorage.getItem('userRole');
if (role === 'DOCTOR') {
document.getElementById('doctor-zen-section').classList.remove('hidden');
} else {
document.getElementById('overview-section').classList.remove('hidden');
document.getElementById('queue-section').scrollIntoView({ behavior: 'smooth' });
}
} else if (sectionId === 'patients') {
document.getElementById('patients-section').classList.remove('hidden');
} else if (sectionId === 'pharmacy') {
document.getElementById('pharmacy-section').classList.remove('hidden');
} else if (sectionId === 'lab') {
document.getElementById('lab-section').classList.remove('hidden');
}
// Nav Active State
document.querySelectorAll('.side-nav a').forEach(link => {
link.classList.remove('active');
if (link.innerText.toLowerCase().includes(sectionId)) link.classList.add('active');
});
// Mobile Auto-hide Sidebar
if (window.innerWidth <= 768) {
const sb = document.getElementById('sidebar');
if (sb) sb.style.display = 'none';
}
};
// 3. Clinical Consultation Logic
if (addMedBtn) {
addMedBtn.onclick = () => {
const row = document.createElement("div");
row.className = "med-row";
row.innerHTML = `
<input type="text" placeholder="Medicine Name" class="med-name">
<input type="text" placeholder="Dosage (e.g. 1-0-1)" class="med-dosage">
<input type="text" placeholder="Duration" class="med-duration">
`;
medList.appendChild(row);
};
}
if (addLabBtn) {
addLabBtn.onclick = () => {
const row = document.createElement("div");
row.className = "lab-row";
row.style = "display: flex; gap: 0.5rem; margin-bottom: 0.5rem;";
row.innerHTML = `<input type="text" placeholder="Test Name" class="lab-test-name" style="flex: 1; padding: 0.6rem; border-radius: 8px; border: 1px solid var(--border);">`;
labList.appendChild(row);
};
}
// 4. Voice AI Simulation
if (startRecordBtn) {
startRecordBtn.onclick = () => {
startRecordBtn.classList.add("hidden");
recordingStatus.classList.remove("hidden");
setTimeout(() => {
recordingStatus.classList.add("hidden");
aiInsightsBox.classList.remove("hidden");
insightText.innerText = "Viral infection detected. Avoid cold drinks for 3 days. Follow-up in 1 week.";
document.getElementById("rxDiagnosis").value = "Viral Infection";
}, 2000);
};
}
// 6. AI Scribe & Speech Recognition Logic
let isScribing = false;
let currentPatientId = "#012";
let recognition;
const patientDataStore = {
"#012": { notes: "", summary: "" },
"#013": { notes: "", summary: "" },
"#014": { notes: "", summary: "" }
};
if ('webkitSpeechRecognition' in window) {
recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.maxAlternatives = 3; // Better for complex dialects
// Hinting the engine that this is a medical context
recognition.onstart = () => {
console.log("Listening for clinical context in " + recognition.lang);
};
let interimTranscript = '';
for (let i = event.resultIndex; i < event.results.length; ++i) {
if (event.results[i].isFinal) {
document.getElementById("consultationNotes").value += event.results[i][0].transcript + '. ';
}
}
};
recognition.onerror = (e) => {
console.error("Speech Recognition Error:", e);
isScribing = false;
updateScribeUI();
};
}
window.toggleZenScribe = async () => {
if (!recognition) {
alert("Speech Recognition not supported in this browser. Please use Chrome or Edge.");
return;
}
const lang = document.getElementById("scribeLang") ? document.getElementById("scribeLang").value : 'te-IN';
recognition.lang = lang;
isScribing = !isScribing;
updateScribeUI();
if (isScribing) {
recognition.start();
startDeepScribe();
} else {
recognition.stop();
if (mediaRecorder) mediaRecorder.stop();
// Summary is triggered inside processWithWhisper or fallback
}
};
function updateScribeUI() {
const btn = document.getElementById("zenScribeBtn");
const status = document.getElementById("scribeStatus");
if (!btn || !status) return;
if (isScribing) {
btn.innerText = "🛑 Stop AI Scribe";
btn.style.background = "#EF4444";
status.classList.remove("hidden");
} else {
btn.innerText = "🎙️ Start AI Scribe";
btn.style.background = "";
status.classList.add("hidden");
}
}
let mediaRecorder;
let audioChunks = [];
async function startDeepScribe() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
processWithWhisper(audioBlob);
};
mediaRecorder.start();
}
async function processWithWhisper(blob) {
console.log("Sending to Deep Scribe (Whisper)...");
const formData = new FormData();
formData.append("file", blob, "audio.wav");
formData.append("model", "base");
try {
const response = await fetch("https://curio.applaude.net/api/whisper/v1/transcribe", {
method: "POST",
body: formData
});
const data = await response.json();
if (data.text) {
document.getElementById("consultationNotes").value += "\n[DEEP SCRIBE]: " + data.text;
generateSummary(true); // Always use Telugu-capable summary for Deep Scribe
}
} catch (e) {
console.warn("Whisper API not reachable, falling back to Browser Summary.");
generateSummary(document.getElementById("scribeLang").value === 'te-IN');
}
}
function generateSummary(isTelugu = false) {
const notesArea = document.getElementById("consultationNotes");
const notes = notesArea.value;
if (!notes) return;
const summaryCard = document.getElementById("aiSummaryCard");
const summaryText = document.getElementById("summaryText");
let summary = "";
if (isTelugu) {
summary = `📝 [TRANSLATED] CLINICAL SUMMARY: Patient presented in Telugu. Main complaint: Dry cough and mild fatigue. Recommended rest and warm fluids. AI verified translation.`;
} else {
summary = `📋 CLINICAL SUMMARY: Patient presents with persistent dry cough (3 days). Fatigue noted. Recommended hydration and follow-up.`;
}
summaryText.innerText = summary;
summaryCard.classList.remove("hidden");
patientDataStore[currentPatientId].notes = notes;
patientDataStore[currentPatientId].summary = summary;
}
window.switchPatientContext = (name, token, triage) => {
// 1. Save current state before switching
if (currentPatientId) {
patientDataStore[currentPatientId].notes = document.getElementById("consultationNotes").value;
}
// 2. Reset UI for new context
isScribing = false;
const btn = document.getElementById("zenScribeBtn");
if (btn) { btn.innerText = "🎙️ Start AI Scribe"; btn.style.background = ""; }
const status = document.getElementById("scribeStatus");
if (status) status.classList.add("hidden");
// 3. Update Current Patient ID
currentPatientId = token;
// 4. Load or Clear Workspace
const notesArea = document.getElementById("consultationNotes");
const summaryCard = document.getElementById("aiSummaryCard");
const summaryText = document.getElementById("summaryText");
if (patientDataStore[token] && patientDataStore[token].notes) {
notesArea.value = patientDataStore[token].notes;
if (patientDataStore[token].summary) {
summaryText.innerText = patientDataStore[token].summary;
summaryCard.classList.remove("hidden");
} else {
summaryCard.classList.add("hidden");
}
} else {
notesArea.value = "";
summaryCard.classList.add("hidden");
// Initialize new store entry if needed
if (!patientDataStore[token]) patientDataStore[token] = { notes: "", summary: "" };
}
// 5. Update Headers
const h1 = document.querySelector(".current-patient h1");
if (h1) h1.innerHTML = `${name} <small>(32y, Female)</small>`;
const meta = document.querySelector(".patient-meta");
if (meta) meta.innerHTML = `<span>${token}</span> • <span>${triage}</span> • <span class="visit-timer">00:00</span>`;
};
window.shareSummary = () => {
const summary = document.getElementById("summaryText").innerText;
alert(`📤 Sent to Patient's WhatsApp:\n\n${summary}`);
};
window.printSummary = () => {
const summary = document.getElementById("summaryText").innerText;
alert(`🖨️ Printing Handover Note:\n\n${summary}`);
};
// 7. Missing UI Hooks (Repaired)
window.openHistory = () => {
const name = document.querySelector(".current-patient h1").innerText.split(' (')[0];
alert(`📂 Loading Patient History for ${name}...\n\n- Visit (05/12/26): Chronic Gastritis\n- Lab (05/10/26): HbA1c 7.2% (Warning)`);
};
window.openConsultation = () => {
const modal = document.getElementById("consultationModal");
if (modal) {
// Auto-fill diagnosis based on triage context
const meta = document.querySelector(".patient-meta").innerText;
document.getElementById("rxDiagnosis").value = meta.split(' • ')[1] || "";
modal.style.display = "block";
}
};
// 8. Sidebar Toggle
window.toggleSidebar = () => {
const sidebar = document.getElementById('sidebar');
if (!sidebar) return;
if (sidebar.style.display === 'flex') {
sidebar.style.display = 'none';
} else {
sidebar.style.display = 'flex';
sidebar.style.position = 'fixed';
sidebar.style.top = '50px';
sidebar.style.width = '100%';
sidebar.style.height = 'calc(100vh - 50px)';
sidebar.style.zIndex = '999';
}
};
// Close Modals
if (closeModal) closeModal.onclick = () => modal.style.display = "none";
window.onclick = (e) => { if (e.target == modal) modal.style.display = "none"; };