// Verify MC Patient Intake Widget // Production-grade, conversion-optimized form with AI assistance (function() { 'use strict'; // State management const state = { formData: {}, aiProcessing: false, sections: { personal: { fields: 5, filled: 0 }, emergency: { fields: 3, filled: 0 }, insurance: { fields: 4, filled: 0 }, medical: { fields: 4, filled: 0 }, visit: { fields: 2, filled: 0 } }, totalFields: 18, filledFields: 0 }; // Initialize document.addEventListener('DOMContentLoaded', init); function init() { loadSavedData(); attachEventListeners(); updateProgress(); initializePhoneFormatting(); } // Event Listeners function attachEventListeners() { const form = document.getElementById('intakeForm'); const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { // Real-time validation and progress input.addEventListener('blur', () => validateField(input)); input.addEventListener('input', () => { updateFieldStatus(input); debouncedProgressUpdate(); }); // AI suggestion on focus for certain fields if (input.name === 'symptoms' || input.name === 'medications') { input.addEventListener('focus', () => showAIHint(input)); } }); // Enter key in AI input document.getElementById('aiInput').addEventListener('keypress', (e) => { if (e.key === 'Enter') processAIInput(); }); // Form submission prevention form.addEventListener('submit', (e) => { e.preventDefault(); submitForm(); }); } // Phone number formatting function initializePhoneFormatting() { const phoneInputs = document.querySelectorAll('input[type="tel"]'); phoneInputs.forEach(input => { input.addEventListener('input', (e) => { let value = e.target.value.replace(/\D/g, ''); if (value.length >= 6) { value = `(${value.slice(0, 3)}) ${value.slice(3, 6)}-${value.slice(6, 10)}`; } else if (value.length >= 3) { value = `(${value.slice(0, 3)}) ${value.slice(3)}`; } e.target.value = value; }); }); } // Field validation function validateField(input) { const isRequired = input.hasAttribute('required'); const isEmpty = !input.value.trim(); if (isRequired && isEmpty) { input.classList.add('error'); showFieldError(input, 'This field is required'); } else if (input.type === 'email' && input.value && !isValidEmail(input.value)) { input.classList.add('error'); showFieldError(input, 'Please enter a valid email'); } else { input.classList.remove('error'); clearFieldError(input); } } function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } function showFieldError(input, message) { clearFieldError(input); const error = document.createElement('p'); error.className = 'field-error text-verify-error text-xs mt-1.5 flex items-center gap-1'; error.innerHTML = `${message}`; input.parentNode.appendChild(error); lucide.createIcons(); } function clearFieldError(input) { const existing = input.parentNode.querySelector('.field-error'); if (existing) existing.remove(); } // Progress tracking function updateFieldStatus(input) { const section = input.dataset.section; if (!section) return; const isFilled = input.type === 'checkbox' ? input.checked : input.value.trim().length > 0; state.formData[input.name] = isFilled ? input.value : null; // Auto-save to localStorage debouncedSave(); } let progressDebounceTimer; function debouncedProgressUpdate() { clearTimeout(progressDebounceTimer); progressDebounceTimer = setTimeout(updateProgress, 100); } function updateProgress() { const sections = ['personal', 'emergency', 'insurance', 'medical', 'visit']; let totalFilled = 0; let totalCount = 0; sections.forEach(sectionKey => { const sectionEl = document.querySelector(`[data-section="${sectionKey}"]`)?.closest('section'); if (!sectionEl) return; const fields = sectionEl.querySelectorAll('input, select, textarea'); let filled = 0; let count = 0; fields.forEach(field => { // Skip checkboxes for simple counting, focus on text inputs if (field.type === 'checkbox') return; count++; totalCount++; const isFilled = field.value.trim().length > 0; if (isFilled) { filled++; totalFilled++; } }); // Update section progress indicator const progressEl = document.getElementById(`${sectionKey}Progress`); if (progressEl) { progressEl.textContent = `${filled}/${count}`; if (filled === count) { progressEl.className = 'text-verify-teal text-xs ml-auto font-medium'; } else { progressEl.className = 'text-verify-muted text-xs ml-auto'; } } }); // Update main progress bars const percentage = Math.round((totalFilled / totalCount) * 100) || 0; document.getElementById('progressBar').style.width = `${percentage}%`; document.getElementById('bottomProgress').style.width = `${percentage}%`; document.getElementById('completionText').textContent = `${percentage}% complete`; // Visual feedback on completion milestones if (percentage === 100 && !state.completedShown) { state.completedShown = true; showToast('All fields completed! Ready to submit.'); } } // AI Processing window.processAIInput = async function() { const input = document.getElementById('aiInput'); const btn = document.getElementById('aiTryBtn'); const suggestions = document.getElementById('aiSuggestions'); const text = input.value.trim(); if (!text) return; // Show processing state state.aiProcessing = true; btn.disabled = true; btn.innerHTML = `Processing...`; lucide.createIcons(); // Simulate AI processing (in production, this would call an API) await new Promise(r => setTimeout(r, 1500)); // Parse and extract information const extracted = extractInfoFromText(text); // Display suggestions displayAISuggestions(extracted, suggestions); // Reset button btn.disabled = false; btn.innerHTML = `Try it`; lucide.createIcons(); state.aiProcessing = false; }; function extractInfoFromText(text) { const info = {}; const lower = text.toLowerCase(); // Name extraction const nameMatch = text.match(/(?:name is|i am|my name is)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)/i) || text.match(/^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)/); if (nameMatch) info.fullName = nameMatch[1]; // Date extraction (various formats) const dateMatch = text.match(/(?:born|birth|dob|birthday)[\s,]*([A-Za-z]+\s+\d{1,2}[,.]?\s*\d{4}|\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4})/i); if (dateMatch) { info.dateOfBirth = parseDate(dateMatch[1]); } // Phone extraction const phoneMatch = text.match(/(\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4})/); if (phoneMatch) info.phone = phoneMatch[1]; // Email extraction const emailMatch = text.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/); if (emailMatch) info.email = emailMatch[1]; // Allergies if (lower.includes('allergic to') || lower.includes('allergy')) { const allergyMatch = text.match(/allergic to\s+([^,.]+)/i); if (allergyMatch) info.allergies = allergyMatch[1]; } // Medications if (lower.includes('take') || lower.includes('medication')) { const medMatch = text.match(/(?:take|taking|on)\s+([^,.]+(?:mg|daily|twice|once)[^,.]*)/i); if (medMatch) info.medications = medMatch[1]; } // Insurance const insuranceKeywords = { 'aetna': 'aetna', 'anthem': 'anthem', 'blue cross': 'bcbs', 'bluecross': 'bcbs', 'cigna': 'cigna', 'kaiser': 'kaiser', 'medicare': 'medicare', 'medicaid': 'medicaid', 'united': 'united' }; for (const [keyword, value] of Object.entries(insuranceKeywords)) { if (lower.includes(keyword)) { info.insuranceProvider = value; break; } } // Conditions const conditions = []; if (lower.includes('diabetes')) conditions.push('diabetes'); if (lower.includes('hypertension') || lower.includes('high blood pressure')) conditions.push('hypertension'); if (lower.includes('asthma')) conditions.push('asthma'); if (lower.includes('heart')) conditions.push('heart-disease'); if (conditions.length) info.conditions = conditions; return info; } function parseDate(dateStr) { // Try to parse various date formats and return YYYY-MM-DD const tryParse = new Date(dateStr); if (!isNaN(tryParse)) { return tryParse.toISOString().split('T')[0]; } return null; } function displayAISuggestions(extracted, container) { container.innerHTML = ''; container.classList.remove('hidden'); const suggestions = Object.entries(extracted).filter(([key, value]) => value); if (suggestions.length === 0) { container.innerHTML = `
${displayValue}