/** * dashboard.js - Main dashboard controller * * This module serves as the main entry point and orchestrator for the dashboard, * importing and initializing all the specialized modules. */ // Import all the modules import AppState from './state.js'; import * as DateUtils from './dateUtils.js'; import * as Filters from './filters.js'; import * as TableManager from './tableManager.js'; import * as UserStates from './userStates.js'; import * as AutoRefresh from './autoRefresh.js'; import * as UserActivity from './userActivity.js'; import * as Api from './api.js'; // Main initialization function document.addEventListener('DOMContentLoaded', function() { // DOM element references const filterButtons = document.querySelectorAll('.btn-filter'); const userFilterInput = document.getElementById('userFilter'); const clearUserFilterBtn = document.getElementById('clearUserFilter'); const loadingSpinner = document.getElementById('loadingSpinner'); const errorMessage = document.getElementById('errorMessage'); const weekDaySelector = document.getElementById('weekDaySelector'); const weekDaySelect = document.getElementById('weekDaySelect'); const refreshStatesBtn = document.getElementById('refreshStates'); const dateNavControls = document.getElementById('dateNavControls'); const prevDateBtn = document.getElementById('prevDateBtn'); const nextDateBtn = document.getElementById('nextDateBtn'); const calendarBtn = document.getElementById('calendarBtn'); const dateSelector = document.getElementById('dateSelector'); // Set initial value for date selector if AppState and getSelectedDate are defined if (AppState && typeof AppState.getSelectedDate === 'function') { const selectedDate = AppState.getSelectedDate(); if (selectedDate) { dateSelector.value = selectedDate; } } // Initialize all components initializeNavigation(); initializeFilters(); initializeWeekDaySelector(); // Initialize sorting, state filters, and auto-refresh Filters.initializeSortingHeaders(); Filters.initializeStateFilters(); AutoRefresh.initializeAutoRefresh(); // Set initial sort indicator based on AppState default sort settings setInitialSortIndicator(); // Initialize user activity modal UserActivity.initializeUserActivityModal(); // First load user states, then load report data UserStates.refreshUserStates(true).then(() => { // Load initial report data loadReportData(); }).catch(error => { console.error('Error loading initial user states:', error); // Still load report data even if states fail. loadReportData(); }); /** * Set the initial sort indicator on the table header based on AppState default values */ function setInitialSortIndicator() { const sortColumn = AppState.getCurrentSortColumn(); const sortDirection = AppState.getCurrentSortDirection(); if (sortColumn && sortDirection) { const header = document.querySelector(`.sortable[data-sort="${sortColumn}"]`); if (header) { header.setAttribute('data-dir', sortDirection); } } } /** * Updates the visibility of UI controls based on the selected reporting period. * @param {string} period - The current reporting period ('daily', 'weekly', 'monthly'). */ function updatePeriodControlsVisibility(period) { if (period === 'weekly') { weekDaySelector.classList.remove('d-none'); populateWeekDaySelector(); // Populate selector when shown dateNavControls.classList.add('d-none'); } else if (period === 'daily') { weekDaySelector.classList.add('d-none'); dateNavControls.classList.remove('d-none'); updateDateDisplay(); // Update date display for daily view } else { // monthly or any other period weekDaySelector.classList.add('d-none'); dateNavControls.classList.add('d-none'); } } /** * Initialize date navigation controls. * Sets up event listeners and initial visibility. */ function initializeNavigation() { // Update the date display updateDateDisplay(); // Show date navigation controls if daily view is active if (AppState.getCurrentPeriod() === 'daily') { dateNavControls.classList.remove('d-none'); } // Add event listeners for date navigation prevDateBtn.addEventListener('click', goToPreviousDay); nextDateBtn.addEventListener('click', goToNextDay); calendarBtn.addEventListener('click', toggleCalendar); dateSelector.addEventListener('change', handleDateSelection); } /** * Initialize filter buttons (Daily, Weekly, Monthly) and the user text filter. * Sets up event listeners for period changes and user input. */ function initializeFilters() { // Add event listeners to filter buttons filterButtons.forEach(button => { button.addEventListener('click', function() { filterButtons.forEach(btn => btn.classList.remove('active')); this.classList.add('active'); const period = this.getAttribute('data-period'); AppState.setCurrentPeriod(period); // Update visibility of period-specific controls updatePeriodControlsVisibility(period); loadReportData(); // Reload data when period changes }); }); // User filter input (with debounce) let userFilterTimeout = null; userFilterInput.addEventListener('input', function() { // Clear any existing timeout if (userFilterTimeout) { clearTimeout(userFilterTimeout); } // Set a new timeout to delay the filter application (300ms debounce) userFilterTimeout = setTimeout(function() { AppState.setUserFilterText(userFilterInput.value.trim()); loadReportData(); }, 300); }); // Clear user filter button clearUserFilterBtn.addEventListener('click', function() { userFilterInput.value = ''; AppState.setUserFilterText(''); loadReportData(); }); // Refresh states button refreshStatesBtn.addEventListener('click', function() { UserStates.refreshUserStates(true); // Force refresh }); } /** * Initialize the week day selector for the weekly view. * Sets up event listener for day selection changes. */ function initializeWeekDaySelector() { // Add event listener for week day selector weekDaySelect.addEventListener('change', function() { AppState.setSelectedWeekDay(this.value); loadReportData(); // Reload data when day selection changes }); } /** * Populate the week day selector dropdown with days of the current week. * Fetches days from DateUtils. */ function populateWeekDaySelector() { weekDaySelect.innerHTML = ''; const days = DateUtils.getDaysOfWeek(); days.forEach(day => { const option = document.createElement('option'); option.value = day.date; option.textContent = `${day.dayName} (${day.displayDate})`; weekDaySelect.appendChild(option); }); } /** * Update the current date display in the UI (e.g., "May 10, 2024"). * Uses the selected date from AppState. */ function updateDateDisplay() { const currentDateDisplay = document.getElementById('currentDateDisplay'); currentDateDisplay.textContent = DateUtils.formatDateForDisplay(AppState.getSelectedDate()); } /** * Navigate to the previous day and reload report data. * Updates AppState and the UI display. */ function goToPreviousDay() { const newDate = DateUtils.getPreviousDay(AppState.getCurrentDate()); AppState.setCurrentDate(newDate); updateDateDisplay(); loadReportData(); } /** * Navigate to the next day and reload report data. * Updates AppState and the UI display. */ function goToNextDay() { const newDate = DateUtils.getNextDay(AppState.getCurrentDate()); AppState.setCurrentDate(newDate); updateDateDisplay(); loadReportData(); } /** * Handle date selection from the calendar input. * Updates AppState, UI display, and reloads report data. */ function handleDateSelection() { if (dateSelector.value) { AppState.setSelectedDate(dateSelector.value); updateDateDisplay(); dateSelector.style.display = 'none'; loadReportData(); } } /** * Toggle the visibility of the calendar date input. */ function toggleCalendar() { if (dateSelector.style.display === 'none') { dateSelector.style.display = 'inline-block'; dateSelector.value = AppState.getSelectedDate(); dateSelector.focus(); } else { dateSelector.style.display = 'none'; } } /** * Load report data from the API and update the table. * Manages loading spinner and error messages. * @param {boolean} [isAutoRefresh=false] - Indicates if the load is triggered by auto-refresh. * If true, loading spinner is not shown. * @async */ async function loadReportData(isAutoRefresh = false) { if (isAutoRefresh) { // For auto-refresh, don't show spinner to avoid UI flicker // Just update the last refresh time if (AppState && typeof AppState.setLastRefreshTime === 'function') { AppState.setLastRefreshTime(Date.now()); } } else { loadingSpinner.classList.remove('d-none'); // Show spinner for manual refresh } errorMessage.classList.add('d-none'); // Hide error message try { // Use the API module to load the report data const data = await Api.loadReportData(isAutoRefresh); // Apply default sorting if needed const sortedData = AppState.getCurrentSortColumn() && AppState.getCurrentSortDirection() ? Filters.sortReportTable(data) : data; // Use the TableManager module to populate the table TableManager.populateTable(sortedData, isAutoRefresh); // If there's an active state filter, reapply it if (AppState && typeof AppState.getCurrentStateFilter === 'function' && AppState.getCurrentStateFilter() !== 'all') { Filters.filterTableByState(AppState.getCurrentStateFilter()); } } catch (error) { console.error('Error fetching report data:', error); errorMessage.textContent = `Error: ${error.message || 'Network error or failed to fetch data.'}`; errorMessage.classList.remove('d-none'); // Use TableManager to display the error in the table area TableManager.showReportTableMessage('Failed to load data. See console for details.', true); } finally { loadingSpinner.classList.add('d-none'); // Hide spinner } } });