/** * Care Book Ultimate - Admin Interface JavaScript * * Modern admin interface with exceptional UX * - Real-time interactions * - <30 second learning curve * - Accessibility support * - Performance optimized * * @package CareBook\Ultimate * @since 1.0.0 */ (function($, wp, careBookUltimate) { 'use strict'; if (typeof careBookUltimate === 'undefined') { console.error('Care Book Ultimate: Configuration not loaded'); return; } /** * Admin Interface Controller * Manages all admin interface functionality */ const AdminInterface = { // State management state: { currentTab: 'doctors', loading: false, selectedItems: new Set(), searchTimeout: null, autoRefreshInterval: null, cache: new Map(), pagination: { doctors: { page: 1, total: 0, perPage: 20 }, services: { page: 1, total: 0, perPage: 20 } }, sortOrder: { doctors: { column: 'name', direction: 'asc' }, services: { column: 'name', direction: 'asc' } } }, // Configuration config: careBookUltimate.config || {}, strings: careBookUltimate.strings || {}, /** * Initialize the admin interface */ init() { this.bindEvents(); this.initTabs(); this.initTheme(); this.initHelp(); this.initTooltips(); this.loadInitialData(); if (this.config.autoRefresh) { this.startAutoRefresh(); } // Keyboard shortcuts this.initKeyboardShortcuts(); console.log('Care Book Ultimate Admin Interface initialized'); }, /** * Bind all event handlers */ bindEvents() { // Tab navigation $('.cbu-nav-tab').on('click', this.handleTabClick.bind(this)); // Search functionality $('#cbu-doctors-search, #cbu-services-search').on('input', this.handleSearch.bind(this)); $('.cbu-search-clear').on('click', this.clearSearch.bind(this)); // Filter controls $('#cbu-doctors-filter, #cbu-services-status-filter, #cbu-services-doctor-filter').on('change', this.handleFilter.bind(this)); // Refresh buttons $('#cbu-refresh-doctors, #cbu-refresh-services').on('click', this.handleRefresh.bind(this)); // Selection handlers $(document).on('change', '.cbu-select-all', this.handleSelectAll.bind(this)); $(document).on('change', '.cbu-doctor-checkbox, .cbu-service-checkbox', this.handleItemSelection.bind(this)); // Toggle buttons $(document).on('click', '.cbu-toggle-button', this.handleToggle.bind(this)); // Bulk actions $('#cbu-bulk-block-doctors, #cbu-bulk-block-services').on('click', this.handleBulkBlock.bind(this)); $('#cbu-bulk-unblock-doctors, #cbu-bulk-unblock-services').on('click', this.handleBulkUnblock.bind(this)); // Pagination $(document).on('click', '.cbu-pagination-btn', this.handlePagination.bind(this)); // Table sorting $(document).on('click', '.cbu-table th[data-sort]', this.handleSort.bind(this)); // Modal and overlay handlers $(document).on('click', '.cbu-modal-close, .cbu-modal-backdrop', this.closeModal.bind(this)); $(document).on('click', '.cbu-toast-close', this.closeToast.bind(this)); // Help toggle $('.cbu-help-toggle').on('click', this.toggleHelp.bind(this)); // Theme toggle $('#cbu-dark-mode').on('change', this.handleThemeToggle.bind(this)); // Window events $(window).on('beforeunload', this.handleBeforeUnload.bind(this)); $(window).on('resize', this.debounce(this.handleResize.bind(this), 250)); }, /** * Initialize tab functionality */ initTabs() { const urlHash = window.location.hash.slice(1); if (urlHash && $('.cbu-nav-tab[data-tab="' + urlHash + '"]').length) { this.switchTab(urlHash); } else { this.switchTab(this.state.currentTab); } }, /** * Initialize theme functionality */ initTheme() { const savedTheme = localStorage.getItem('cbu_dark_mode'); const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (savedTheme === 'true' || (savedTheme === null && prefersDark)) { this.enableDarkMode(); $('#cbu-dark-mode').prop('checked', true); } // Listen for system theme changes window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { if (localStorage.getItem('cbu_dark_mode') === null) { if (e.matches) { this.enableDarkMode(); } else { this.disableDarkMode(); } } }); }, /** * Initialize help system */ initHelp() { // Close help panel when clicking outside $(document).on('click', (e) => { const helpPanel = $('.cbu-help-panel'); const helpToggle = $('.cbu-help-toggle'); if (!helpPanel.is(e.target) && !helpPanel.has(e.target).length && !helpToggle.is(e.target) && !helpToggle.has(e.target).length) { this.hideHelp(); } }); }, /** * Initialize tooltips for better UX */ initTooltips() { // Add tooltips to buttons and controls $('[data-tooltip]').each(function() { const $this = $(this); const title = $this.data('tooltip'); $this.attr('title', title).removeAttr('data-tooltip'); }); }, /** * Initialize keyboard shortcuts */ initKeyboardShortcuts() { $(document).on('keydown', (e) => { // Don't trigger shortcuts when typing in inputs if ($(e.target).is('input, textarea, select')) { return; } switch (e.key) { case '/': e.preventDefault(); this.focusSearch(); break; case 'Escape': this.closeModal(); this.hideHelp(); break; case '?': if (e.shiftKey) { e.preventDefault(); this.toggleHelp(); } break; } // Tab shortcuts (1-4) if (e.key >= '1' && e.key <= '4' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); const tabs = ['doctors', 'services', 'bulk', 'settings']; const tabIndex = parseInt(e.key) - 1; if (tabs[tabIndex]) { this.switchTab(tabs[tabIndex]); } } // Ctrl+A for select all if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); this.selectAllVisible(); } }); }, /** * Load initial data */ loadInitialData() { this.loadDoctors(); this.loadSystemStatus(); }, /** * Start auto-refresh if enabled */ startAutoRefresh() { if (this.state.autoRefreshInterval) { clearInterval(this.state.autoRefreshInterval); } this.state.autoRefreshInterval = setInterval(() => { if (!this.state.loading && document.visibilityState === 'visible') { this.refreshCurrentTab(); } }, this.config.refreshInterval || 30000); }, /** * Stop auto-refresh */ stopAutoRefresh() { if (this.state.autoRefreshInterval) { clearInterval(this.state.autoRefreshInterval); this.state.autoRefreshInterval = null; } }, /** * Handle tab clicks */ handleTabClick(e) { e.preventDefault(); const tab = $(e.currentTarget).data('tab'); this.switchTab(tab); }, /** * Switch to a specific tab */ switchTab(tab) { // Update navigation $('.cbu-nav-tab').removeClass('cbu-nav-tab-active').attr('aria-selected', 'false'); $('.cbu-nav-tab[data-tab="' + tab + '"]').addClass('cbu-nav-tab-active').attr('aria-selected', 'true'); // Update panels $('.cbu-tab-panel').removeClass('cbu-tab-panel-active').attr('aria-hidden', 'true'); $('#cbu-' + tab + '-panel').addClass('cbu-tab-panel-active').attr('aria-hidden', 'false'); // Update state this.state.currentTab = tab; // Update URL without page reload if (history.pushState) { history.pushState(null, null, '#' + tab); } // Load tab data if needed switch (tab) { case 'doctors': this.loadDoctors(); break; case 'services': this.loadServices(); break; case 'bulk': this.updateBulkStats(); break; case 'settings': this.loadSettings(); break; } // Focus management for accessibility $('#cbu-tab-' + tab).focus(); }, /** * Handle search input */ handleSearch(e) { const searchTerm = $(e.target).val(); const searchType = e.target.id.includes('doctors') ? 'doctors' : 'services'; // Clear existing timeout if (this.state.searchTimeout) { clearTimeout(this.state.searchTimeout); } // Debounce search this.state.searchTimeout = setTimeout(() => { this.performSearch(searchType, searchTerm); }, this.config.searchDelay || 300); // Show/hide clear button const clearBtn = $(e.target).siblings('.cbu-search-clear'); if (searchTerm) { clearBtn.show(); } else { clearBtn.hide(); } }, /** * Clear search */ clearSearch(e) { const searchInput = $(e.target).siblings('.cbu-search-input'); searchInput.val('').trigger('input').focus(); $(e.target).hide(); }, /** * Perform search */ performSearch(type, term) { if (type === 'doctors') { this.loadDoctors({ search: term, page: 1 }); } else { this.loadServices({ search: term, page: 1 }); } }, /** * Handle filter changes */ handleFilter(e) { const $filter = $(e.target); const filterType = $filter.attr('id'); if (filterType.includes('doctors')) { this.loadDoctors({ page: 1 }); } else { this.loadServices({ page: 1 }); } }, /** * Handle refresh buttons */ handleRefresh(e) { e.preventDefault(); const type = e.target.id.includes('doctors') ? 'doctors' : 'services'; // Clear cache this.clearCache(type); if (type === 'doctors') { this.loadDoctors({ page: 1 }); } else { this.loadServices({ page: 1 }); } this.showToast('success', this.strings.success, 'Data refreshed successfully'); }, /** * Handle select all checkbox */ handleSelectAll(e) { const isChecked = $(e.target).is(':checked'); const type = e.target.id.includes('doctors') ? 'doctor' : 'service'; $('.cbu-' + type + '-checkbox').prop('checked', isChecked).trigger('change'); }, /** * Handle individual item selection */ handleItemSelection(e) { const $checkbox = $(e.target); const itemId = $checkbox.val(); const isChecked = $checkbox.is(':checked'); const type = $checkbox.hasClass('cbu-doctor-checkbox') ? 'doctors' : 'services'; if (isChecked) { this.state.selectedItems.add(type + ':' + itemId); } else { this.state.selectedItems.delete(type + ':' + itemId); } this.updateBulkActionButtons(); this.updateSelectAllState(type); this.updateBulkStats(); }, /** * Update bulk action button states */ updateBulkActionButtons() { const hasSelection = this.state.selectedItems.size > 0; $('.cbu-button-bulk-block, .cbu-button-bulk-unblock').prop('disabled', !hasSelection); }, /** * Update select all checkbox state */ updateSelectAllState(type) { const checkboxes = $('.cbu-' + type.slice(0, -1) + '-checkbox'); const checkedCheckboxes = checkboxes.filter(':checked'); const selectAll = $('#cbu-select-all-' + type); if (checkedCheckboxes.length === 0) { selectAll.prop('indeterminate', false).prop('checked', false); } else if (checkedCheckboxes.length === checkboxes.length) { selectAll.prop('indeterminate', false).prop('checked', true); } else { selectAll.prop('indeterminate', true).prop('checked', false); } }, /** * Handle toggle buttons */ handleToggle(e) { e.preventDefault(); const $button = $(e.currentTarget); const doctorId = $button.data('doctor-id'); const serviceId = $button.data('service-id'); const currentlyBlocked = $button.data('blocked'); // Optimistic UI update this.updateToggleButton($button, !currentlyBlocked); const data = { action: 'care_book_toggle_restriction', nonce: careBookUltimate.nonce, restriction_type: doctorId ? 'doctor' : 'service', target_id: doctorId || serviceId, doctor_id: doctorId || $button.data('doctor-id'), is_blocked: !currentlyBlocked }; this.makeAjaxRequest(data) .done((response) => { if (response.success) { this.showToast('success', this.strings.success, response.data.message); this.updateRowState($button.closest('tr'), !currentlyBlocked); this.loadSystemStatus(); // Update dashboard } else { // Revert optimistic update this.updateToggleButton($button, currentlyBlocked); this.showToast('error', this.strings.error, response.data.message); } }) .fail(() => { // Revert optimistic update this.updateToggleButton($button, currentlyBlocked); this.showToast('error', this.strings.error, 'Request failed. Please try again.'); }); }, /** * Update toggle button state */ updateToggleButton($button, isBlocked) { $button.data('blocked', isBlocked); if (isBlocked) { $button.removeClass('cbu-toggle-block').addClass('cbu-toggle-unblock'); $button.find('.dashicons').removeClass('dashicons-hidden').addClass('dashicons-visibility'); $button.find('.cbu-toggle-text').text('Unblock'); } else { $button.removeClass('cbu-toggle-unblock').addClass('cbu-toggle-block'); $button.find('.dashicons').removeClass('dashicons-visibility').addClass('dashicons-hidden'); $button.find('.cbu-toggle-text').text('Block'); } }, /** * Update row visual state */ updateRowState($row, isBlocked) { const $statusBadge = $row.find('.cbu-status-badge'); if (isBlocked) { $row.addClass('cbu-row-blocked'); $statusBadge.removeClass('cbu-status-available').addClass('cbu-status-blocked').text('Blocked'); } else { $row.removeClass('cbu-row-blocked'); $statusBadge.removeClass('cbu-status-blocked').addClass('cbu-status-available').text('Available'); } }, /** * Handle bulk block action */ handleBulkBlock(e) { e.preventDefault(); this.performBulkAction('block'); }, /** * Handle bulk unblock action */ handleBulkUnblock(e) { e.preventDefault(); this.performBulkAction('unblock'); }, /** * Perform bulk action */ performBulkAction(action) { if (this.state.selectedItems.size === 0) { this.showToast('warning', 'Warning', this.strings.selectItems); return; } const confirmMessage = action === 'block' ? `Block ${this.state.selectedItems.size} selected items?` : `Unblock ${this.state.selectedItems.size} selected items?`; if (!confirm(confirmMessage)) { return; } const restrictions = Array.from(this.state.selectedItems).map(item => { const [type, id] = item.split(':'); return { restriction_type: type.slice(0, -1), // Remove 's' from 'doctors'/'services' target_id: id, is_blocked: action === 'block', doctor_id: type === 'services' ? this.getDoctorIdForService(id) : id }; }); const data = { action: 'care_book_bulk_update', nonce: careBookUltimate.nonce, restrictions: restrictions }; this.showLoadingOverlay(); this.makeAjaxRequest(data) .done((response) => { this.hideLoadingOverlay(); if (response.success) { this.showToast('success', this.strings.success, response.data.message); this.refreshCurrentTab(); this.clearSelection(); } else { this.showToast('error', this.strings.error, response.data.message); } }) .fail(() => { this.hideLoadingOverlay(); this.showToast('error', this.strings.error, 'Bulk action failed. Please try again.'); }); }, /** * Handle pagination */ handlePagination(e) { e.preventDefault(); const $button = $(e.currentTarget); const page = parseInt($button.data('page')); const type = this.state.currentTab; if (page && !$button.is('[disabled]')) { this.state.pagination[type].page = page; if (type === 'doctors') { this.loadDoctors({ page: page }); } else if (type === 'services') { this.loadServices({ page: page }); } } }, /** * Handle table sorting */ handleSort(e) { const $th = $(e.currentTarget); const column = $th.data('sort'); const type = this.state.currentTab; let direction = 'asc'; if (this.state.sortOrder[type].column === column && this.state.sortOrder[type].direction === 'asc') { direction = 'desc'; } this.state.sortOrder[type] = { column, direction }; // Update visual indicators $th.siblings().removeAttr('data-sort-direction'); $th.attr('data-sort-direction', direction); // Reload data with new sorting if (type === 'doctors') { this.loadDoctors({ page: 1 }); } else if (type === 'services') { this.loadServices({ page: 1 }); } }, /** * Load doctors data */ loadDoctors(options = {}) { const params = { action: 'care_book_get_restrictions', nonce: careBookUltimate.nonce, restriction_type: 'doctor', page: options.page || this.state.pagination.doctors.page, per_page: this.state.pagination.doctors.perPage, search: options.search || $('#cbu-doctors-search').val(), filter: $('#cbu-doctors-filter').val(), sort_by: this.state.sortOrder.doctors.column, sort_order: this.state.sortOrder.doctors.direction }; // Check cache first const cacheKey = JSON.stringify(params); if (this.state.cache.has(cacheKey)) { this.renderDoctors(this.state.cache.get(cacheKey)); return; } this.showLoading('doctors'); this.makeAjaxRequest(params) .done((response) => { if (response.success) { // Cache the response this.state.cache.set(cacheKey, response.data); this.renderDoctors(response.data); this.updatePagination('doctors', response.data); this.updateTabBadge('doctors', response.data.total || 0); } else { this.showError('doctors', response.data.message); } }) .fail(() => { this.showError('doctors', 'Failed to load doctors data'); }) .always(() => { this.hideLoading('doctors'); }); }, /** * Load services data */ loadServices(options = {}) { const params = { action: 'care_book_get_restrictions', nonce: careBookUltimate.nonce, restriction_type: 'service', page: options.page || this.state.pagination.services.page, per_page: this.state.pagination.services.perPage, search: options.search || $('#cbu-services-search').val(), doctor_id: $('#cbu-services-doctor-filter').val(), filter: $('#cbu-services-status-filter').val(), sort_by: this.state.sortOrder.services.column, sort_order: this.state.sortOrder.services.direction }; const cacheKey = JSON.stringify(params); if (this.state.cache.has(cacheKey)) { this.renderServices(this.state.cache.get(cacheKey)); return; } this.showLoading('services'); this.makeAjaxRequest(params) .done((response) => { if (response.success) { this.state.cache.set(cacheKey, response.data); this.renderServices(response.data); this.updatePagination('services', response.data); this.updateTabBadge('services', response.data.total || 0); } else { this.showError('services', response.data.message); } }) .fail(() => { this.showError('services', 'Failed to load services data'); }) .always(() => { this.hideLoading('services'); }); }, /** * Load system status */ loadSystemStatus() { this.makeAjaxRequest({ action: 'care_book_system_status', nonce: careBookUltimate.nonce }).done((response) => { if (response.success) { this.updateDashboard(response.data); } }); }, /** * Render doctors table */ renderDoctors(data) { const $tbody = $('#cbu-doctors-list'); const template = $('#cbu-doctor-row-template').html(); if (!data.restrictions || data.restrictions.length === 0) { $tbody.html(`
No doctors match your current search and filter criteria.
No services match your current search and filter criteria.
${this.strings.loading}
${message}