275 lines
11 KiB
JavaScript
275 lines
11 KiB
JavaScript
/**
|
|
* 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 = `<tr><td colspan="${colspan}" class="text-center ${cssClass}">${message}</td></tr>`;
|
|
}
|
|
|
|
/**
|
|
* 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 = `
|
|
<td><a class="user-link" data-user="${entry.username}">${entry.username}</a></td>
|
|
<td>${formattedPeriod || 'N/A'}</td>
|
|
<td>${formatDurationHours(entry.real_hours_counted)}</td>
|
|
<td class="user-state-cell">${userStateDisplay}</td>
|
|
`;
|
|
}
|
|
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}`);
|
|
}
|
|
});
|
|
}
|