thors1's picture
Initial DeepSite commit
20345c8 verified
// Initialize Lucide Icons
lucide.createIcons();
// Page Management
const pages = {
'dashboard': {
title: 'Enterprise Dashboard',
subtitle: 'Real-time analytics and reporting'
},
'revenue': {
title: 'Revenue Analytics',
subtitle: 'Detailed revenue breakdown and forecasting'
},
'patient-intelligence': {
title: 'Patient Intelligence',
subtitle: 'Demographics, conditions, and patient analytics'
},
'expiring-patients': {
title: 'Expiring Patient Reports',
subtitle: 'Monitor and manage patient expiration risk'
},
'renewal-reports': {
title: 'Renewal & Approval Reports',
subtitle: 'Track renewal rates and approval metrics'
},
'provider-performance': {
title: 'Provider Performance',
subtitle: 'Provider analytics and performance tracking'
},
'appointment-reports': {
title: 'Appointment Reports',
subtitle: 'Appointment analytics and scheduling insights'
},
'one-click-reports': {
title: 'One-Click Reports',
subtitle: 'Pre-built reports for instant insights'
},
'custom-reports': {
title: 'Custom Reports Builder',
subtitle: 'Build, save, and schedule custom reports'
}
};
// Chart instances storage
const charts = {};
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
initializeCharts();
initializeEventListeners();
});
// Page Navigation
function showPage(pageId) {
// Hide all pages
document.querySelectorAll('.page').forEach(page => {
page.classList.remove('active');
});
// Show selected page
const pageElement = document.getElementById(pageId);
if (pageElement) {
pageElement.classList.add('active');
// Update page title
const pageInfo = pages[pageId];
if (pageInfo) {
document.getElementById('page-title').textContent = pageInfo.title;
document.getElementById('page-subtitle').textContent = pageInfo.subtitle;
}
// Update navigation active state
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active', 'bg-primary-light', 'text-primary');
item.classList.add('text-gray-700', 'hover:bg-gray-100');
});
const activeNav = document.querySelector(`[onclick="showPage('${pageId}')"]`);
if (activeNav) {
activeNav.classList.add('active', 'bg-primary-light', 'text-primary');
activeNav.classList.remove('text-gray-700', 'hover:bg-gray-100');
}
// Refresh charts for the new page
setTimeout(() => {
refreshPageCharts(pageId);
}, 100);
}
}
// Slide-out Panel Management
function openSlideOut(panelId) {
const panel = document.getElementById('slide-out-panel');
panel.classList.add('open');
// Load panel content based on panelId
loadPanelContent(panelId);
}
function closeSlideOut() {
const panel = document.getElementById('slide-out-panel');
panel.classList.remove('open');
}
// Modal Management
function openModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.remove('hidden');
modal.classList.add('flex');
}
}
function closeModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
}
// Initialize Charts
function initializeCharts() {
// Revenue Trend Chart
const revenueCtx = document.getElementById('revenueTrendChart');
if (revenueCtx) {
charts.revenueTrend = new Chart(revenueCtx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: [{
label: 'Revenue ($)',
data: [320000, 350000, 380000, 420000, 450000, 480000, 510000, 540000, 580000, 620000, 650000, 690000],
borderColor: '#0e9692',
backgroundColor: 'rgba(14, 150, 146, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
y: {
beginAtZero: false,
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
callback: function(value) {
return '$' + (value / 1000).toFixed(0) + 'K';
}
}
},
x: {
grid: {
display: false
}
}
}
}
});
}
// Patient Distribution Chart
const patientCtx = document.getElementById('patientDistributionChart');
if (patientCtx) {
charts.patientDistribution = new Chart(patientCtx, {
type: 'doughnut',
data: {
labels: ['Chronic Pain', 'Anxiety', 'Sleep Disorders', 'PTSD', 'Cancer', 'Other'],
datasets: [{
data: [35, 25, 15, 12, 8, 5],
backgroundColor: [
'#0e9692',
'#10b981',
'#3b82f6',
'#8b5cf6',
'#ec4899',
'#6b7280'
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
}
}
}
});
}
// Expiring Chart
const expiringCtx = document.getElementById('expiringChart');
if (expiringCtx) {
charts.expiring = new Chart(expiringCtx, {
type: 'doughnut',
data: {
labels: ['Expiring', 'Active'],
datasets: [{
data: [312, 8430],
backgroundColor: ['#f59e0b', '#0e9692'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
}
}
});
}
// Revenue by Category Chart
const categoryCtx = document.getElementById('revenueByCategoryChart');
if (categoryCtx) {
charts.revenueByCategory = new Chart(categoryCtx, {
type: 'bar',
data: {
labels: ['Consultations', 'Products', 'Renewals', 'Follow-ups', 'Other'],
datasets: [{
label: 'Revenue ($)',
data: [185000, 245000, 98000, 65000, 42000],
backgroundColor: '#0e9692',
borderRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
callback: function(value) {
return '$' + (value / 1000).toFixed(0) + 'K';
}
}
},
x: {
grid: {
display: false
}
}
}
}
});
}
// Monthly Revenue Chart
const monthlyCtx = document.getElementById('monthlyRevenueChart');
if (monthlyCtx) {
charts.monthlyRevenue = new Chart(monthlyCtx, {
type: 'line',
data: {
labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4'],
datasets: [{
label: 'Current Month',
data: [85000, 92000, 98000, 105000],
borderColor: '#0e9692',
backgroundColor: 'rgba(14, 150, 146, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
}, {
label: 'Previous Month',
data: [82000, 88000, 91000, 95000],
borderColor: '#9ca3af',
backgroundColor: 'transparent',
borderWidth: 2,
borderDash: [5, 5],
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: {
usePointStyle: true
}
}
},
scales: {
y: {
beginAtZero: false,
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
callback: function(value) {
return '$' + (value / 1000).toFixed(0) + 'K';
}
}
},
x: {
grid: {
display: false
}
}
}
}
});
}
// Age Distribution Chart
const ageCtx = document.getElementById('ageDistributionChart');
if (ageCtx) {
charts.ageDistribution = new Chart(ageCtx, {
type: 'bar',
data: {
labels: ['18-25', '26-35', '36-45', '46-55', '56-65', '65+'],
datasets: [{
label: 'Patients',
data: [12, 28, 35, 25, 15, 5],
backgroundColor: '#0e9692',
borderRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
callback: function(value) {
return value + '%';
}
}
},
x: {
grid: {
display: false
}
}
}
}
});
}
// Conditions Chart
const conditionsCtx = document.getElementById('conditionsChart');
if (conditionsCtx) {
charts.conditions = new Chart(conditionsCtx, {
type: 'polarArea',
data: {
labels: ['Chronic Pain', 'Anxiety', 'Insomnia', 'PTSD', 'Cancer', 'Migraine'],
datasets: [{
data: [42, 28, 15, 8, 4, 3],
backgroundColor: [
'#0e9692',
'#10b981',
'#3b82f6',
'#8b5cf6',
'#ec4899',
'#f59e0b'
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
}
}
}
});
}
// Growth Gauge Chart
const growthCtx = document.getElementById('growthGauge');
if (growthCtx) {
charts.growthGauge = new Chart(growthCtx, {
type: 'doughnut',
data: {
labels: ['Growth', 'Remaining'],
datasets: [{
data: [24.8, 75.2],
backgroundColor: ['#0e9692', '#e5e7eb'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return context.label + ': ' + context.parsed + '%';
}
}
}
},
cutout: '80%'
}
});
}
// Provider Trend Chart for slide-out
const providerTrendCtx = document.getElementById('providerTrendChart');
if (providerTrendCtx) {
charts.providerTrend = new Chart(providerTrendCtx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Satisfaction Score',
data: [88, 90, 92, 91, 94, 95],
borderColor: '#0e9692',
backgroundColor: 'rgba(14, 150, 146, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: false,
min: 85,
max: 100,
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.05)'
},
ticks: {
callback: function(value) {
return value + '%';
}
}
},
x: {
grid: {
display: false
}
}
}
}
});
}
}
// Initialize Event Listeners
function initializeEventListeners() {
// Close modals when clicking outside
document.querySelectorAll('[id$="-modal"]').forEach(modal => {
modal.addEventListener('click', function(e) {
if (e.target === this) {
closeModal(modal.id);
}
});
});
// Close slide-out when clicking outside
const slideOut = document.getElementById('slide-out-panel');
if (slideOut) {
slideOut.addEventListener('click', function(e) {
if (e.target === this) {
closeSlideOut();
}
});
}
// Close report detail panel when clicking outside
const reportPanel = document.getElementById('report-detail-panel');
if (reportPanel) {
reportPanel.addEventListener('click', function(e) {
if (e.target === this) {
closeSlideOut();
}
});
}
// Escape key to close modals
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeSlideOut();
closeModal('report-detail-panel');
document.querySelectorAll('[id$="-modal"]').forEach(modal => {
if (!modal.classList.contains('hidden')) {
closeModal(modal.id);
}
});
}
});
// Initialize drag and drop for custom reports
initializeDragAndDrop();
}
// Refresh charts for specific page
function refreshPageCharts(pageId) {
switch(pageId) {
case 'dashboard':
if (charts.revenueTrend) charts.revenueTrend.update();
if (charts.patientDistribution) charts.patientDistribution.update();
if (charts.expiring) charts.expiring.update();
break;
case 'revenue':
if (charts.revenueByCategory) charts.revenueByCategory.update();
if (charts.monthlyRevenue) charts.monthlyRevenue.update();
if (charts.growthGauge) charts.growthGauge.update();
break;
case 'patient-intelligence':
if (charts.ageDistribution) charts.ageDistribution.update();
if (charts.conditions) charts.conditions.update();
break;
}
}
// Load panel content
function loadPanelContent(panelId) {
// In a real application, this would fetch content from a server
// For demo, we'll just update some elements
console.log('Loading panel content for:', panelId);
}
// Generate Report
function generateReport(reportType) {
const reports = {
'monthly-performance': {
title: 'Monthly Performance Report',
message: 'Generating comprehensive monthly performance report...'
},
'provider-metrics': {
title: 'Provider Metrics Report',
message: 'Creating detailed provider performance analysis...'
},
'expiration-risk': {
title: 'Expiration Risk Report',
message: 'Compiling expiration risk assessment...'
},
'ai-recommendations': {
title: 'AI Recommendations Report',
message: 'Generating personalized AI recommendations...'
}
};
const report = reports[reportType];
if (report) {
// Show loading state
const button = event.target.closest('button');
const originalText = button ? button.innerHTML : '';
if (button) {
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Generating...';
button.disabled = true;
}
// Simulate report generation
setTimeout(() => {
if (button) {
button.innerHTML = originalText;
button.disabled = false;
}
// Show success message
alert(`βœ… ${report.title} generated successfully!`);
// In a real app, this would download the report
console.log(`Generated report: ${reportType}`);
}, 1500);
}
}
// Export Report
function exportReport() {
// Show loading
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Exporting...';
button.disabled = true;
// Simulate export process
setTimeout(() => {
button.innerHTML = originalText;
button.disabled = false;
closeModal('export-modal');
// Show success
alert('βœ… Report exported successfully! The download will begin shortly.');
}, 2000);
}
// Initialize tooltips
function initializeTooltips() {
// This would initialize tooltip library in a real app
console.log('Tooltips initialized');
}
// Custom Reports Functions
function initializeDragAndDrop() {
const draggableComponents = document.querySelectorAll('.draggable-component');
const reportCanvas = document.getElementById('report-canvas');
draggableComponents.forEach(component => {
component.addEventListener('dragstart', function(e) {
e.dataTransfer.setData('text/plain', this.dataset.component);
this.classList.add('opacity-50');
});
component.addEventListener('dragend', function() {
this.classList.remove('opacity-50');
});
});
if (reportCanvas) {
reportCanvas.addEventListener('dragover', function(e) {
e.preventDefault();
this.classList.add('border-primary', 'border-solid');
this.classList.remove('border-dashed', 'border-gray-300');
});
reportCanvas.addEventListener('dragleave', function() {
this.classList.remove('border-primary', 'border-solid');
this.classList.add('border-dashed', 'border-gray-300');
});
reportCanvas.addEventListener('drop', function(e) {
e.preventDefault();
this.classList.remove('border-primary', 'border-solid');
this.classList.add('border-dashed', 'border-gray-300');
const componentType = e.dataTransfer.getData('text/plain');
addComponentToCanvas(componentType);
});
}
}
function addComponentToCanvas(type) {
const canvas = document.getElementById('report-canvas');
// Remove placeholder if present
const placeholder = canvas.querySelector('.text-center');
if (placeholder) {
placeholder.remove();
}
let componentHTML = '';
switch(type) {
case 'kpi':
componentHTML = `
<div class="bg-gray-50 rounded-lg p-6 border border-gray-200">
<div class="flex items-center justify-between mb-4">
<h4 class="font-medium text-gray-900">KPI Component</h4>
<button onclick="removeComponent(this)" class="text-gray-400 hover:text-gray-600">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="bg-white rounded-lg p-4">
<p class="text-sm text-gray-600">Metric 1</p>
<p class="text-xl font-bold text-gray-900">$12,450</p>
</div>
<div class="bg-white rounded-lg p-4">
<p class="text-sm text-gray-600">Metric 2</p>
<p class="text-xl font-bold text-gray-900">84.2%</p>
</div>
</div>
</div>
`;
break;
case 'chart':
componentHTML = `
<div class="bg-gray-50 rounded-lg p-6 border border-gray-200">
<div class="flex items-center justify-between mb-4">
<h4 class="font-medium text-gray-900">Chart Component</h4>
<div class="flex items-center space-x-2">
<select class="border border-gray-300 rounded-lg px-2 py-1 text-sm">
<option>Line Chart</option>
<option>Bar Chart</option>
<option>Pie Chart</option>
</select>
<button onclick="removeComponent(this)" class="text-gray-400 hover:text-gray-600">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
</div>
<div class="h-48 bg-white rounded-lg flex items-center justify-center">
<div class="text-center">
<i data-lucide="pie-chart" class="w-8 h-8 text-gray-400 mx-auto mb-2"></i>
<p class="text-sm text-gray-600">Chart preview will appear here</p>
</div>
</div>
</div>
`;
break;
case 'table':
componentHTML = `
<div class="bg-gray-50 rounded-lg p-6 border border-gray-200">
<div class="flex items-center justify-between mb-4">
<h4 class="font-medium text-gray-900">Table Component</h4>
<button onclick="removeComponent(this)" class="text-gray-400 hover:text-gray-600">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="bg-white rounded-lg overflow-hidden">
<table class="min-w-full">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Column 1</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Column 2</th>
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">Column 3</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-gray-200">
<td class="px-4 py-2 text-sm text-gray-700">Data 1</td>
<td class="px-4 py-2 text-sm text-gray-700">Data 2</td>
<td class="px-4 py-2 text-sm text-gray-700">Data 3</td>
</tr>
<tr>
<td class="px-4 py-2 text-sm text-gray-700">Data 4</td>
<td class="px-4 py-2 text-sm text-gray-700">Data 5</td>
<td class="px-4 py-2 text-sm text-gray-700">Data 6</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
break;
case 'filter':
componentHTML = `
<div class="bg-gray-50 rounded-lg p-6 border border-gray-200">
<div class="flex items-center justify-between mb-4">
<h4 class="font-medium text-gray-900">Filter Component</h4>
<button onclick="removeComponent(this)" class="text-gray-400 hover:text-gray-600">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<div class="flex flex-wrap gap-3">
<select class="border border-gray-300 rounded-lg px-4 py-2 text-sm">
<option>Date Range</option>
<option>Last 7 days</option>
<option>Last 30 days</option>
<option>Last quarter</option>
</select>
<select class="border border-gray-300 rounded-lg px-4 py-2 text-sm">
<option>Location</option>
<option>All locations</option>
<option>Downtown Clinic</option>
<option>Westside Clinic</option>
</select>
<select class="border border-gray-300 rounded-lg px-4 py-2 text-sm">
<option>Provider</option>
<option>All providers</option>
<option>Dr. Smith</option>
<option>Dr. Johnson</option>
</select>
</div>
</div>
`;
break;
}
canvas.insertAdjacentHTML('beforeend', componentHTML);
lucide.createIcons();
}
function removeComponent(button) {
const component = button.closest('.bg-gray-50');
if (component) {
component.remove();
// Show placeholder if canvas is empty
const canvas = document.getElementById('report-canvas');
if (canvas.children.length === 0) {
canvas.innerHTML = `
<div class="text-center py-12">
<i data-lucide="layout" class="w-12 h-12 text-gray-400 mx-auto mb-4"></i>
<p class="text-gray-600">Drag and drop components here to build your report</p>
<p class="text-sm text-gray-500 mt-1">Start by dragging components from the left panel</p>
</div>
`;
}
}
}
function clearCanvas() {
const canvas = document.getElementById('report-canvas');
if (canvas) {
canvas.innerHTML = `
<div class="text-center py-12">
<i data-lucide="layout" class="w-12 h-12 text-gray-400 mx-auto mb-4"></i>
<p class="text-gray-600">Drag and drop components here to build your report</p>
<p class="text-sm text-gray-500 mt-1">Start by dragging components from the left panel</p>
</div>
`;
}
}
function saveCustomReport() {
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Saving...';
button.disabled = true;
setTimeout(() => {
button.innerHTML = originalText;
button.disabled = false;
alert('βœ… Custom report saved successfully!');
}, 1500);
}
function generateCustomReport() {
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Generating...';
button.disabled = true;
setTimeout(() => {
button.innerHTML = originalText;
button.disabled = false;
alert('βœ… Custom report generated successfully!\n\nThe report has been added to your saved reports and is ready for export.');
}, 2000);
}
function previewReport() {
alert('πŸ” Report Preview\n\nThis would open a preview of your custom report in a new window or modal.');
}
function createNewReport() {
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Creating...';
button.disabled = true;
setTimeout(() => {
button.innerHTML = originalText;
button.disabled = false;
closeModal('new-report-modal');
alert('βœ… New report template created!\n\nYou can now start building your custom report by dragging components to the canvas.');
}, 1500);
}
function runReport(reportId) {
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Running...';
button.disabled = true;
setTimeout(() => {
button.innerHTML = originalText;
button.disabled = false;
alert(`βœ… Report "${reportId}" executed successfully!\n\nThe report has been generated and is ready for review.`);
}, 2000);
}
function deleteReport(reportId) {
if (confirm(`Are you sure you want to delete the "${reportId}" report? This action cannot be undone.`)) {
const button = event.target;
const row = button.closest('tr');
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i>';
button.disabled = true;
setTimeout(() => {
row.style.opacity = '0.5';
setTimeout(() => {
row.remove();
alert(`βœ… Report "${reportId}" has been deleted.`);
}, 300);
}, 1000);
}
}
function saveReportConfig() {
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Saving...';
button.disabled = true;
setTimeout(() => {
button.innerHTML = originalText;
button.disabled = false;
closeSlideOut();
alert('βœ… Report configuration saved successfully!');
}, 1500);
}
function testReport() {
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Testing...';
button.disabled = true;
setTimeout(() => {
button.innerHTML = originalText;
button.disabled = false;
alert('βœ… Report test completed successfully!\n\nThe test report has been generated and sent to your email for review.');
}, 2000);
}
// Search functionality
function initializeSearch() {
const searchInput = document.querySelector('input[placeholder="Search reports..."]');
if (searchInput) {
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const query = this.value.trim();
if (query) {
performSearch(query);
}
}
});
}
}
function performSearch(query) {
console.log('Searching for:', query);
// In a real app, this would make an API call
alert(`Searching for: ${query}\n\nThis would show search results in a real application.`);
}
// Initialize search on load
initializeSearch();