============== ---- One-Sentence Engine ---- function handleOneSentenceSubmit() { const input = document.getElementById('one-sentence-input'); const engine = document.getElementById('one-sentence-engine'); if (!input || state.oneSentenceUsed) return; const text = input.value.trim(); if (!text) return; state.oneSentenceUsed = true; // Parse the sentence const parsed = parseOneSentence(text); // Apply parsed data to state Object.keys(parsed).forEach(key => { if (parsed[key]) { state.patientData[key] = parsed[key]; state.skippedFields.add(key); } }); // Update UI updateRightPanel(); updateProgress(); // Visual feedback - fade the engine if (engine) { engine.classList.add('processed'); } // Determine next step intelligently const nextMissingField = findNextMissingField(); if (nextMissingField) { // Skip to the missing field const stepIndex = STEPS.findIndex(s => s.key === nextMissingField); if (stepIndex !== -1) { state.currentStep = stepIndex - 1; // Show acknowledgment setTimeout(() => { appendAssistantMessage("Got it — I've filled most of this in.", null); setTimeout(() => { advanceStep(); }, 400); }, 200); return; } } // If all filled or no match, show completion message setTimeout(() => { appendAssistantMessage("Got it — I've filled in everything I could find. Let me know if you'd like to add or change anything.", null); setTimeout(() => { showReviewStep(); }, 400); }, 200); } function parseOneSentence(text) { const result = { fullName: '', dob: '', gender: '', phone: '', email: '', address: '', qualifyingCondition: '', notes: '' }; const lower = text.toLowerCase(); // Name extraction - look for capitalized words at start or common patterns const nameMatch = text.match(/^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)/) || text.match(/(?:name is|patient is|called)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)/i); if (nameMatch) { result.fullName = nameMatch[1].trim(); } // Date of birth - various formats const dobPatterns = [ /(?:born|dob|birth|birthdate)[\s:]?\s*(\d{1,2}[\/\-\.]\d{1,2}[\/\-\.]\d{2,4})/i, /(\d{1,2}[\/\-\.]\d{1,2}[\/\-\.]\d{2,4})/, /(?:born|dob)[\s:]?\s*([A-Za-z]+\s+\d{1,2},?\s+\d{4})/i ]; for (const pattern of dobPatterns) { const match = text.match(pattern); if (match) { result.dob = formatDOB(match[1]); break; } } // Gender const genderMatch = text.match(/\b(male|female|non-binary|non binary|man|woman)\b/i); if (genderMatch) { const g = genderMatch[1].toLowerCase(); result.gender = g === 'man' ? 'Male' : g === 'woman' ? 'Female' : g === 'non binary' ? 'Non-binary' : g.charAt(0).toUpperCase() + g.slice(1); } // Phone number const phoneMatch = text.match(/(\(?\d{3}\)?[\s\-\.]?\d{3}[\s\-\.]?\d{4})/) || text.match(/(\d{3}[\s\-\.]\d{3}[\s\-\.]\d{4})/); if (phoneMatch) { result.phone = formatPhone(phoneMatch[1]); } // Email const emailMatch = text.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/); if (emailMatch) { result.email = emailMatch[1]; } // Address - look for number + street patterns const addressMatch = text.match(/(\d+\s+[A-Za-z\s]+(?:street|st|avenue|ave|road|rd|drive|dr|lane|ln|boulevard|blvd)[.,\s]+[A-Za-z\s]+(?:\d{5}(?:-\d{4})?)?)/i) || text.match(/(\d+\s+[A-Za-z\s]+,\s*[A-Za-z\s]+(?:,\s*[A-Za-z]{2})?\s*\d{5}(?:-\d{4})?)/i); if (addressMatch) { result.address = addressMatch[1].trim(); } // Qualifying conditions const conditionKeywords = { 'Chronic Pain': ['chronic pain', 'pain'], 'Anxiety': ['anxiety'], 'PTSD': ['ptsd', 'post traumatic', 'post-traumatic'], 'Cancer': ['cancer'], 'Arthritis': ['arthritis'], 'Severe Nausea': ['severe nausea', 'nausea'], 'Migraines': ['migraine', 'migraines'], 'Insomnia': ['insomnia', 'sleep'], 'Muscle Spasms': ['muscle spasm', 'muscle spasms', 'spasms'], 'Seizure Disorder': ['seizure', 'epilepsy'], 'HIV/AIDS': ['hiv', 'aids'], 'Glaucoma': ['glaucoma'], 'Neuropathy': ['neuropathy', 'nerve pain'], 'Chronic Inflammation': ['inflammation', 'inflammatory'] }; const foundConditions = []; for (const [condition, keywords] of Object.entries(conditionKeywords)) { for (const keyword of keywords) { if (lower.includes(keyword)) { foundConditions.push(condition); break; } } } if (foundConditions.length > 0) { result.qualifyingCondition = foundConditions.join(', '); } // Notes - anything after common delimiters that doesn't fit above const noteIndicators = ['notes:', 'note:', 'additional', 'also', 'suffering from', 'has']; for (const indicator of noteIndicators) { const idx = lower.indexOf(indicator); if (idx !== -1) { const noteText = text.slice(idx + indicator.length).trim(); if (noteText && noteText.length > 3) { result.notes = noteText; break; } } } return result; } function findNextMissingField() { // Find first step whose key is not in patientData or is empty for (let i = 0; i < STEPS.length; i++) { const step = STEPS[i]; const val = state.patientData[step.key]; if (!val || val === '' || val === 'Skipped' || val === 'Not provided') { return step.key; } } return null; } // ---- Utilities ---- function formatValue(key, value) {function startAIFlow() { state.currentStep = -1; // Wait briefly to see if user uses one-sentence input setTimeout(() => { if (!state.oneSentenceUsed && state.currentStep === -1) { advanceStep(); } }, 600); }function setupEventListeners() { sendBtn.addEventListener('click', handleSend); chatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }); skipBtn.addEventListener('click', handleSkip); switchModeBtn.addEventListener('click', toggleMode); saveDraftBtn.addEventListener('click', () => showToast('Draft saved')); mobileRecordFab.addEventListener('click', openMobileSheet); sheetOverlay.addEventListener('click', closeMobileSheet); closeSheet.addEventListener('click', closeMobileSheet); manualSaveDraft.addEventListener('click', () => showToast('Draft saved')); manualCreatePatient.addEventListener('click', handleManualCreate); // One-Sentence Engine listeners const oneSentenceInput = document.getElementById('one-sentence-input'); const oneSentenceSend = document.getElementById('one-sentence-send'); if (oneSentenceInput && oneSentenceSend) { oneSentenceInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); handleOneSentenceSubmit(); } }); oneSentenceSend.addEventListener('click', handleOneSentenceSubmit); }const state = { mode: 'ai', currentStep: -1, patientData: {}, selectedConditions: new Set(), isTyping: false, flowComplete: false, stepActive: false, oneSentenceUsed: false, skippedFields: new Set() };// ============================================ // Verify MC — Add Patient — Application Logic // ============================================ // ---- Constants ---- const CONDITIONS = [ 'Chronic Pain', 'Anxiety', 'PTSD', 'Cancer', 'Arthritis', 'Severe Nausea', 'Migraines', 'Insomnia', 'Muscle Spasms', 'Seizure Disorder', 'HIV/AIDS', 'Glaucoma', 'Neuropathy', 'Chronic Inflammation', 'Other' ]; const STEPS = [ { key: 'fullName', question: "Let's begin the patient intake. What is the patient's full name?", placeholder: "Type the patient's full name...", type: 'text', helper: "You can type naturally — I'll parse details from a sentence too." }, { key: 'dob', question: "What is the patient's date of birth?", placeholder: "e.g. 03/15/1990 or March 15, 1990", type: 'text' }, { key: 'gender', question: "What is the patient's gender?", type: 'chips', options: ['Male', 'Female', 'Non-binary', 'Prefer not to say'] }, { key: 'phone', question: "What's the best phone number for the patient?", placeholder: "Type the patient's phone number...", type: 'text' }, { key: 'email', question: "And their email address?", placeholder: "Type the patient's email address...", type: 'text' }, { key: 'address', question: "What is the patient's home address?", placeholder: "Street, city, state, zip...", type: 'text' }, { key: 'preferredContact', question: "How should we prefer to contact the patient?", type: 'chips', options: ['Phone', 'Email', 'Text', 'No preference'] }, { key: 'state', question: "Which state is the patient located in?", type: 'chips', options: ['California', 'New York', 'Florida', 'Texas', 'Other'] }, { key: 'qualifyingCondition', question: "What qualifying condition(s) apply? Select all that apply.", type: 'conditions', note: 'Condition options may vary based on provider review and state guidance.' }, { key: 'medications', question: "Is the patient currently taking any medications?", placeholder: "List current medications, or type 'None'...", type: 'text', optional: true, quickOptions: ['None', 'Unknown'] }, { key: 'allergies', question: "Does the patient have any known allergies?", placeholder: "List allergies, or type 'None known'...", type: 'text', optional: true, quickOptions: ['None known', 'Unknown'] }, { key: 'pcp', question: "Does the patient have a primary care physician?", placeholder: "PCP name, or type 'None'...", type: 'text', optional: true, quickOptions: ['None', 'Not provided'] }, { key: 'insurance', question: "What is the patient's insurance status?", type: 'chips', options: ['Insured', 'Uninsured', 'Self-pay', 'Prefer not to say'] }, { key: 'emergencyContact', question: "Emergency contact information?", placeholder: "Contact name and phone number...", type: 'text', optional: true, quickOptions: ['Not provided', 'Add later'] }, { key: 'visitType', question: "What type of visit is the patient scheduling?", type: 'chips', options: ['New Patient', 'Follow-up', 'Consultation', 'Renewal'] }, { key: 'notes', question: "Any additional intake notes?", placeholder: "Add any relevant notes...", type: 'text', optional: true, quickOptions: ['None', 'Add later'] } ]; const TOTAL_FIELDS = STEPS.length; // ---- State ---- const state = { mode: 'ai', currentStep: -1, patientData: {}, selectedConditions: new Set(), isTyping: false, flowComplete: false, stepActive: false }; // ---- DOM Refs ---- const chatMessages = document.getElementById('chat-messages'); const chatInput = document.getElementById('chat-input'); const sendBtn = document.getElementById('send-btn'); const skipBtn = document.getElementById('skip-btn'); const quickOptions = document.getElementById('quick-options'); const switchModeBtn = document.getElementById('switch-mode-btn'); const saveDraftBtn = document.getElementById('save-draft-btn'); const progressBar = document.getElementById('progress-bar'); const progressPct = document.getElementById('progress-pct'); const aiLayout = document.getElementById('ai-layout'); const manualLayout = document.getElementById('manual-layout'); const liveBadge = document.getElementById('live-badge'); const pageDescription = document.getElementById('page-description'); const mobileRecordFab = document.getElementById('mobile-record-fab'); const mobileRecordSheet = document.getElementById('mobile-record-sheet'); const sheetOverlay = document.getElementById('sheet-overlay'); const closeSheet = document.getElementById('close-sheet'); const mobileRecordContent = document.getElementById('mobile-record-content'); const manualSaveDraft = document.getElementById('manual-save-draft'); const manualCreatePatient = document.getElementById('manual-create-patient'); // ---- Init ---- function init() { lucide.createIcons(); setupEventListeners(); setTimeout(() => startAIFlow(), 400); } function startAIFlow() { state.currentStep = -1; advanceStep(); } // ---- Event Listeners ---- function setupEventListeners() { sendBtn.addEventListener('click', handleSend); chatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }); skipBtn.addEventListener('click', handleSkip); switchModeBtn.addEventListener('click', toggleMode); saveDraftBtn.addEventListener('click', () => showToast('Draft saved')); mobileRecordFab.addEventListener('click', openMobileSheet); sheetOverlay.addEventListener('click', closeMobileSheet); closeSheet.addEventListener('click', closeMobileSheet); manualSaveDraft.addEventListener('click', () => showToast('Draft saved')); manualCreatePatient.addEventListener('click', handleManualCreate); // Manual form field listeners manualLayout.querySelectorAll('.manual-input').forEach(input => { input.addEventListener('input', () => { const field = input.dataset.field; if (field) { state.patientData[field] = input.value || ''; updateRightPanel(); updateProgress(); } }); // Pre-fill from AI data const field = input.dataset.field; if (field && state.patientData[field]) { input.value = state.patientData[field]; } }); } // ---- Chat Rendering ---- function appendAssistantMessage(text, extras) { const wrapper = document.createElement('div'); wrapper.className = 'chat-msg flex items-start gap-2.5'; const avatar = document.createElement('div'); avatar.className = 'w-7 h-7 rounded-full bg-vmc-teal/8 border border-vmc-teal/15 flex items-center justify-center flex-shrink-0 mt-0.5'; avatar.innerHTML = ''; const bubble = document.createElement('div'); bubble.className = 'assistant-bubble flex-1 min-w-0'; const p = document.createElement('p'); p.textContent = text; bubble.appendChild(p); if (extras) { extras(bubble); } wrapper.appendChild(avatar); wrapper.appendChild(bubble); chatMessages.appendChild(wrapper); scrollToBottom(); } function appendUserResponse(text) { const wrapper = document.createElement('div'); wrapper.className = 'chat-msg flex justify-end'; const bubble = document.createElement('div'); bubble.className = 'user-bubble'; bubble.textContent = text; wrapper.appendChild(bubble); chatMessages.appendChild(wrapper); scrollToBottom(); } function appendChips(options, stepKey) { const container = document.createElement('div'); container.className = 'chat-msg flex flex-wrap gap-2 mt-2 ml-[38px]'; options.forEach(opt => { const chip = document.createElement('button'); chip.className = 'chip-option'; chip.textContent = opt; chip.addEventListener('click', () => { // Mark selected visually container.querySelectorAll('.chip-option').forEach(c => c.classList.remove('selected')); chip.classList.add('selected'); // Process answer processAnswer(opt); }); container.appendChild(chip); }); chatMessages.appendChild(container); scrollToBottom(); } function appendConditionsGrid() { const container = document.createElement('div'); container.className = 'chat-msg mt-2 ml-[38px]'; container.id = 'conditions-container'; const note = document.createElement('p'); note.className = 'text-[12px] text-vmc-muted mb-3'; note.textContent = 'Condition options may vary based on provider review and state guidance.'; container.appendChild(note); const grid = document.createElement('div'); grid.className = 'grid grid-cols-2 sm:grid-cols-3 gap-2 mb-3'; CONDITIONS.forEach(cond => { const card = document.createElement('button'); card.className = 'condition-card'; card.textContent = cond; card.addEventListener('click', () => { if (state.selectedConditions.has(cond)) { state.selectedConditions.delete(cond); card.classList.remove('selected'); } else { state.selectedConditions.add(cond); card.classList.add('selected'); } updateConditionsContinue(); }); grid.appendChild(card); }); container.appendChild(grid); const continueRow = document.createElement('div'); continueRow.className = 'flex items-center gap-2'; const continueBtn = document.createElement('button'); continueBtn.className = 'primary-btn px-5 py-2 rounded-xl text-[13px] font-medium'; continueBtn.textContent = 'Continue'; continueBtn.id = 'conditions-continue'; continueBtn.disabled = true; continueBtn.style.opacity = '0.5'; continueBtn.addEventListener('click', () => { const selected = Array.from(state.selectedConditions); if (selected.length > 0) { processAnswer(selected.join(', ')); } else { processAnswer('Not specified'); } }); const skipLink = document.createElement('button'); skipLink.className = 'ghost-btn text-[12px] px-2 py-1 rounded'; skipLink.textContent = 'Skip for now'; skipLink.addEventListener('click', () => { processAnswer('Not specified'); }); continueRow.appendChild(continueBtn); continueRow.appendChild(skipLink); container.appendChild(continueRow); chatMessages.appendChild(container); scrollToBottom(); } function updateConditionsContinue() { const btn = document.getElementById('conditions-continue'); if (btn) { const hasSelection = state.selectedConditions.size > 0; btn.disabled = !hasSelection; btn.style.opacity = hasSelection ? '1' : '0.5'; } } function appendTypingIndicator() { const wrapper = document.createElement('div'); wrapper.className = 'chat-msg flex items-start gap-2.5'; wrapper.id = 'typing-indicator'; const avatar = document.createElement('div'); avatar.className = 'w-7 h-7 rounded-full bg-vmc-teal/8 border border-vmc-teal/15 flex items-center justify-center flex-shrink-0 mt-0.5'; avatar.innerHTML = ''; const dots = document.createElement('div'); dots.className = 'typing-indicator'; dots.innerHTML = ''; wrapper.appendChild(avatar); wrapper.appendChild(dots); chatMessages.appendChild(wrapper); scrollToBottom(); } function removeTypingIndicator() { const indicator = document.getElementById('typing-indicator'); if (indicator) indicator.remove(); } function appendReviewCard() { const container = document.createElement('div'); container.className = 'chat-msg mt-2 ml-[38px]'; container.id = 'review-container'; const card = document.createElement('div'); card.className = 'review-card'; // Build sections const sections = [ { title: 'Personal Information', fields: [ { key: 'fullName', label: 'Full Name' }, { key: 'dob', label: 'Date of Birth' }, { key: 'gender', label: 'Gender' } ] }, { title: 'Contact Details', fields: [ { key: 'phone', label: 'Phone' }, { key: 'email', label: 'Email' }, { key: 'address', label: 'Address' }, { key: 'preferredContact', label: 'Preferred Contact' } ] }, { title: 'Medical Information', fields: [ { key: 'qualifyingCondition', label: 'Condition' }, { key: 'medications', label: 'Medications' }, { key: 'allergies', label: 'Allergies' }, { key: 'pcp', label: 'PCP' }, { key: 'insurance', label: 'Insurance' } ] }, { title: 'Visit Details', fields: [ { key: 'visitType', label: 'Visit Type' }, { key: 'emergencyContact', label: 'Emergency Contact' }, { key: 'notes', label: 'Notes' } ] } ]; sections.forEach((sec, sIdx) => { const secDiv = document.createElement('div'); secDiv.className = 'review-section'; const secTitle = document.createElement('div'); secTitle.className = 'review-section-title'; secTitle.textContent = sec.title; secDiv.appendChild(secTitle); sec.fields.forEach(f => { const row = document.createElement('div'); row.className = 'review-field'; const label = document.createElement('span'); label.className = 'review-field-label'; label.textContent = f.label; const value = document.createElement('span'); const val = state.patientData[f.key]; value.className = 'review-field-value' + (val ? '' : ' empty'); value.textContent = val || 'Not provided'; row.appendChild(label); row.appendChild(value); secDiv.appendChild(row); }); card.appendChild(secDiv); }); container.appendChild(card); // Action buttons const actions = document.createElement('div'); actions.className = 'flex items-center gap-3 mt-4'; const createBtn = document.createElement('button'); createBtn.className = 'primary-btn px-6 py-2.5 rounded-xl text-[14px] font-medium'; createBtn.textContent = 'Create Patient'; createBtn.addEventListener('click', handleCreatePatient); const draftBtn = document.createElement('button'); draftBtn.className = 'secondary-btn px-5 py-2.5 rounded-xl text-[14px] font-medium'; draftBtn.textContent = 'Save Draft'; draftBtn.addEventListener('click', () => showToast('Draft saved')); actions.appendChild(createBtn); actions.appendChild(draftBtn); container.appendChild(actions); chatMessages.appendChild(container); scrollToBottom(); } function appendSuccessState() { const container = document.createElement('div'); container.className = 'chat-msg mt-4 flex justify-center'; const inner = document.createElement('div'); inner.className = 'success-state'; const check = document.createElement('div'); check.className = 'success-check'; check.innerHTML = ''; const title = document.createElement('h3'); title.className = 'text-[17px] font-semibold text-vmc-text mb-1'; title.textContent = 'Patient created.'; const sub = document.createElement('p'); sub.className = 'text-[13px] text-vmc-muted'; const name = state.patientData.fullName || 'Patient'; sub.textContent = `${name} added to the system.`; const doneBtn = document.createElement('button'); doneBtn.className = 'secondary-btn px-5 py-2 rounded-xl text-[13px] font-medium mt-5'; doneBtn.textContent = 'Done'; doneBtn.addEventListener('click', () => { showToast('Returning to patients list...'); }); inner.appendChild(check); inner.appendChild(title); inner.appendChild(sub); inner.appendChild(doneBtn); container.appendChild(inner); chatMessages.appendChild(container); scrollToBottom(); } // ---- Flow Control ---- function advanceStep() { state.currentStep++; state.stepActive = true; if (state.currentStep >= STEPS.length) { // Review step showReviewStep(); return; } const step = STEPS[state.currentStep]; if (!step) return; // ADAPTIVE FLOW: Check if this field is already filled const existingValue = state.patientData[step.key]; if (existingValue && existingValue !== '' && existingValue !== 'Skipped' && existingValue !== 'Not provided') { // Skip this step and move to next state.stepActive = false; advanceStep(); return; } state.isTyping = true; disableComposer(); // Show typing indicator appendTypingIndicator(); const delay = state.currentStep === 0 ? 500 : 400; setTimeout(() => { removeTypingIndicator(); state.isTyping = false; // Show assistant message appendAssistantMessage(step.question, step.helper ? ((bubble) => { const helper = document.createElement('div'); helper.className = 'helper-text'; helper.innerHTML = ` ${step.helper}`; bubble.appendChild(helper); }) : null); // Show interactive elements based on type if (step.type === 'chips') { appendChips(step.options, step.key); updateComposerForStep(step); } else if (step.type === 'conditions') { appendConditionsGrid(); chatInput.placeholder = 'Or type a condition...'; chatInput.disabled = false; sendBtn.disabled = false; } else { updateComposerForStep(step); } // Show skip for optional fields if (step.optional) { skipBtn.classList.remove('hidden'); } else { skipBtn.classList.add('hidden'); } // Show quick options renderQuickOptions(step.quickOptions); scrollToBottom(); }, delay); } function updateComposerForStep(step) { if (step.placeholder) { chatInput.placeholder = step.placeholder; } chatInput.disabled = false; sendBtn.disabled = false; chatInput.focus(); } function disableComposer() { chatInput.disabled = true; sendBtn.disabled = true; skipBtn.classList.add('hidden'); quickOptions.innerHTML = ''; } function renderQuickOptions(options) { quickOptions.innerHTML = ''; if (!options || options.length === 0) return; options.forEach(opt => { const btn = document.createElement('button'); btn.className = 'quick-option'; btn.textContent = opt; btn.addEventListener('click', () => { processAnswer(opt); }); quickOptions.appendChild(btn); }); } function handleSend() { if (state.isTyping || state.flowComplete) return; const value = chatInput.value.trim(); if (!value) return; chatInput.value = ''; processAnswer(value); } function handleSkip() { if (state.isTyping || state.flowComplete) return; const step = STEPS[state.currentStep]; if (!step) return; processAnswer('Skipped'); } function processAnswer(value) { if (state.flowComplete) return; const step = STEPS[state.currentStep]; if (!step) return; state.stepActive = false; // Clean up conditions container if present const condContainer = document.getElementById('conditions-container'); if (condContainer) condContainer.remove(); // Store data const formattedValue = formatValue(step.key, value); state.patientData[step.key] = formattedValue; // Show user response appendUserResponse(formattedValue); // Clear quick options quickOptions.innerHTML = ''; // Disable composer briefly disableComposer(); // Update right panel updateRightPanel(); updateProgress(); // Advance to next step after a short natural delay setTimeout(() => { advanceStep(); }, 350); } function showReviewStep() { state.isTyping = true; disableComposer(); appendTypingIndicator(); setTimeout(() => { removeTypingIndicator(); state.isTyping = false; appendAssistantMessage("Please review and confirm.", null); setTimeout(() => { appendReviewCard(); chatInput.placeholder = 'Review above...'; chatInput.disabled = true; sendBtn.disabled = true; skipBtn.classList.add('hidden'); }, 180); }, 500); } function handleCreatePatient() { const reviewContainer = document.getElementById('review-container'); if (reviewContainer) reviewContainer.remove(); state.flowComplete = true; disableComposer(); chatInput.placeholder = 'Patient intake complete'; liveBadge.style.display = 'none'; appendSuccessState(); updateProgress(); } function handleManualCreate() { // Gather all manual form data manualLayout.querySelectorAll('.manual-input').forEach(input => { const field = input.dataset.field; if (field) { state.patientData[field] = input.value || ''; } }); updateRightPanel(); updateProgress(); showToast('Patient created successfully'); } // ---- Right Panel Update ---- function updateRightPanel() { const fieldRows = document.querySelectorAll('#record-panel .field-row, #mobile-record-content .field-row'); fieldRows.forEach(row => { const key = row.dataset.field; if (!key) return; const valueEl = row.querySelector('.field-value'); if (!valueEl) return; const val = state.patientData[key]; const oldVal = valueEl.textContent; if (val && val !== 'Skipped' && val !== 'Not provided' && val !== 'Not specified') { valueEl.textContent = val; valueEl.className = 'field-value font-medium'; // Trigger update animation if value changed if (oldVal !== val && oldVal !== '—') { valueEl.classList.add('updated'); setTimeout(() => valueEl.classList.remove('updated'), 160); } } else if (val === 'Skipped') { valueEl.textContent = 'Skipped'; valueEl.className = 'field-value skipped'; } else if (val === 'Not provided' || val === 'Not specified') { valueEl.textContent = 'Not provided'; valueEl.className = 'field-value skipped'; } else { valueEl.textContent = '—'; valueEl.className = 'field-value waiting'; } }); // Update mobile record content updateMobileRecordContent(); } function updateProgress() { const answered = Object.keys(state.patientData).filter(k => { const v = state.patientData[k]; return v && v !== '' && v !== 'Skipped' && v !== 'Not provided' && v !== 'Not specified'; }).length; const pct = Math.round((answered / TOTAL_FIELDS) * 100); progressBar.style.width = pct + '%'; progressPct.textContent = pct + '%'; // Also update mobile if visible const mobilePct = document.getElementById('mobile-progress-pct'); const mobileBar = document.getElementById('mobile-progress-bar'); if (mobilePct) mobilePct.textContent = pct + '%'; if (mobileBar) mobileBar.style.width = pct + '%'; } // ---- Mobile Record Sheet ---- function updateMobileRecordContent() { if (!mobileRecordContent) return; const sections = [ { title: 'Personal Information', fields: [ { key: 'fullName', label: 'Full Name' }, { key: 'dob', label: 'Date of Birth' }, { key: 'gender', label: 'Gender' } ] }, { title: 'Contact Details', fields: [ { key: 'phone', label: 'Phone' }, { key: 'email', label: 'Email' }, { key: 'address', label: 'Address' }, { key: 'preferredContact', label: 'Preferred Contact' } ] }, { title: 'Medical Information', fields: [ { key: 'qualifyingCondition', label: 'Condition' }, { key: 'medications', label: 'Medications' }, { key: 'allergies', label: 'Allergies' }, { key: 'pcp', label: 'PCP' }, { key: 'insurance', label: 'Insurance' } ] }, { title: 'Visit Details', fields: [ { key: 'visitType', label: 'Visit Type' }, { key: 'emergencyContact', label: 'Emergency Contact' }, { key: 'notes', label: 'Notes' } ] } ]; const answered = Object.keys(state.patientData).filter(k => { const v = state.patientData[k]; return v && v !== '' && v !== 'Skipped' && v !== 'Not provided' && v !== 'Not specified'; }).length; const pct = Math.round((answered / TOTAL_FIELDS) * 100); let html = `