diff --git "a/chatbot/admin_advanced.js" "b/chatbot/admin_advanced.js" new file mode 100644--- /dev/null +++ "b/chatbot/admin_advanced.js" @@ -0,0 +1,3264 @@ +/** + * AIMHSA Advanced Admin Dashboard JavaScript + * Enhanced functionality with AdminLTE 4, DataTables, Charts, and more + */ + +(() => { + 'use strict'; + + // Get API Configuration from Config Manager + const getAPIRoot = () => { + if (window.AIMHSA && window.AIMHSA.Config) { + return window.AIMHSA.Config.getApiBaseUrl(); + } + + // Fallback to intelligent detection + let apiRoot; + try { + const loc = window.location; + if (loc.port === '8000') { + apiRoot = `${loc.protocol}//${loc.hostname}:5057`; + } else if (loc.port === '5057' || loc.port === '') { + apiRoot = loc.origin; + } else { + apiRoot = 'http://localhost:5057'; + } + } catch (_) { + apiRoot = 'http://localhost:5057'; + } + return apiRoot; + }; + + const API_ROOT = getAPIRoot(); + + // Global variables + let currentSection = 'dashboard'; + let charts = {}; + let dataTables = {}; + let currentProfessionalId = null; + let currentUser = null; + let userRole = 'admin'; // Default role + + // Initialize when DOM is ready + $(document).ready(function() { + console.log('Admin Dashboard Initializing...'); + + try { + // Check if jQuery is loaded + if (typeof $ === 'undefined') { + console.error('jQuery not loaded'); + showErrorMessage('jQuery library not loaded. Please refresh the page.'); + return; + } + + console.log('jQuery loaded'); + + // Check user authentication and role + checkUserAuthentication(); + + // Initialize components + initializeAdminLTE(); + initializeNavigation(); + initializeDataTables(); + initializeCharts(); + initializeSelect2(); + initializeExpertiseAreas(); + initializeEventHandlers(); + + // Load dashboard data with error handling + loadDashboardData(); + + // Start auto-refresh + startAutoRefresh(); + + console.log(' Admin Dashboard initialized successfully'); + + // Show success message + setTimeout(() => { + if (typeof Swal !== 'undefined') { + Swal.fire({ + title: 'Dashboard Ready!', + text: `Welcome ${currentUser?.username || 'Admin'}! Dashboard loaded successfully.`, + icon: 'success', + timer: 2000, + toast: true, + position: 'top-end' + }); + } + }, 1000); + + } catch (error) { + console.error(' Error initializing admin dashboard:', error); + showErrorMessage('Dashboard initialization failed: ' + error.message); + } + }); + + /** + * Check user authentication and role + */ + function checkUserAuthentication() { + console.log('๐Ÿ” Checking user authentication...'); + + // Check localStorage for user session + const adminSession = localStorage.getItem('aimhsa_admin'); + const professionalSession = localStorage.getItem('aimhsa_professional'); + const userSession = localStorage.getItem('aimhsa_account'); + + if (adminSession) { + try { + currentUser = JSON.parse(adminSession); + userRole = 'admin'; + console.log(' Admin user authenticated:', currentUser.username); + updateUserInterface(); + } catch (error) { + console.warn(' Invalid admin session, using default'); + setDefaultUser(); + } + } else if (professionalSession) { + try { + currentUser = JSON.parse(professionalSession); + userRole = 'professional'; + console.log(' Professional user authenticated:', currentUser.username); + updateUserInterface(); + } catch (error) { + console.warn(' Invalid professional session, using default'); + setDefaultUser(); + } + } else if (userSession) { + try { + currentUser = JSON.parse(userSession); + userRole = 'user'; + console.log(' Regular user authenticated:', currentUser.username); + updateUserInterface(); + } catch (error) { + console.warn(' Invalid user session, using default'); + setDefaultUser(); + } + } else { + console.warn(' No user session found, using default admin'); + setDefaultUser(); + } + } + + /** + * Set default user when no session is found + */ + function setDefaultUser() { + currentUser = { + username: 'admin', + email: 'admin@aimhsa.rw', + fullname: 'System Administrator', + role: 'admin' + }; + userRole = 'admin'; + updateUserInterface(); + } + + /** + * Update user interface based on current user + */ + function updateUserInterface() { + console.log('๐Ÿ‘ค Updating user interface for:', currentUser.username, 'Role:', userRole); + + // Update sidebar user info + $('.user-panel .info a').text(currentUser.fullname || currentUser.username); + $('.user-panel .info small').text(getRoleDisplayName(userRole)); + + // Update navbar user info + $('.navbar-nav .nav-item:last-child .nav-link span').text(currentUser.fullname || currentUser.username); + + // Update page title based on role + if (userRole === 'professional') { + $('#pageTitle').text('Professional Dashboard'); + $('.brand-text').text('AIMHSA Professional'); + } else if (userRole === 'user') { + $('#pageTitle').text('User Dashboard'); + $('.brand-text').text('AIMHSA User'); + } else { + $('#pageTitle').text('Admin Dashboard'); + $('.brand-text').text('AIMHSA Admin'); + } + + // Show/hide sections based on role + updateNavigationForRole(); + } + + /** + * Get display name for user role + */ + function getRoleDisplayName(role) { + const roleNames = { + 'admin': 'System Administrator', + 'professional': 'Mental Health Professional', + 'user': 'User Account' + }; + return roleNames[role] || 'User'; + } + + /** + * Update navigation based on user role + */ + function updateNavigationForRole() { + if (userRole === 'professional') { + // Hide admin-only sections + $('.nav-item[data-section="professionals"]').hide(); + $('.nav-item[data-section="reports"]').hide(); + $('.nav-item[data-section="settings"]').hide(); + + // Show professional-specific sections + $('.nav-item[data-section="bookings"]').show(); + $('.nav-item[data-section="risk-monitor"]').show(); + $('.nav-item[data-section="analytics"]').show(); + } else if (userRole === 'user') { + // Hide admin and professional sections + $('.nav-item[data-section="professionals"]').hide(); + $('.nav-item[data-section="reports"]').hide(); + $('.nav-item[data-section="settings"]').hide(); + $('.nav-item[data-section="bookings"]').hide(); + + // Show user-specific sections + $('.nav-item[data-section="risk-monitor"]').show(); + $('.nav-item[data-section="analytics"]').show(); + } else { + // Admin - show all sections + $('.nav-item').show(); + } + } + + /** + * Show error message to user + */ + function showErrorMessage(message) { + const errorHtml = ` + + `; + $('.content-wrapper').prepend(errorHtml); + } + + /** + * Initialize AdminLTE components + */ + function initializeAdminLTE() { + console.log('๐Ÿ”ง Initializing AdminLTE components...'); + + try { + // Initialize push menu with fallback + if (typeof $.fn.PushMenu !== 'undefined') { + $('[data-widget="pushmenu"]').PushMenu('toggle'); + } else { + // Fallback for push menu + $('[data-widget="pushmenu"]').on('click', function(e) { + e.preventDefault(); + $('body').toggleClass('sidebar-collapse'); + }); + } + + // Initialize tooltips + if (typeof $.fn.tooltip !== 'undefined') { + $('[data-toggle="tooltip"]').tooltip(); + } + + // Initialize popovers + if (typeof $.fn.popover !== 'undefined') { + $('[data-toggle="popover"]').popover(); + } + + // Initialize card widgets with fallback + if (typeof $.fn.cardWidget !== 'undefined') { + $('.card').cardWidget(); + } else { + // Fallback for card widgets + $('[data-card-widget="collapse"]').on('click', function(e) { + e.preventDefault(); + const card = $(this).closest('.card'); + card.toggleClass('collapsed-card'); + }); + + $('[data-card-widget="remove"]').on('click', function(e) { + e.preventDefault(); + const card = $(this).closest('.card'); + card.fadeOut(300, function() { + $(this).remove(); + }); + }); + } + + // Initialize direct chat with fallback + if (typeof $.fn.DirectChat !== 'undefined') { + $('[data-widget="chat-pane-toggle"]').DirectChat('toggle'); + } else { + $('[data-widget="chat-pane-toggle"]').on('click', function(e) { + e.preventDefault(); + $(this).closest('.direct-chat').toggleClass('direct-chat-contacts-open'); + }); + } + + console.log(' AdminLTE components initialized'); + + } catch (error) { + console.warn(' Some AdminLTE components failed to initialize:', error); + } + } + + /** + * Initialize navigation system + */ + function initializeNavigation() { + // Handle sidebar navigation + $('.nav-sidebar .nav-link').on('click', function(e) { + e.preventDefault(); + + const section = $(this).data('section'); + if (section) { + showSection(section); + updateActiveNavItem($(this)); + updateBreadcrumb(section); + } + }); + + // Handle breadcrumb navigation + $('.breadcrumb a').on('click', function(e) { + e.preventDefault(); + const section = $(this).attr('href').substring(1); + showSection(section); + }); + } + + /** + * Show specific section and hide others + */ + function showSection(sectionName) { + // Hide all sections with fade effect + $('.content-section').fadeOut(200, function() { + // Show target section + $(`#${sectionName}-section`).fadeIn(200); + }); + + // Update current section + console.log('๐Ÿ”„ Switching to section:', sectionName); + currentSection = sectionName; + + // Load section-specific data + loadSectionData(sectionName); + } + + /** + * Update active navigation item + */ + function updateActiveNavItem(activeItem) { + $('.nav-sidebar .nav-link').removeClass('active'); + activeItem.addClass('active'); + } + + /** + * Update breadcrumb + */ + function updateBreadcrumb(section) { + const sectionNames = { + 'dashboard': 'Dashboard', + 'professionals': 'Professionals', + 'bookings': 'Bookings', + 'risk-monitor': 'Risk Monitor', + 'analytics': 'Analytics', + 'rag-status': 'RAG Status', + 'reports': 'Reports', + 'settings': 'Settings' + }; + + $('#pageTitle').text(sectionNames[section] || 'Dashboard'); + $('#breadcrumbActive').text(sectionNames[section] || 'Dashboard'); + } + + /** + * Initialize DataTables + */ + function initializeDataTables() { + // Professionals table + if ($('#professionalsTable').length) { + dataTables.professionals = $('#professionalsTable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + pageLength: 25, + order: [[0, 'desc']], + columnDefs: [ + { targets: [-1], orderable: false } + ], + language: { + search: "Search:", + lengthMenu: "Show _MENU_ entries per page", + info: "Showing _START_ to _END_ of _TOTAL_ entries", + paginate: { + first: "First", + last: "Last", + next: "Next", + previous: "Previous" + } + } + }); + } + + // Bookings table + if ($('#bookingsTable').length) { + dataTables.bookings = $('#bookingsTable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + pageLength: 25, + order: [[0, 'desc']], + columnDefs: [ + { targets: [-1], orderable: false } + ], + language: { + search: "Search:", + lengthMenu: "Show _MENU_ entries per page", + info: "Showing _START_ to _END_ of _TOTAL_ entries", + paginate: { + first: "First", + last: "Last", + next: "Next", + previous: "Previous" + } + } + }); + } + + } + + /** + * Initialize charts + */ + function initializeCharts() { + // Risk trend chart - will be updated with real data + if ($('#riskTrendChart').length) { + const ctx = document.getElementById('riskTrendChart').getContext('2d'); + charts.riskTrend = new Chart(ctx, { + type: 'line', + data: { + labels: [], + datasets: [{ + label: 'Critical', + data: [], + borderColor: '#dc3545', + backgroundColor: 'rgba(220, 53, 69, 0.1)', + tension: 0.4 + }, { + label: 'High', + data: [], + borderColor: '#ffc107', + backgroundColor: 'rgba(255, 193, 7, 0.1)', + tension: 0.4 + }, { + label: 'Medium', + data: [], + borderColor: '#17a2b8', + backgroundColor: 'rgba(23, 162, 184, 0.1)', + tension: 0.4 + }, { + label: 'Low', + data: [], + borderColor: '#28a745', + backgroundColor: 'rgba(40, 167, 69, 0.1)', + tension: 0.4 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true + } + } + } + }); + + // Load real data for risk trend chart after a short delay + setTimeout(() => { + loadRiskTrendData(); + }, 100); + } + + // Risk distribution chart - will be updated with real data + if ($('#riskDistributionChart').length) { + const ctx = document.getElementById('riskDistributionChart').getContext('2d'); + charts.riskDistribution = new Chart(ctx, { + type: 'doughnut', + data: { + labels: ['Critical', 'High', 'Medium', 'Low'], + datasets: [{ + data: [0, 0, 0, 0], + backgroundColor: ['#dc3545', '#ffc107', '#17a2b8', '#28a745'], + borderWidth: 2 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom' + } + } + } + }); + + // Load real data for risk distribution chart after a short delay + setTimeout(() => { + loadRiskDistributionData(); + }, 100); + } + + // Monthly trends chart + if ($('#monthlyTrendsChart').length) { + const ctx = document.getElementById('monthlyTrendsChart').getContext('2d'); + charts.monthlyTrends = new Chart(ctx, { + type: 'bar', + data: { + labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], + datasets: [{ + label: 'Bookings', + data: [45, 52, 38, 61, 55, 67], + backgroundColor: '#007bff' + }, { + label: 'Completed', + data: [42, 48, 35, 58, 52, 63], + backgroundColor: '#28a745' + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true + } + } + } + }); + } + + // Professional performance chart + if ($('#professionalPerformanceChart').length) { + const ctx = document.getElementById('professionalPerformanceChart').getContext('2d'); + charts.professionalPerformance = new Chart(ctx, { + type: 'radar', + data: { + labels: ['Sessions', 'Satisfaction', 'Response Time', 'Availability', 'Quality'], + datasets: [{ + label: 'Dr. Marie', + data: [85, 92, 88, 90, 87], + borderColor: '#007bff', + backgroundColor: 'rgba(0, 123, 255, 0.2)' + }, { + label: 'Dr. John', + data: [78, 85, 82, 88, 84], + borderColor: '#28a745', + backgroundColor: 'rgba(40, 167, 69, 0.2)' + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + r: { + beginAtZero: true, + max: 100 + } + } + } + }); + } + } + + /** + * Load risk trend data for chart + */ + function loadRiskTrendData() { + if (!charts.riskTrend) { + console.warn('Risk trend chart not initialized yet'); + return; + } + + fetch(`${API_ROOT}/admin/risk-assessments?limit=100`) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (data.assessments && charts.riskTrend) { + // Group assessments by day for the last 7 days + const last7Days = []; + for (let i = 6; i >= 0; i--) { + const date = new Date(); + date.setDate(date.getDate() - i); + last7Days.push(date.toISOString().split('T')[0]); + } + + const riskCounts = { + critical: new Array(7).fill(0), + high: new Array(7).fill(0), + medium: new Array(7).fill(0), + low: new Array(7).fill(0) + }; + + data.assessments.forEach(assessment => { + const assessmentDate = new Date(assessment.assessment_timestamp * 1000).toISOString().split('T')[0]; + const dayIndex = last7Days.indexOf(assessmentDate); + if (dayIndex !== -1) { + const riskLevel = assessment.risk_level.toLowerCase(); + if (riskCounts[riskLevel]) { + riskCounts[riskLevel][dayIndex]++; + } + } + }); + + // Update chart data + charts.riskTrend.data.labels = last7Days.map(date => { + const d = new Date(date); + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); + }); + charts.riskTrend.data.datasets[0].data = riskCounts.critical; + charts.riskTrend.data.datasets[1].data = riskCounts.high; + charts.riskTrend.data.datasets[2].data = riskCounts.medium; + charts.riskTrend.data.datasets[3].data = riskCounts.low; + charts.riskTrend.update(); + } + }) + .catch(error => { + console.error('Error loading risk trend data:', error); + }); + } + + /** + * Load risk distribution data for chart + */ + function loadRiskDistributionData() { + if (!charts.riskDistribution) { + console.warn('Risk distribution chart not initialized yet'); + return; + } + + fetch(`${API_ROOT}/admin/risk-assessments?limit=100`) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (data.assessments && charts.riskDistribution) { + const riskCounts = { + critical: 0, + high: 0, + medium: 0, + low: 0 + }; + + data.assessments.forEach(assessment => { + const riskLevel = assessment.risk_level.toLowerCase(); + if (riskCounts.hasOwnProperty(riskLevel)) { + riskCounts[riskLevel]++; + } + }); + + // Update chart data + charts.riskDistribution.data.datasets[0].data = [ + riskCounts.critical, + riskCounts.high, + riskCounts.medium, + riskCounts.low + ]; + charts.riskDistribution.update(); + } + }) + .catch(error => { + console.error('Error loading risk distribution data:', error); + }); + } + + /** + * Initialize Select2 + */ + function initializeSelect2() { + $('.select2').select2({ + theme: 'bootstrap4', + width: '100%' + }); + } + + /** + * Initialize event handlers + */ + function initializeEventHandlers() { + // Professional management + $('#addProfessionalBtn').on('click', function() { + console.log('โž• Opening Add Professional modal...'); + resetProfessionalForm(); + $('#modalTitle').text('Add New Professional'); + $('#passwordRequired').text('*'); + $('#passwordHelp').hide(); + $('#professionalModal').modal('show'); + + // Ensure inputs work properly + setTimeout(() => { + ensureInputsWorking(); + forceInputFunctionality(); + debugFormInputs(); + const firstInput = $('#professionalModal input[required]').first(); + if (firstInput.length) { + firstInput.focus(); + console.log(' Focused on first input:', firstInput.attr('name')); + } + }, 300); + }); + + // Handle form submission + $('#professionalForm').on('submit', function(e) { + e.preventDefault(); + saveProfessional(); + }); + + // Also handle button click as backup + $('#saveProfessionalBtn').on('click', function() { + $('#professionalForm').submit(); + }); + + // Refresh buttons + $('[id$="RefreshBtn"], [id$="refreshBtn"]').on('click', function() { + const section = $(this).closest('.content-section').attr('id').replace('-section', ''); + loadSectionData(section); + }); + + // Global refresh button + $('#refreshAllBtn').on('click', function() { + refreshAllData(); + }); + + // Expertise areas change handler + $(document).on('change', 'input[name="expertise"]', function() { + updateExpertiseValidation(); + }); + + // Export buttons + $('#exportBookingsBtn').on('click', function() { + exportTableToCSV('bookingsTable', 'bookings.csv'); + }); + + // Initialize bookings filtering + initializeBookingsFiltering(); + + // Search functionality + $('#professionalSearch').on('keyup', function() { + if (dataTables.professionals) { + dataTables.professionals.search(this.value).draw(); + } + }); + + // Filter functionality + $('#statusFilter, #riskLevelFilter, #specializationFilter').on('change', function() { + applyFilters(); + }); + + // Professional search functionality + $('#professionalSearch').on('input', function() { + const searchTerm = $(this).val().toLowerCase(); + filterProfessionals(searchTerm); + }); + + // Professional specialization filter + $('#professionalSpecializationFilter').on('change', function() { + const specialization = $(this).val(); + filterProfessionalsBySpecialization(specialization); + }); + + // Logout + $('#logoutBtn').on('click', function() { + Swal.fire({ + title: 'Logout?', + text: `Are you sure you want to logout, ${currentUser?.username || 'User'}?`, + icon: 'question', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, logout!' + }).then((result) => { + if (result.isConfirmed) { + logoutUser(); + } + }); + }); + } + + /** + * Check API health + */ + function checkAPIHealth() { + const endpoints = [ + '/admin/bookings', + '/admin/professionals', + '/admin/risk-assessments', + '/monitor/risk-stats', + '/monitor/recent-assessments' + ]; + + Promise.all(endpoints.map(endpoint => + fetch(`${API_ROOT}${endpoint}`) + .then(response => ({ endpoint, status: response.ok, statusCode: response.status })) + .catch(error => ({ endpoint, status: false, error: error.message })) + )).then(results => { + const failedEndpoints = results.filter(r => !r.status); + if (failedEndpoints.length > 0) { + console.warn('Some API endpoints are not responding:', failedEndpoints); + // Show a warning to the user + if (failedEndpoints.length === endpoints.length) { + Swal.fire({ + title: 'API Connection Error', + text: 'Unable to connect to the backend API. Please check if the server is running.', + icon: 'error', + timer: 5000 + }); + } + } + }); + } + + /** + * Load dashboard data + */ + function loadDashboardData() { + console.log(' Loading dashboard data for role:', userRole); + + // Show loading state + showLoadingState(); + + // Check API health first + checkAPIHealth(); + + // Load role-specific data + if (userRole === 'admin') { + loadAdminDashboardData(); + } else if (userRole === 'professional') { + loadProfessionalDashboardData(); + } else if (userRole === 'user') { + loadUserDashboardData(); + } else { + loadAdminDashboardData(); // Default to admin + } + + // Hide loading state after a delay + setTimeout(() => { + hideLoadingState(); + }, 2000); + } + + /** + * Load admin dashboard data + */ + function loadAdminDashboardData() { + console.log('๐Ÿ‘‘ Loading admin dashboard data...'); + loadKPIData(); + loadRecentBookings(); + loadSystemStatus(); + loadNotifications(); + } + + /** + * Load professional dashboard data + */ + function loadProfessionalDashboardData() { + console.log(' Loading professional dashboard data...'); + loadProfessionalKPIData(); + loadProfessionalBookings(); + loadProfessionalNotifications(); + loadNotifications(); + } + + /** + * Load user dashboard data + */ + function loadUserDashboardData() { + console.log('๐Ÿ‘ค Loading user dashboard data...'); + loadUserKPIData(); + loadUserRiskHistory(); + loadUserBookings(); + loadNotifications(); + } + + /** + * Show loading state + */ + function showLoadingState() { + $('#loadingOverlay').show(); + // Add loading class to KPI cards + $('.small-box .inner h3').html(''); + } + + /** + * Hide loading state + */ + function hideLoadingState() { + $('#loadingOverlay').hide(); + } + + /** + * Load section-specific data + */ + function loadSectionData(section) { + console.log('๐Ÿ”„ Loading section data for:', section); + try { + switch (section) { + case 'professionals': + console.log('๐Ÿ“‹ Loading professionals...'); + loadProfessionals(); + break; + case 'bookings': + console.log('๐Ÿ“‹ Loading bookings...'); + loadBookings(); + break; + case 'risk-monitor': + loadRiskData(); + break; + case 'analytics': + loadAnalyticsData(); + break; + case 'rag-status': + loadRAGStatus(); + break; + default: + console.warn(`Unknown section: ${section}`); + } + } catch (error) { + console.error(`Error loading section data for ${section}:`, error); + } + } + + /** + * Load KPI data from database + */ + function loadKPIData() { + console.log(' Loading KPI data...'); + + // Show loading state + $('#kpiActiveBookings, #kpiCritical, #kpiProfessionals, #kpiAssessments').html(''); + + // Try to load data from API endpoints with timeout + const timeout = 5000; // 5 second timeout + + const apiPromises = [ + fetch(`${API_ROOT}/admin/bookings`, { signal: AbortSignal.timeout(timeout) }) + .then(res => res.ok ? res.json() : null) + .catch(err => { + console.warn('Bookings API failed:', err); + return null; + }), + fetch(`${API_ROOT}/admin/professionals`, { signal: AbortSignal.timeout(timeout) }) + .then(res => res.ok ? res.json() : null) + .catch(err => { + console.warn('Professionals API failed:', err); + return null; + }), + fetch(`${API_ROOT}/admin/risk-assessments?limit=100`, { signal: AbortSignal.timeout(timeout) }) + .then(res => res.ok ? res.json() : null) + .catch(err => { + console.warn('Risk assessments API failed:', err); + return null; + }), + fetch(`${API_ROOT}/monitor/risk-stats`, { signal: AbortSignal.timeout(timeout) }) + .then(res => res.ok ? res.json() : null) + .catch(err => { + console.warn('Risk stats API failed:', err); + return null; + }) + ]; + + Promise.all(apiPromises).then(([bookingsData, professionalsData, riskData, riskStats]) => { + console.log(' API Data received:', { bookingsData, professionalsData, riskData, riskStats }); + + let hasRealData = false; + + // Active bookings (pending + confirmed) + const activeBookings = bookingsData?.bookings ? + bookingsData.bookings.filter(b => ['pending', 'confirmed'].includes(b.booking_status)).length : 0; + $('#kpiActiveBookings').text(activeBookings); + if (activeBookings > 0) hasRealData = true; + + // Critical risks + const criticalRisks = riskStats?.critical || + (riskData?.assessments ? riskData.assessments.filter(r => r.risk_level === 'critical').length : 0); + $('#kpiCritical').text(criticalRisks); + if (criticalRisks > 0) hasRealData = true; + + // Total professionals + const totalProfessionals = professionalsData?.professionals ? professionalsData.professionals.length : 0; + $('#kpiProfessionals').text(totalProfessionals); + if (totalProfessionals > 0) hasRealData = true; + + // Assessments today + const today = new Date().toISOString().split('T')[0]; + const assessmentsToday = riskData?.assessments ? + riskData.assessments.filter(r => { + const assessmentDate = new Date(r.assessment_timestamp * 1000).toISOString().split('T')[0]; + return assessmentDate === today; + }).length : 0; + $('#kpiAssessments').text(assessmentsToday); + if (assessmentsToday > 0) hasRealData = true; + + if (!hasRealData) { + console.log(' No real data found, showing demo data'); + showDemoData(); + } else { + console.log(' KPI data loaded successfully'); + } + + }).catch(error => { + console.warn(' API not available, using demo data:', error); + showDemoData(); + }); + } + + /** + * Show demo data when API is not available + */ + function showDemoData() { + // Show demo data when API is not available + $('#kpiActiveBookings').html('12'); + $('#kpiCritical').html('3'); + $('#kpiProfessionals').html('8'); + $('#kpiAssessments').text('25'); + + // Show demo mode notification + if (typeof Swal !== 'undefined') { + Swal.fire({ + title: 'Demo Mode', + text: 'Backend API not available. Showing demo data.', + icon: 'info', + timer: 3000, + toast: true, + position: 'top-end' + }); + } + } + + /** + * Load recent bookings from database + */ + function loadRecentBookings() { + console.log('๐Ÿ“‹ Loading recent bookings...'); + + const tbody = $('#recentBookingsTable'); + tbody.html(' Loading...'); + + // Try to load real data with timeout + fetch(`${API_ROOT}/admin/bookings?limit=5`, { signal: AbortSignal.timeout(5000) }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + return response.json(); + }) + .then(data => { + console.log('๐Ÿ“‹ Bookings data received:', data); + tbody.empty(); + + if (data.bookings && data.bookings.length > 0) { + data.bookings.forEach(booking => { + const row = ` + + ${booking.booking_id || 'N/A'} + ${booking.user_account || 'Guest'} + ${booking.risk_level || 'Unknown'} + ${booking.booking_status || 'Unknown'} + + `; + tbody.append(row); + }); + console.log(' Recent bookings loaded successfully'); + } else { + showDemoBookings(); + } + }) + .catch(error => { + console.warn(' Error loading recent bookings, showing demo data:', error); + showDemoBookings(); + }); + } + + /** + * Show demo bookings data + */ + function showDemoBookings() { + const tbody = $('#recentBookingsTable'); + tbody.html(` + + BK001 + Demo User + Critical + Pending + + + BK002 + Test User + High + Confirmed + + + BK003 + Sample User + Medium + Completed + + + + Demo data - API not available + + + `); + } + + /** + * Load professional KPI data + */ + function loadProfessionalKPIData() { + console.log(' Loading professional KPI data...'); + + // Update KPI labels for professional + $('#kpiActiveBookings').parent().find('p').text('My Sessions'); + $('#kpiCritical').parent().find('p').text('High Risk Cases'); + $('#kpiProfessionals').parent().find('p').text('Total Patients'); + $('#kpiAssessments').parent().find('p').text('Today\'s Assessments'); + + // Try to load professional-specific data + fetch(`${API_ROOT}/professional/dashboard-stats`, { signal: AbortSignal.timeout(5000) }) + .then(response => response.json()) + .then(data => { + console.log(' Professional data received:', data); + + $('#kpiActiveBookings').text(data.totalSessions || 0); + $('#kpiCritical').text(data.highRiskCases || 0); + $('#kpiProfessionals').text(data.activeUsers || 0); + $('#kpiAssessments').text(data.unreadNotifications || 0); + + console.log(' Professional KPI data loaded successfully'); + }) + .catch(error => { + console.warn(' Professional API not available, using demo data:', error); + showProfessionalDemoData(); + }); + } + + /** + * Show professional demo data + */ + function showProfessionalDemoData() { + $('#kpiActiveBookings').html('15'); + $('#kpiCritical').html('2'); + $('#kpiProfessionals').html('8'); + $('#kpiAssessments').text('5'); + } + + /** + * Load user KPI data + */ + function loadUserKPIData() { + console.log('๐Ÿ‘ค Loading user KPI data...'); + + // Update KPI labels for user + $('#kpiActiveBookings').parent().find('p').text('My Bookings'); + $('#kpiCritical').parent().find('p').text('Risk Level'); + $('#kpiProfessionals').parent().find('p').text('Sessions Completed'); + $('#kpiAssessments').parent().find('p').text('Assessments Done'); + + // Show user-specific demo data + $('#kpiActiveBookings').html('3'); + $('#kpiCritical').html('Medium'); + $('#kpiProfessionals').html('2'); + $('#kpiAssessments').text('12'); + } + + /** + * Load professional bookings + */ + function loadProfessionalBookings() { + console.log(' Loading professional bookings...'); + // This would load bookings specific to the professional + loadRecentBookings(); // Use existing function for now + } + + /** + * Load professional notifications + */ + function loadProfessionalNotifications() { + console.log(' Loading professional notifications...'); + // This would load notifications for the professional + // For now, just show demo data + } + + /** + * Load user risk history + */ + function loadUserRiskHistory() { + console.log('๐Ÿ‘ค Loading user risk history...'); + // This would load the user's risk assessment history + } + + /** + * Load user bookings + */ + function loadUserBookings() { + console.log('๐Ÿ‘ค Loading user bookings...'); + // This would load bookings specific to the user + loadRecentBookings(); // Use existing function for now + } + + /** + * Logout user + */ + function logoutUser() { + console.log('๐Ÿšช Logging out user:', currentUser?.username); + + // Clear all session data + localStorage.removeItem('aimhsa_admin'); + localStorage.removeItem('aimhsa_professional'); + localStorage.removeItem('aimhsa_account'); + + // Show logout message + if (typeof Swal !== 'undefined') { + Swal.fire({ + title: 'Logged Out', + text: 'You have been successfully logged out.', + icon: 'success', + timer: 2000 + }).then(() => { + // Redirect to login page + window.location.href = 'login.html'; + }); + } else { + // Fallback redirect + window.location.href = 'login.html'; + } + } + + /** + * Load system status + */ + function loadSystemStatus() { + // System status is already set in HTML + // This function can be used to update real-time status + } + + /** + * Load notifications from database + */ + function loadNotifications() { + console.log('๐Ÿ”” Loading notifications from database...'); + + fetch(`${API_ROOT}/notifications`, { signal: AbortSignal.timeout(5000) }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + return response.json(); + }) + .then(data => { + console.log('๐Ÿ”” Notifications data received:', data); + updateNotificationUI(data); + }) + .catch(error => { + console.warn(' Error loading notifications, using demo data:', error); + showDemoNotifications(); + }); + } + + /** + * Update notification UI with real data + */ + function updateNotificationUI(data) { + // Update notification badge + const totalNotifications = data.totalNotifications || 0; + $('.nav-link .badge').text(totalNotifications); + + // Update notification dropdown content + const notificationsHtml = generateNotificationsHTML(data); + $('.dropdown-menu.notifications-menu').html(notificationsHtml); + + console.log(' Notifications UI updated with real data'); + } + + /** + * Generate notifications HTML from database data + */ + function generateNotificationsHTML(data) { + const notifications = data.notifications || []; + const totalNotifications = data.totalNotifications || 0; + + let html = ` + ${totalNotifications} Notifications + + `; + + if (notifications.length === 0) { + html += ` + + + No new notifications + + `; + } else { + notifications.slice(0, 5).forEach(notification => { + const iconClass = getNotificationIcon(notification.type); + const timeAgo = notification.timeAgo || 'Unknown'; + const isRead = notification.isRead ? '' : 'font-weight-bold'; + + html += ` + + + ${notification.title} + ${timeAgo} + + `; + }); + } + + html += ` + + See All Notifications + `; + + return html; + } + + /** + * Get icon class for notification type + */ + function getNotificationIcon(type) { + const iconMap = { + 'booking': 'fas fa-calendar-check text-success', + 'risk': 'fas fa-exclamation-triangle text-danger', + 'message': 'fas fa-envelope text-info', + 'user': 'fas fa-user text-primary', + 'system': 'fas fa-cog text-secondary', + 'default': 'fas fa-bell text-warning' + }; + return iconMap[type] || iconMap['default']; + } + + /** + * Show demo notifications when API is not available + */ + function showDemoNotifications() { + const demoData = { + totalNotifications: 15, + notifications: [ + { + id: 1, + title: '4 new messages', + type: 'message', + timeAgo: '3 mins', + isRead: false + }, + { + id: 2, + title: '8 friend requests', + type: 'user', + timeAgo: '12 hours', + isRead: false + }, + { + id: 3, + title: '3 new reports', + type: 'system', + timeAgo: '2 days', + isRead: true + } + ] + }; + + updateNotificationUI(demoData); + console.log('๐Ÿ“ฑ Demo notifications displayed'); + } + + /** + * Load professionals data from database + */ + function loadProfessionals() { + console.log('๐Ÿ‘ฅ Loading professionals...'); + const tbody = $('#professionalsTableBody'); + tbody.html(' Loading...'); + + fetch(`${API_ROOT}/admin/professionals`) + .then(response => { + console.log('๐Ÿ“ก Professionals API response status:', response.status); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + return response.json(); + }) + .then(data => { + console.log(' Professionals data received:', data); + tbody.empty(); + + if (data.professionals && data.professionals.length > 0) { + data.professionals.forEach(prof => { + const fullName = `${prof.first_name || ''} ${prof.last_name || ''}`.trim(); + const statusClass = prof.is_active ? 'success' : 'secondary'; + const statusText = prof.is_active ? 'Active' : 'Inactive'; + + const row = ` + + ${prof.id} + ${fullName || 'N/A'} + ${prof.specialization || 'N/A'} + ${prof.email || 'N/A'} + ${prof.phone || 'N/A'} + ${prof.experience_years || 0} years + ${statusText} + + + + + + + `; + tbody.append(row); + }); + console.log(' Professionals loaded successfully'); + } else { + tbody.html('No professionals found'); + } + + // Update DataTable if it exists + if (dataTables.professionals) { + dataTables.professionals.clear().rows.add($(tbody).find('tr')).draw(); + } + }) + .catch(error => { + console.error(' Error loading professionals:', error); + tbody.html(` + + + Error loading professionals +
${error.message} + + + `); + }); + } + + /** + * Load bookings data from database with full user details + */ + function loadBookings() { + console.log(' Starting loadBookings function...'); + const tbody = $('#bookingsTableBody'); + console.log('Table body element:', tbody); + console.log('Table body length:', tbody.length); + console.log('Table is visible:', tbody.is(':visible')); + + tbody.html(' Loading bookings...'); + + // Show loading state in stats + updateBookingStats({ total: 0, confirmed: 0, pending: 0, critical: 0 }); + + fetch(`${API_ROOT}/admin/bookings`) + .then(response => response.json()) + .then(data => { + tbody.empty(); + + if (data.bookings && data.bookings.length > 0) { + // Update stats + updateBookingStats(data); + + // Log the data for debugging + console.log(' Bookings data received:', { + total: data.total, + confirmed: data.confirmed, + pending: data.pending, + critical: data.critical, + bookingsCount: data.bookings ? data.bookings.length : 0 + }); + + console.log('๐Ÿ”„ Processing bookings data...'); + data.bookings.forEach((booking, index) => { + console.log(`๐Ÿ“‹ Processing booking ${index + 1}:`, { + id: booking.booking_id, + user: booking.user_fullname, + professional: booking.professional_name, + status: booking.booking_status, + risk: booking.risk_level + }); + + const scheduledTime = new Date(booking.scheduled_datetime * 1000).toLocaleString(); + const professionalName = booking.professional_name || 'Unassigned'; + const professionalSpecialization = booking.professional_specialization || ''; + + // Get user initials for avatar + const userInitials = getUserInitials(booking.user_fullname || booking.user_account || 'Guest'); + + // Create user details HTML + const userDetails = createUserDetailsHTML(booking); + + // Create professional details HTML + const professionalDetails = createProfessionalDetailsHTML(booking); + + // Create risk badge HTML + const riskBadge = createRiskBadgeHTML(booking.risk_level); + + // Create status badge HTML + const statusBadge = createStatusBadgeHTML(booking.booking_status); + + // Create action buttons HTML + const actionButtons = createActionButtonsHTML(booking.booking_id, booking.booking_status); + + const row = ` + + +
+ ${booking.booking_id.substring(0, 8)}... + ${booking.booking_id} +
+ + ${userDetails} + ${professionalDetails} + ${riskBadge} + +
+ + ${scheduledTime} +
+ + ${statusBadge} + ${actionButtons} + + `; + tbody.append(row); + console.log(` Added row ${index + 1} to table`); + }); + + console.log(' Total rows in tbody:', tbody.find('tr').length); + } else { + tbody.html(` + + + +

No Bookings Found

+

There are currently no bookings in the system.

+ + + `); + updateBookingStats({ total: 0, confirmed: 0, pending: 0, critical: 0 }); + } + + // Update DataTable + console.log(' Checking DataTable status...'); + console.log('DataTable object:', dataTables.bookings); + console.log('DataTable exists:', !!dataTables.bookings); + + // First, let's try to show the data without DataTable + console.log(' Table body HTML after adding rows:'); + console.log(tbody.html()); + + if (dataTables.bookings) { + console.log('๐Ÿ”„ Updating DataTable with', tbody.find('tr').length, 'rows'); + try { + dataTables.bookings.clear().rows.add($(tbody).find('tr')).draw(); + console.log(' DataTable updated successfully'); + } catch (error) { + console.error(' Error updating DataTable:', error); + console.log('๐Ÿ”„ Trying to destroy and recreate DataTable...'); + try { + dataTables.bookings.destroy(); + dataTables.bookings = $('#bookingsTable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + pageLength: 25, + order: [[0, 'desc']], + columnDefs: [ + { targets: [-1], orderable: false } + ], + language: { + search: "Search:", + lengthMenu: "Show _MENU_ entries per page", + info: "Showing _START_ to _END_ of _TOTAL_ entries", + paginate: { + first: "First", + last: "Last", + next: "Next", + previous: "Previous" + } + } + }); + console.log(' DataTable recreated successfully'); + } catch (recreateError) { + console.error(' Error recreating DataTable:', recreateError); + } + } + } else { + console.log(' DataTable not initialized - trying to reinitialize...'); + // Try to reinitialize the DataTable + if ($('#bookingsTable').length) { + dataTables.bookings = $('#bookingsTable').DataTable({ + responsive: true, + processing: true, + serverSide: false, + pageLength: 25, + order: [[0, 'desc']], + columnDefs: [ + { targets: [-1], orderable: false } + ], + language: { + search: "Search:", + lengthMenu: "Show _MENU_ entries per page", + info: "Showing _START_ to _END_ of _TOTAL_ entries", + paginate: { + first: "First", + last: "Last", + next: "Next", + previous: "Previous" + } + } + }); + console.log(' DataTable reinitialized'); + } + } + + console.log(' Bookings loaded successfully:', data.bookings?.length || 0, 'bookings'); + }) + .catch(error => { + console.error('Error loading bookings:', error); + tbody.html(` + + + + Error loading bookings data + + + `); + }); + } + + /** + * Create user details HTML with full information + */ + function createUserDetailsHTML(booking) { + const userFullName = booking.user_fullname || booking.user_account || 'Guest User'; + const userEmail = booking.user_email || 'No email provided'; + const userPhone = booking.user_phone || 'No phone provided'; + const userLocation = booking.user_location || 'Location not specified'; + const userInitials = getUserInitials(userFullName); + + return ` +
+
+ ${userInitials} +
+
+
${userFullName}
+ +
+ + ${userLocation} +
+
+
+ `; + } + + /** + * Create professional details HTML + */ + function createProfessionalDetailsHTML(booking) { + const professionalName = booking.professional_name || 'Unassigned'; + const professionalSpecialization = booking.professional_specialization || ''; + const professionalInitials = getUserInitials(professionalName); + + if (professionalName === 'Unassigned') { + return ` +
+
+ +
+
+
Unassigned
+
Awaiting assignment
+
+
+ `; + } + + return ` +
+
+ ${professionalInitials} +
+
+
${professionalName}
+
${professionalSpecialization}
+
+
+ `; + } + + /** + * Create risk badge HTML + */ + function createRiskBadgeHTML(riskLevel) { + const riskIcons = { + critical: 'fas fa-exclamation-triangle', + high: 'fas fa-exclamation-circle', + medium: 'fas fa-info-circle', + low: 'fas fa-check-circle' + }; + + const icon = riskIcons[riskLevel.toLowerCase()] || 'fas fa-question-circle'; + + return ` + + + ${riskLevel.toUpperCase()} + + `; + } + + /** + * Create status badge HTML + */ + function createStatusBadgeHTML(status) { + const statusIcons = { + pending: 'fas fa-clock', + confirmed: 'fas fa-check-circle', + completed: 'fas fa-check-double', + declined: 'fas fa-times-circle', + cancelled: 'fas fa-ban' + }; + + const icon = statusIcons[status.toLowerCase()] || 'fas fa-question-circle'; + + return ` + + + ${status.toUpperCase()} + + `; + } + + /** + * Create action buttons HTML + */ + function createActionButtonsHTML(bookingId, status) { + const canEdit = status === 'pending' || status === 'confirmed'; + const canComplete = status === 'confirmed'; + const canCancel = status === 'pending' || status === 'confirmed'; + + return ` +
+ + ${canEdit ? ` + + ` : ''} + ${canComplete ? ` + + ` : ''} + ${canCancel ? ` + + ` : ''} +
+ `; + } + + /** + * Get user initials for avatar + */ + function getUserInitials(name) { + if (!name || name === 'Guest') return 'G'; + + const words = name.trim().split(' '); + if (words.length >= 2) { + return (words[0][0] + words[1][0]).toUpperCase(); + } + return name[0].toUpperCase(); + } + + /** + * Update booking statistics + */ + function updateBookingStats(data) { + const stats = { + total: data.total || 0, + confirmed: data.confirmed || 0, + pending: data.pending || 0, + critical: data.critical || 0 + }; + + $('#totalBookings').text(stats.total); + $('#confirmedBookings').text(stats.confirmed); + $('#pendingBookings').text(stats.pending); + $('#criticalBookings').text(stats.critical); + } + + /** + * Load risk data from database + */ + function loadRiskData() { + // Show loading state + $('#criticalCount, #highCount, #mediumCount, #lowCount').text('...'); + + fetch(`${API_ROOT}/admin/risk-assessments?limit=100`) + .then(response => response.json()) + .then(data => { + if (data.assessments) { + const riskCounts = { + critical: 0, + high: 0, + medium: 0, + low: 0 + }; + + data.assessments.forEach(assessment => { + const riskLevel = assessment.risk_level.toLowerCase(); + if (riskCounts.hasOwnProperty(riskLevel)) { + riskCounts[riskLevel]++; + } + }); + + $('#criticalCount').text(riskCounts.critical); + $('#highCount').text(riskCounts.high); + $('#mediumCount').text(riskCounts.medium); + $('#lowCount').text(riskCounts.low); + + // Update recent assessments + updateRecentAssessments(data.assessments.slice(0, 10)); + } else { + $('#criticalCount, #highCount, #mediumCount, #lowCount').text('0'); + } + }) + .catch(error => { + console.error('Error loading risk data:', error); + $('#criticalCount, #highCount, #mediumCount, #lowCount').text('0'); + }); + } + + /** + * Update recent assessments display + */ + function updateRecentAssessments(assessments) { + const container = $('#recentAssessments'); + container.empty(); + + if (assessments.length === 0) { + container.html('

No recent assessments

'); + return; + } + + assessments.forEach(assessment => { + const timeAgo = getTimeAgo(assessment.assessment_timestamp); + const riskClass = getRiskBadgeClass(assessment.risk_level); + + const assessmentItem = ` +
+ ${timeAgo} +

+ ${assessment.risk_level.toUpperCase()} + Risk Assessment +

+
+

User: ${assessment.user_account || 'Guest'}

+

Score: ${(assessment.risk_score * 100).toFixed(1)}%

+

Indicators: ${assessment.detected_indicators || 'None detected'}

+
+
+ `; + container.append(assessmentItem); + }); + } + + /** + * Get time ago string + */ + function getTimeAgo(timestamp) { + const now = Date.now() / 1000; + const diff = now - timestamp; + + if (diff < 60) return 'Just now'; + if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; + if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; + return `${Math.floor(diff / 86400)}d ago`; + } + + /** + * Load analytics data from database + */ + function loadAnalyticsData() { + // Show loading state + $('#totalProfessionals, #activeBookings, #completedSessions, #assessmentsToday').text('...'); + + Promise.all([ + fetch(`${API_ROOT}/admin/professionals`).then(res => res.json()), + fetch(`${API_ROOT}/admin/bookings`).then(res => res.json()), + fetch(`${API_ROOT}/admin/risk-assessments?limit=100`).then(res => res.json()) + ]).then(([professionalsData, bookingsData, riskData]) => { + // Total professionals + const totalProfessionals = professionalsData.professionals ? professionalsData.professionals.length : 0; + $('#totalProfessionals').text(totalProfessionals); + + // Active bookings + const activeBookings = bookingsData.bookings ? + bookingsData.bookings.filter(b => ['pending', 'confirmed'].includes(b.booking_status)).length : 0; + $('#activeBookings').text(activeBookings); + + // Completed sessions + const completedSessions = bookingsData.bookings ? + bookingsData.bookings.filter(b => b.booking_status === 'completed').length : 0; + $('#completedSessions').text(completedSessions); + + // Assessments today + const today = new Date().toISOString().split('T')[0]; + const assessmentsToday = riskData.assessments ? + riskData.assessments.filter(r => { + const assessmentDate = new Date(r.assessment_timestamp * 1000).toISOString().split('T')[0]; + return assessmentDate === today; + }).length : 0; + $('#assessmentsToday').text(assessmentsToday); + + }).catch(error => { + console.error('Error loading analytics data:', error); + $('#totalProfessionals, #activeBookings, #completedSessions, #assessmentsToday').text('0'); + }); + } + + /** + * Load RAG status + */ + function loadRAGStatus() { + // RAG status is already set in HTML + // This function can be used to update real-time status + } + + + /** + * Save professional + */ + function saveProfessional() { + console.log('Saving professional...'); + + // Validate form + if (!validateProfessionalForm()) { + console.log('Form validation failed'); + return; + } + + // Get form data + const form = $('#professionalForm'); + const formData = new FormData(form[0]); + const data = Object.fromEntries(formData.entries()); + + console.log('Form data collected:', data); + + // Get expertise areas + const expertiseAreas = $('input[name="expertise"]:checked').map(function() { + return this.value; + }).get(); + + console.log('Expertise areas selected:', expertiseAreas); + + const professionalData = { + username: data.username, + first_name: data.first_name, + last_name: data.last_name, + email: data.email, + phone: data.phone || '', + specialization: data.specialization, + experience_years: parseInt(data.experience_years) || 0, + expertise_areas: expertiseAreas, + district: data.district || '', + consultation_fee: parseFloat(data.consultation_fee) || 0, + bio: data.bio || '', + languages: ['english'], // Default languages + qualifications: [], // Default qualifications + availability_schedule: {} // Default schedule + }; + + // Add password only for new professionals or if provided in edit mode + const isEditMode = $('#modalTitle').text().includes('Edit'); + if (!isEditMode) { + professionalData.password = data.password; + } else { + // For edit mode, only include password if provided + const password = data.password; + if (password && password.trim()) { + professionalData.password = password; + } + } + + console.log('Professional data to send:', professionalData); + + // Show loading state + $('#saveProfessionalBtn').prop('disabled', true).html(' Saving...'); + + const url = isEditMode ? + `${API_ROOT}/admin/professionals/${currentProfessionalId}` : + `${API_ROOT}/admin/professionals`; + const method = isEditMode ? 'PUT' : 'POST'; + + console.log('Sending request to:', url, 'Method:', method); + + fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(professionalData) + }) + .then(response => response.json()) + .then(data => { + console.log(' Response data:', data); + if (data.success) { + Swal.fire({ + title: 'Success!', + text: isEditMode ? 'Professional updated successfully!' : 'Professional added successfully!', + icon: 'success', + timer: 2000 + }).then(() => { + $('#professionalModal').modal('hide'); + loadProfessionals(); + resetProfessionalForm(); + }); + } else { + Swal.fire({ + title: 'Error!', + text: data.error || 'Failed to save professional.', + icon: 'error' + }); + } + }) + .catch(error => { + console.error('Error saving professional:', error); + Swal.fire({ + title: 'Error!', + text: 'Failed to save professional. Please try again.', + icon: 'error' + }); + }) + .finally(() => { + $('#saveProfessionalBtn').prop('disabled', false).html('Save Professional'); + }); + } + + /** + * Validate professional form + */ + function validateProfessionalForm() { + console.log('Validating professional form...'); + + const requiredFields = ['username', 'first_name', 'last_name', 'email', 'specialization']; + const isEditMode = $('#modalTitle').text().includes('Edit'); + + // Check if password is required (only for new professionals) + if (!isEditMode) { + requiredFields.push('password'); + } else { + // For edit mode, make password optional + $('#password').prop('required', false); + } + + let isValid = true; + let errorMessage = ''; + + // Clear previous validation errors + $('.is-invalid').removeClass('is-invalid'); + + // Check required fields + requiredFields.forEach(field => { + const value = $(`#${field}`).val().trim(); + if (!value) { + $(`#${field}`).addClass('is-invalid'); + isValid = false; + const fieldName = field.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()); + errorMessage += `${fieldName} is required.\n`; + } else { + $(`#${field}`).removeClass('is-invalid'); + } + }); + + // Validate email format + const email = $('#email').val().trim(); + if (email && !isValidEmail(email)) { + $('#email').addClass('is-invalid'); + isValid = false; + errorMessage += 'Please enter a valid email address.\n'; + } + + // Validate phone format (if provided) + const phone = $('#phone').val().trim(); + if (phone && !isValidPhone(phone)) { + $('#phone').addClass('is-invalid'); + isValid = false; + errorMessage += 'Please enter a valid phone number.\n'; + } + + // Check if at least one expertise area is selected + if (!validateExpertiseAreas()) { + isValid = false; + errorMessage += 'Please select at least one expertise area.\n'; + } + + // Validate experience years + const experienceYears = parseInt($('#experience_years').val()) || 0; + if (experienceYears < 0) { + $('#experience_years').addClass('is-invalid'); + isValid = false; + errorMessage += 'Experience years cannot be negative.\n'; + } + + // Validate consultation fee + const consultationFee = parseFloat($('#consultation_fee').val()) || 0; + if (consultationFee < 0) { + $('#consultation_fee').addClass('is-invalid'); + isValid = false; + errorMessage += 'Consultation fee cannot be negative.\n'; + } + + if (!isValid) { + console.error(' Form validation failed:', errorMessage); + Swal.fire({ + title: 'Validation Error', + text: errorMessage.trim(), + icon: 'error', + confirmButtonText: 'Fix Issues' + }); + } else { + console.log(' Form validation passed'); + } + + return isValid; + } + + /** + * Validate email format + */ + function isValidEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + } + + /** + * Validate phone format + */ + function isValidPhone(phone) { + const phoneRegex = /^[\+]?[0-9\s\-\(\)]{10,}$/; + return phoneRegex.test(phone); + } + + + /** + * Reset professional form + */ + function resetProfessionalForm() { + $('#professionalForm')[0].reset(); + $('input[name="expertise"]').prop('checked', false); + $('.is-invalid').removeClass('is-invalid'); + currentProfessionalId = null; + $('#password').prop('required', true); + } + + /** + * Ensure all inputs are working properly + */ + function ensureInputsWorking() { + const form = $('#professionalForm'); + const inputs = form.find('input, select, textarea'); + + console.log('๐Ÿ”ง Ensuring input functionality for', inputs.length, 'inputs'); + + inputs.each(function() { + const input = $(this); + + // Ensure all inputs are enabled + input.prop('disabled', false); + input.prop('readonly', false); + + // Force CSS properties + input.css({ + 'background-color': '#fff !important', + 'color': '#495057 !important', + 'pointer-events': 'auto !important', + 'user-select': 'text !important', + 'cursor': 'text !important' + }); + + // Add click handler to ensure focus + input.off('click.ensureFocus').on('click.ensureFocus', function() { + $(this).focus(); + console.log(' Input clicked:', $(this).attr('name')); + }); + + // Add keydown handler to ensure typing works + input.off('keydown.ensureTyping').on('keydown.ensureTyping', function(e) { + console.log(' Key pressed:', e.key, 'in', $(this).attr('name')); + // Allow all normal typing + if (e.key.length === 1 || e.key === 'Backspace' || e.key === 'Delete' || + e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'Tab') { + return true; + } + }); + + // Add input handler for real-time validation + input.off('input.validate').on('input.validate', function() { + console.log(' Input changed:', $(this).attr('name'), '=', $(this).val()); + validateInput($(this)); + }); + }); + + console.log(' Input functionality ensured for', inputs.length, 'inputs'); + } + + /** + * Validate individual input + */ + function validateInput(input) { + const value = input.val().trim(); + const isRequired = input.prop('required'); + + if (isRequired && !value) { + input.removeClass('is-valid').addClass('is-invalid'); + } else if (value) { + input.removeClass('is-invalid').addClass('is-valid'); + } else { + input.removeClass('is-invalid is-valid'); + } + + // Check form validity + checkFormValidity(); + } + + /** + * Check overall form validity + */ + function checkFormValidity() { + const form = $('#professionalForm'); + const requiredFields = form.find('[required]'); + let isValid = true; + + requiredFields.each(function() { + const field = $(this); + const value = field.val().trim(); + if (!value) { + isValid = false; + return false; + } + }); + + // Check expertise areas + if (!validateExpertiseAreas()) { + isValid = false; + } + + // Enable/disable submit button + const submitBtn = form.find('button[type="submit"]'); + if (submitBtn.length) { + submitBtn.prop('disabled', !isValid); + } + + return isValid; + } + + /** + * Debug form inputs + */ + function debugFormInputs() { + const form = $('#professionalForm'); + const inputs = form.find('input, select, textarea'); + + console.log(' Debugging form inputs:'); + inputs.each(function(index) { + const input = $(this); + const isFocusable = function() { + try { + input.focus(); + return document.activeElement === input[0]; + } catch (e) { + return false; + } + }(); + + console.log(`Input ${index}:`, { + type: input.attr('type') || input.prop('tagName').toLowerCase(), + name: input.attr('name'), + id: input.attr('id'), + value: input.val(), + disabled: input.prop('disabled'), + readonly: input.prop('readonly'), + focusable: isFocusable, + style: input.attr('style') + }); + }); + } + + /** + * Force input functionality + */ + function forceInputFunctionality() { + const form = $('#professionalForm'); + const inputs = form.find('input, select, textarea'); + + inputs.each(function() { + const input = $(this); + + // Force enable inputs + input.prop('disabled', false); + input.prop('readonly', false); + + // Force CSS properties + input.css({ + 'background-color': '#fff', + 'color': '#495057', + 'pointer-events': 'auto', + 'user-select': 'text', + 'cursor': 'text' + }); + + // Add event listeners + input.off('click.force').on('click.force', function() { + $(this).focus(); + console.log(' Input clicked:', $(this).attr('name')); + }); + + input.off('keydown.force').on('keydown.force', function(e) { + console.log(' Key pressed:', e.key, 'in', $(this).attr('name')); + }); + + input.off('input.force').on('input.force', function() { + console.log(' Input changed:', $(this).attr('name'), '=', $(this).val()); + }); + }); + + console.log('๐Ÿ”ง Forced input functionality for', inputs.length, 'inputs'); + } + + /** + * Filter professionals by search term + */ + function filterProfessionals(searchTerm) { + console.log(' Filtering professionals by:', searchTerm); + + if (dataTables.professionals) { + dataTables.professionals.search(searchTerm).draw(); + } else { + // Fallback: filter table rows manually + $('#professionalsTableBody tr').each(function() { + const row = $(this); + const text = row.text().toLowerCase(); + if (text.includes(searchTerm)) { + row.show(); + } else { + row.hide(); + } + }); + } + } + + /** + * Filter professionals by specialization + */ + function filterProfessionalsBySpecialization(specialization) { + console.log(' Filtering professionals by specialization:', specialization); + + if (dataTables.professionals) { + if (specialization === '') { + dataTables.professionals.column(2).search('').draw(); + } else { + dataTables.professionals.column(2).search(specialization).draw(); + } + } else { + // Fallback: filter table rows manually + $('#professionalsTableBody tr').each(function() { + const row = $(this); + const specializationCell = row.find('td:eq(2)').text().toLowerCase(); + if (specialization === '' || specializationCell.includes(specialization.toLowerCase())) { + row.show(); + } else { + row.hide(); + } + }); + } + } + + /** + * Apply filters + */ + function applyFilters() { + const status = $('#statusFilter').val(); + const riskLevel = $('#riskLevelFilter').val(); + const specialization = $('#specializationFilter').val(); + + // Apply filters to DataTables + if (dataTables.bookings) { + dataTables.bookings.column(5).search(status).draw(); + } + if (dataTables.professionals) { + dataTables.professionals.column(2).search(specialization).draw(); + } + } + + /** + * Export table to CSV + */ + function exportTableToCSV(tableId, filename) { + const table = document.getElementById(tableId); + const rows = table.querySelectorAll('tr'); + let csv = []; + + for (let i = 0; i < rows.length; i++) { + const row = []; + const cols = rows[i].querySelectorAll('td, th'); + + for (let j = 0; j < cols.length; j++) { + row.push(cols[j].innerText); + } + csv.push(row.join(',')); + } + + const csvContent = csv.join('\n'); + const blob = new Blob([csvContent], { type: 'text/csv' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); + } + + /** + * Get risk badge class + */ + function getRiskBadgeClass(riskLevel) { + const classes = { + 'critical': 'danger', + 'high': 'warning', + 'medium': 'info', + 'low': 'success' + }; + return classes[riskLevel.toLowerCase()] || 'secondary'; + } + + /** + * Get status badge class + */ + function getStatusBadgeClass(status) { + const classes = { + 'pending': 'warning', + 'confirmed': 'info', + 'completed': 'success', + 'declined': 'danger', + 'active': 'success', + 'inactive': 'secondary' + }; + return classes[status.toLowerCase()] || 'secondary'; + } + + /** + * Get current professional ID for editing + */ + function getCurrentProfessionalId() { + return currentProfessionalId; + } + + /** + * Handle API errors gracefully + */ + function handleAPIError(error, context = 'API call') { + console.error(`Error in ${context}:`, error); + + // Show user-friendly error message + Swal.fire({ + title: 'Connection Error', + text: 'Unable to connect to the server. Please check your internet connection and try again.', + icon: 'error', + timer: 5000 + }); + } + + /** + * Refresh all data + */ + function refreshAllData() { + // Show loading state + const refreshBtn = $('#refreshAllBtn'); + const originalText = refreshBtn.html(); + refreshBtn.prop('disabled', true).html(' Refreshing...'); + + // Refresh current section data + if (currentSection === 'dashboard') { + loadDashboardData(); + } else { + loadSectionData(currentSection); + } + + // Reset button after a delay + setTimeout(() => { + refreshBtn.prop('disabled', false).html(originalText); + }, 2000); + } + + /** + * Start auto-refresh + */ + function startAutoRefresh() { + setInterval(() => { + if (currentSection === 'dashboard') { + loadDashboardData(); + } else { + loadSectionData(currentSection); + } + }, 30000); // Refresh every 30 seconds + } + + /** + * Toggle professional status + */ + function toggleProfessionalStatus(profId) { + // Get current status from the button + const button = $(`button[onclick="toggleProfessionalStatus(${profId})"]`); + const isCurrentlyActive = button.hasClass('btn-warning'); // warning = active, success = inactive + + fetch(`${API_ROOT}/admin/professionals/${profId}/status`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + is_active: !isCurrentlyActive + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + Swal.fire({ + title: 'Success!', + text: data.message || 'Professional status updated.', + icon: 'success', + timer: 2000 + }).then(() => { + loadProfessionals(); + }); + } else { + Swal.fire({ + title: 'Error!', + text: data.error || 'Failed to update professional status.', + icon: 'error' + }); + } + }) + .catch(error => { + console.error('Error toggling professional status:', error); + Swal.fire({ + title: 'Error!', + text: 'Failed to update professional status.', + icon: 'error' + }); + }); + } + + /** + * Global functions for onclick handlers + */ + window.editProfessional = function(id) { + console.log(' Editing professional with ID:', id); + + // Show loading state + Swal.fire({ + title: 'Loading...', + text: 'Loading professional data...', + allowOutsideClick: false, + didOpen: () => { + Swal.showLoading(); + } + }); + + // Load professional data and populate form + fetch(`${API_ROOT}/admin/professionals`) + .then(response => { + console.log('๐Ÿ“ก Edit professional API response status:', response.status); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + return response.json(); + }) + .then(data => { + console.log(' Professional data for editing:', data); + const professional = data.professionals.find(p => p.id === id); + + if (professional) { + console.log(' Professional found:', professional); + + // Store current professional ID for editing + currentProfessionalId = id; + + // Populate form with professional data + $('#username').val(professional.username || ''); + $('#first_name').val(professional.first_name || ''); + $('#last_name').val(professional.last_name || ''); + $('#email').val(professional.email || ''); + $('#phone').val(professional.phone || ''); + $('#specialization').val(professional.specialization || ''); + $('#experience_years').val(professional.experience_years || 0); + $('#district').val(professional.district || ''); + $('#consultation_fee').val(professional.consultation_fee || 0); + $('#bio').val(professional.bio || ''); + + // Set expertise checkboxes + if (professional.expertise_areas) { + let expertiseAreas = []; + if (Array.isArray(professional.expertise_areas)) { + expertiseAreas = professional.expertise_areas; + } else if (typeof professional.expertise_areas === 'string') { + expertiseAreas = professional.expertise_areas.split(',').map(area => area.trim()); + } + + $('input[name="expertise"]').prop('checked', false); + expertiseAreas.forEach(area => { + const trimmedArea = area.trim(); + if (trimmedArea) { + $(`#expertise_${trimmedArea}`).prop('checked', true); + } + }); + } else { + $('input[name="expertise"]').prop('checked', false); + } + + // Update modal for edit mode + $('#modalTitle').text('Edit Professional'); + $('#passwordRequired').text(''); + $('#passwordHelp').show(); + $('#password').prop('required', false).val(''); + + // Close loading dialog and show modal + Swal.close(); + $('#professionalModal').modal('show'); + + // Ensure inputs work properly after modal is shown + setTimeout(() => { + ensureInputsWorking(); + forceInputFunctionality(); + debugFormInputs(); + const firstInput = $('#professionalModal input[required]').first(); + if (firstInput.length) { + firstInput.focus(); + console.log(' Focused on first input:', firstInput.attr('name')); + } + }, 300); + + console.log(' Professional form populated successfully'); + } else { + console.error(' Professional not found with ID:', id); + Swal.fire('Error', 'Professional not found.', 'error'); + } + }) + .catch(error => { + console.error(' Error loading professional:', error); + Swal.fire('Error', `Failed to load professional data: ${error.message}`, 'error'); + }); + }; + + window.deleteProfessional = function(id) { + Swal.fire({ + title: 'Delete Professional?', + text: 'This action cannot be undone!', + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6', + confirmButtonText: 'Yes, delete it!' + }).then((result) => { + if (result.isConfirmed) { + fetch(`${API_ROOT}/admin/professionals/${id}`, { + method: 'DELETE' + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + Swal.fire('Deleted!', 'Professional has been deleted.', 'success'); + loadProfessionals(); + } else { + Swal.fire('Error!', data.error || 'Failed to delete professional.', 'error'); + } + }) + .catch(error => { + console.error('Error deleting professional:', error); + Swal.fire('Error!', 'Failed to delete professional.', 'error'); + }); + } + }); + }; + + window.toggleProfessionalStatus = toggleProfessionalStatus; + + window.viewBooking = function(id) { + // Show loading state + $('#bookingDetails').html('
Loading booking details...
'); + $('#bookingModal').modal('show'); + + // Load booking details + fetch(`${API_ROOT}/admin/bookings`) + .then(response => response.json()) + .then(data => { + const booking = data.bookings.find(b => b.booking_id === id); + if (booking) { + const scheduledTime = new Date(booking.scheduled_datetime * 1000).toLocaleString(); + const createdTime = new Date(booking.created_ts * 1000).toLocaleString(); + const userInitials = getUserInitials(booking.user_fullname || booking.user_account || 'Guest'); + const professionalInitials = getUserInitials(booking.professional_name || 'Unassigned'); + + const bookingDetails = ` + +
+
+
+

+ + Booking Details +

+

Booking ID: ${booking.booking_id}

+
+
+ + + ${booking.booking_status.toUpperCase()} + +
+
+
+ + +
+
+
+ User Information +
+
+
+
+
+
+ ${userInitials} +
+
+
+
+
+
Personal Details
+

Full Name: ${booking.user_fullname || booking.user_account || 'Guest User'}

+

Username: ${booking.user_account || 'N/A'}

+

Email: + + ${booking.user_email || 'No email provided'} + +

+

Phone: + + ${booking.user_phone || 'No phone provided'} + +

+
+
+
Location Information
+

Province: ${booking.user_province || 'Not specified'}

+

District: ${booking.user_district || 'Not specified'}

+

Full Location: ${booking.user_location || 'Location not specified'}

+

IP Address: ${booking.user_ip || 'N/A'}

+
+
+
+
+
+
+ + +
+
+
+ Professional Information +
+
+
+
+
+
+ ${professionalInitials} +
+
+
+
+
+
Professional Details
+

Name: ${booking.professional_name || 'Unassigned'}

+

Specialization: ${booking.professional_specialization || 'Not specified'}

+

Email: + + ${booking.professional_email || 'No email provided'} + +

+

Phone: + + ${booking.professional_phone || 'No phone provided'} + +

+
+
+
Assignment Status
+

Status: ${booking.professional_name ? 'Assigned' : 'Unassigned'}

+

Assignment Date: ${booking.professional_name ? createdTime : 'Pending'}

+

Professional ID: ${booking.professional_id || 'N/A'}

+

Experience: ${booking.professional_experience || 'N/A'} years

+
+
+
+
+
+
+ + +
+
+
+ Booking Details +
+
+
+
+
+
Schedule Information
+

Scheduled Time: ${scheduledTime}

+

Created: ${createdTime}

+

Session Type: ${booking.session_type || 'Emergency'}

+

Duration: ${booking.session_duration || '60 minutes'}

+
+
+
Risk Assessment
+

Risk Level: + + + ${booking.risk_level.toUpperCase()} + +

+

Risk Score: ${(booking.risk_score * 100).toFixed(1)}%

+

Detected Indicators: ${booking.detected_indicators || 'None detected'}

+

Assessment Time: ${new Date(booking.assessment_timestamp * 1000).toLocaleString()}

+
+
+
+
+ + +
+
+
+ Additional Information +
+
+
+
+
+
Session Details
+

Location Preference: ${booking.location_preference || 'Not specified'}

+

Session Notes: ${booking.session_notes || 'No notes available'}

+

Treatment Plan: ${booking.treatment_plan || 'Not available'}

+
+
+
System Information
+

Conversation ID: ${booking.conv_id || 'N/A'}

+

Booking Source: ${booking.booking_source || 'Automated'}

+

Last Updated: ${new Date(booking.updated_ts * 1000).toLocaleString()}

+

System Notes: ${booking.notes || 'No additional notes'}

+
+
+
+
+ `; + $('#bookingDetails').html(bookingDetails); + } else { + $('#bookingDetails').html('
Booking not found.
'); + } + }) + .catch(error => { + console.error('Error loading booking details:', error); + $('#bookingDetails').html('
Error loading booking details.
'); + }); + }; + + /** + * Get status icon for display + */ + function getStatusIcon(status) { + const statusIcons = { + pending: 'clock', + confirmed: 'check-circle', + completed: 'check-double', + declined: 'times-circle', + cancelled: 'ban' + }; + return statusIcons[status.toLowerCase()] || 'question-circle'; + } + + /** + * Get risk icon for display + */ + function getRiskIcon(riskLevel) { + const riskIcons = { + critical: 'exclamation-triangle', + high: 'exclamation-circle', + medium: 'info-circle', + low: 'check-circle' + }; + return riskIcons[riskLevel.toLowerCase()] || 'question-circle'; + } + + window.editBooking = function(id) { + Swal.fire({ + title: 'Edit Booking', + text: `Edit booking with ID: ${id}`, + icon: 'info', + showCancelButton: true, + confirmButtonText: 'View Details', + cancelButtonText: 'Cancel' + }).then((result) => { + if (result.isConfirmed) { + viewBooking(id); + } + }); + }; + + + + // Global debug functions + window.debugFormInputs = debugFormInputs; + window.forceInputFunctionality = forceInputFunctionality; + window.ensureInputsWorking = ensureInputsWorking; + window.testInputs = function() { + console.log('๐Ÿงช Testing input functionality...'); + const form = $('#professionalForm'); + if (form.length === 0) { + console.log(' Form not found'); + return; + } + + const inputs = form.find('input, select, textarea'); + console.log('๐Ÿ“ Found', inputs.length, 'inputs'); + + inputs.each(function(index) { + const input = $(this); + console.log(`Input ${index}:`, { + name: input.attr('name'), + type: input.attr('type'), + value: input.val(), + disabled: input.prop('disabled'), + readonly: input.prop('readonly') + }); + }); + + // Test focus + const firstInput = inputs.first(); + if (firstInput.length) { + firstInput.focus(); + console.log(' Focused on first input:', firstInput.attr('name')); + } + }; + + // Show dashboard by default + showSection('dashboard'); + + /** + * Initialize expertise areas functionality + */ + function initializeExpertiseAreas() { + console.log('๐Ÿง  Initializing expertise areas functionality...'); + + // Select All button + $('#selectAllExpertise').on('click', function() { + $('input[name="expertise"]').prop('checked', true); + updateExpertiseCount(); + updateExpertiseValidation(); + console.log(' All expertise areas selected'); + }); + + // Clear All button + $('#clearAllExpertise').on('click', function() { + $('input[name="expertise"]').prop('checked', false); + updateExpertiseCount(); + updateExpertiseValidation(); + console.log(' All expertise areas cleared'); + }); + + // Individual checkbox change + $('input[name="expertise"]').on('change', function() { + updateExpertiseCount(); + updateExpertiseValidation(); + + // Add visual feedback + const label = $(this).next('.expertise-label'); + if ($(this).is(':checked')) { + label.addClass('selected'); + console.log(' Expertise selected:', $(this).val()); + } else { + label.removeClass('selected'); + console.log(' Expertise deselected:', $(this).val()); + } + }); + + // Initialize count + updateExpertiseCount(); + updateExpertiseValidation(); + + console.log(' Expertise areas functionality initialized'); + } + + /** + * Update expertise selection count + */ + function updateExpertiseCount() { + const selectedCount = $('input[name="expertise"]:checked').length; + const totalCount = $('input[name="expertise"]').length; + + $('#selectedCount').text(selectedCount); + + // Update select all button state + const selectAllBtn = $('#selectAllExpertise'); + const clearAllBtn = $('#clearAllExpertise'); + + if (selectedCount === totalCount) { + selectAllBtn.addClass('btn-primary').removeClass('btn-outline-primary'); + selectAllBtn.html(' All Selected'); + } else { + selectAllBtn.addClass('btn-outline-primary').removeClass('btn-primary'); + selectAllBtn.html(' Select All'); + } + + if (selectedCount === 0) { + clearAllBtn.addClass('btn-secondary').removeClass('btn-outline-secondary'); + clearAllBtn.html(' All Cleared'); + } else { + clearAllBtn.addClass('btn-outline-secondary').removeClass('btn-secondary'); + clearAllBtn.html(' Clear All'); + } + } + + /** + * Update expertise validation state + */ + function updateExpertiseValidation() { + const selectedCount = $('input[name="expertise"]:checked').length; + const expertiseContainer = $('.form-group:has(input[name="expertise"])'); + + if (selectedCount === 0) { + expertiseContainer.addClass('is-invalid').removeClass('is-valid'); + $('input[name="expertise"]').addClass('is-invalid'); + } else { + expertiseContainer.removeClass('is-invalid').addClass('is-valid'); + $('input[name="expertise"]').removeClass('is-invalid'); + } + } + + /** + * Validate expertise areas selection + */ + function validateExpertiseAreas() { + const selectedCount = $('input[name="expertise"]:checked').length; + return selectedCount > 0; + } + + /** + * Initialize bookings filtering functionality + */ + function initializeBookingsFiltering() { + console.log(' Initializing bookings filtering...'); + + // Filter change events + $('#statusFilter, #riskLevelFilter, #professionalFilter, #fromDateFilter, #toDateFilter').on('change', function() { + applyBookingsFilters(); + }); + + // Search input + $('#bookingSearch').on('keyup', function() { + clearTimeout(this.searchTimeout); + this.searchTimeout = setTimeout(() => { + applyBookingsFilters(); + }, 300); + }); + + // Clear filters button + $('#clearFiltersBtn').on('click', function() { + clearBookingsFilters(); + }); + + // Apply filters button + $('#applyFiltersBtn').on('click', function() { + applyBookingsFilters(); + }); + + console.log(' Bookings filtering initialized'); + } + + /** + * Apply bookings filters + */ + function applyBookingsFilters() { + const status = $('#statusFilter').val(); + const riskLevel = $('#riskLevelFilter').val(); + const professional = $('#professionalFilter').val(); + const fromDate = $('#fromDateFilter').val(); + const toDate = $('#toDateFilter').val(); + const search = $('#bookingSearch').val().toLowerCase(); + + console.log(' Applying filters:', { status, riskLevel, professional, fromDate, toDate, search }); + + if (dataTables.bookings) { + dataTables.bookings.column(5).search(status); // Status column + dataTables.bookings.column(3).search(riskLevel); // Risk level column + dataTables.bookings.column(2).search(professional); // Professional column + dataTables.bookings.search(search).draw(); + } + + // Update filter button states + updateFilterButtonStates(); + } + + /** + * Clear all bookings filters + */ + function clearBookingsFilters() { + $('#statusFilter, #riskLevelFilter, #professionalFilter').val(''); + $('#fromDateFilter, #toDateFilter').val(''); + $('#bookingSearch').val(''); + + if (dataTables.bookings) { + dataTables.bookings.search('').columns().search('').draw(); + } + + updateFilterButtonStates(); + console.log('๐Ÿงน Filters cleared'); + } + + /** + * Update filter button states + */ + function updateFilterButtonStates() { + const hasActiveFilters = $('#statusFilter').val() || + $('#riskLevelFilter').val() || + $('#professionalFilter').val() || + $('#fromDateFilter').val() || + $('#toDateFilter').val() || + $('#bookingSearch').val(); + + if (hasActiveFilters) { + $('#clearFiltersBtn').removeClass('btn-outline-secondary').addClass('btn-secondary'); + $('#applyFiltersBtn').removeClass('btn-outline-primary').addClass('btn-primary'); + } else { + $('#clearFiltersBtn').removeClass('btn-secondary').addClass('btn-outline-secondary'); + $('#applyFiltersBtn').removeClass('btn-primary').addClass('btn-outline-primary'); + } + } + + /** + * Complete booking action + */ + window.completeBooking = function(bookingId) { + Swal.fire({ + title: 'Complete Booking', + text: 'Are you sure you want to mark this booking as completed?', + icon: 'question', + showCancelButton: true, + confirmButtonText: 'Yes, Complete', + cancelButtonText: 'Cancel', + confirmButtonColor: '#10b981' + }).then((result) => { + if (result.isConfirmed) { + // Update booking status + updateBookingStatus(bookingId, 'completed'); + } + }); + }; + + /** + * Cancel booking action + */ + window.cancelBooking = function(bookingId) { + Swal.fire({ + title: 'Cancel Booking', + text: 'Are you sure you want to cancel this booking?', + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Yes, Cancel', + cancelButtonText: 'Keep Booking', + confirmButtonColor: '#ef4444' + }).then((result) => { + if (result.isConfirmed) { + // Update booking status + updateBookingStatus(bookingId, 'cancelled'); + } + }); + }; + + /** + * Update booking status + */ + function updateBookingStatus(bookingId, newStatus) { + console.log(`๐Ÿ“ Updating booking ${bookingId} to ${newStatus}`); + + // Show loading + Swal.fire({ + title: 'Updating...', + text: 'Please wait while we update the booking status.', + allowOutsideClick: false, + showConfirmButton: false, + didOpen: () => { + Swal.showLoading(); + } + }); + + // Simulate API call (replace with actual API call) + setTimeout(() => { + Swal.fire({ + title: 'Success!', + text: `Booking has been ${newStatus}.`, + icon: 'success', + confirmButtonText: 'OK' + }).then(() => { + // Reload bookings + loadBookings(); + }); + }, 1000); + } + +})(); +