work-tracing/static/js/tableManager.js
2025-05-16 17:55:30 +04:00

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}`);
}
});
}