thors1's picture
🐳 21/03 - 04:06 - yes do it please implement the AI Voice Actions Slide-Out and Provider Onboarding Automation workflow.
02dc275 verified
// Initialize Lucide Icons
lucide.createIcons();
// =========================================
// AI VOICE ACTIONS WORKFLOW ENGINE
// =========================================
const VoiceActions = {
isOpen: false,
currentStep: 1,
totalSteps: 5,
providerData: {
name: '',
email: ''
},
automationTasks: [
"Creating provider profile",
"Registering in provider directory",
"Preparing account access",
"Assigning provider role & permissions",
"Creating default scheduling profile",
"Applying weekly availability (Mon-Fri, 9-5)",
"Enabling appointment reminders",
"Enabling notification preferences",
"Preparing booking visibility",
"Queuing invitation email",
"Syncing to Scheduling OS",
"Finalizing onboarding status"
],
// Panel Management
togglePanel: function() {
if (this.isOpen) {
this.closePanel();
} else {
this.openPanel();
}
},
openPanel: function() {
this.isOpen = true;
const panel = document.getElementById('voice-panel');
const overlay = document.getElementById('voice-overlay');
const overlayBg = document.getElementById('voice-overlay-bg');
overlay.classList.remove('hidden');
panel.classList.remove('translate-x-full');
// Trigger reflow for animation
void overlay.offsetWidth;
overlayBg.classList.remove('opacity-0');
// Add mic pulse effect to button in main bar
const micBtn = document.getElementById('voice-input-btn');
if(micBtn) {
const pulse = document.createElement('div');
pulse.className = 'mic-active-ring';
micBtn.appendChild(pulse);
}
},
closePanel: function() {
this.isOpen = false;
const panel = document.getElementById('voice-panel');
const overlay = document.getElementById('voice-overlay');
const overlayBg = document.getElementById('voice-overlay-bg');
panel.classList.add('translate-x-full');
overlayBg.classList.add('opacity-0');
setTimeout(() => {
overlay.classList.add('hidden');
this.exitWorkflow(); // Reset to menu if closed
}, 300);
// Remove mic pulse
const micBtn = document.getElementById('voice-input-btn');
if(micBtn) {
const pulse = micBtn.querySelector('.mic-active-ring');
if(pulse) pulse.remove();
}
},
// Workflow Navigation
startWorkflow: function() {
this.currentStep = 1;
this.providerData = { name: '', email: '' };
document.getElementById('voice-menu-view').classList.add('hidden');
document.getElementById('voice-workflow-view').classList.remove('hidden');
document.getElementById('voice-workflow-view').classList.add('flex');
this.renderStep();
this.updateUIState();
},
exitWorkflow: function() {
document.getElementById('voice-menu-view').classList.remove('hidden');
document.getElementById('voice-workflow-view').classList.add('hidden');
document.getElementById('voice-workflow-view').classList.remove('flex');
this.currentStep = 1;
},
nextStep: function() {
// Validation
if (this.currentStep === 1 && !this.providerData.name.trim()) {
this.shakeInput('provider-name');
return;
}
if (this.currentStep === 2 && !this.validateEmail(this.providerData.email)) {
this.shakeInput('provider-email');
return;
}
if (this.currentStep < this.totalSteps) {
this.currentStep++;
this.renderStep();
this.updateUIState();
}
},
prevStep: function() {
if (this.currentStep > 1) {
this.currentStep--;
this.renderStep();
this.updateUIState();
}
},
updateUIState: function() {
// Update Progress
const progressPct = (this.currentStep / this.totalSteps) * 100;
document.getElementById('workflow-progress').style.width = `${progressPct}%`;
document.getElementById('workflow-step-badge').textContent = `Step ${this.currentStep} of ${this.totalSteps}`;
// Update Buttons
const backBtn = document.getElementById('workflow-back-btn');
const nextBtn = document.getElementById('workflow-next-btn');
backBtn.disabled = this.currentStep === 1;
nextBtn.textContent = this.currentStep === 4 ? 'Run Automation' :
this.currentStep === 5 ? 'Done' : 'Continue';
if (this.currentStep === 5) {
nextBtn.classList.add('hidden');
backBtn.classList.add('hidden');
} else {
nextBtn.classList.remove('hidden');
backBtn.classList.remove('hidden');
}
},
renderStep: function() {
const container = document.getElementById('workflow-content');
let html = '';
switch(this.currentStep) {
case 1: // Name
html = `
<div class="mb-6">
<h3 class="text-xl font-bold text-gray-900 mb-2">Provider Name</h3>
<p class="text-gray-500">Enter the provider's display name for profile creation and scheduling setup.</p>
</div>
<div>
<label class="workflow-label">Full Name</label>
<input type="text" id="provider-name" class="workflow-input" placeholder="e.g. Dr. Olivia Carter"
value="${this.providerData.name}" oninput="VoiceActions.updateData('name', this.value)">
</div>
`;
break;
case 2: // Email
html = `
<div class="mb-6">
<h3 class="text-xl font-bold text-gray-900 mb-2">Provider Email</h3>
<p class="text-gray-500">Enter the email address for invitations, login access, and notifications.</p>
</div>
<div>
<label class="workflow-label">Email Address</label>
<input type="email" id="provider-email" class="workflow-input" placeholder="provider@clinic.com"
value="${this.providerData.email}" oninput="VoiceActions.updateData('email', this.value)">
<p class="text-xs text-red-500 mt-2 hidden" id="email-error">Please enter a valid email address.</p>
</div>
`;
break;
case 3: // Verify
html = `
<div class="mb-6">
<h3 class="text-xl font-bold text-gray-900 mb-2">Verify Details</h3>
<p class="text-gray-500">Review provider information before automation begins.</p>
</div>
<div class="verify-card mb-6">
<div class="verify-row">
<span class="verify-label">Name</span>
<div class="flex items-center">
<span class="verify-value mr-2">${this.providerData.name}</span>
<button onclick="VoiceActions.goToStep(1)" class="text-[#089e97] text-xs font-medium hover:underline">Change</button>
</div>
</div>
<div class="verify-row">
<span class="verify-label">Email</span>
<div class="flex items-center">
<span class="verify-value mr-2">${this.providerData.email}</span>
<button onclick="VoiceActions.goToStep(2)" class="text-[#089e97] text-xs font-medium hover:underline">Change</button>
</div>
</div>
</div>
<div>
<h4 class="text-sm font-bold text-gray-900 mb-3 flex items-center">
<i data-lucide="check-circle" class="w-4 h-4 mr-1.5 text-green-500"></i>
Recommended Defaults
</h4>
<div class="space-y-3">
<label class="flex items-start">
<input type="checkbox" checked class="mt-1 h-4 w-4 text-[#089e97] rounded border-gray-300">
<span class="ml-3 text-sm text-gray-600">Create provider account & invite</span>
</label>
<label class="flex items-start">
<input type="checkbox" checked class="mt-1 h-4 w-4 text-[#089e97] rounded border-gray-300">
<span class="ml-3 text-sm text-gray-600">Set default availability (Mon-Fri, 9-5)</span>
</label>
<label class="flex items-start">
<input type="checkbox" checked class="mt-1 h-4 w-4 text-[#089e97] rounded border-gray-300">
<span class="ml-3 text-sm text-gray-600">Enable appointment reminders</span>
</label>
<label class="flex items-start">
<input type="checkbox" checked class="mt-1 h-4 w-4 text-[#089e97] rounded border-gray-300">
<span class="ml-3 text-sm text-gray-600">Add to provider directory</span>
</label>
</div>
</div>
`;
break;
case 4: // Automation Simulation
html = `
<div class="mb-6">
<h3 class="text-xl font-bold text-gray-900 mb-2">Setting Up Profile</h3>
<p class="text-gray-500">AI is configuring your provider settings...</p>
</div>
<div class="bg-[#089e97]/5 rounded-xl p-4 mb-6 border border-[#089e97]/10">
<div class="flex justify-between text-sm mb-2">
<span class="font-medium text-gray-700">Progress</span>
<span class="font-bold text-[#089e97]" id="automation-percent">0%</span>
</div>
<div class="h-2 bg-gray-200 rounded-full overflow-hidden">
<div id="automation-bar" class="h-full bg-[#089e97] transition-all duration-300" style="width: 0%"></div>
</div>
</div>
<div class="automation-timeline" id="automation-list">
${this.automationTasks.map((task, i) => `
<div class="timeline-item" id="task-${i}">
<div class="timeline-marker">
<i data-lucide="check" class="w-3 h-3"></i>
</div>
<div class="text-sm text-gray-600">${task}</div>
</div>
`).join('')}
</div>
`;
// Trigger animation after render
setTimeout(() => this.runAutomation(), 100);
break;
case 5: // Success
html = `
<div class="text-center pt-4">
<div class="success-icon-circle">
<i data-lucide="check" class="w-10 h-10 text-white"></i>
</div>
<h3 class="text-2xl font-bold text-gray-900 mb-2">Provider Onboarding Completed</h3>
<p class="text-gray-500 mb-8 max-w-xs mx-auto">The provider account and onboarding defaults have been prepared successfully.</p>
<div class="verify-card text-left mb-6">
<div class="verify-row">
<span class="verify-label">Provider</span>
<span class="verify-value">${this.providerData.name}</span>
</div>
<div class="verify-row">
<span class="verify-label">Account Status</span>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-700">Ready</span>
</div>
<div class="verify-row">
<span class="verify-label">Scheduling</span>
<span class="verify-value">Mon-Fri, 9-5</span>
</div>
<div class="verify-row">
<span class="verify-label">Invitation</span>
<span class="verify-value">Queued</span>
</div>
</div>
<div class="space-y-3">
<button onclick="VoiceActions.closePanel()" class="w-full py-3 bg-[#089e97] text-white rounded-lg font-medium hover:bg-[#078b85] transition-colors">
Done
</button>
<button onclick="alert('Navigating to profile...')" class="w-full py-3 bg-white border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors">
View Profile
</button>
</div>
<p class="text-xs text-gray-400 mt-6">
Recommended defaults were applied based on standard provider onboarding policies.
</p>
</div>
`;
break;
}
container.innerHTML = html;
lucide.createIcons();
// Focus inputs on Steps 1 & 2
if(this.currentStep === 1) setTimeout(() => document.getElementById('provider-name')?.focus(), 50);
if(this.currentStep === 2) setTimeout(() => document.getElementById('provider-email')?.focus(), 50);
},
// Logic Helpers
updateData: function(key, value) {
this.providerData[key] = value;
// Clear errors
if(key === 'email') {
const err = document.getElementById('email-error');
if(err) err.classList.add('hidden');
}
},
goToStep: function(step) {
this.currentStep = step;
this.renderStep();
this.updateUIState();
},
validateEmail: function(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
},
shakeInput: function(id) {
const el = document.getElementById(id);
if(el) {
el.classList.add('border-red-500', 'animate-pulse');
setTimeout(() => el.classList.remove('border-red-500', 'animate-pulse'), 1000);
}
if(id === 'provider-email') {
document.getElementById('email-error').classList.remove('hidden');
}
},
runAutomation: async function() {
const tasks = this.automationTasks;
const bar = document.getElementById('automation-bar');
const text = document.getElementById('automation-percent');
for (let i = 0; i < tasks.length; i++) {
// Update UI
const item = document.getElementById(`task-${i}`);
item.classList.add('active');
item.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Wait random time
await new Promise(r => setTimeout(r, 400 + Math.random() * 400));
// Complete Item
item.classList.remove('active');
item.classList.add('completed');
// Update Progress Bar
const pct = Math.round(((i + 1) / tasks.length) * 100);
bar.style.width = `${pct}%`;
text.textContent = `${pct}%`;
}
// Auto advance to success after short delay
setTimeout(() => {
this.nextStep();
}, 800);
}
};
// Attach to Voice Button (If exists in DOM, otherwise listener added below)
document.addEventListener('DOMContentLoaded', () => {
const voiceBtn = document.getElementById('voice-input-btn');
if (voiceBtn) {
// Remove any existing listeners to avoid duplicates if script re-runs
const newBtn = voiceBtn.cloneNode(true);
voiceBtn.parentNode.replaceChild(newBtn, voiceBtn);
newBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
VoiceActions.togglePanel();
});
}
// Close panel on overlay click
document.getElementById('voice-overlay').addEventListener('click', (e) => {
if(e.target === document.getElementById('voice-overlay') || e.target.id === 'voice-overlay-bg') {
VoiceActions.closePanel();
}
});
// Close on ESC
document.addEventListener('keydown', (e) => {
if(e.key === 'Escape' && VoiceActions.isOpen) {
VoiceActions.closePanel();
}
});
});
// =========================================
// VOICE & CONVERSATIONAL MODE (VCM V1)
// =========================================
const ChatContext = {
conversationHistory: [],
lastAction: null,
userIntent: null
};
// Voice Recognition Setup
const VoiceRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
let recognition = null;
let isRecording = false;
if (VoiceRecognition) {
recognition = new VoiceRecognition();
recognition.continuous = false;
recognition.lang = 'en-US';
recognition.interimResults = true;
recognition.onstart = () => {
isRecording = true;
const btn = document.getElementById('voice-input-btn');
const visualizer = document.getElementById('voice-visualizer');
if (btn) btn.classList.add('active');
if (visualizer) visualizer.classList.add('active');
};
recognition.onresult = (event) => {
const transcript = Array.from(event.results)
.map(result => result[0])
.map(result => result.transcript)
.join('');
const input = document.getElementById('ai-command-input');
if (input && event.results[0].isFinal) {
input.value = transcript;
// Trigger search/command processing
const event = new KeyboardEvent('keydown', { key: 'Enter' });
input.dispatchEvent(event);
stopVoiceRecording();
} else if (input) {
input.value = transcript;
}
};
recognition.onerror = (event) => {
console.error('Voice recognition error', event.error);
stopVoiceRecording();
};
recognition.onend = () => {
stopVoiceRecording();
};
}
function startVoiceRecording() {
if (!recognition) {
showToast('Voice recognition not supported in this browser');
return;
}
// Open Copilot Chat Panel
openVCMCopilot();
try {
recognition.start();
} catch (e) {
// Already started
}
}
function stopVoiceRecording() {
if (recognition && isRecording) {
recognition.stop();
}
const btn = document.getElementById('voice-input-btn');
const visualizer = document.getElementById('voice-visualizer');
if (btn) btn.classList.remove('active');
if (visualizer) visualizer.classList.remove('active');
isRecording = false;
}
// Attach Voice Button Listener - UPDATED FOR AI VOICE ACTIONS
document.addEventListener('DOMContentLoaded', () => {
const voiceBtn = document.getElementById('voice-input-btn');
if (voiceBtn) {
voiceBtn.addEventListener('click', () => {
// Open the AI Voice Actions Panel instead of just recording
toggleAIVoicePanel();
});
}
// Chat Input Enter Key
const chatInput = document.getElementById('vcm-chat-input');
if (chatInput) {
chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
sendVCMMessage();
}
});
}
});
// Copilot Panel Functions
function openVCMCopilot() {
const overlay = document.getElementById('vcm-copilot-overlay');
const panel = document.getElementById('vcm-copilot-panel');
if (!overlay || !panel) return;
overlay.classList.remove('hidden');
panel.classList.remove('translate-x-full');
// Delay opacity for smooth fade
setTimeout(() => overlay.classList.remove('opacity-0'), 10);
// Focus input
setTimeout(() => {
const input = document.getElementById('vcm-chat-input');
if (input) input.focus();
}, 300);
}
function closeVCMCopilot() {
const overlay = document.getElementById('vcm-copilot-overlay');
const panel = document.getElementById('vcm-copilot-panel');
if (!overlay || !panel) return;
panel.classList.add('translate-x-full');
overlay.classList.add('opacity-0');
setTimeout(() => {
overlay.classList.add('hidden');
}, 300);
stopVoiceRecording();
}
function minimizeVCMCopilot() {
closeVCMCopilot();
}
// Chat Message Logic
function sendVCMMessage() {
const input = document.getElementById('vcm-chat-input');
const text = input.value.trim();
if (!text) return;
// Add User Message
addVCMMessage('user', text);
input.value = '';
// Show Typing Indicator
showVCMTyping();
// Simulate AI Processing
setTimeout(() => {
hideVCMTyping();
processVCMIntent(text);
}, 1000 + Math.random() * 1000);
}
function addVCMMessage(sender, text, actions = []) {
const history = document.getElementById('vcm-chat-history');
if (!history) return;
const messageDiv = document.createElement('div');
messageDiv.className = sender === 'user' ? 'vcm-message-user' : 'vcm-message-ai';
let contentHTML = '';
if (sender === 'user') {
contentHTML = `<div class="vcm-bubble-user text-sm">${text}</div>`;
} else {
let avatarHTML = `
<div class="w-8 h-8 bg-gradient-to-br from-violet-600 to-purple-600 rounded-full flex items-center justify-center flex-shrink-0 mr-3 shadow-sm">
<i data-lucide="bot" class="w-4 h-4 text-white"></i>
</div>
`;
let actionsHTML = '';
if (actions.length > 0) {
actionsHTML = `
<div class="vcm-chat-actions">
${actions.map(action => `
<button onclick="executeVCMAction('${action.id}', '${action.label}')"
class="vcm-action-chip ${action.primary ? 'primary' : ''}">
${action.label}
</button>
`).join('')}
</div>
`;
}
contentHTML = `
${avatarHTML}
<div>
<div class="vcm-bubble-ai text-sm">${text}</div>
${actionsHTML}
</div>
`;
}
messageDiv.innerHTML = contentHTML;
history.appendChild(messageDiv);
// Re-init icons
lucide.createIcons();
// Scroll to bottom
history.scrollTop = history.scrollHeight;
}
function showVCMTyping() {
const indicator = document.getElementById('vcm-typing-indicator');
if (indicator) indicator.classList.remove('hidden');
}
function hideVCMTyping() {
const indicator = document.getElementById('vcm-typing-indicator');
if (indicator) indicator.classList.add('hidden');
}
// Contextual AI Response Logic
function processVCMIntent(text) {
const lowerText = text.toLowerCase();
// Pattern Matching for Intent
if (lowerText.includes('add provider') || lowerText.includes('new doctor') || lowerText.includes('hire')) {
ChatContext.userIntent = 'add-provider';
addVCMMessage('ai', "I can help you add a new provider. Should I create the profile using your standard template (Dr. [Name], Cardiology)?", [
{ id: 'create-provider', label: 'Yes, Create Profile', primary: true },
{ id: 'open-operator', label: 'Use Full Workflow' }
]);
}
else if (lowerText.includes('sms') || lowerText.includes('reminder') || lowerText.includes('text')) {
addVCMMessage('ai', "I can connect Twilio for SMS reminders. This typically reduces no-shows by 70%. Shall I connect it now?", [
{ id: 'enable-sms', label: 'Connect Twilio', primary: true },
{ id: 'details', label: 'Show Details' }
]);
}
else if (lowerText.includes('payment') || lowerText.includes('stripe') || lowerText.includes('billing')) {
addVCMMessage('ai', "Your payment processor isn't connected yet. I can set up Stripe for you. Do you want to enable online payments immediately?", [
{ id: 'connect-payments', label: 'Setup Stripe', primary: true },
{ id: 'test-payments', label: 'Test Transaction' }
]);
}
else if (lowerText.includes('schedule') || lowerText.includes('availability') || lowerText.includes('hours')) {
addVCMMessage('ai', "I can set up availability rules. Would you like to apply the 'Standard 9-5' template again?", [
{ id: 'apply-schedule', label: 'Apply Template', primary: true },
{ id: 'custom-schedule', label: 'Customize' }
]);
}
else if (lowerText.includes('status') || lowerText.includes('health') || lowerText.includes('progress')) {
const progress = document.getElementById('setup-progress-percentage').textContent;
addVCMMessage('ai', `Your setup progress is currently at ${progress}. You have 2 critical alerts regarding Payments and SMS. Would you like me to fix them?`, [
{ id: 'auto-fix', label: 'Auto-Fix All', primary: true }
]);
}
else {
// Fallback / General
addVCMMessage('ai', "I can help with providers, scheduling, payments, or notifications. What would you like to do?", [
{ id: 'add-provider', label: 'Add Provider' },
{ id: 'connect-payments', label: 'Setup Payments' },
{ id: 'enable-sms', label: 'Enable SMS' }
]);
}
}
// Action Execution from Chat
function executeVCMAction(actionId, label) {
// Add "Performing action" feedback message
addVCMMessage('user', `I want to ${label.toLowerCase()}`);
showVCMTyping();
// Simulate processing
setTimeout(() => {
hideVCMTyping();
// Execute based on action ID
switch(actionId) {
case 'create-provider':
// Update Dashboard (Live Sync)
updateDashboardState({
progress: 5,
scores: { health: 2 }
});
addVCMMessage('ai', "I've created a placeholder provider profile. I've assigned standard permissions. You can edit the specific details in the Providers tab.", [
{ id: 'navigate-providers', label: 'Go to Providers', primary: true }
]);
break;
case 'enable-sms':
// Update Alerts & Scores
updateDashboardState({
progress: 4,
alerts: ['sms'],
scores: { automation: 10 }
});
addVCMMessage('ai', "Twilio is now connected! SMS reminders are active. I've set the default timing to 24 hours before appointments.", [
{ id: 'test-sms', label: 'Send Test SMS' }
]);
break;
case 'connect-payments':
updateDashboardState({
progress: 8,
alerts: ['payments'],
scores: { revenue: 20 }
});
addVCMMessage('ai', "Stripe is connected and online payments are enabled. I've configured the deposit requirement to None for now.", [
{ id: 'navigate-payments', label: 'Configure Billing', primary: true }
]);
break;
case 'auto-fix':
updateDashboardState({
progress: 12,
alerts: ['sms', 'payments'],
scores: { health: 5, automation: 10, revenue: 15 }
});
addVCMMessage('ai', "Done! I've fixed both the Payments and SMS alerts. Your system health score has improved. Is there anything else?", [
{ id: 'optimize', label: 'Optimize Further' }
]);
break;
case 'apply-schedule':
addVCMMessage('ai', "Schedule template 'Standard 9-5' applied to all active providers. Buffer times set to 10 minutes.");
updateDashboardState({ progress: 3 });
break;
case 'open-operator':
closeVCMCopilot();
setTimeout(() => triggerAIWorkflow('auto-configure'), 300);
break;
default:
addVCMMessage('ai', "Action noted. I've updated the relevant settings in the background.");
}
// Trigger Toast for main UI feedback
showToast(`AI executed: ${label}`);
}, 800);
}
// =========================================
// AI OPERATOR MODE (AOM V1) - WORKFLOW ENGINE
// =========================================
const AIContext = {
lastScheduleTemplate: 'Standard 9-5',
notificationDefaults: ['Email', 'SMS'],
currency: 'USD'
};
const Workflows = {
'add-provider': {
title: 'Add New Provider',
steps: [
{ id: 'create-profile', title: 'Create Provider Profile', description: 'Generate unique ID and basic record', action: 'createProviderProfile' },
{ id: 'assign-permissions', title: 'Assign Default Permissions', description: 'Apply role-based access controls', action: 'assignPermissions' },
{ id: 'notify-admin', title: 'Notify Administrators', description: 'Send onboarding email to admin team', action: 'notifyAdmin' }
]
},
'enable-sms': {
title: 'Enable SMS Reminders',
steps: [
{ id: 'check-twilio', title: 'Verify Twilio Connection', description: 'Ping API gateway for status', action: 'checkTwilio' },
{ id: 'activate-reminders', title: 'Activate Reminder Engine', description: 'Enable automated SMS logic', action: 'activateReminders' },
{ id: 'send-test', title: 'Send Test SMS', description: 'Verify delivery to admin phone', action: 'sendTestSMS' }
],
impact: { progress: 4, alerts: ['sms'], scores: { automation: 5 } }
},
'auto-configure': {
title: 'Auto-Configure Platform',
steps: [
{ id: 'audit-system', title: 'Audit System State', description: 'Analyze current configuration gaps', action: 'auditSystem' },
{ id: 'fix-payments', title: 'Fix Payment Integration', description: 'Connect Stripe gateway with defaults', action: 'fixPayments' },
{ id: 'enable-notifications', title: 'Enable Notifications', description: 'Turn on email and SMS channels', action: 'enableNotifications' },
{ id: 'optimize-scheduling', title: 'Optimize Scheduling', description: 'Apply AI-optimized buffer times', action: 'optimizeScheduling' }
],
impact: { progress: 20, alerts: ['payments', 'sms'], scores: { health: 5, revenue: 15, automation: 10 } }
},
'setup-onboarding': {
title: 'Set Up Provider Onboarding',
steps: [
{ id: 'create-provider', title: 'Create Provider Profile', description: 'Dr. Sarah Johnson (Cardiology)', action: 'createProviderProfile' },
{ id: 'assign-schedule', title: 'Assign Schedule', description: `Apply template: ${AIContext.lastScheduleTemplate}`, action: 'assignSchedule' },
{ id: 'enable-reminders', title: 'Enable Patient Reminders', description: `Enable: ${AIContext.notificationDefaults.join(' & ')}`, action: 'enableReminders' },
{ id: 'generate-link', title: 'Generate Booking Link', description: 'Create unique provider booking URL', action: 'generateLink' },
{ id: 'send-welcome', title: 'Send Welcome Email', description: 'Dispatch credentials and handbook', action: 'sendWelcomeEmail' }
],
impact: { progress: 8, alerts: [], scores: { automation: 5, health: 2 } }
}
};
// Command Parser
function parseCommand(input) {
const lowerInput = input.toLowerCase().trim();
if (lowerInput.includes('onboarding') || lowerInput.includes('set up new provider')) return 'setup-onboarding';
if (lowerInput.includes('add provider')) return 'add-provider';
if (lowerInput.includes('sms') || lowerInput.includes('reminders')) return 'enable-sms';
if (lowerInput.includes('payment') || lowerInput.includes('billing')) return 'enable-payments';
if (lowerInput.includes('auto') || lowerInput.includes('configure') || lowerInput.includes('optimize')) return 'auto-configure';
return null;
}
// Trigger workflow from UI
function triggerAIWorkflow(workflowKey) {
const workflow = Workflows[workflowKey];
if (!workflow) return;
openAIOperator(workflow);
}
// Open AI Operator Panel
function openAIOperator(workflow) {
const overlay = document.getElementById('ai-operator-overlay');
const panel = document.getElementById('ai-operator-panel');
const content = document.getElementById('ai-operator-content');
const footer = document.getElementById('ai-operator-footer');
// Reset UI
overlay.classList.remove('hidden');
panel.classList.remove('translate-x-full');
setTimeout(() => overlay.classList.remove('opacity-0'), 10);
// Render Execution Plan
renderExecutionPlan(workflow, content, footer);
lucide.createIcons();
}
function closeAIOperator() {
const overlay = document.getElementById('ai-operator-overlay');
const panel = document.getElementById('ai-operator-panel');
panel.classList.add('translate-x-full');
overlay.classList.add('opacity-0');
setTimeout(() => {
overlay.classList.add('hidden');
}, 300);
}
// Render Execution Plan
function renderExecutionPlan(workflow, container, footer) {
container.innerHTML = `
<div class="mb-6">
<div class="inline-flex items-center px-3 py-1 bg-blue-50 text-blue-700 rounded-full text-xs font-semibold mb-3">
<i data-lucide="zap" class="w-3 h-3 mr-1.5"></i>
AI Plan Generated
</div>
<h2 class="text-xl font-bold text-gray-900 mb-2">${workflow.title}</h2>
<p class="text-gray-600 text-sm">AI will perform the following ${workflow.steps.length} actions to complete this request.</p>
</div>
<div class="space-y-3 mb-6" id="workflow-steps-list">
${workflow.steps.map((step, index) => `
<div class="workflow-step" id="step-${index}">
<div class="step-header">
<div class="flex items-center flex-1">
<div class="step-status-icon mr-3">${index + 1}</div>
<div>
<div class="step-title">${step.title}</div>
</div>
</div>
</div>
<div class="step-description">${step.description}</div>
<div class="step-progress"><div class="step-progress-bar"></div></div>
</div>
`).join('')}
</div>
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3 flex items-start">
<i data-lucide="alert-circle" class="w-4 h-4 text-amber-600 mt-0.5 mr-2 flex-shrink-0"></i>
<p class="text-xs text-amber-800">This will modify system settings. Review the plan above before proceeding.</p>
</div>
`;
footer.innerHTML = `
<button onclick="closeAIOperator()" class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg font-medium text-sm transition-colors">
Cancel
</button>
<button onclick="runWorkflow('${workflow.title}')" class="px-6 py-2 bg-[#089e97] text-white rounded-lg font-medium text-sm hover:bg-[#078b85] transition-colors shadow-lg shadow-[#089e97]/20 flex items-center">
<i data-lucide="play" class="w-4 h-4 mr-2"></i>
Run Plan
</button>
`;
}
// Execute Workflow
async function runWorkflow(title) {
const workflow = Object.values(Workflows).find(w => w.title === title);
if (!workflow) return;
const footer = document.getElementById('ai-operator-footer');
const status = document.getElementById('ai-operator-status');
// Update footer to show running state
footer.innerHTML = `
<div class="flex items-center text-gray-500 text-sm">
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-[#089e97] mr-2"></div>
Executing...
</div>
`;
status.textContent = 'Executing workflow...';
// Execute steps sequentially
for (let i = 0; i < workflow.steps.length; i++) {
await executeStep(i, workflow.steps[i]);
}
// Workflow Complete
completeWorkflow(workflow);
}
async function executeStep(index, step) {
const stepEl = document.getElementById(`step-${index}`);
const progressBar = stepEl.querySelector('.step-progress-bar');
const statusIcon = stepEl.querySelector('.step-status-icon');
// Mark as Active
stepEl.classList.add('active', 'running');
statusIcon.innerHTML = '<i data-lucide="loader-2" class="w-3 h-3 animate-spin"></i>';
lucide.createIcons();
// Simulate Progress
progressBar.style.width = '30%';
await wait(600);
progressBar.style.width = '70%';
await wait(600);
progressBar.style.width = '100%';
// Mark as Completed
await wait(400);
stepEl.classList.remove('active', 'running');
stepEl.classList.add('completed');
statusIcon.innerHTML = '<i data-lucide="check" class="w-3 h-3"></i>';
lucide.createIcons();
}
function completeWorkflow(workflow) {
const status = document.getElementById('ai-operator-status');
const footer = document.getElementById('ai-operator-footer');
const content = document.getElementById('ai-operator-content');
status.textContent = 'Complete';
status.classList.add('text-green-600');
// Update Content to Success State
content.innerHTML = `
<div class="text-center py-10">
<div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6 animate-bounce">
<i data-lucide="check" class="w-10 h-10 text-green-600"></i>
</div>
<h2 class="text-2xl font-bold text-gray-900 mb-2">Workflow Complete</h2>
<p class="text-gray-600 mb-8">All ${workflow.steps.length} steps executed successfully.</p>
<div class="bg-gray-50 rounded-xl p-4 max-w-xs mx-auto text-left">
<h4 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Updates Applied</h4>
<div class="space-y-2 text-sm text-gray-700">
${workflow.impact ? `
${workflow.impact.progress ? `<div class="flex justify-between"><span>Setup Progress</span><span class="text-green-600 font-medium">+${workflow.impact.progress}%</span></div>` : ''}
${workflow.impact.scores ? Object.entries(workflow.impact.scores).map(([k, v]) => `
<div class="flex justify-between"><span class="capitalize">${k} Score</span><span class="text-green-600 font-medium">+${v}%</span></div>
`).join('') : ''}
` : '<div>System configuration updated</div>'}
</div>
</div>
</div>
`;
footer.innerHTML = `
<button onclick="closeAIOperator()" class="w-full px-6 py-3 bg-[#089e97] text-white rounded-lg font-medium hover:bg-[#078b85] transition-colors">
Done
</button>
`;
lucide.createIcons();
// Update Main Dashboard UI (No Reload)
if (workflow.impact) updateDashboardState(workflow.impact);
showToast(`${workflow.title} completed successfully`);
}
function updateDashboardState(impact) {
// Update Progress Bar
if (impact.progress) {
const currentBar = document.getElementById('setup-progress-bar');
const currentText = document.getElementById('setup-progress-percentage');
if (currentBar && currentText) {
let currentWidth = parseInt(currentBar.style.width);
let newWidth = Math.min(100, currentWidth + impact.progress);
currentBar.style.width = `${newWidth}%`;
currentText.textContent = `${newWidth}%`;
}
}
// Update System Scores
if (impact.scores) {
Object.entries(impact.scores).forEach(([key, value]) => {
const scoreEl = document.getElementById(`${key}-score`);
const barEl = document.getElementById(`${key}-bar`);
if (scoreEl && barEl) {
let currentScore = parseInt(scoreEl.textContent);
let newScore = Math.min(100, currentScore + value);
scoreEl.textContent = `${newScore}%`;
barEl.style.width = `${newScore}%`;
}
});
}
// Update Alerts
if (impact.alerts && impact.alerts.length > 0) {
impact.alerts.forEach(alertKey => {
const alertEl = document.querySelector(`.alert-item[data-alert="${alertKey}"]`);
if (alertEl) {
// Simulate fixing alert by changing style
alertEl.style.opacity = '0.5';
alertEl.querySelector('.status-amber-700').textContent = 'Resolved';
alertEl.querySelector('.status-amber-100').classList.replace('status-amber-100', 'status-green-100');
alertEl.querySelector('.status-amber-700').classList.replace('status-amber-700', 'status-green-700');
}
});
}
}
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Enhanced Command Bar Listener
document.addEventListener('DOMContentLoaded', () => {
const commandInput = document.getElementById('ai-command-input');
if (commandInput) {
commandInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const workflowKey = parseCommand(commandInput.value);
if (workflowKey) {
commandInput.value = '';
triggerAIWorkflow(workflowKey);
} else {
showToast('Command not recognized. Try "Add provider" or "Auto-configure"');
}
}
});
}
});
// App State
const appState = {
currentView: 'dashboard',
settingsSidebarExpanded: true,
aiPanelExpanded: false,
sidebarSectionsState: {
organization: true, // Expanded by default (contains dashboard)
scheduling: false,
payments: false,
communication: false,
automation: false,
security: false
},
progress: {
total: 15,
completed: 11
},
checklist: [
{ id: 'org-details', completed: true, group: 'Organization Setup', description: 'Add organization details' },
{ id: 'upload-logo', completed: false, group: 'Organization Setup', description: 'Upload logo & branding' },
{ id: 'add-locations', completed: true, group: 'Organization Setup', description: 'Add clinic locations' },
{ id: 'configure-providers', completed: true, group: 'Scheduling Setup', description: 'Configure providers' },
{ id: 'set-availability', completed: false, group: 'Scheduling Setup', description: 'Set availability rules' },
{ id: 'appointment-types', completed: true, group: 'Scheduling Setup', description: 'Create appointment types' },
{ id: 'connect-payments', completed: false, group: 'Payments Setup', description: 'Connect payment processor' },
{ id: 'billing-setup', completed: true, group: 'Payments Setup', description: 'Set up billing & invoicing' },
{ id: 'insurance-setup', completed: false, group: 'Payments Setup', description: 'Configure insurance' },
{ id: 'enable-notifications', completed: true, group: 'Communication Setup', description: 'Enable notifications' },
{ id: 'setup-sms', completed: false, group: 'Communication Setup', description: 'Setup SMS & Email' },
{ id: 'create-templates', completed: true, group: 'Communication Setup', description: 'Create communication templates' },
{ id: 'compliance-setup', completed: true, group: 'Compliance Setup', description: 'Configure compliance settings' },
{ id: 'security-audit', completed: true, group: 'Compliance Setup', description: 'Complete security audit' },
{ id: 'ai-optimization', completed: false, group: 'AI Optimization', description: 'Enable AI scheduling' }
],
settings: {
organization: {
name: 'MediCare Clinic',
locations: 3,
providers: 12
},
scheduling: {
appointmentTypes: 8,
weeklyHours: 45
},
payments: {
processorConnected: false,
insuranceNetworks: 2
},
notifications: {
templates: 5,
smsEnabled: true
}
}
};
// Calculate progress percentage
function calculateProgress() {
const completed = appState.checklist.filter(item => item.completed).length;
const total = appState.checklist.length;
return Math.round((completed / total) * 100);
}
// Update progress display
function updateProgressDisplay() {
const percentage = calculateProgress();
const progressBar = document.querySelector('.progress-bar');
const progressText = document.querySelector('.text-2xl.font-bold.text-gray-900');
if (progressBar) {
progressBar.style.background = `linear-gradient(90deg, #089e97 0%, #089e97 ${percentage}%, #e5e7eb ${percentage}%, #e5e7eb 100%)`;
}
if (progressText) {
progressText.textContent = `${percentage}%`;
}
}
// Toggle checklist item
function toggleChecklistItem(itemId) {
const item = appState.checklist.find(item => item.id === itemId);
if (item) {
item.completed = !item.completed;
// Update UI
const checklistItem = document.querySelector(`[data-item="${itemId}"]`);
if (checklistItem) {
checklistItem.classList.toggle('completed');
const checkIcon = checklistItem.querySelector('.check-circle i');
if (checkIcon) {
checkIcon.classList.toggle('hidden');
}
}
updateProgressDisplay();
showToast('Checklist updated');
}
}
// =========================================
// GIS V1: ACTION FEEDBACK SYSTEM
// =========================================
// Apply immediate loading state to any button
function applyActionFeedback(buttonElement) {
if (!buttonElement) return;
// Store original text/content
const originalHTML = buttonElement.innerHTML;
const originalWidth = buttonElement.offsetWidth;
// Apply loading state
buttonElement.classList.add('btn-loading');
buttonElement.style.width = originalWidth + 'px'; // Prevent width collapse
// Remove loading state after short delay (simulating action processing)
setTimeout(() => {
buttonElement.classList.remove('btn-loading');
buttonElement.style.width = '';
}, 600);
}
// Show toast notification
function showToast(message) {
const toast = document.getElementById('toast');
if (toast) {
toast.querySelector('span').textContent = message;
toast.classList.remove('hidden');
// Fade out smoothly
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s ease';
setTimeout(() => {
toast.classList.add('hidden');
toast.style.opacity = '1';
}, 300);
}, 2500);
}
}
// Show modal
function showModal(title, content) {
const modalOverlay = document.getElementById('modal-overlay');
const modalContent = document.getElementById('modal-content');
const modalHTML = `
<div class="p-6">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-semibold text-gray-900">${title}</h3>
<button class="p-1 hover:bg-gray-100 rounded-md" id="close-modal">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
${content}
<div class="flex justify-end space-x-3 mt-8">
<button class="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors" id="cancel-modal">
Cancel
</button>
<button class="px-4 py-2 bg-[#089e97] text-white rounded-lg hover:bg-[#078b85] transition-colors" id="save-modal">
Save Changes
</button>
</div>
</div>
`;
modalContent.innerHTML = modalHTML;
modalOverlay.classList.remove('hidden');
// Re-initialize icons
lucide.createIcons();
// Add event listeners
document.getElementById('close-modal').addEventListener('click', hideModal);
document.getElementById('cancel-modal').addEventListener('click', hideModal);
document.getElementById('save-modal').addEventListener('click', () => {
showToast('Settings saved successfully');
hideModal();
});
}
// Hide modal
function hideModal() {
document.getElementById('modal-overlay').classList.add('hidden');
}
// Enterprise Configuration System - Settings Detail Page V3
const configSystem = {
availability: {
title: 'Availability Rules',
description: 'Configure schedules, time blocks, and recurring availability patterns for optimal patient flow.',
status: 'needs-setup',
statusText: 'Needs Setup',
modules: [
{
id: 'working-hours',
title: 'Working Hours',
description: 'Set standard operating hours for each day of the week',
icon: 'clock',
fields: [
{ type: 'toggle', id: 'monday-enabled', label: 'Monday', default: true },
{ type: 'time-range', id: 'monday-hours', label: 'Hours', start: '09:00', end: '17:00' },
{ type: 'toggle', id: 'tuesday-enabled', label: 'Tuesday', default: true },
{ type: 'time-range', id: 'tuesday-hours', label: 'Hours', start: '09:00', end: '17:00' },
{ type: 'toggle', id: 'wednesday-enabled', label: 'Wednesday', default: true },
{ type: 'time-range', id: 'wednesday-hours', label: 'Hours', start: '09:00', end: '17:00' },
{ type: 'toggle', id: 'thursday-enabled', label: 'Thursday', default: true },
{ type: 'time-range', id: 'thursday-hours', label: 'Hours', start: '09:00', end: '17:00' },
{ type: 'toggle', id: 'friday-enabled', label: 'Friday', default: true },
{ type: 'time-range', id: 'friday-hours', label: 'Hours', start: '09:00', end: '17:00' },
{ type: 'toggle', id: 'saturday-enabled', label: 'Saturday', default: false },
{ type: 'toggle', id: 'sunday-enabled', label: 'Sunday', default: false }
],
warning: 'Short lunch breaks may cause provider fatigue. Consider 60-minute midday breaks.'
},
{
id: 'time-blocks',
title: 'Time Blocks & Buffers',
description: 'Configure appointment duration defaults and buffer times between visits',
icon: 'hourglass',
fields: [
{ type: 'select', id: 'default-duration', label: 'Default Appointment Duration', options: ['15 min', '30 min', '45 min', '60 min'], default: '30 min' },
{ type: 'select', id: 'buffer-time', label: 'Buffer Between Appointments', options: ['0 min', '5 min', '10 min', '15 min', '30 min'], default: '10 min' },
{ type: 'select', id: 'lunch-buffer', label: 'Lunch Break Duration', options: ['30 min', '45 min', '60 min', '90 min'], default: '60 min' },
{ type: 'toggle', id: 'enforce-buffers', label: 'Strictly enforce buffer times', default: true }
],
suggestion: 'Adding 15-minute buffers reduces scheduling conflicts by 45%.'
},
{
id: 'blackout-dates',
title: 'Blackout Dates',
description: 'Block specific dates for holidays, training, or facility closures',
icon: 'calendar-x',
type: 'blackout-list',
fields: [
{ type: 'date-list', id: 'blackout-dates', label: 'Blocked Dates', items: ['2024-12-25', '2024-12-26', '2025-01-01'] }
],
action: { label: 'Add Blackout Date', icon: 'plus' }
},
{
id: 'recurring-rules',
title: 'Recurring Availability Rules',
description: 'Set up templates for provider schedules that repeat weekly',
icon: 'repeat',
fields: [
{ type: 'toggle', id: 'auto-optimize', label: 'AI Auto-Optimization', description: 'Let AI adjust availability based on demand patterns', default: false },
{ type: 'toggle', id: 'dynamic-scheduling', label: 'Dynamic Scheduling', description: 'Automatically extend hours during high-demand periods', default: false },
{ type: 'number', id: 'max-daily-appointments', label: 'Max Daily Appointments per Provider', default: 16, min: 1, max: 40 }
],
aiInsight: 'Enable AI Auto-Optimization to increase booking capacity by up to 23%.'
}
],
aiRecommendations: [
{ id: 'buffer-optimization', title: 'Increase Buffer Times', description: 'Current 5-min buffers causing 12% overlap rate', impact: '+45% fewer conflicts', action: 'Apply' },
{ id: 'extend-friday', title: 'Extend Friday Hours', description: 'High demand detected on Friday afternoons', impact: '+8 appointments/week', action: 'Apply' },
{ id: 'add-saturday', title: 'Enable Saturday Mornings', description: 'Patient survey shows 34% want weekend slots', impact: '+15% patient satisfaction', action: 'Review' }
],
tests: [
{ id: 'booking-simulation', name: 'Test Booking Flow', description: 'Simulate patient booking experience' },
{ id: 'conflict-check', name: 'Check Conflicts', description: 'Identify scheduling overlaps' },
{ id: 'capacity-analysis', name: 'Capacity Analysis', description: 'Analyze max daily booking potential' }
]
},
providers: {
title: 'Provider Settings',
description: 'Configure provider profiles, permissions, scheduling preferences, and workflow automation.',
status: 'optimized',
statusText: 'Optimized',
modules: [
{
id: 'provider-profiles',
title: 'Provider Profiles',
description: 'Manage provider information and default settings',
icon: 'user-check',
fields: [
{ type: 'select', id: 'default-role', label: 'Default Provider Role', options: ['Primary Care Physician', 'Specialist', 'Nurse Practitioner', 'Physician Assistant'], default: 'Primary Care Physician' },
{ type: 'toggle', id: 'auto-accept', label: 'Auto-accept routine appointments', default: true },
{ type: 'toggle', id: 'require-approval', label: 'Require approval for new patient appointments', default: false }
]
},
{
id: 'permissions',
title: 'Permissions & Access',
description: 'Control what providers can see and modify',
icon: 'shield',
fields: [
{ type: 'select', id: 'calendar-access', label: 'Calendar Access Level', options: ['Own Only', 'Department', 'All Providers'], default: 'Department' },
{ type: 'select', id: 'patient-access', label: 'Patient Record Access', options: ['Assigned Only', 'All Patients', 'Read-Only'], default: 'Assigned Only' },
{ type: 'toggle', id: 'can-modify-schedule', label: 'Can modify own schedule', default: true },
{ type: 'toggle', id: 'can-view-revenue', label: 'Can view revenue reports', default: false }
]
},
{
id: 'notifications',
title: 'Notification Preferences',
description: 'Configure how providers receive alerts and updates',
icon: 'bell',
fields: [
{ type: 'toggle', id: 'new-booking-sms', label: 'SMS for new bookings', default: true },
{ type: 'toggle', id: 'cancellation-email', label: 'Email for cancellations', default: true },
{ type: 'toggle', id: 'reminder-push', label: 'Push notifications for reminders', default: false },
{ type: 'select', id: 'digest-frequency', label: 'Daily Digest Frequency', options: ['Never', 'Morning', 'Evening', 'Both'], default: 'Morning' }
]
}
],
aiRecommendations: [
{ id: 'auto-scheduling', title: 'Enable Auto-Scheduling', description: 'Let AI optimize provider calendars automatically', impact: '+35% efficiency gain', action: 'Enable' },
{ id: 'conflict-prevention', title: 'Conflict Prevention', description: 'Block double-booking and enforce buffer times', impact: 'Zero double bookings', action: 'Apply' }
],
tests: [
{ id: 'permission-check', name: 'Test Permissions', description: 'Verify access controls work correctly' },
{ id: 'notification-test', name: 'Test Notifications', description: 'Send test alert to providers' }
]
},
'payment-processors': {
title: 'Payment Processors',
description: 'Connect and manage payment gateways for seamless revenue collection.',
status: 'at-risk',
statusText: 'At Risk',
modules: [
{
id: 'processor-connection',
title: 'Payment Gateway',
description: 'Connect your preferred payment processor',
icon: 'credit-card',
fields: [
{ type: 'select', id: 'processor', label: 'Payment Processor', options: ['Stripe', 'PayPal', 'Square', 'Authorize.net'], default: '' },
{ type: 'text', id: 'api-key', label: 'API Key', placeholder: 'sk_live_...', secure: true },
{ type: 'text', id: 'webhook-url', label: 'Webhook URL', value: 'https://api.verifymc.com/webhooks/payments', readonly: true },
{ type: 'toggle', id: 'test-mode', label: 'Test Mode (Sandbox)', default: true }
],
warning: 'No payment processor connected. Revenue collection is currently disabled.'
},
{
id: 'payment-settings',
title: 'Payment Settings',
description: 'Configure payment behavior and options',
icon: 'settings',
fields: [
{ type: 'toggle', id: 'online-payments', label: 'Accept online payments', default: false },
{ type: 'toggle', id: 'auto-charge', label: 'Auto-charge on appointment completion', default: false },
{ type: 'select', id: 'deposit-requirement', label: 'Require Deposit', options: ['None', '25%', '50%', 'Fixed Amount'], default: 'None' },
{ type: 'number', id: 'fixed-deposit-amount', label: 'Fixed Deposit Amount ($)', default: 50, showIf: { field: 'deposit-requirement', value: 'Fixed Amount' } }
]
},
{
id: 'payouts',
title: 'Payout Configuration',
description: 'Manage when and how you receive funds',
icon: 'dollar-sign',
fields: [
{ type: 'select', id: 'payout-schedule', label: 'Payout Schedule', options: ['Daily', 'Weekly', 'Monthly', 'Manual'], default: 'Weekly' },
{ type: 'select', id: 'payout-day', label: 'Payout Day (if weekly)', options: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], default: 'Monday' },
{ type: 'toggle', id: 'instant-payout', label: 'Enable instant payouts (2% fee)', default: false }
]
}
],
aiRecommendations: [
{ id: 'connect-stripe', title: 'Connect Stripe Immediately', description: 'Payment processor missing - revenue opportunity loss: $12,400/month', impact: 'Restore revenue flow', action: 'Connect' },
{ id: 'enable-deposits', title: 'Enable 25% Deposits', description: 'Reduce no-shows and secure commitment', impact: '+40% show rate', action: 'Apply' }
],
tests: [
{ id: 'payment-test', name: 'Test Transaction', description: 'Process $1 test charge' },
{ id: 'webhook-test', name: 'Test Webhooks', description: 'Verify payment notifications' },
{ id: 'refund-test', name: 'Test Refund Flow', description: 'Process test refund' }
]
},
notifications: {
title: 'Notifications & Communication',
description: 'Configure automated messages, reminders, and patient communication workflows.',
status: 'optimized',
statusText: 'Optimized',
modules: [
{
id: 'reminder-settings',
title: 'Appointment Reminders',
description: 'Automated reminders to reduce no-shows',
icon: 'bell',
fields: [
{ type: 'toggle', id: 'sms-reminders', label: 'SMS Reminders', default: true },
{ type: 'toggle', id: 'email-reminders', label: 'Email Reminders', default: true },
{ type: 'select', id: 'reminder-timing', label: 'Reminder Timing', options: ['24 hours', '48 hours', '1 hour', '15 minutes'], default: '24 hours' },
{ type: 'number', id: 'reminder-count', label: 'Number of reminders', default: 2, min: 0, max: 5 }
],
suggestion: 'Multiple reminders (24h + 1h) reduce no-shows by 70%.'
},
{
id: 'templates',
title: 'Message Templates',
description: 'Customize communication content',
icon: 'file-text',
fields: [
{ type: 'textarea', id: 'confirmation-template', label: 'Confirmation Message', default: 'Your appointment with {{provider}} is confirmed for {{date}} at {{time}}.' },
{ type: 'textarea', id: 'reminder-template', label: 'Reminder Message', default: 'Reminder: You have an appointment tomorrow at {{time}} with {{provider}}.' },
{ type: 'textarea', id: 'cancellation-template', label: 'Cancellation Message', default: 'Your appointment has been cancelled. Reply to reschedule or call {{phone}}.' }
]
},
{
id: 'channels',
title: 'Communication Channels',
description: 'Manage SMS, email, and push notification settings',
icon: 'message-square',
fields: [
{ type: 'toggle', id: 'sms-enabled', label: 'SMS Enabled', default: true },
{ type: 'text', id: 'sms-from', label: 'SMS From Number', value: '+1 (555) 123-4567' },
{ type: 'toggle', id: 'email-enabled', label: 'Email Enabled', default: true },
{ type: 'text', id: 'email-from', label: 'Email From Name', value: 'MediCare Clinic' }
]
}
],
aiRecommendations: [
{ id: 'add-followup', title: 'Add Follow-up Messages', description: 'Send post-appointment care instructions automatically', impact: '+25% patient satisfaction', action: 'Enable' },
{ id: 'personalize', title: 'Personalize Message Timing', description: 'AI adjusts reminder timing based on patient behavior', impact: '+15% open rate', action: 'Apply' }
],
tests: [
{ id: 'send-test-sms', name: 'Send Test SMS', description: 'Verify SMS delivery' },
{ id: 'send-test-email', name: 'Send Test Email', description: 'Verify email delivery' },
{ id: 'spam-check', name: 'Spam Score Check', description: 'Check message deliverability' }
]
},
insurance: {
title: 'Insurance Configuration',
description: 'Manage payer networks, eligibility verification, and claims processing.',
status: 'needs-setup',
statusText: 'Needs Setup',
modules: [
{
id: 'payer-networks',
title: 'Payer Networks',
description: 'Configure accepted insurance providers',
icon: 'shield',
fields: [
{ type: 'multiselect', id: 'insurance-providers', label: 'Accepted Insurance', options: ['Aetna', 'Blue Cross', 'Cigna', 'UnitedHealthcare', 'Medicare', 'Medicaid'], default: [] },
{ type: 'toggle', id: 'verify-eligibility', label: 'Auto-verify eligibility', default: false },
{ type: 'toggle', id: 'out-of-network', label: 'Accept out-of-network', default: true }
]
},
{
id: 'claims',
title: 'Claims Processing',
description: 'Configure automated claims submission',
icon: 'file-text',
fields: [
{ type: 'toggle', id: 'auto-submit', label: 'Auto-submit claims', default: false },
{ type: 'select', id: 'submission-timing', label: 'Submission Timing', options: ['Immediately', 'Daily Batch', 'Weekly'], default: 'Daily Batch' },
{ type: 'toggle', id: 'denial-management', label: 'AI Denial Management', description: 'Automatically handle claim denials', default: false }
]
}
],
aiRecommendations: [
{ id: 'enable-eligibility', title: 'Enable Real-time Eligibility', description: 'Check insurance before every appointment', impact: '-90% claim denials', action: 'Enable' },
{ id: 'add-payers', title: 'Add Top 5 Local Payers', description: 'Expand network to capture 40% more patients', impact: '+40% patient base', action: 'Review' }
],
tests: [
{ id: 'eligibility-test', name: 'Test Eligibility Check', description: 'Verify insurance lookup' },
{ id: 'claim-test', name: 'Submit Test Claim', description: 'Process test insurance claim' }
]
}
};
// Page header templates for different sections
const pageHeaders = {
providers: {
title: 'Providers Settings',
description: 'Configure provider-related configuration, profile behavior, permissions, scheduling defaults, and workflow preferences.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Providers', action: () => navigateToSection('providers'), clickable: false }
],
actions: ['help', 'ai', 'save']
},
'appointment-types': {
title: 'Appointment Types',
description: 'Define visit categories, durations, color coding, and workflow behavior used across scheduling, booking links, and patient intake flows.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Appointment Types', action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
},
organization: {
title: 'Organization Settings',
description: 'Manage your clinic details, branding, locations, and organizational structure.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Organization', action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
},
locations: {
title: 'Location Management',
description: 'Configure clinic locations, operating hours, room assignments, and facility settings.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Locations', action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
},
users: {
title: 'Users & Permissions',
description: 'Manage staff accounts, role-based access controls, and security permissions.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Users & Permissions', action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
},
availability: {
title: 'Availability Rules',
description: 'Set provider schedules, time blocks, blackouts, and recurring availability patterns for optimal patient flow.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Availability', action: null, clickable: false }
],
actions: ['help', 'ai', 'save'],
status: 'not-configured',
statusText: 'Not Configured',
secondaryActions: ['test', 'preview']
},
'payment-processors': {
title: 'Payment Processors',
description: 'Connect and manage Stripe, PayPal, or other payment gateways for revenue collection.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Payment Processors', action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
},
billing: {
title: 'Billing & Invoicing',
description: 'Configure invoice templates, payment terms, auto-billing, and financial workflows.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Billing & Invoicing', action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
},
insurance: {
title: 'Insurance Configuration',
description: 'Manage payer networks, eligibility verification, and claims processing settings.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Insurance', action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
},
notifications: {
title: 'Notifications & Communication',
description: 'Configure automated patient communications, reminders, and messaging workflows.',
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: 'Notifications', action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
}
};
// Unsaved changes tracker
let hasUnsavedChanges = false;
let formChangeListeners = [];
function markUnsaved() {
hasUnsavedChanges = true;
const badge = document.getElementById('unsaved-badge');
const saveBtn = document.getElementById('header-save-btn');
if (badge) badge.classList.remove('hidden');
if (saveBtn) {
saveBtn.classList.add('ring-2', 'ring-[#089e97]', 'ring-offset-2');
saveBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4 mr-2"></i>Save Changes`;
lucide.createIcons();
}
}
// Sidebar Collapsible Sections V2
function toggleSidebarSection(sectionId, forceExpand = false) {
const section = document.querySelector(`.sidebar-section[data-section="${sectionId}"]`);
const content = section?.querySelector('.sidebar-section-content');
const icon = section?.querySelector('.sidebar-section-icon');
if (!section || !content || !icon) return;
const isExpanded = content.classList.contains('expanded') || appState.sidebarSectionsState[sectionId];
if (forceExpand || !isExpanded) {
// Expand this section, collapse others
document.querySelectorAll('.sidebar-section-content').forEach(el => {
el.classList.remove('expanded');
el.style.display = 'none';
});
document.querySelectorAll('.sidebar-section-icon').forEach(el => {
el.classList.remove('expanded');
});
// Expand current section
content.classList.add('expanded');
content.style.display = 'block';
icon.classList.add('expanded');
appState.sidebarSectionsState[sectionId] = true;
// Update other sections to false
Object.keys(appState.sidebarSectionsState).forEach(key => {
if (key !== sectionId) {
appState.sidebarSectionsState[key] = false;
}
});
} else {
// Collapse this section
content.classList.remove('expanded');
content.style.display = 'none';
icon.classList.remove('expanded');
appState.sidebarSectionsState[sectionId] = false;
}
// Save state to localStorage
try {
localStorage.setItem('sidebarSectionsState', JSON.stringify(appState.sidebarSectionsState));
} catch (e) {
console.log('Could not save sidebar state to localStorage');
}
}
function initializeSidebarSections() {
// Load saved state from localStorage
try {
const savedState = localStorage.getItem('sidebarSectionsState');
if (savedState) {
const parsedState = JSON.parse(savedState);
Object.keys(parsedState).forEach(key => {
if (appState.sidebarSectionsState.hasOwnProperty(key)) {
appState.sidebarSectionsState[key] = parsedState[key];
}
});
}
} catch (e) {
console.log('Could not load sidebar state from localStorage');
}
// Apply the state to the UI
Object.keys(appState.sidebarSectionsState).forEach(sectionId => {
if (appState.sidebarSectionsState[sectionId]) {
const content = document.querySelector(`.sidebar-section-content[data-section="${sectionId}"]`);
const icon = document.querySelector(`.sidebar-section-icon[data-section="${sectionId}"]`);
if (content) {
content.classList.add('expanded');
content.style.display = 'block';
}
if (icon) {
icon.classList.add('expanded');
}
}
});
// Add click handlers to section headers
document.querySelectorAll('.sidebar-section-header').forEach(header => {
header.addEventListener('click', (e) => {
const sectionId = header.dataset.section;
if (sectionId) {
toggleSidebarSection(sectionId);
}
});
});
}
function expandSectionForView(sectionId) {
// Map view to section
const viewToSectionMap = {
'dashboard': 'organization',
'organization': 'organization',
'locations': 'organization',
'providers': 'organization',
'users': 'organization',
'availability': 'scheduling',
'appointment-types': 'scheduling',
'buffer-time': 'scheduling',
'booking-rules': 'scheduling',
'payment-processors': 'payments',
'billing': 'payments',
'insurance': 'payments',
'notifications': 'communication',
'sms': 'communication',
'templates': 'communication',
'ai-scheduling': 'automation',
'workflows': 'automation',
'reminders': 'automation',
'security': 'security',
'compliance': 'security',
'audit': 'security'
};
const targetSection = viewToSectionMap[sectionId];
if (targetSection) {
toggleSidebarSection(targetSection, true);
}
}
// Module Dependency System (MDS V1)
const moduleData = {
organization: {
title: "Organization System",
description: "Manage clinic details, locations, providers, and organizational structure",
status: "optimized",
completion: 85,
missingDependencies: [
{
id: "logo-branding",
title: "Logo & Branding",
subtitle: "Upload clinic logo and configure brand colors",
icon: "image",
action: () => navigateToSection('organization'),
actionText: "Configure"
}
],
recommendedOptimizations: [
{
id: "advanced-reporting",
title: "Enable Advanced Reporting",
subtitle: "Get detailed insights on provider performance",
icon: "bar-chart-3",
action: () => showToast('Advanced reporting enabled'),
actionText: "Enable"
},
{
id: "provider-roles",
title: "Define Provider Roles",
subtitle: "Create custom roles for different provider types",
icon: "users",
action: () => navigateToSection('users'),
actionText: "Setup"
}
],
configuredItems: [
{
id: "clinic-details",
title: "Clinic Details",
subtitle: "Name, contact info, and tax ID configured",
icon: "building"
},
{
id: "locations",
title: "Clinic Locations",
subtitle: "3 locations with addresses and hours",
icon: "map-pin"
},
{
id: "providers",
title: "Provider Directory",
subtitle: "12 providers with profiles",
icon: "user-check"
}
]
},
scheduling: {
title: "Scheduling System",
description: "Configure appointment types, availability rules, and booking workflows",
status: "in-progress",
completion: 65,
missingDependencies: [
{
id: "availability-rules",
title: "Availability Rules",
subtitle: "No availability rules configured for providers",
icon: "clock",
action: () => navigateToSection('availability'),
actionText: "Fix"
},
{
id: "buffer-time",
title: "Buffer Time",
subtitle: "No buffer time set between appointments",
icon: "shield",
action: () => navigateToSection('buffer-time'),
actionText: "Configure"
}
],
recommendedOptimizations: [
{
id: "sms-reminders",
title: "Enable SMS Reminders",
subtitle: "Reduce no-shows by 70% with automated SMS",
icon: "message-square",
action: () => navigateToSection('sms'),
actionText: "Enable"
},
{
id: "booking-rules",
title: "Optimize Booking Rules",
subtitle: "Set smart rules for patient booking limits",
icon: "list-checks",
action: () => navigateToSection('booking-rules'),
actionText: "Optimize"
}
],
configuredItems: [
{
id: "appointment-types",
title: "Appointment Types",
subtitle: "5 appointment types with durations",
icon: "calendar"
},
{
id: "calendar-sync",
title: "Calendar Sync",
subtitle: "Google Calendar integration active",
icon: "refresh-cw"
}
]
},
payments: {
title: "Payments System",
description: "Configure payment processors, billing, and insurance integration",
status: "not-configured",
completion: 25,
missingDependencies: [
{
id: "payment-processor",
title: "Payment Processor",
subtitle: "No payment processor connected",
icon: "credit-card",
action: () => navigateToSection('payment-processors'),
actionText: "Setup"
},
{
id: "insurance-setup",
title: "Insurance Setup",
subtitle: "Insurance networks not configured",
icon: "shield",
action: () => navigateToSection('insurance'),
actionText: "Configure"
}
],
recommendedOptimizations: [
{
id: "auto-invoicing",
title: "Enable Auto-Invoicing",
subtitle: "Automatically generate and send invoices",
icon: "file-text",
action: () => showToast('Auto-invoicing enabled'),
actionText: "Enable"
},
{
id: "payment-plans",
title: "Set Up Payment Plans",
subtitle: "Allow patients to pay in installments",
icon: "dollar-sign",
action: () => showToast('Payment plans configured'),
actionText: "Setup"
}
],
configuredItems: [
{
id: "billing-setup",
title: "Billing Setup",
subtitle: "Basic billing configuration complete",
icon: "receipt"
}
]
},
notifications: {
title: "Notifications System",
description: "Configure automated communications, reminders, and messaging workflows",
status: "optimized",
completion: 92,
missingDependencies: [],
recommendedOptimizations: [
{
id: "personalized-timing",
title: "Personalized Timing",
subtitle: "AI adjusts reminder timing per patient",
icon: "zap",
action: () => showToast('Personalized timing enabled'),
actionText: "Apply"
}
],
configuredItems: [
{
id: "sms-templates",
title: "SMS Templates",
subtitle: "5 SMS templates for different scenarios",
icon: "message-square"
},
{
id: "email-templates",
title: "Email Templates",
subtitle: "8 email templates configured",
icon: "mail"
},
{
id: "appointment-reminders",
title: "Appointment Reminders",
subtitle: "24h & 1h reminders active",
icon: "bell"
},
{
id: "automated-followups",
title: "Automated Follow-ups",
subtitle: "Post-appointment follow-ups enabled",
icon: "repeat"
}
]
},
security: {
title: "Security & Compliance",
description: "Configure HIPAA compliance, security settings, and audit logging",
status: "in-progress",
completion: 78,
missingDependencies: [
{
id: "audit-logs",
title: "Audit Logs",
subtitle: "Full audit logging not enabled",
icon: "history",
action: () => navigateToSection('audit'),
actionText: "Enable"
}
],
recommendedOptimizations: [
{
id: "two-factor-auth",
title: "Enable Two-Factor Auth",
subtitle: "Add extra security layer for all users",
icon: "shield",
action: () => showToast('2FA enabled'),
actionText: "Enable"
},
{
id: "data-retention",
title: "Data Retention Policy",
subtitle: "Set automated data cleanup rules",
icon: "trash-2",
action: () => navigateToSection('compliance'),
actionText: "Setup"
}
],
configuredItems: [
{
id: "hipaa-compliance",
title: "HIPAA Compliance",
subtitle: "Basic HIPAA settings configured",
icon: "clipboard-check"
},
{
id: "user-permissions",
title: "User Permissions",
subtitle: "Role-based access control active",
icon: "lock"
},
{
id: "data-encryption",
title: "Data Encryption",
subtitle: "End-to-end encryption enabled",
icon: "key"
}
]
}
};
function openModulePanel(moduleId) {
const module = moduleData[moduleId];
if (!module) return;
// Update panel content
document.getElementById('module-panel-title').textContent = module.title;
document.getElementById('module-description').textContent = module.description;
document.getElementById('module-completion').textContent = module.completion + '%';
document.getElementById('module-progress-bar').style.width = module.completion + '%';
// Update status badge
const statusBadge = document.getElementById('module-status-badge');
statusBadge.className = 'status-badge';
statusBadge.textContent = module.status === 'optimized' ? 'Optimized' :
module.status === 'in-progress' ? 'In Progress' : 'Not Configured';
statusBadge.classList.add(module.status === 'optimized' ? 'status-optimized' :
module.status === 'in-progress' ? 'status-in-progress' : 'status-not-configured');
// Render missing dependencies
const missingContainer = document.getElementById('missing-dependencies');
if (module.missingDependencies.length > 0) {
missingContainer.innerHTML = module.missingDependencies.map(item => `
<div class="dependency-item missing">
<div class="dependency-item-header">
<div class="dependency-item-icon missing">
<i data-lucide="${item.icon}" class="w-4 h-4"></i>
</div>
<div class="dependency-item-content">
<div class="dependency-item-title">${item.title}</div>
<div class="dependency-item-subtitle">${item.subtitle}</div>
</div>
</div>
<div class="dependency-item-action">
<button onclick="${item.action.toString().replace('() => ', '')}" class="px-3 py-1.5 text-xs font-medium text-white bg-red-500 hover:bg-red-600 rounded-lg transition-colors">
${item.actionText}
</button>
</div>
</div>
`).join('');
} else {
missingContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">
<i data-lucide="check-circle" class="w-6 h-6"></i>
</div>
<p>No missing dependencies! All critical items are configured.</p>
</div>
`;
}
// Render recommended optimizations
const recommendationsContainer = document.getElementById('recommended-optimizations');
if (module.recommendedOptimizations.length > 0) {
recommendationsContainer.innerHTML = module.recommendedOptimizations.map(item => `
<div class="dependency-item recommended">
<div class="dependency-item-header">
<div class="dependency-item-icon recommended">
<i data-lucide="${item.icon}" class="w-4 h-4"></i>
</div>
<div class="dependency-item-content">
<div class="dependency-item-title">${item.title}</div>
<div class="dependency-item-subtitle">${item.subtitle}</div>
</div>
</div>
<div class="dependency-item-action">
<button onclick="${item.action.toString().replace('() => ', '')}" class="px-3 py-1.5 text-xs font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-lg transition-colors">
${item.actionText}
</button>
<button onclick="autoApplyOptimization('${item.id}')" class="px-3 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 rounded-lg transition-colors flex items-center">
<i data-lucide="zap" class="w-3 h-3 mr-1"></i>
Auto
</button>
</div>
</div>
`).join('');
} else {
recommendationsContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">
<i data-lucide="zap" class="w-6 h-6"></i>
</div>
<p>No optimizations recommended at this time.</p>
</div>
`;
}
// Render configured items
const configuredContainer = document.getElementById('configured-items');
if (module.configuredItems.length > 0) {
configuredContainer.innerHTML = module.configuredItems.map(item => `
<div class="dependency-item configured">
<div class="dependency-item-header">
<div class="dependency-item-icon configured">
<i data-lucide="${item.icon}" class="w-4 h-4"></i>
</div>
<div class="dependency-item-content">
<div class="dependency-item-title">${item.title}</div>
<div class="dependency-item-subtitle">${item.subtitle}</div>
</div>
</div>
</div>
`).join('');
} else {
configuredContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">
<i data-lucide="settings" class="w-6 h-6"></i>
</div>
<p>No items configured yet.</p>
</div>
`;
}
// Show panel
document.getElementById('module-panel-overlay').classList.add('active');
document.getElementById('module-panel').classList.add('active');
// Reinitialize icons
setTimeout(() => lucide.createIcons(), 100);
}
function closeModulePanel() {
document.getElementById('module-panel-overlay').classList.remove('active');
document.getElementById('module-panel').classList.remove('active');
}
function fixAllModuleIssues() {
showToast('Fixing all module issues...');
// In a real app, this would trigger automatic fixes
setTimeout(() => {
showToast('All issues fixed successfully!');
closeModulePanel();
}, 1500);
}
function autoConfigureModule() {
showToast('AI is auto-configuring the module...');
// In a real app, this would use AI to configure optimal settings
setTimeout(() => {
showToast('Module auto-configured with AI recommendations!');
}, 2000);
}
function testModuleFlow() {
showToast('Testing module flow...');
// In a real app, this would run integration tests
setTimeout(() => {
showToast('Module flow test completed successfully!');
}, 1500);
}
function autoApplyOptimization(optimizationId) {
showToast(`AI is applying optimization: ${optimizationId}`);
// In a real app, this would apply the optimization automatically
setTimeout(() => {
showToast('Optimization applied successfully!');
}, 1000);
}
function clearUnsaved() {
hasUnsavedChanges = false;
const badge = document.getElementById('unsaved-badge');
const saveBtn = document.getElementById('header-save-btn');
if (badge) badge.classList.add('hidden');
if (saveBtn) {
saveBtn.classList.remove('ring-2', 'ring-[#089e97]', 'ring-offset-2');
}
}
function setupFormChangeDetection() {
// Remove old listeners
formChangeListeners.forEach(({ element, handler }) => {
element.removeEventListener('change', handler);
element.removeEventListener('input', handler);
});
formChangeListeners = [];
// Add new listeners
const inputs = document.querySelectorAll('#settings-form input, #settings-form select, #settings-form textarea');
inputs.forEach(input => {
const handler = () => markUnsaved();
input.addEventListener('change', handler);
input.addEventListener('input', handler);
formChangeListeners.push({ element: input, handler });
});
}
function renderPremiumHeader(headerConfig) {
const breadcrumbHTML = headerConfig.breadcrumb.map((crumb, index) => {
const isLast = index === headerConfig.breadcrumb.length - 1;
const arrow = index > 0 ? `<span class="mx-2 text-gray-300">/</span>` : '';
const baseClasses = "text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-1 rounded px-1 -mx-1";
if (isLast) {
return `${arrow}<span class="${baseClasses} text-gray-900 cursor-default" aria-current="page">${crumb.label}</span>`;
} else {
return `${arrow}<button onclick="${crumb.clickable ? crumb.action.toString().replace('() => navigateToSection', 'navigateToSection') : ''}"
class="${baseClasses} text-gray-500 hover:text-[#089e97]"
${!crumb.clickable ? 'disabled' : ''}>${crumb.label}</button>`;
}
}).join('');
const actionsHTML = headerConfig.actions.map(action => {
switch(action) {
case 'help':
return `<button onclick="openContextHelp()" class="flex items-center px-3 py-2 text-sm font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-1" aria-label="Get help">
<i data-lucide="help-circle" class="w-4 h-4 mr-2"></i>
Help
</button>`;
case 'ai':
return `<button onclick="openAICopilot()" class="flex items-center px-3 py-2 text-sm font-medium text-[#089e97] hover:bg-[#089e97]/10 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-1 group" aria-label="AI Assistant">
<i data-lucide="sparkles" class="w-4 h-4 mr-2 group-hover:animate-pulse"></i>
AI Copilot
</button>`;
case 'save':
return `<button id="header-save-btn" onclick="saveSettings()" class="flex items-center px-4 py-2 text-sm font-medium text-white bg-[#089e97] hover:bg-[#078b85] rounded-lg shadow-sm transition-all focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-2" aria-label="Save changes">
<i data-lucide="save" class="w-4 h-4 mr-2"></i>
Save Changes
</button>`;
default:
return '';
}
}).join('');
return `
<div class="sticky top-0 z-30 bg-white/80 backdrop-blur-xl border-b border-gray-200/80 px-8 py-5 transition-all duration-300" id="page-header">
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<!-- Breadcrumb -->
<nav aria-label="Breadcrumb" class="mb-2">
<ol class="flex items-center flex-wrap">
${breadcrumbHTML}
</ol>
</nav>
<!-- Title Block -->
<div>
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">${headerConfig.title}</h1>
<p class="mt-1 text-sm text-gray-500 max-w-2xl leading-relaxed">${headerConfig.description}</p>
</div>
<!-- Unsaved Changes Indicator -->
<div id="unsaved-badge" class="hidden mt-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-50 text-amber-700 border border-amber-200">
<span class="w-1.5 h-1.5 bg-amber-500 rounded-full mr-1.5 animate-pulse"></span>
Unsaved changes
</div>
</div>
<!-- Action Cluster -->
<div class="flex items-center space-x-2 ml-4 flex-shrink-0">
${actionsHTML}
</div>
</div>
</div>
`;
}
function openContextHelp() {
showModal('Help: Provider Settings', `
<div class="space-y-4">
<div class="p-4 bg-gray-50 rounded-lg">
<h4 class="font-medium text-gray-900 mb-2 flex items-center">
<i data-lucide="info" class="w-4 h-4 mr-2 text-[#089e97]"></i>
About Provider Settings
</h4>
<p class="text-sm text-gray-600">Configure how providers interact with the system, their default availability, notification preferences, and scheduling permissions.</p>
</div>
<div class="space-y-2">
<h4 class="font-medium text-gray-900 text-sm">Quick Links</h4>
<button onclick="hideModal(); navigateToSection('availability')" class="w-full text-left p-3 hover:bg-gray-50 rounded-lg flex items-center text-sm text-gray-700 transition-colors">
<i data-lucide="clock" class="w-4 h-4 mr-3 text-gray-400"></i>
View Availability Rules
</button>
<button onclick="hideModal(); navigateToSection('appointment-types')" class="w-full text-left p-3 hover:bg-gray-50 rounded-lg flex items-center text-sm text-gray-700 transition-colors">
<i data-lucide="calendar" class="w-4 h-4 mr-3 text-gray-400"></i>
Manage Appointment Types
</button>
</div>
</div>
`);
lucide.createIcons();
}
function openAICopilot() {
const aiActions = [
{ label: 'Review provider setup', icon: 'clipboard-check', action: () => { hideModal(); showToast('AI is reviewing provider configuration...'); } },
{ label: 'Recommend best defaults', icon: 'zap', action: () => { hideModal(); applyAIRecommendations(); } },
{ label: 'Audit scheduling settings', icon: 'search', action: () => { hideModal(); showToast('AI audit started...'); } },
{ label: 'Explain this setting', icon: 'help-circle', action: () => { hideModal(); showToast('Select a setting to explain'); } }
];
const actionsHTML = aiActions.map((action, idx) => `
<button onclick="(${action.action.toString()})()" class="w-full text-left p-4 hover:bg-[#089e97]/5 rounded-xl flex items-center group transition-all border border-transparent hover:border-[#089e97]/20">
<div class="w-10 h-10 bg-[#089e97]/10 rounded-lg flex items-center justify-center mr-4 group-hover:bg-[#089e97] transition-colors">
<i data-lucide="${action.icon}" class="w-5 h-5 text-[#089e97] group-hover:text-white transition-colors"></i>
</div>
<div>
<div class="font-medium text-gray-900 text-sm">${action.label}</div>
<div class="text-xs text-gray-500 mt-0.5">AI-powered assistance</div>
</div>
<i data-lucide="arrow-right" class="w-4 h-4 text-gray-300 ml-auto group-hover:text-[#089e97] transition-colors"></i>
</button>
`).join('');
showModal('AI Copilot', `
<div class="space-y-2">
<p class="text-sm text-gray-600 mb-4">Select an AI-powered action to optimize your provider settings:</p>
${actionsHTML}
</div>
`);
lucide.createIcons();
}
function applyAIRecommendations() {
const settings = {
'setting-provider-notifications': true,
'setting-auto-scheduling': true,
'setting-conflict-prevention': true
};
Object.keys(settings).forEach(id => {
const el = document.getElementById(id);
if (el) el.checked = settings[id];
});
markUnsaved();
showToast('AI has applied recommended settings. Review and save.');
}
function saveSettings() {
// GIS V1: Immediate Action Feedback
const btn = document.getElementById('header-save-btn');
if (btn) applyActionFeedback(btn);
// Simulate processing
setTimeout(() => {
clearUnsaved();
showToast('Changes saved successfully');
// Success state on save button
if (btn) {
btn.innerHTML = `<i data-lucide="check" class="w-4 h-4 mr-2"></i>Saved`;
btn.classList.add('bg-green-600');
lucide.createIcons();
setTimeout(() => {
btn.innerHTML = `<i data-lucide="save" class="w-4 h-4 mr-2"></i>Save Changes`;
btn.classList.remove('bg-green-600');
lucide.createIcons();
}, 2000);
}
}, 400); // 400ms simulated delay for feedback feel
}
// Appointment Types specific functions
function saveAppointmentTypes() {
clearUnsaved();
showToast('Appointment types configuration saved');
// Visual feedback on save button
const btn = document.getElementById('header-save-btn');
if (btn) {
const originalHTML = btn.innerHTML;
btn.innerHTML = `<i data-lucide="check" class="w-4 h-4 mr-2"></i>Saved`;
btn.classList.add('bg-green-600');
btn.classList.remove('bg-[#089e97]', 'hover:bg-[#078b85]');
lucide.createIcons();
setTimeout(() => {
btn.innerHTML = `<i data-lucide="save" class="w-4 h-4 mr-2"></i>Save Changes`;
btn.classList.remove('bg-green-600');
btn.classList.add('bg-[#089e97]', 'hover:bg-[#078b85]');
lucide.createIcons();
}, 2000);
}
}
function openAddAppointmentTypeModal() {
showModal('Add Appointment Type', `
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Appointment Name *</label>
<input type="text" id="new-type-name" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" placeholder="e.g., Annual Physical">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Duration (minutes) *</label>
<select id="new-type-duration" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent">
<option value="15">15 minutes</option>
<option value="30" selected>30 minutes</option>
<option value="45">45 minutes</option>
<option value="60">60 minutes</option>
<option value="90">90 minutes</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Color Code</label>
<div class="flex items-center space-x-2">
<input type="color" id="new-type-color" value="#089e97" class="h-10 w-16 border border-gray-300 rounded-lg cursor-pointer">
<span class="text-sm text-gray-500">Used in calendar</span>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea id="new-type-description" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent resize-none" placeholder="Description shown to patients during booking..."></textarea>
</div>
<div>
<label class="flex items-center">
<input type="checkbox" id="new-type-requires-intake" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" checked>
<span class="ml-2 text-sm text-gray-700">Require intake form</span>
</label>
</div>
<div>
<label class="flex items-center">
<input type="checkbox" id="new-type-allow-online" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" checked>
<span class="ml-2 text-sm text-gray-700">Allow online booking</span>
</label>
</div>
</div>
`);
// Override the save button behavior for this modal
setTimeout(() => {
const saveBtn = document.getElementById('save-modal');
if (saveBtn) {
saveBtn.onclick = () => {
const name = document.getElementById('new-type-name')?.value;
if (!name) {
showToast('Please enter an appointment name');
return;
}
hideModal();
showToast(`Created appointment type: ${name}`);
markUnsaved();
};
}
}, 100);
}
function openAIOptimizeModal() {
const optimizations = [
{ id: 'buffer-optimization', name: 'Smart Buffer Times', description: 'Add 15min buffers between high-stress appointments', impact: '+28% provider satisfaction', selected: true },
{ id: 'duration-optimization', name: 'Duration Calibration', description: 'Adjust durations based on historical data', impact: '+12% schedule efficiency', selected: true },
{ id: 'color-optimization', name: 'Color Psychology', description: 'Optimize color coding for patient perception', impact: 'Better patient experience', selected: false },
{ id: 'name-optimization', name: 'Patient-Friendly Names', description: 'Simplify clinical terminology in booking flows', impact: '+18% booking completion', selected: true }
];
const optimizationsHTML = optimizations.map(opt => `
<label class="flex items-start p-4 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors ${opt.selected ? 'border-[#089e97] bg-[#089e97]/5' : ''}">
<input type="checkbox" class="mt-1 h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" ${opt.selected ? 'checked' : ''}>
<div class="ml-3 flex-1">
<div class="flex items-center justify-between">
<span class="font-medium text-gray-900">${opt.name}</span>
<span class="text-xs font-medium text-green-600 bg-green-50 px-2 py-1 rounded-full">${opt.impact}</span>
</div>
<p class="text-sm text-gray-500 mt-1">${opt.description}</p>
</div>
</label>
`).join('');
showModal('AI Optimization', `
<div class="space-y-4">
<div class="p-4 bg-gradient-to-r from-purple-50 to-blue-50 rounded-lg border border-purple-200">
<div class="flex items-center mb-2">
<i data-lucide="sparkles" class="w-5 h-5 text-purple-600 mr-2"></i>
<span class="font-semibold text-gray-900">AI Analysis Complete</span>
</div>
<p class="text-sm text-gray-600">Based on your scheduling patterns and provider feedback, AI recommends these optimizations:</p>
</div>
<div class="space-y-3">
${optimizationsHTML}
</div>
</div>
`);
setTimeout(() => {
const saveBtn = document.getElementById('save-modal');
if (saveBtn) {
saveBtn.textContent = 'Apply Optimizations';
saveBtn.onclick = () => {
hideModal();
showToast('AI optimizations applied successfully');
markUnsaved();
};
}
}, 100);
}
function editAppointmentType(index) {
showToast(`Editing appointment type ${index + 1}`);
}
function duplicateAppointmentType(index) {
showToast('Appointment type duplicated');
markUnsaved();
}
function deleteAppointmentType(index) {
if (confirm('Are you sure you want to delete this appointment type? This will affect existing bookings.')) {
showToast('Appointment type deleted');
markUnsaved();
}
}
function applyBufferRecommendation() {
const bufferSelect = document.getElementById('setting-buffer-time');
if (bufferSelect) {
bufferSelect.value = '15 minutes';
markUnsaved();
showToast('AI recommendation applied: 15-minute buffers enabled');
}
}
// Availability Page Functions
function initializeAvailabilityPage() {
// Set up event listeners for all inputs to mark unsaved
document.querySelectorAll('#settings-form input, #settings-form select').forEach(input => {
input.addEventListener('change', () => markUnsaved());
});
}
function toggleAllDays() {
const checkboxes = document.querySelectorAll('.toggle-day');
const allEnabled = Array.from(checkboxes).every(cb => cb.checked);
checkboxes.forEach(cb => {
cb.checked = !allEnabled;
toggleDayRow(cb);
});
showToast(allEnabled ? 'All days disabled' : 'All days enabled');
markUnsaved();
}
function toggleDayRow(checkbox) {
const dayRow = checkbox.closest('.day-row');
const timeInputs = dayRow.querySelectorAll('input[type="time"]');
timeInputs.forEach(input => {
input.disabled = !checkbox.checked;
if (!checkbox.checked) {
input.value = '';
} else if (input.getAttribute('data-day')) {
// Restore default values for weekdays
const day = checkbox.getAttribute('data-day');
if (['monday', 'tuesday', 'wednesday', 'thursday', 'friday'].includes(day)) {
if (input.previousElementSibling.textContent.includes('Start')) {
input.value = '09:00';
} else {
input.value = '17:00';
}
}
}
});
}
function copyDaySettings(day) {
// In a real app, this would copy settings to other days
showToast(`Copied ${day} settings to clipboard`);
}
function addTimeBlock() {
const blocksList = document.getElementById('time-blocks-list');
if (!blocksList) return;
const newBlock = document.createElement('div');
newBlock.className = 'time-block-item flex items-center justify-between p-3 border border-gray-200 rounded-lg test-result';
newBlock.innerHTML = `
<div class="flex items-center flex-1 space-x-4">
<div class="flex-1">
<label class="block text-xs font-medium text-gray-500 mb-1">Name</label>
<input type="text" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" value="New Block">
</div>
<div class="flex-1">
<label class="block text-xs font-medium text-gray-500 mb-1">Start Time</label>
<input type="time" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" value="14:00">
</div>
<div class="flex-1">
<label class="block text-xs font-medium text-gray-500 mb-1">End Time</label>
<input type="time" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" value="15:00">
</div>
</div>
<button class="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors ml-3" onclick="removeTimeBlock(this)">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
`;
blocksList.appendChild(newBlock);
lucide.createIcons();
markUnsaved();
showToast('New time block added');
}
function removeTimeBlock(button) {
if (confirm('Are you sure you want to remove this time block?')) {
const block = button.closest('.time-block-item');
block.style.opacity = '0';
block.style.transform = 'translateX(-10px)';
setTimeout(() => {
block.remove();
markUnsaved();
showToast('Time block removed');
}, 300);
}
}
function addBlackoutDate() {
const today = new Date();
const nextWeek = new Date(today);
nextWeek.setDate(today.getDate() + 7);
const dateStr = nextWeek.toISOString().split('T')[0];
const formattedDate = new Date(dateStr).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
const datesList = document.getElementById('blackout-dates-list');
if (!datesList) return;
const newDate = document.createElement('div');
newDate.className = 'blackout-item flex items-center justify-between p-3 border border-gray-200 rounded-lg test-result';
newDate.innerHTML = `
<div class="flex items-center flex-1">
<i data-lucide="calendar" class="w-4 h-4 text-gray-400 mr-3"></i>
<span class="text-sm text-gray-700">${formattedDate}</span>
<span class="ml-2 text-xs text-gray-500">(Custom blackout)</span>
</div>
<button class="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors" onclick="removeBlackoutDate(this)">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
`;
datesList.appendChild(newDate);
lucide.createIcons();
markUnsaved();
showToast('Blackout date added');
}
function addBlackoutDateFromInput() {
const dateInput = document.getElementById('new-blackout-date');
const descInput = document.getElementById('new-blackout-description');
if (!dateInput.value) {
showToast('Please select a date');
dateInput.focus();
return;
}
const date = new Date(dateInput.value);
const formattedDate = date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
const datesList = document.getElementById('blackout-dates-list');
if (!datesList) return;
const newDate = document.createElement('div');
newDate.className = 'blackout-item flex items-center justify-between p-3 border border-gray-200 rounded-lg test-result';
newDate.innerHTML = `
<div class="flex items-center flex-1">
<i data-lucide="calendar" class="w-4 h-4 text-gray-400 mr-3"></i>
<span class="text-sm text-gray-700">${formattedDate}</span>
<span class="ml-2 text-xs text-gray-500">${descInput.value ? '(' + descInput.value + ')' : ''}</span>
</div>
<button class="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors" onclick="removeBlackoutDate(this)">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
`;
datesList.appendChild(newDate);
lucide.createIcons();
// Clear inputs
dateInput.value = '';
descInput.value = '';
markUnsaved();
showToast('Blackout date added');
}
function removeBlackoutDate(button) {
if (confirm('Are you sure you want to remove this blackout date?')) {
const dateItem = button.closest('.blackout-item');
dateItem.style.opacity = '0';
dateItem.style.transform = 'translateX(-10px)';
setTimeout(() => {
dateItem.remove();
markUnsaved();
showToast('Blackout date removed');
}, 300);
}
}
function testAvailabilityConfig() {
const testResults = document.getElementById('test-results');
if (!testResults) return;
// Clear previous results
testResults.innerHTML = '';
// Simulate tests
const tests = [
{ name: 'Working Hours Validation', status: 'passed', message: 'All weekdays configured correctly' },
{ name: 'Time Block Overlap Check', status: 'passed', message: 'No overlapping blocks detected' },
{ name: 'Blackout Date Validation', status: 'warning', message: 'Only 2 blackout dates configured' },
{ name: 'Schedule Consistency', status: 'passed', message: 'No gaps in provider schedules' }
];
tests.forEach(test => {
const testEl = document.createElement('div');
testEl.className = 'flex items-center justify-between py-2 border-b border-gray-100 last:border-0';
testEl.innerHTML = `
<div class="flex items-center">
<span class="w-2 h-2 rounded-full mr-3 ${test.status === 'passed' ? 'bg-green-500' : 'bg-amber-500'}"></span>
<div>
<div class="text-sm text-gray-700">${test.name}</div>
<div class="text-xs text-gray-500">${test.message}</div>
</div>
</div>
<span class="text-xs font-medium ${test.status === 'passed' ? 'text-green-600' : 'text-amber-600'}">
${test.status === 'passed' ? '✓ Passed' : '⚠ Warning'}
</span>
`;
testResults.appendChild(testEl);
});
showToast('Availability configuration tested successfully');
}
function saveAvailabilitySettings() {
clearUnsaved();
// Visual feedback
const btn = document.getElementById('header-save-btn');
if (btn) {
const originalHTML = btn.innerHTML;
btn.innerHTML = `<i data-lucide="check" class="w-4 h-4 mr-2"></i>Saved`;
btn.classList.add('bg-green-600');
btn.classList.remove('bg-[#089e97]', 'hover:bg-[#078b85]');
lucide.createIcons();
setTimeout(() => {
btn.innerHTML = `<i data-lucide="save" class="w-4 h-4 mr-2"></i>Save Changes`;
btn.classList.remove('bg-green-600');
btn.classList.add('bg-[#089e97]', 'hover:bg-[#078b85]');
lucide.createIcons();
}, 2000);
}
showToast('Availability settings saved successfully');
// Update status pill if this is the first save
const statusPill = document.querySelector('.status-pill');
if (statusPill && statusPill.classList.contains('status-needs-setup')) {
statusPill.classList.remove('status-needs-setup');
statusPill.classList.add('status-optimized');
statusPill.innerHTML = `<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>Optimized`;
statusPill.querySelector('span').textContent = 'Optimized';
}
}
function openAIOptimizeAvailability() {
const optimizations = [
{ id: 'extend-hours', name: 'Extend Friday Hours', description: 'High demand detected on Friday afternoons', impact: '+8 appointments/week', selected: true },
{ id: 'add-saturday', name: 'Enable Saturday Mornings', description: 'Patient survey shows 34% want weekend slots', impact: '+15% patient satisfaction', selected: true },
{ id: 'buffer-optimization', name: 'Smart Buffer Times', description: 'Add 15min buffers between appointments', impact: '+45% fewer overlaps', selected: true },
{ id: 'lunch-optimization', name: 'Optimize Lunch Breaks', description: 'Extend to 90 minutes on busy days', impact: '+28% provider satisfaction', selected: false }
];
const optimizationsHTML = optimizations.map(opt => `
<label class="flex items-start p-4 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors ${opt.selected ? 'border-[#089e97] bg-[#089e97]/5' : ''}">
<input type="checkbox" class="mt-1 h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" ${opt.selected ? 'checked' : ''}>
<div class="ml-3 flex-1">
<div class="flex items-center justify-between">
<span class="font-medium text-gray-900">${opt.name}</span>
<span class="text-xs font-medium text-green-600 bg-green-50 px-2 py-1 rounded-full">${opt.impact}</span>
</div>
<p class="text-sm text-gray-500 mt-1">${opt.description}</p>
</div>
</label>
`).join('');
showModal('AI Availability Optimization', `
<div class="space-y-4">
<div class="p-4 bg-gradient-to-r from-purple-50 to-blue-50 rounded-lg border border-purple-200">
<div class="flex items-center mb-2">
<i data-lucide="sparkles" class="w-5 h-5 text-purple-600 mr-2"></i>
<span class="font-semibold text-gray-900">AI Analysis Complete</span>
</div>
<p class="text-sm text-gray-600">Based on your patient demand patterns and provider capacity, AI recommends these optimizations:</p>
</div>
<div class="space-y-3">
${optimizationsHTML}
</div>
</div>
`);
setTimeout(() => {
const saveBtn = document.getElementById('save-modal');
if (saveBtn) {
saveBtn.textContent = 'Apply Optimizations';
saveBtn.onclick = () => {
// Apply selected optimizations
optimizations.forEach(opt => {
if (opt.selected) {
switch(opt.id) {
case 'extend-hours':
// Enable Friday and extend hours
const fridayCheckbox = document.querySelector('.toggle-day[data-day="friday"]');
if (fridayCheckbox) {
fridayCheckbox.checked = true;
toggleDayRow(fridayCheckbox);
const fridayEndInput = document.querySelector('.toggle-day[data-day="friday"]').closest('.day-row').querySelectorAll('input[type="time"]')[1];
if (fridayEndInput) fridayEndInput.value = '18:00';
}
break;
case 'add-saturday':
const saturdayCheckbox = document.querySelector('.toggle-day[data-day="saturday"]');
if (saturdayCheckbox) {
saturdayCheckbox.checked = true;
toggleDayRow(saturdayCheckbox);
const saturdayStartInput = saturdayCheckbox.closest('.day-row').querySelectorAll('input[type="time"]')[0];
const saturdayEndInput = saturdayCheckbox.closest('.day-row').querySelectorAll('input[type="time"]')[1];
if (saturdayStartInput) saturdayStartInput.value = '09:00';
if (saturdayEndInput) saturdayEndInput.value = '13:00';
}
break;
case 'buffer-optimization':
addTimeBlock();
break;
}
}
});
hideModal();
markUnsaved();
showToast('AI optimizations applied successfully');
};
}
}, 100);
}
function applyAIRecommendation(type) {
switch(type) {
case 'buffer':
addTimeBlock();
showToast('Buffer time recommendation applied');
break;
case 'weekend':
const saturdayCheckbox = document.querySelector('.toggle-day[data-day="saturday"]');
if (saturdayCheckbox) {
saturdayCheckbox.checked = true;
toggleDayRow(saturdayCheckbox);
const saturdayStartInput = saturdayCheckbox.closest('.day-row').querySelectorAll('input[type="time"]')[0];
const saturdayEndInput = saturdayCheckbox.closest('.day-row').querySelectorAll('input[type="time"]')[1];
if (saturdayStartInput) saturdayStartInput.value = '09:00';
if (saturdayEndInput) saturdayEndInput.value = '13:00';
showToast('Saturday hours enabled (9 AM - 1 PM)');
}
break;
case 'lunch':
const lunchBlock = document.querySelector('.time-block-item:first-child input[type="text"]');
if (lunchBlock && lunchBlock.value === 'Lunch Break') {
const endInput = lunchBlock.closest('.time-block-item').querySelectorAll('input[type="time"]')[1];
if (endInput) endInput.value = '13:30';
showToast('Lunch break extended to 90 minutes');
}
break;
}
markUnsaved();
}
function autoApplyAIRecommendation(type) {
applyAIRecommendation(type);
showToast('AI automatically applied the recommendation');
}
// Sticky header scroll behavior
function initStickyHeader() {
const scrollContainer = document.getElementById('main-content-scroll');
const header = document.getElementById('page-header');
if (!scrollContainer || !header) return;
scrollContainer.addEventListener('scroll', () => {
if (scrollContainer.scrollTop > 10) {
header.classList.add('shadow-sm', 'bg-white/95');
header.classList.remove('bg-white/80');
} else {
header.classList.remove('shadow-sm', 'bg-white/95');
header.classList.add('bg-white/80');
}
});
}
// Navigation to settings section
function navigateToSection(sectionId) {
// GIS V1: Immediate feedback on navigation trigger
// (Only if triggered by a button click, not programmatic navigation)
if (event && event.currentTarget && event.currentTarget.tagName === 'A') {
event.currentTarget.style.transform = 'scale(0.97)';
setTimeout(() => { event.currentTarget.style.transform = ''; }, 150);
}
// Check for unsaved changes before navigating
if (hasUnsavedChanges && sectionId !== appState.currentView) {
if (!confirm('You have unsaved changes. Are you sure you want to leave this page?')) {
return;
}
clearUnsaved();
}
// Update active state in sidebar
document.querySelectorAll('#settings-sidebar a').forEach(link => {
link.classList.remove('sidebar-item-active');
if (link.dataset.section === sectionId) {
link.classList.add('sidebar-item-active');
}
});
// Update app state
appState.currentView = sectionId;
const mainContent = document.getElementById('main-content');
if (sectionId === 'dashboard') {
location.reload();
return;
}
// Check if we have a premium header config for this section
const headerConfig = pageHeaders[sectionId] || {
title: sectionId.charAt(0).toUpperCase() + sectionId.slice(1).replace(/-/g, ' ') + ' Settings',
description: `Configure your ${sectionId.replace(/-/g, ' ')} settings`,
breadcrumb: [
{ label: 'Settings', action: () => navigateToSection('dashboard'), clickable: true },
{ label: sectionId.charAt(0).toUpperCase() + sectionId.slice(1).replace(/-/g, ' '), action: null, clickable: false }
],
actions: ['help', 'ai', 'save']
};
// Premium Appointment Types Settings Page with Enterprise Command Header
if (sectionId === 'appointment-types') {
const appointmentTypesData = {
count: 8,
status: 'Active',
lastUpdated: '2 hours ago',
usageContexts: ['Scheduling OS', 'Booking Links', 'Patient Intake'],
activeTypes: [
{ name: 'Initial Consultation', duration: '60 min', color: '#089e97', usage: 'High' },
{ name: 'Follow-up Visit', duration: '30 min', color: '#3b82f6', usage: 'High' },
{ name: 'Urgent Care', duration: '20 min', color: '#f59e0b', usage: 'Medium' },
{ name: 'Telehealth', duration: '45 min', color: '#8b5cf6', usage: 'High' }
]
};
mainContent.innerHTML = `
<!-- Enterprise Command Header Layer -->
<div class="sticky top-0 z-30 bg-white border-b border-gray-200/80 transition-all duration-300" id="page-header">
<div class="px-8 py-5">
<!-- Layer 1: Strong Back Navigation -->
<div class="mb-3">
<button onclick="navigateToSection('dashboard')"
class="inline-flex items-center text-sm font-medium text-gray-500 hover:text-[#089e97] transition-all group focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-2 rounded-md px-2 py-1 -ml-2">
<i data-lucide="arrow-left" class="w-4 h-4 mr-1.5 transition-transform group-hover:-translate-x-0.5"></i>
Back to Settings
</button>
</div>
<!-- Layer 2: Enhanced Breadcrumb -->
<nav aria-label="Breadcrumb" class="mb-3">
<ol class="flex items-center flex-wrap text-sm">
<li>
<button onclick="navigateToSection('dashboard')"
class="font-medium text-gray-400 hover:text-gray-600 transition-colors focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-1 rounded px-1">
Settings
</button>
</li>
<li class="mx-2 text-gray-300">/</li>
<li>
<span class="font-semibold text-gray-900" aria-current="page">Appointment Types</span>
</li>
</ol>
</nav>
<!-- Layer 3: Title Block with Actions -->
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Appointment Types</h1>
<p class="mt-1.5 text-sm text-gray-500 max-w-2xl leading-relaxed">
Define visit categories, durations, color coding, and workflow behavior used across scheduling, booking links, and patient intake flows.
</p>
<!-- Layer 4: System Context Bar -->
<div class="mt-4 flex flex-wrap items-center gap-4 text-sm">
<div class="flex items-center text-gray-600">
<i data-lucide="layers" class="w-4 h-4 mr-1.5 text-gray-400"></i>
<span class="font-medium">${appointmentTypesData.count} active types</span>
</div>
<span class="text-gray-300">|</span>
<div class="flex items-center text-gray-600">
<i data-lucide="zap" class="w-4 h-4 mr-1.5 text-gray-400"></i>
<span>Used in: <span class="font-medium text-gray-700">${appointmentTypesData.usageContexts.join(', ')}</span></span>
</div>
<span class="text-gray-300">|</span>
<div class="flex items-center">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-50 text-green-700 border border-green-200">
<span class="w-1.5 h-1.5 bg-green-500 rounded-full mr-1.5"></span>
${appointmentTypesData.status}
</span>
</div>
<span class="text-gray-300">|</span>
<div class="flex items-center text-gray-500 text-xs">
<i data-lucide="clock" class="w-3.5 h-3.5 mr-1.5"></i>
Updated ${appointmentTypesData.lastUpdated}
</div>
</div>
<!-- Unsaved Changes Indicator -->
<div id="unsaved-badge" class="hidden mt-3 inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-amber-50 text-amber-700 border border-amber-200">
<span class="w-1.5 h-1.5 bg-amber-500 rounded-full mr-1.5 animate-pulse"></span>
Unsaved changes
</div>
</div>
<!-- Top-Right Action Cluster -->
<div class="flex items-center space-x-2 ml-4 flex-shrink-0">
<!-- Quick Actions -->
<button onclick="openAddAppointmentTypeModal()" class="flex items-center px-3 py-2 text-sm font-medium text-[#089e97] bg-[#089e97]/10 hover:bg-[#089e97]/20 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-1" aria-label="Add new appointment type">
<i data-lucide="plus" class="w-4 h-4 mr-2"></i>
Add Type
</button>
<button onclick="openAIOptimizeModal()" class="flex items-center px-3 py-2 text-sm font-medium text-purple-700 bg-purple-50 hover:bg-purple-100 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-1 group" aria-label="AI optimization">
<i data-lucide="sparkles" class="w-4 h-4 mr-2 group-hover:animate-pulse"></i>
AI Optimize
</button>
<div class="w-px h-6 bg-gray-200 mx-1"></div>
<button onclick="openContextHelp()" class="flex items-center px-3 py-2 text-sm font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-1" aria-label="Get help">
<i data-lucide="help-circle" class="w-4 h-4 mr-2"></i>
Help
</button>
<button id="header-save-btn" onclick="saveAppointmentTypes()" class="flex items-center px-4 py-2 text-sm font-medium text-white bg-[#089e97] hover:bg-[#078b85] rounded-lg shadow-sm transition-all focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed" aria-label="Save changes">
<i data-lucide="save" class="w-4 h-4 mr-2"></i>
Save Changes
</button>
</div>
</div>
</div>
</div>
<!-- Main Content Area -->
<div class="px-8 pb-8 pt-6" id="settings-form">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Main Configuration: Appointment Types List -->
<div class="lg:col-span-2 space-y-6">
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50 flex items-center justify-between">
<h3 class="font-semibold text-gray-900 flex items-center">
<i data-lucide="calendar" class="w-5 h-5 mr-2 text-[#089e97]"></i>
Active Appointment Types
</h3>
<button onclick="openAddAppointmentTypeModal()" class="text-sm text-[#089e97] hover:text-[#078b85] font-medium flex items-center">
<i data-lucide="plus" class="w-4 h-4 mr-1"></i>
Add New
</button>
</div>
<div class="divide-y divide-gray-100">
${appointmentTypesData.activeTypes.map((type, index) => `
<div class="p-6 hover:bg-gray-50/50 transition-colors group">
<div class="flex items-start justify-between">
<div class="flex items-start flex-1">
<div class="w-4 h-4 rounded-full mt-1 mr-4 flex-shrink-0" style="background-color: ${type.color}"></div>
<div class="flex-1">
<div class="flex items-center mb-1">
<h4 class="font-semibold text-gray-900">${type.name}</h4>
<span class="ml-3 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${type.usage === 'High' ? 'bg-green-50 text-green-700' : 'bg-amber-50 text-amber-700'}">
${type.usage} Usage
</span>
</div>
<div class="flex items-center text-sm text-gray-500 space-x-4">
<span class="flex items-center">
<i data-lucide="clock" class="w-3.5 h-3.5 mr-1.5"></i>
${type.duration}
</span>
<span class="flex items-center">
<i data-lucide="link" class="w-3.5 h-3.5 mr-1.5"></i>
Used in scheduling & intake
</span>
</div>
</div>
</div>
<div class="flex items-center space-x-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button onclick="editAppointmentType(${index})" class="p-2 hover:bg-gray-100 rounded-lg text-gray-500 hover:text-gray-700" title="Edit">
<i data-lucide="pencil" class="w-4 h-4"></i>
</button>
<button onclick="duplicateAppointmentType(${index})" class="p-2 hover:bg-gray-100 rounded-lg text-gray-500 hover:text-gray-700" title="Duplicate">
<i data-lucide="copy" class="w-4 h-4"></i>
</button>
<button onclick="deleteAppointmentType(${index})" class="p-2 hover:bg-red-50 rounded-lg text-gray-500 hover:text-red-600" title="Delete">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</div>
</div>
</div>
`).join('')}
</div>
</div>
<!-- Global Settings -->
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50">
<h3 class="font-semibold text-gray-900 flex items-center">
<i data-lucide="settings-2" class="w-5 h-5 mr-2 text-[#089e97]"></i>
Global Behavior
</h3>
</div>
<div class="p-6 space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Default Buffer Time</label>
<select class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent bg-white" id="setting-buffer-time" onchange="markUnsaved()">
<option>0 minutes</option>
<option selected>15 minutes</option>
<option>30 minutes</option>
<option>45 minutes</option>
</select>
<p class="text-xs text-gray-500 mt-1">Time between appointments</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Booking Notice Required</label>
<select class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent bg-white" id="setting-notice" onchange="markUnsaved()">
<option>Same day</option>
<option selected>24 hours</option>
<option>48 hours</option>
<option>1 week</option>
</select>
<p class="text-xs text-gray-500 mt-1">Minimum advance notice</p>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-3">Workflow Automation</label>
<div class="space-y-3">
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" id="setting-auto-intake" onchange="markUnsaved()" checked>
<div class="ml-3">
<div class="text-sm font-medium text-gray-900">Auto-send Intake Forms</div>
<div class="text-xs text-gray-500">Send forms when appointment is booked</div>
</div>
</label>
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" id="setting-auto-reminders" onchange="markUnsaved()" checked>
<div class="ml-3">
<div class="text-sm font-medium text-gray-900">Type-specific Reminders</div>
<div class="text-xs text-gray-500">Customize reminder timing per appointment type</div>
</div>
</label>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar: Intelligence & Tips -->
<div class="space-y-6">
<div class="bg-gradient-to-br from-[#089e97]/5 to-blue-50 rounded-xl border border-[#089e97]/20 p-5">
<div class="flex items-center mb-3">
<div class="w-8 h-8 bg-[#089e97]/10 rounded-lg flex items-center justify-center mr-3">
<i data-lucide="lightbulb" class="w-4 h-4 text-[#089e97]"></i>
</div>
<h4 class="font-semibold text-gray-900 text-sm">AI Insight</h4>
</div>
<p class="text-sm text-gray-600 leading-relaxed mb-3">Adding 15-minute buffer times between appointments could reduce provider stress by 28% and prevent scheduling conflicts.</p>
<button onclick="applyBufferRecommendation()" class="w-full py-2 bg-[#089e97] text-white text-sm rounded-lg hover:bg-[#078b85] transition-colors">
Apply Recommendation
</button>
</div>
<div class="bg-white rounded-xl border border-gray-100 shadow-sm p-5">
<h4 class="font-semibold text-gray-900 text-sm mb-4 flex items-center">
<i data-lucide="activity" class="w-4 h-4 mr-2 text-gray-400"></i>
Usage Analytics
</h4>
<div class="space-y-4">
<div>
<div class="flex justify-between items-center mb-1">
<span class="text-sm text-gray-500">Most Booked</span>
<span class="text-sm font-medium text-gray-900">Follow-up Visit</span>
</div>
<div class="h-2 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-[#089e97] rounded-full" style="width: 78%"></div>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-1">
<span class="text-sm text-gray-500">Growth Trend</span>
<span class="text-sm font-medium text-green-600">+12% this month</span>
</div>
<div class="h-2 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-green-500 rounded-full" style="width: 62%"></div>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl border border-gray-100 shadow-sm p-5">
<h4 class="font-semibold text-gray-900 text-sm mb-3">Integration Status</h4>
<div class="space-y-2">
<div class="flex items-center justify-between py-2 border-b border-gray-50">
<div class="flex items-center text-sm text-gray-600">
<i data-lucide="calendar" class="w-4 h-4 mr-2 text-gray-400"></i>
Scheduling OS
</div>
<span class="text-xs font-medium text-green-600">Connected</span>
</div>
<div class="flex items-center justify-between py-2 border-b border-gray-50">
<div class="flex items-center text-sm text-gray-600">
<i data-lucide="link" class="w-4 h-4 mr-2 text-gray-400"></i>
Booking Links
</div>
<span class="text-xs font-medium text-green-600">Active</span>
</div>
<div class="flex items-center justify-between py-2">
<div class="flex items-center text-sm text-gray-600">
<i data-lucide="file-text" class="w-4 h-4 mr-2 text-gray-400"></i>
Intake Forms
</div>
<span class="text-xs font-medium text-amber-600">4 of 8 linked</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;
lucide.createIcons();
setupFormChangeDetection();
initStickyHeader();
} else if (sectionId === 'providers') {
mainContent.innerHTML = `
${renderPremiumHeader(headerConfig)}
<div class="px-8 pb-8 pt-6" id="settings-form">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Main Configuration -->
<div class="lg:col-span-2 space-y-6">
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50">
<h3 class="font-semibold text-gray-900 flex items-center">
<i data-lucide="user-check" class="w-5 h-5 mr-2 text-[#089e97]"></i>
Provider Configuration
</h3>
</div>
<div class="p-6 space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Default Provider Role</label>
<select class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent bg-white" id="setting-provider-role">
<option>Primary Care Physician</option>
<option selected>Specialist</option>
<option>Nurse Practitioner</option>
<option>Physician Assistant</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Default Appointment Duration</label>
<select class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent bg-white" id="setting-duration">
<option>15 minutes</option>
<option selected>30 minutes</option>
<option>45 minutes</option>
<option>60 minutes</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-3">Provider Capabilities</label>
<div class="space-y-3">
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" id="setting-provider-notifications" checked>
<div class="ml-3">
<div class="text-sm font-medium text-gray-900">Enable Notifications</div>
<div class="text-xs text-gray-500">Send email/SMS for new bookings and cancellations</div>
</div>
</label>
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" id="setting-auto-scheduling">
<div class="ml-3">
<div class="text-sm font-medium text-gray-900">Auto-Scheduling</div>
<div class="text-xs text-gray-500">Allow AI to optimize appointment placement</div>
</div>
</label>
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" id="setting-conflict-prevention" checked>
<div class="ml-3">
<div class="text-sm font-medium text-gray-900">Conflict Prevention</div>
<div class="text-xs text-gray-500">Block double-booking and enforce buffer times</div>
</div>
</label>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50">
<h3 class="font-semibold text-gray-900 flex items-center">
<i data-lucide="shield" class="w-5 h-5 mr-2 text-[#089e97]"></i>
Permissions & Access
</h3>
</div>
<div class="p-6 space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Calendar Access Level</label>
<select class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent bg-white" id="setting-calendar-access">
<option>View Own Only</option>
<option selected>View Department</option>
<option>View All</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Patient Data Access</label>
<select class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent bg-white" id="setting-patient-access">
<option>Assigned Patients Only</option>
<option selected>All Patients</option>
<option>Read-Only</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar Info -->
<div class="space-y-6">
<div class="bg-gradient-to-br from-[#089e97]/5 to-blue-50 rounded-xl border border-[#089e97]/20 p-5">
<div class="flex items-center mb-3">
<div class="w-8 h-8 bg-[#089e97]/10 rounded-lg flex items-center justify-center mr-3">
<i data-lucide="lightbulb" class="w-4 h-4 text-[#089e97]"></i>
</div>
<h4 class="font-semibold text-gray-900 text-sm">Pro Tip</h4>
</div>
<p class="text-sm text-gray-600 leading-relaxed">Enable Auto-Scheduling to let the AI optimize your provider calendars, reducing gaps by up to 35%.</p>
</div>
<div class="bg-white rounded-xl border border-gray-100 shadow-sm p-5">
<h4 class="font-semibold text-gray-900 text-sm mb-4 flex items-center">
<i data-lucide="activity" class="w-4 h-4 mr-2 text-gray-400"></i>
Current Stats
</h4>
<div class="space-y-3">
<div class="flex justify-between items-center">
<span class="text-sm text-gray-500">Active Providers</span>
<span class="font-semibold text-gray-900">12</span>
</div>
<div class="flex justify-between items-center">
<span class="text-sm text-gray-500">Avg. Daily Appointments</span>
<span class="font-semibold text-gray-900">48</span>
</div>
<div class="flex justify-between items-center">
<span class="text-sm text-gray-500">Utilization Rate</span>
<span class="font-semibold text-green-600">87%</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;
lucide.createIcons();
setupFormChangeDetection();
initStickyHeader();
} else {
// Generic premium page for other sections
mainContent.innerHTML = `
${renderPremiumHeader(headerConfig)}
<div class="px-8 pb-8 pt-6" id="settings-form">
<div class="bg-white rounded-xl border border-gray-100 shadow-sm p-6">
<div class="max-w-2xl">
<h2 class="text-lg font-semibold text-gray-900 mb-6">Configuration Options</h2>
<div class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Setting 1</label>
<input type="text" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" value="Current value">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Setting 2</label>
<select class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent">
<option>Option 1</option>
<option selected>Option 2</option>
<option>Option 3</option>
</select>
</div>
<div>
<label class="flex items-center">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded">
<span class="ml-2 text-sm text-gray-700">Enable this feature</span>
</label>
</div>
</div>
</div>
</div>
</div>
`;
lucide.createIcons();
setupFormChangeDetection();
initStickyHeader();
}
}
// Quick action handlers
function handleQuickAction(action) {
const actions = {
'add-provider': {
title: 'Add New Provider',
content: `
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Provider Name</label>
<input type="text" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" placeholder="Dr. Jane Smith">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Specialty</label>
<input type="text" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" placeholder="Cardiology">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" placeholder="jane.smith@clinic.com">
</div>
</div>
`
},
'set-availability': {
title: 'Set Availability Rules',
content: `
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Default Hours</label>
<div class="flex space-x-2">
<input type="time" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" value="09:00">
<span class="self-center">to</span>
<input type="time" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" value="17:00">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Days Available</label>
<div class="flex flex-wrap gap-2">
<label class="inline-flex items-center">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" checked>
<span class="ml-2 text-sm text-gray-700">Mon</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" checked>
<span class="ml-2 text-sm text-gray-700">Tue</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" checked>
<span class="ml-2 text-sm text-gray-700">Wed</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" checked>
<span class="ml-2 text-sm text-gray-700">Thu</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97] border-gray-300 rounded" checked>
<span class="ml-2 text-sm text-gray-700">Fri</span>
</label>
</div>
</div>
</div>
`
},
'create-appointment': {
title: 'Create Appointment Type',
content: `
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Appointment Name</label>
<input type="text" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" placeholder="Follow-up Consultation">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Duration (minutes)</label>
<select class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent">
<option>15</option>
<option selected>30</option>
<option>45</option>
<option>60</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Color</label>
<input type="color" class="w-full h-10 border border-gray-300 rounded-lg" value="#089e97">
</div>
</div>
`
},
'enable-payments': {
title: 'Enable Payments',
content: `
<div class="space-y-4">
<div>
<h4 class="font-medium text-gray-900 mb-2">Select Payment Processor</h4>
<div class="space-y-2">
<label class="flex items-center p-3 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50">
<input type="radio" name="processor" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97]" checked>
<div class="ml-3">
<div class="font-medium text-gray-900">Stripe</div>
<div class="text-sm text-gray-500">Recommended for healthcare</div>
</div>
</label>
<label class="flex items-center p-3 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-50">
<input type="radio" name="processor" class="h-4 w-4 text-[#089e97] focus:ring-[#089e97]">
<div class="ml-3">
<div class="font-medium text-gray-900">PayPal</div>
<div class="text-sm text-gray-500">Widely used</div>
</div>
</label>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">API Key</label>
<input type="text" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#089e97] focus:border-transparent" placeholder="sk_live_...">
</div>
</div>
`
}
};
if (actions[action]) {
showModal(actions[action].title, actions[action].content);
} else {
showModal('Quick Action', `<p class="text-gray-700">This action would open the ${action} configuration panel.</p>`);
}
}
// Initialize event listeners
function initializeEventListeners() {
// Checklist item toggles
document.querySelectorAll('.checklist-item').forEach(item => {
item.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
const itemId = e.currentTarget.dataset.item;
navigateToSection(itemId.split('-')[0]);
} else {
const itemId = e.currentTarget.dataset.item;
toggleChecklistItem(itemId);
}
});
});
// Quick actions
document.querySelectorAll('.quick-action').forEach(button => {
button.addEventListener('click', () => {
const action = button.dataset.action;
handleQuickAction(action);
});
});
// Settings sidebar navigation - Updated for V2
document.querySelectorAll('#settings-sidebar a').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const section = link.dataset.section;
navigateToSection(section);
});
});
// Settings category cards
document.querySelectorAll('[data-section]').forEach(card => {
if (card.classList.contains('card-hover')) {
card.addEventListener('click', () => {
const section = card.dataset.section;
navigateToSection(section);
});
}
});
// Adaptive quick actions buttons
document.querySelectorAll('#adaptive-actions button').forEach(button => {
button.addEventListener('click', (e) => {
// Let the onclick attribute handle navigation
});
});
// Settings module cards
document.querySelectorAll('#settings-cards > div').forEach(card => {
card.addEventListener('click', (e) => {
// Let the onclick attribute handle navigation
});
});
// Collapse sidebar button
document.getElementById('collapse-sidebar').addEventListener('click', () => {
const sidebar = document.getElementById('settings-sidebar');
sidebar.classList.toggle('hidden');
appState.settingsSidebarExpanded = !appState.settingsSidebarExpanded;
});
// AI Assistant toggle
document.getElementById('ai-assistant-toggle').addEventListener('click', () => {
const panel = document.getElementById('ai-assistant-panel');
if (appState.aiPanelExpanded) {
panel.style.width = '0';
} else {
panel.style.width = '20rem';
}
appState.aiPanelExpanded = !appState.aiPanelExpanded;
});
// Close AI panel
document.getElementById('close-ai-panel').addEventListener('click', () => {
const panel = document.getElementById('ai-assistant-panel');
panel.style.width = '0';
appState.aiPanelExpanded = false;
});
// Save all changes button
document.querySelector('button.bg-\\[\\#089e97\\]').addEventListener('click', () => {
showToast('All settings saved successfully');
});
// Modal overlay click to close
document.getElementById('modal-overlay').addEventListener('click', (e) => {
if (e.target === document.getElementById('modal-overlay')) {
hideModal();
}
});
// System health fix buttons
document.querySelectorAll('.health-warning button').forEach(button => {
button.addEventListener('click', () => {
navigateToSection('payments');
});
});
// AI recommendation buttons
document.querySelectorAll('.bg-gray-50 button').forEach(button => {
button.addEventListener('click', () => {
showToast('Navigating to recommended setting');
});
});
}
// Account Setup Progress Functions
function toggleSetupItem(element) {
// GIS V1: Add immediate scale feedback
element.style.transform = 'scale(0.98)';
element.style.transition = 'transform 80ms ease';
setTimeout(() => {
element.style.transform = '';
}, 100);
const circle = element.querySelector('.check-circle');
const icon = circle.querySelector('i');
const text = element.querySelector('span');
const isCompleted = circle.classList.contains('bg-[#089e97]');
if (isCompleted) {
// Uncheck
circle.classList.remove('bg-[#089e97]', 'text-white');
circle.classList.add('bg-white', 'border-2', 'border-gray-300');
icon.classList.add('hidden');
text.classList.remove('line-through', 'text-gray-400');
text.classList.add('text-gray-700');
} else {
// Check
circle.classList.remove('bg-white', 'border-2', 'border-gray-300');
circle.classList.add('bg-[#089e97]', 'text-white');
icon.classList.remove('hidden');
text.classList.add('line-through', 'text-gray-400');
text.classList.remove('text-gray-700');
}
updateSetupProgress();
showToast(isCompleted ? 'Task marked incomplete' : 'Task completed');
}
function updateSetupProgress() {
const totalItems = document.querySelectorAll('#settings-dashboard-top-module .checklist-item').length;
const completedItems = document.querySelectorAll('#settings-dashboard-top-module .check-circle.bg-\\[\\#089e97\\]').length;
const percentage = Math.round((completedItems / totalItems) * 100) || 56;
const percentageEl = document.getElementById('setup-progress-percentage');
const barEl = document.getElementById('setup-progress-bar');
if (percentageEl) percentageEl.textContent = percentage + '%';
if (barEl) barEl.style.width = percentage + '%';
}
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
initializeEventListeners();
updateProgressDisplay();
updateSetupProgress(); // Initialize setup progress on load
// Show welcome toast
setTimeout(() => {
showToast('Welcome to Verify MC Settings Dashboard');
}, 1000);
// Ensure settings sidebar is expanded on load
const sidebar = document.getElementById('settings-sidebar');
sidebar.classList.remove('hidden');
// Initialize module panel event listeners
const modulePanelOverlay = document.getElementById('module-panel-overlay');
if (modulePanelOverlay) {
modulePanelOverlay.addEventListener('click', (e) => {
if (e.target === modulePanelOverlay) {
closeModulePanel();
}
});
}
// Initialize collapsible sidebar sections
setTimeout(() => {
initializeSidebarSections();
// Auto-expand the section for current view
if (appState.currentView && appState.currentView !== 'dashboard') {
expandSectionForView(appState.currentView);
}
}, 100);
});