/** * tableManager.js - Table management functionality * * This module handles all table-related operations including populating, * updating, and managing the data display in tables. */ import AppState from './state.js'; // import { formatTimeToGMT4, formatDurationHours } from './formatters.js'; // formatTimeToGMT4 likely not needed import { formatDurationHours } from './formatters.js'; // Keep for consistency if displaying hours import { formatDateForDisplay } from './dateUtils.js'; import { getUserStateDisplay } from './userStates.js'; // The userActivityModal is invoked via a custom event 'user-activity-requested'. // Direct import of userActivity.js is not needed here as userActivity.js listens for the event. // // Import will be added when userActivity module is created // // import { showUserActivityModal } from './userActivity.js'; /** * Helper function to determine if the "First Login Time" column should be hidden. * This column is being removed for the new real_work_hours display. * @returns {boolean} - True, as it's always hidden now. */ function _alwaysHideLoginTime() { return true; } /** * Helper function to show a message in the table body with correct colspan. * @param {string} message - The message to display. * @param {string} cssClass - CSS class for the message (e.g., 'text-danger', 'text-muted'). */ function _showTableMessage(message, cssClass = 'text-muted') { const reportBody = document.getElementById('reportBody'); if (!reportBody) { console.error("Table reportBody not found in _showTableMessage."); return; } // Try to determine current period and day for colspan, even if AppState is partially available let currentPeriodForColspan = 'daily'; // Default if AppState not fully usable let selectedWeekDayForColspan = null; if (AppState && typeof AppState.getCurrentPeriod === 'function') { currentPeriodForColspan = AppState.getCurrentPeriod(); } if (AppState && typeof AppState.getSelectedWeekDay === 'function') { selectedWeekDayForColspan = AppState.getSelectedWeekDay(); } const hideLogin = _alwaysHideLoginTime(); const colspan = hideLogin ? 3 : 4; reportBody.innerHTML = `${message}`; } /** * Public function to show a message (e.g., error or custom status) in the main report table. * @param {string} message - The message to display. * @param {boolean} isError - If true, cssClass will be 'text-danger', otherwise 'text-muted'. */ export function showReportTableMessage(message, isError = false) { _showTableMessage(message, isError ? 'text-danger' : 'text-muted'); } /** * Populate the main report table with data * @param {Array} data - Report data to display * @param {boolean} isAutoRefresh - Whether this is an auto-refresh operation */ export function populateTable(data, isAutoRefresh = false) { const reportBody = document.getElementById('reportBody'); const periodHeader = document.getElementById('periodHeader'); if (!AppState || typeof AppState.setReportData !== 'function' || typeof AppState.getCurrentPeriod !== 'function' || typeof AppState.getSelectedWeekDay !== 'function') { console.error("AppState or its methods not available in populateTable"); _showTableMessage("Error loading table: AppState not ready.", "text-danger"); return; } if (!reportBody || !periodHeader) { console.error("Table reportBody or periodHeader not found in populateTable."); // If reportBody is missing, _showTableMessage won't work either. return; } AppState.setReportData([...data]); if (!isAutoRefresh) { const currentPeriod = AppState.getCurrentPeriod(); switch (currentPeriod) { case 'daily': periodHeader.textContent = 'Day'; break; case 'weekly': periodHeader.textContent = 'Date'; break; case 'monthly': periodHeader.textContent = 'Date'; break; default: periodHeader.textContent = 'Date'; } // Remove this block as it's hiding the State column header // const firstLoginHeader = document.querySelector('#reportTable th:nth-child(4)'); // const hideLoginTime = _alwaysHideLoginTime(); // if (firstLoginHeader) { // firstLoginHeader.style.display = hideLoginTime ? 'none' : ''; // } else { // console.warn("First login time header cell not found for show/hide logic."); // } // Removed the data.length === 0 check here, populateTableRows will handle it. } if (isAutoRefresh && reportBody.children.length > 0 && data.length > 0) { // Only update if there's data updateTableData(data); } else { populateTableRows(data); // Let populateTableRows handle empty data message too } } /** * Populate table rows with data * @param {Array} data - Data to display in the table */ export function populateTableRows(data) { const reportBody = document.getElementById('reportBody'); if (!AppState || typeof AppState.getCurrentPeriod !== 'function' || typeof AppState.getSelectedWeekDay !== 'function') { console.error("AppState or its methods not available in populateTableRows"); _showTableMessage("Error populating rows: AppState not ready.", "text-danger"); return; } // reportBody check is now implicitly handled by _showTableMessage if we reach here // but _showTableMessage has its own check. const currentPeriod = AppState.getCurrentPeriod(); const selectedWeekDay = AppState.getSelectedWeekDay(); const hideLogin = _alwaysHideLoginTime(); reportBody.innerHTML = ''; if (!data || data.length === 0) { // Use the helper for "no data" message as well _showTableMessage("No data available.", "text-muted"); return; } data.forEach(entry => { const row = document.createElement('tr'); let periodValue = ''; // Determine which period key to use based on currentPeriod switch (currentPeriod) { case 'daily': periodValue = entry.day; break; case 'weekly': periodValue = entry.week_start; break; case 'monthly': periodValue = entry.month_start; break; } // Format date string from ISO (YYYY-MM-DD) to DD/MM/YYYY let formattedPeriod = formatDateForDisplay(entry.work_date); // Get user state const userStateDisplay = getUserStateDisplay(entry.username); // Show/hide First Login Time column based on period and day selection // const hideLogin = _alwaysHideLoginTime(); // Already defined above if (hideLogin) { // Don't include First Login Time cell row.innerHTML = ` ${entry.username} ${formattedPeriod || 'N/A'} ${formatDurationHours(entry.real_hours_counted)} ${userStateDisplay} `; } reportBody.appendChild(row); }); // Remove old event listeners if any were attached directly, though innerHTML='' should handle it. // Add a single event listener to reportBody for user links (event delegation) // This replaces: document.querySelectorAll('.user-link').forEach(link => { ... }); // Ensure only one listener is attached, remove if already present if (reportBody._userLinkClickListener) { reportBody.removeEventListener('click', reportBody._userLinkClickListener); } reportBody._userLinkClickListener = function(event) { const targetLink = event.target.closest('.user-link'); if (targetLink) { event.preventDefault(); // Prevent default anchor action if any const username = targetLink.getAttribute('data-user'); if (username) { // Dispatch a custom event. userActivity.js listens for this event // to show the modal, as initialized in dashboard.js. const customEvent = new CustomEvent('user-activity-requested', { detail: { username: username } }); document.dispatchEvent(customEvent); } } }; reportBody.addEventListener('click', reportBody._userLinkClickListener); } /** * Update existing table data without full redraw (for smoother auto-refresh) * @param {Array} data - Updated data to display */ export function updateTableData(data) { const reportBody = document.getElementById('reportBody'); if (!AppState || typeof AppState.getCurrentPeriod !== 'function' || typeof AppState.getSelectedWeekDay !== 'function') { console.error("AppState or its methods not available in updateTableData"); return; } if (!reportBody) { console.error("Table reportBody not found in updateTableData."); return; } // Log update for debugging console.log('Updating table data during auto-refresh:', data); // Create a map of username + work_date to data for quick lookup const dataMap = {}; data.forEach(entry => { // Use a composite key of username and work_date to uniquely identify each row const key = `${entry.username}_${entry.work_date}`; dataMap[key] = entry; }); // For each row in the table, update the data const rows = reportBody.querySelectorAll('tr'); rows.forEach(row => { const userLink = row.querySelector('.user-link'); if (!userLink) return; const username = userLink.getAttribute('data-user'); // Get the date from the second cell const dateCell = row.querySelector('td:nth-child(2)'); if (!dateCell) return; // Try to find matching data for this row // We need to try different formats since the display format might differ from the API format const displayedDate = dateCell.textContent; // First try direct match with username only (if we don't have date in key) let userData = null; // Try to match by username and work_date from the data for (const entry of data) { if (entry.username === username) { // Format the date for display to compare with what's in the cell const formattedDate = formatDateForDisplay(entry.work_date); if (formattedDate === displayedDate || displayedDate === 'N/A') { userData = entry; break; } } } if (userData) { // Update duration - make sure to use real_hours_counted const durationCell = row.querySelector('td:nth-child(3)'); if (durationCell) { durationCell.textContent = formatDurationHours(userData.real_hours_counted); // Log successful updates for debugging console.log(`Updated ${username} real_hours_counted to ${userData.real_hours_counted}`); } // Update state const stateCell = row.querySelector('td:last-child'); if (stateCell) { stateCell.innerHTML = getUserStateDisplay(username); } } else { console.warn(`No matching data found for user ${username} with date ${displayedDate}`); } }); }