thors1's picture
Initial DeepSite commit
d4d1a6b verified
// Verify MC - Add Encounter Application
// Enterprise-grade medical marijuana SaaS workflow
class VerifyMCApp {
constructor() {
this.currentStep = 1;
this.totalSteps = 5;
this.patients = [];
this.selectedPatient = null;
this.encounterData = {
conditions: [],
therapies: [],
productForms: [],
compliance: {},
details: {}
};
this.transcript = [];
this.isRecording = false;
this.isPaused = false;
this.recordingTime = 0;
this.recordingInterval = null;
this.currentFilter = 'all';
this.copilotOpen = false;
this.init();
}
init() {
this.generateMockPatients();
this.renderStepper();
this.renderPatients();
this.initWaveform();
this.updateProgress();
this.startAutoSave();
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.metaKey && e.key === 'k') {
e.preventDefault();
this.toggleCopilot();
}
if (e.key === 'Escape') {
this.closeWalkInModal();
this.closePreview();
}
});
}
// Data Generation
generateMockPatients() {
const conditions = ['Chronic Pain', 'PTSD', 'Neuropathy', 'MS Spasticity', 'Cancer-related Nausea'];
const statuses = ['checked-in', 'in-room', 'waiting', 'renewal', 'high-risk'];
this.patients = [
{
id: 1,
firstName: 'James',
lastName: 'Wilson',
dob: '1985-03-15',
mrn: 'MRN-2024-001',
registryId: 'CA-MMJ-789456',
status: 'checked-in',
lastVisit: '2024-01-15',
nextRenewal: '2025-01-15',
conditions: ['Chronic Pain', 'PTSD'],
avatar: 'JW',
aiSuggested: true,
reason: 'Follow-up appointment scheduled'
},
{
id: 2,
firstName: 'Maria',
lastName: 'Garcia',
dob: '1978-11-22',
mrn: 'MRN-2024-002',
registryId: 'CA-MMJ-123789',
status: 'in-room',
lastVisit: '2024-11-20',
nextRenewal: '2024-12-20',
conditions: ['Neuropathy'],
avatar: 'MG',
aiSuggested: true,
reason: 'Renewal due in 30 days'
},
{
id: 3,
firstName: 'Robert',
lastName: 'Chen',
dob: '1990-07-08',
mrn: 'MRN-2024-003',
registryId: null,
status: 'waiting',
lastVisit: null,
nextRenewal: null,
conditions: [],
avatar: 'RC',
aiSuggested: false,
reason: 'New patient - initial certification'
},
{
id: 4,
firstName: 'Sarah',
lastName: 'Johnson',
dob: '1982-04-30',
mrn: 'MRN-2024-004',
registryId: 'CA-MMJ-456123',
status: 'renewal',
lastVisit: '2024-10-01',
nextRenewal: '2024-12-01',
conditions: ['MS Spasticity', 'Chronic Pain'],
avatar: 'SJ',
aiSuggested: true,
reason: 'Renewal overdue'
},
{
id: 5,
firstName: 'David',
lastName: 'Smith',
dob: '1975-09-12',
mrn: 'MRN-2024-005',
registryId: 'CA-MMJ-789123',
status: 'high-risk',
lastVisit: '2024-11-10',
nextRenewal: '2025-02-10',
conditions: ['PTSD'],
avatar: 'DS',
aiSuggested: false,
reason: 'Multiple medications - review required'
},
{
id: 6,
firstName: 'Emily',
lastName: 'Davis',
dob: '1988-12-05',
mrn: 'MRN-2024-006',
registryId: 'CA-MMJ-321654',
status: 'checked-in',
lastVisit: '2024-09-20',
nextRenewal: '2025-09-20',
conditions: ['Cancer-related Nausea'],
avatar: 'ED',
aiSuggested: false,
reason: 'Regular follow-up'
}
];
}
// Stepper Navigation
renderStepper() {
const steps = [
{ num: 1, label: 'Patient' },
{ num: 2, label: 'Details' },
{ num: 3, label: 'Scribe' },
{ num: 4, label: 'Notes' },
{ num: 5, label: 'Review' }
];
const container = document.getElementById('stepper-container');
if (!container) return;
container.innerHTML = steps.map((step, index) => {
const isActive = step.num === this.currentStep;
const isCompleted = step.num < this.currentStep;
const isLast = index === steps.length - 1;
let classes = 'w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold transition-all ';
if (isActive) {
classes += 'bg-emerald-600 text-white shadow-lg ring-4 ring-emerald-100';
} else if (isCompleted) {
classes += 'bg-emerald-100 text-emerald-700';
} else {
classes += 'bg-gray-100 text-gray-500';
}
return `
<div class="flex items-center">
<button onclick="app.goToStep(${step.num})" class="${classes}">
${isCompleted ? '<i data-lucide="check" class="w-5 h-5"></i>' : step.num}
</button>
<span class="ml-2 text-xs font-medium ${isActive ? 'text-emerald-700' : 'text-gray-500'} hidden lg:block">${step.label}</span>
${!isLast ? '<div class="w-8 h-0.5 bg-gray-200 mx-2"></div>' : ''}
</div>
`;
}).join('');
// Update footer
const stepNames = ['Patient Selection', 'Encounter Details', 'AI Scribe', 'AI Documentation', 'Review & Finalize'];
document.getElementById('footer-step-current').textContent = this.currentStep;
document.getElementById('footer-step-total').textContent = this.totalSteps;
document.getElementById('footer-step-name').textContent = stepNames[this.currentStep - 1];
// Update buttons
document.getElementById('btn-prev').disabled = this.currentStep === 1;
if (this.currentStep === this.totalSteps) {
document.getElementById('btn-next').classList.add('hidden');
document.getElementById('btn-submit-final').classList.remove('hidden');
} else {
document.getElementById('btn-next').classList.remove('hidden');
document.getElementById('btn-submit-final').classList.add('hidden');
}
lucide.createIcons();
}
goToStep(stepNum) {
if (stepNum < 1 || stepNum > this.totalSteps) return;
// Validation before moving forward
if (stepNum > this.currentStep) {
if (this.currentStep === 1 && !this.selectedPatient) {
this.showToast('Please select a patient first', 'error');
return;
}
}
this.currentStep = stepNum;
this.renderStepper();
// Hide all steps
document.querySelectorAll('.step-content').forEach(el => {
el.classList.remove('active');
});
// Show current step
document.getElementById(`step-${this.currentStep}`).classList.add('active');
// Scroll to top
document.getElementById('main-container').scrollTop = 0;
}
nextStep() {
if (this.currentStep < this.totalSteps) {
this.goToStep(this.currentStep + 1);
}
}
prevStep() {
if (this.currentStep > 1) {
this.goToStep(this.currentStep - 1);
}
}
// Patient Selection
renderPatients() {
const grid = document.getElementById('patient-grid');
const aiContainer = document.getElementById('ai-suggested-container');
if (!grid) return;
// Filter patients
let filtered = this.patients;
if (this.currentFilter !== 'all') {
filtered = this.patients.filter(p => p.status === this.currentFilter);
}
// Render AI suggested
const aiSuggested = this.patients.filter(p => p.aiSuggested);
aiContainer.innerHTML = aiSuggested.map(p => this.createPatientCard(p, true)).join('');
// Render main grid
grid.innerHTML = filtered.map(p => this.createPatientCard(p, false)).join('');
lucide.createIcons();
}
createPatientCard(patient, isCompact) {
const isSelected = this.selectedPatient?.id === patient.id;
const statusColors = {
'checked-in': 'bg-blue-100 text-blue-700',
'in-room': 'bg-emerald-100 text-emerald-700',
'waiting': 'bg-amber-100 text-amber-700',
'renewal': 'bg-purple-100 text-purple-700',
'high-risk': 'bg-red-100 text-red-700'
};
if (isCompact) {
return `
<div onclick="app.selectPatient(${patient.id})" class="patient-card cursor-pointer border-2 ${isSelected ? 'border-emerald-500 bg-emerald-50' : 'border-gray-200 bg-white'} rounded-xl p-4 hover:border-emerald-300 hover:shadow-md transition-all">
<div class="flex items-center gap-3 mb-2">
<div class="w-10 h-10 rounded-full bg-emerald-600 text-white flex items-center justify-center font-bold text-sm">${patient.avatar}</div>
<div class="flex-1 min-w-0">
<div class="font-semibold text-gray-900 truncate">${patient.firstName} ${patient.lastName}</div>
<div class="text-xs text-gray-500">MRN: ${patient.mrn}</div>
</div>
</div>
<div class="flex items-center justify-between">
<span class="text-xs px-2 py-0.5 rounded-full ${statusColors[patient.status]}">${patient.status.replace('-', ' ')}</span>
<span class="text-xs text-gray-400">${patient.reason}</span>
</div>
</div>
`;
}
return `
<div onclick="app.selectPatient(${patient.id})" class="patient-card cursor-pointer border-2 ${isSelected ? 'border-emerald-500 bg-emerald-50' : 'border-gray-200 bg-white'} rounded-xl p-5 hover:border-emerald-300 hover:shadow-md transition-all">
<div class="flex items-start justify-between mb-3">
<div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-full bg-emerald-600 text-white flex items-center justify-center font-bold">${patient.avatar}</div>
<div>
<div class="font-semibold text-gray-900">${patient.firstName} ${patient.lastName}</div>
<div class="text-sm text-gray-500">DOB: ${patient.dob}</div>
</div>
</div>
<span class="text-xs px-2 py-1 rounded-full ${statusColors[patient.status]}">${patient.status.replace('-', ' ')}</span>
</div>
<div class="space-y-2 mb-3">
<div class="text-xs text-gray-500">MRN: ${patient.mrn}</div>
<div class="text-xs text-gray-500">Registry: ${patient.registryId || 'Pending'}</div>
${patient.conditions.length ? `
<div class="flex flex-wrap gap-1 mt-2">
${patient.conditions.map(c => `<span class="text-xs px-2 py-0.5 bg-gray-100 text-gray-700 rounded">${c}</span>`).join('')}
</div>
` : ''}
</div>
<div class="flex items-center justify-between text-xs text-gray-400 border-t pt-3">
<span>Last visit: ${patient.lastVisit || 'New patient'}</span>
<span>Renewal: ${patient.nextRenewal || 'N/A'}</span>
</div>
</div>
`;
}
selectPatient(patientId) {
this.selectedPatient = this.patients.find(p => p.id === patientId);
this.renderPatients();
this.updateProgress();
this.showToast(`Selected ${this.selectedPatient.firstName} ${this.selectedPatient.lastName}`, 'success');
}
filterPatients(query) {
const grid = document.getElementById('patient-grid');
const filtered = this.patients.filter(p =>
p.firstName.toLowerCase().includes(query.toLowerCase()) ||
p.lastName.toLowerCase().includes(query.toLowerCase()) ||
p.mrn.toLowerCase().includes(query.toLowerCase())
);
grid.innerHTML = filtered.map(p => this.createPatientCard(p, false)).join('');
lucide.createIcons();
}
sortPatients(method) {
// Implementation for sorting
this.renderPatients();
}
setFilter(filter) {
this.currentFilter = filter;
// Update chip styles
document.querySelectorAll('.filter-chip').forEach(chip => {
if (chip.dataset.filter === filter) {
chip.className = 'filter-chip px-3 py-1.5 rounded-full text-xs font-medium bg-emerald-100 text-emerald-700 border border-emerald-200 transition-all';
} else {
chip.className = 'filter-chip px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 border border-gray-200 hover:bg-gray-200 transition-all';
}
});
this.renderPatients();
}
// Walk-in Modal
openWalkInModal() {
document.getElementById('walkin-modal').classList.remove('hidden');
}
closeWalkInModal() {
document.getElementById('walkin-modal').classList.add('hidden');
}
createWalkIn() {
const first = document.getElementById('walkin-first').value;
const last = document.getElementById('walkin-last').value;
const dob = document.getElementById('walkin-dob').value;
const phone = document.getElementById('walkin-phone').value;
if (!first || !last || !dob) {
this.showToast('Please fill in all required fields', 'error');
return;
}
const newPatient = {
id: Date.now(),
firstName: first,
lastName: last,
dob: dob,
mrn: `MRN-${Date.now()}`,
registryId: null,
status: 'waiting',
lastVisit: null,
nextRenewal: null,
conditions: [],
avatar: `${first[0]}${last[0]}`.toUpperCase(),
aiSuggested: false,
reason: 'Walk-in patient'
};
this.patients.unshift(newPatient);
this.selectPatient(newPatient.id);
this.closeWalkInModal();
this.showToast('New walk-in patient created', 'success');
}
// Encounter Details
updateEncounterDetail(field, value) {
this.encounterData.details[field] = value;
if (field === 'severity') {
document.getElementById('severity-value').textContent = value;
}
this.updateProgress();
this.updatePreSummary();
}
addCondition(condition) {
if (!condition || this.encounterData.conditions.includes(condition)) return;
this.encounterData.conditions.push(condition);
this.renderConditionTags();
this.updatePreSummary();
document.getElementById('condition-select').value = '';
}
renderConditionTags() {
const container = document.getElementById('condition-tags');
container.innerHTML = this.encounterData.conditions.map(c => `
<span class="px-3 py-1 bg-emerald-100 text-emerald-700 rounded-full text-sm flex items-center gap-1">
${c}
<button onclick="app.removeCondition('${c}')" class="hover:text-emerald-900">
<i data-lucide="x" class="w-3 h-3"></i>
</button>
</span>
`).join('');
lucide.createIcons();
}
removeCondition(condition) {
this.encounterData.conditions = this.encounterData.conditions.filter(c => c !== condition);
this.renderConditionTags();
this.updatePreSummary();
}
toggleTherapy(therapy) {
if (this.encounterData.therapies.includes(therapy)) {
this.encounterData.therapies = this.encounterData.therapies.filter(t => t !== therapy);
} else {
this.encounterData.therapies.push(therapy);
}
this.updatePreSummary();
}
toggleProductForm(btn, form) {
btn.classList.toggle('bg-emerald-100');
btn.classList.toggle('text-emerald-700');
btn.classList.toggle('border-emerald-500');
if (this.encounterData.productForms.includes(form)) {
this.encounterData.productForms = this.encounterData.productForms.filter(f => f !== form);
} else {
this.encounterData.productForms.push(form);
}
}
updateCompliance(type, value) {
this.encounterData.compliance[type] = value;
this.updatePreSummary();
}
updatePreSummary() {
const qualification = document.getElementById('summary-qualification');
const missing = document.getElementById('summary-missing');
const complexityBar = document.getElementById('complexity-bar');
const complexityText = document.getElementById('complexity-text');
// Update qualification
if (this.encounterData.conditions.length > 0) {
qualification.textContent = 'Likely Eligible - ' + this.encounterData.conditions.join(', ');
qualification.className = 'text-sm font-semibold text-emerald-700';
} else {
qualification.textContent = 'Select conditions to assess';
qualification.className = 'text-sm font-semibold text-gray-500';
}
// Update missing items
const missingItems = [];
if (!this.encounterData.compliance.residency) missingItems.push('Residency verification');
if (!this.encounterData.compliance.identity) missingItems.push('Identity check');
if (!this.encounterData.compliance.consent) missingItems.push('Consent forms');
missing.innerHTML = missingItems.length ? missingItems.map(item => `
<li class="flex items-center gap-1 text-amber-600">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
<span>${item}</span>
</li>
`).join('') : '<li class="flex items-center gap-1 text-emerald-600"><i data-lucide="check" class="w-3 h-3"></i><span>All required items complete</span></li>';
// Update complexity
const complexity = Math.min(100, (this.encounterData.conditions.length * 15) + (this.encounterData.therapies.length * 5) + 10);
complexityBar.style.width = `${complexity}%`;
if (complexity < 30) {
complexityText.textContent = 'Low';
complexityBar.className = 'h-full bg-emerald-500 rounded-full';
} else if (complexity < 70) {
complexityText.textContent = 'Moderate';
complexityBar.className = 'h-full bg-amber-500 rounded-full';
} else {
complexityText.textContent = 'High';
complexityBar.className = 'h-full bg-red-500 rounded-full';
}
lucide.createIcons();
}
simulateUpload() {
this.showToast('Uploading document...', 'info');
setTimeout(() => {
this.showToast('Document uploaded successfully', 'success');
}, 1500);
}
// AI Scribe
initWaveform() {
const container = document.getElementById('waveform-container');
if (!container) return;
container.innerHTML = Array(40).fill(0).map(() =>
`<div class="w-1 bg-emerald-400 rounded-full waveform-bar" style="height: 20%; animation-delay: ${Math.random() * 0.5}s"></div>`
).join('');
}
startRecording() {
this.isRecording = true;
this.isPaused = false;
document.getElementById('recording-status').textContent = 'Recording';
document.getElementById('recording-status').className = 'px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-600 recording-pulse';
document.getElementById('btn-start').classList.add('hidden');
document.getElementById('btn-pause').classList.remove('hidden');
document.getElementById('btn-stop').classList.remove('hidden');
document.getElementById('waveform-container').classList.remove('opacity-30');
this.recordingInterval = setInterval(() => {
this.recordingTime++;
const mins = Math.floor(this.recordingTime / 60).toString().padStart(2, '0');
const secs = (this.recordingTime % 60).toString().padStart(2, '0');
document.getElementById('recording-timer').textContent = `${mins}:${secs}`;
}, 1000);
// Simulate transcript generation
this.simulateTranscriptGeneration();
}
pauseRecording() {
this.isPaused = true;
clearInterval(this.recordingInterval);
document.getElementById('recording-status').textContent = 'Paused';
document.getElementById('recording-status').className = 'px-2 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-600';
document.getElementById('btn-pause').classList.add('hidden');
document.getElementById('btn-resume').classList.remove('hidden');
document.getElementById('recording-overlay').classList.remove('hidden');
document.getElementById('waveform-container').classList.add('opacity-30');
}
resumeRecording() {
this.isPaused = false;
document.getElementById('recording-status').textContent = 'Recording';
document.getElementById('recording-status').className = 'px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-600 recording-pulse';
document.getElementById('btn-resume').classList.add('hidden');
document.getElementById('btn-pause').classList.remove('hidden');
document.getElementById('recording-overlay').classList.add('hidden');
document.getElementById('waveform-container').classList.remove('opacity-30');
this.recordingInterval = setInterval(() => {
this.recordingTime++;
const mins = Math.floor(this.recordingTime / 60).toString().padStart(2, '0');
const secs = (this.recordingTime % 60).toString().padStart(2, '0');
document.getElementById('recording-timer').textContent = `${mins}:${secs}`;
}, 1000);
}
stopRecording() {
this.isRecording = false;
clearInterval(this.recordingInterval);
document.getElementById('recording-status').textContent = 'Completed';
document.getElementById('recording-status').className = 'px-2 py-1 rounded-full text-xs font-medium bg-emerald-100 text-emerald-600';
document.getElementById('btn-pause').classList.add('hidden');
document.getElementById('btn-resume').classList.add('hidden');
document.getElementById('btn-stop').classList.add('hidden');
document.getElementById('btn-start').classList.remove('hidden');
document.getElementById('btn-start').innerHTML = '<i data-lucide="mic" class="w-5 h-5"></i> New Recording';
document.getElementById('waveform-container').classList.add('opacity-30');
document.getElementById('recording-overlay').classList.add('hidden');
this.showToast('Recording saved', 'success');
this.updateProgress();
}
simulateTranscriptGeneration() {
const lines = [
{ speaker: 'Dr', text: 'Good morning, James. How are you feeling today?' },
{ speaker: 'Pt', text: 'Good morning, Doctor. The back pain is still there, maybe a 7 out of 10.' },
{ speaker: 'Dr', text: 'I see. And how about the sleep issues we discussed last time?' },
{ speaker: 'Pt', text: 'Still having nightmares. Maybe 3 or 4 times a week.' },
{ speaker: 'Dr', text: 'Have you tried the physical therapy exercises?' },
{ speaker: 'Pt', text: 'Yes, but they only help for an hour or so, then the pain returns.' }
];
let delay = 0;
lines.forEach((line, index) => {
delay += 2000 + Math.random() * 1000;
setTimeout(() => {
if (this.isRecording && !this.isPaused) {
this.addTranscriptLine(line.speaker, line.text);
}
}, delay);
});
}
addTranscriptLine(speaker, text) {
const container = document.getElementById('transcript-lines');
// Remove placeholder if exists
if (container.querySelector('.italic')) {
container.innerHTML = '';
}
const line = document.createElement('div');
line.className = 'transcript-line flex gap-3 p-2 rounded-lg hover:bg-gray-50';
line.innerHTML = `
<div class="w-16 shrink-0 text-xs font-semibold ${speaker === 'Dr' ? 'text-emerald-600' : 'text-blue-600'}">${speaker}:</div>
<div class="flex-1 text-sm text-gray-700">${text}</div>
<div class="text-xs text-gray-400">${new Date().toLocaleTimeString()}</div>
`;
container.appendChild(line);
container.scrollTop = container.scrollHeight;
// Update word count
const words = container.innerText.split(/\s+/).length;
document.getElementById('word-count').textContent = `${words} words`;
}
simulateConversation() {
this.startRecording();
this.showToast('Simulating conversation...', 'info');
}
uploadAudio() {
this.showToast('Audio upload feature would open file picker', 'info');
}
useSampleVisit() {
document.getElementById('transcript-lines').innerHTML = '';
const sampleLines = [
{ speaker: 'Dr', text: 'Hello James, I see you\'re here for your initial certification visit.' },
{ speaker: 'Pt', text: 'Yes, I\'ve been dealing with chronic pain from an old injury.' },
{ speaker: 'Dr', text: 'Can you describe the pain for me?' },
{ speaker: 'Pt', text: 'It\'s a sharp burning sensation in my lower back, radiating down my left leg.' },
{ speaker: 'Dr', text: 'Rate the pain on a scale of 1-10.' },
{ speaker: 'Pt', text: 'Usually around a 7, sometimes 8 in the evenings.' },
{ speaker: 'Dr', text: 'What treatments have you tried?' },
{ speaker: 'Pt', text: 'Physical therapy, ibuprofen, gabapentin... nothing really helps long-term.' },
{ speaker: 'Dr', text: 'I see. And you mentioned PTSD in your intake form?' },
{ speaker: 'Pt', text: 'Yes, from my time in the service. The nightmares are the worst part.' }
];
sampleLines.forEach((line, index) => {
setTimeout(() => {
this.addTranscriptLine(line.speaker, line.text);
}, index * 500);
});
this.showToast('Sample visit loaded', 'success');
}
switchTab(tabName) {
// Hide all tabs
document.querySelectorAll('.tab-panel').forEach(p => p.classList.add('hidden'));
document.getElementById(`tab-${tabName}`).classList.remove('hidden');
// Update button styles
document.querySelectorAll('.tab-btn').forEach(btn => {
if (btn.dataset.tab === tabName) {
btn.className = 'tab-btn active px-3 py-1.5 text-xs font-medium rounded-md bg-emerald-100 text-emerald-700';
} else {
btn.className = 'tab-btn px-3 py-1.5 text-xs font-medium rounded-md text-gray-600 hover:bg-gray-100';
}
});
}
clearTranscript() {
document.getElementById('transcript-lines').innerHTML = '<div class="text-sm text-gray-400 italic text-center py-8">Recording not started. Click "Start Recording" or "Simulate Conversation" to begin.</div>';
document.getElementById('word-count').textContent = '0 words';
}
copyTranscript() {
const text = document.getElementById('transcript-lines').innerText;
navigator.clipboard.writeText(text);
this.showToast('Transcript copied to clipboard', 'success');
}
expandTranscript() {
this.showToast('Expand view would open full-screen transcript', 'info');
}
insertCommand(command) {
this.showToast(`Voice command: "${command}"`, 'info');
if (this.isRecording && !this.isPaused) {
setTimeout(() => {
this.addTranscriptLine('System', `[AI Command: ${command}]`);
}, 500);
}
}
// AI Notes
generateAINotes() {
const btn = document.getElementById('btn-generate-notes');
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Generating...';
lucide.createIcons();
setTimeout(() => {
document.getElementById('ai-notes-container').style.opacity = '1';
document.getElementById('ai-notes-container').style.pointerEvents = 'auto';
document.getElementById('quality-score-card').classList.remove('hidden');
btn.innerHTML = '<i data-lucide="refresh-cw" class="w-5 h-5"></i> Regenerate';
lucide.createIcons();
this.showToast('AI notes generated successfully', 'success');
this.updateProgress();
}, 2000);
}
regenerateSection(section) {
this.showToast(`Regenerating ${section} section...`, 'info');
setTimeout(() => {
this.showToast(`${section} section updated`, 'success');
}, 1000);
}
lockSection(section) {
this.showToast(`${section} section locked`, 'success');
}
selectCode(element) {
document.querySelectorAll('#coding-suggestions > div').forEach(el => {
el.classList.remove('bg-purple-50', 'border-purple-300');
});
element.classList.add('bg-purple-50', 'border-purple-300');
}
showMoreCodes() {
this.showToast('Additional coding suggestions would appear here', 'info');
}
jumpToSection(section) {
this.showToast(`Navigating to ${section}...`, 'info');
}
generateCertPDF() {
this.showToast('Generating certification PDF...', 'info');
setTimeout(() => {
this.showToast('PDF generated and ready for download', 'success');
}, 1500);
}
generateRegistryDoc() {
this.showToast('Preparing registry documents...', 'info');
}
sendToRegistry() {
this.showToast('Submitting to state registry...', 'info');
setTimeout(() => {
this.showToast('Registry submission successful', 'success');
}, 2000);
}
// Review & Finalize
toggleComplianceCheck(checkbox, type) {
this.showToast(`${type} compliance ${checkbox.checked ? 'verified' : 'pending'}`, 'info');
}
submitEncounter() {
this.showToast('Submitting encounter...', 'info');
setTimeout(() => {
this.showToast('Encounter submitted successfully!', 'success');
setTimeout(() => {
if (confirm('Encounter complete! Return to dashboard?')) {
this.showToast('Returning to dashboard...', 'info');
}
}, 500);
}, 2000);
}
saveDraft() {
this.showToast('Draft saved successfully', 'success');
}
scheduleFollowUp() {
this.showToast('Opening scheduling calendar...', 'info');
}
previewDoc(type) {
const titles = {
soap: 'SOAP Note Preview',
cert: 'Certification Letter Preview',
registry: 'Registry Form Preview',
bill: 'Encounter Bill Preview'
};
document.getElementById('preview-title').textContent = titles[type];
document.getElementById('preview-content').innerHTML = `
<div class="bg-white p-8 rounded-lg shadow-sm border">
<div class="text-center mb-6">
<h2 class="text-2xl font-bold text-gray-900">${titles[type]}</h2>
<p class="text-gray-500">Verify MC Medical Marijuana Platform</p>
</div>
<div class="space-y-4 text-sm text-gray-700">
<div class="border-b pb-2"><strong>Patient:</strong> ${this.selectedPatient?.firstName} ${this.selectedPatient?.lastName}</div>
<div class="border-b pb-2"><strong>Date:</strong> ${new Date().toLocaleDateString()}</div>
<div class="border-b pb-2"><strong>Provider:</strong> Dr. Sarah Chen, MD</div>
<div class="h-32 bg-gray-100 rounded flex items-center justify-center text-gray-400">Document content would appear here</div>
</div>
</div>
`;
document.getElementById('preview-modal').classList.remove('hidden');
}
closePreview() {
document.getElementById('preview-modal').classList.add('hidden');
}
downloadPreview() {
this.showToast('Downloading PDF...', 'success');
}
// AI Copilot
toggleCopilot() {
this.copilotOpen = !this.copilotOpen;
const panel = document.getElementById('copilot-panel');
if (this.copilotOpen) {
panel.classList.remove('hidden');
document.getElementById('copilot-badge').classList.add('hidden');
} else {
panel.classList.add('hidden');
}
}
sendCopilotMessage() {
const input = document.getElementById('copilot-input');
const message = input.value.trim();
if (!message) return;
this.addCopilotMessage('user', message);
input.value = '';
// Simulate AI response
setTimeout(() => {
const responses = [
'Based on the patient profile, I recommend documenting chronic pain with severity rating.',
'Would you like me to suggest appropriate dosing guidelines for this condition?',
'I can help generate the assessment section. Should I focus on prior treatment failures?',
'Reminder: This patient needs registry ID verification before submission.'
];
const randomResponse = responses[Math.floor(Math.random() * responses.length)];
this.addCopilotMessage('ai', randomResponse);
}, 1000);
}
quickAsk(query) {
document.getElementById('copilot-input').value = query;
this.sendCopilotMessage();
}
addCopilotMessage(sender, text) {
const container = document.getElementById('copilot-messages');
const messageDiv = document.createElement('div');
if (sender === 'ai') {
messageDiv.className = 'p-3 bg-blue-50 rounded-lg border border-blue-200';
messageDiv.innerHTML = `
<div class="flex items-start gap-2">
<i data-lucide="sparkles" class="w-4 h-4 text-blue-600 mt-0.5"></i>
<div class="text-sm text-blue-900">${text}</div>
</div>
`;
} else {
messageDiv.className = 'p-3 bg-gray-100 rounded-lg border border-gray-200 ml-8';
messageDiv.innerHTML = `<div class="text-sm text-gray-900">${text}</div>`;
}
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
lucide.createIcons();
}
// Progress & Status
updateProgress() {
let completed = 0;
const total = 5;
if (this.selectedPatient) completed++;
if (this.encounterData.conditions.length > 0) completed++;
if (this.recordingTime > 0) completed++;
if (document.getElementById('ai-notes-container')?.style.opacity === '1') completed++;
if (this.currentStep === 5) completed = 5;
const percentage = Math.round((completed / total) * 100);
// Update circle progress
const circle = document.getElementById('progress-circle');
const radius = 20;
const circumference = radius * 2 * Math.PI;
const offset = circumference - (percentage / 100) * circumference;
if (circle) {
circle.style.strokeDashoffset = offset;
}
document.getElementById('progress-text').textContent = `${percentage}%`;
// Update title and badges
const titles = ['Needs Data', 'Getting Started', 'In Progress', 'Almost Complete', 'Ready to Submit'];
const titleIndex = Math.floor((completed / total) * (titles.length - 1));
document.getElementById('readiness-title').textContent = titles[titleIndex];
const missingItems = [];
if (!this.selectedPatient) missingItems.push('Select patient');
if (this.encounterData.conditions.length === 0) missingItems.push('Add conditions');
if (this.recordingTime === 0) missingItems.push('Record encounter');
document.getElementById('missing-items').textContent = missingItems.length > 0
? `Missing: ${missingItems.join(', ')}`
: 'All requirements met';
// Update status badges
const badgesContainer = document.getElementById('status-badges');
if (percentage === 100) {
badgesContainer.innerHTML = '<span class="px-3 py-1 rounded-full text-xs font-medium bg-emerald-100 text-emerald-700">Ready to Submit</span>';
} else if (percentage > 50) {
badgesContainer.innerHTML = '<span class="px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700">In Progress</span>';
} else {
badgesContainer.innerHTML = '<span class="px-3 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-600">Draft</span>';
}
}
// Auto-save
startAutoSave() {
setInterval(() => {
const now = new Date();
const timeStr = now.toLocaleTimeString();
const statusEl = document.getElementById('auto-save-status');
if (statusEl) {
statusEl.textContent = `Auto-saved ${Math.floor(Math.random() * 10) + 1}s ago`;
}
}, 10000);
}
// Footer Actions
quickAction(action) {
switch(action) {
case 'help':
this.toggleCopilot();
break;
case 'calculator':
this.showToast('Dosage calculator would open', 'info');
break;
case 'print':
window.print();
break;
}
}
saveAndExit() {
this.showToast('Saving encounter...', 'info');
setTimeout(() => {
this.showToast('Saved! You can safely exit.', 'success');
}, 1000);
}
submitFinal() {
this.submitEncounter();
}
// Utilities
showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
const colors = {
success: 'bg-emerald-50 border-emerald-200 text-emerald-800',
error: 'bg-red-50 border-red-200 text-red-800',
info: 'bg-blue-50 border-blue-200 text-blue-800',
warning: 'bg-amber-50 border-amber-200 text-amber-800'
};
const icons = {
success: 'check-circle',
error: 'x-circle',
info: 'info',
warning: 'alert-triangle'
};
toast.className = `flex items-center gap-2 px-4 py-3 rounded-lg border shadow-lg ${colors[type]} fade-in`;
toast.innerHTML = `
<i data-lucide="${icons[type]}" class="w-5 h-5"></i>
<span class="text-sm font-medium">${message}</span>
`;
container.appendChild(toast);
lucide.createIcons();
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px)';
setTimeout(() => toast.remove(), 300);
}, 4000);
}
}
// Initialize app
const app = new VerifyMCApp();