446 lines
18 KiB
JavaScript
446 lines
18 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function() {
|
|
const filterButtons = document.querySelectorAll('.btn-filter');
|
|
const userFilterInput = document.getElementById('userFilter');
|
|
const clearUserFilterBtn = document.getElementById('clearUserFilter');
|
|
const reportBody = document.getElementById('reportBody');
|
|
const periodHeader = document.getElementById('periodHeader');
|
|
const loadingSpinner = document.getElementById('loadingSpinner');
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
const weekDaySelector = document.getElementById('weekDaySelector');
|
|
const weekDaySelect = document.getElementById('weekDaySelect');
|
|
|
|
// User activity modal elements
|
|
const userActivityModal = document.getElementById('userActivityModal');
|
|
const modalUsername = document.getElementById('modalUsername');
|
|
const startDateInput = document.getElementById('startDate');
|
|
const endDateInput = document.getElementById('endDate');
|
|
const applyDateRangeBtn = document.getElementById('applyDateRange');
|
|
const userActivityBody = document.getElementById('userActivityBody');
|
|
const modalLoadingSpinner = document.getElementById('modalLoadingSpinner');
|
|
const modalErrorMessage = document.getElementById('modalErrorMessage');
|
|
|
|
// Initialize date inputs with current date
|
|
const today = new Date().toISOString().split('T')[0];
|
|
startDateInput.value = today;
|
|
endDateInput.value = today;
|
|
|
|
let currentPeriod = 'daily'; // Default period
|
|
let userActivityModalInstance = null;
|
|
let selectedWeekDay = null;
|
|
let currentDate = new Date(); // Track the currently selected date
|
|
let selectedDate = formatDateForAPI(new Date()); // Current date in YYYY-MM-DD format
|
|
|
|
// User filter debounce timer
|
|
let userFilterTimeout = null;
|
|
|
|
// Format date as YYYY-MM-DD for API
|
|
function formatDateForAPI(date) {
|
|
return date.toISOString().split('T')[0];
|
|
}
|
|
|
|
// Format date for display (DD/MM/YYYY)
|
|
function formatDateForDisplay(dateStr) {
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString('en-GB', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric'
|
|
});
|
|
}
|
|
|
|
// Update the current date display in the UI
|
|
function updateDateDisplay() {
|
|
const currentDateDisplay = document.getElementById('currentDateDisplay');
|
|
currentDateDisplay.textContent = formatDateForDisplay(selectedDate);
|
|
}
|
|
|
|
// Function to navigate to previous day
|
|
function goToPreviousDay() {
|
|
currentDate.setDate(currentDate.getDate() - 1);
|
|
selectedDate = formatDateForAPI(currentDate);
|
|
updateDateDisplay();
|
|
loadReportData();
|
|
}
|
|
|
|
// Function to navigate to next day
|
|
function goToNextDay() {
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
selectedDate = formatDateForAPI(currentDate);
|
|
updateDateDisplay();
|
|
loadReportData();
|
|
}
|
|
|
|
// Function to handle date selection from calendar
|
|
function handleDateSelection() {
|
|
const dateSelector = document.getElementById('dateSelector');
|
|
if (dateSelector.value) {
|
|
selectedDate = dateSelector.value;
|
|
currentDate = new Date(selectedDate);
|
|
updateDateDisplay();
|
|
dateSelector.style.display = 'none';
|
|
loadReportData();
|
|
}
|
|
}
|
|
|
|
// Function to toggle calendar visibility
|
|
function toggleCalendar() {
|
|
const dateSelector = document.getElementById('dateSelector');
|
|
if (dateSelector.style.display === 'none') {
|
|
dateSelector.style.display = 'inline-block';
|
|
dateSelector.value = selectedDate;
|
|
dateSelector.focus();
|
|
} else {
|
|
dateSelector.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Function to get days of the current week (Monday to Sunday)
|
|
function getDaysOfWeek() {
|
|
const today = new Date();
|
|
const currentDay = today.getDay(); // 0 is Sunday, 1 is Monday, ...
|
|
const mondayOffset = currentDay === 0 ? -6 : 1 - currentDay; // Calculate days from today to Monday
|
|
|
|
const days = [];
|
|
const monday = new Date(today);
|
|
monday.setDate(today.getDate() + mondayOffset);
|
|
|
|
// Generate array with dates for Monday through Sunday
|
|
for (let i = 0; i < 7; i++) {
|
|
const date = new Date(monday);
|
|
date.setDate(monday.getDate() + i);
|
|
days.push({
|
|
date: date.toISOString().split('T')[0], // YYYY-MM-DD format
|
|
dayName: date.toLocaleDateString('en-US', { weekday: 'long' }), // Monday, Tuesday, etc.
|
|
displayDate: date.toLocaleDateString('en-GB') // DD/MM/YYYY format for display
|
|
});
|
|
}
|
|
return days;
|
|
}
|
|
|
|
// Function to populate the week day selector dropdown
|
|
function populateWeekDaySelector() {
|
|
weekDaySelect.innerHTML = '<option value="">All Week</option>';
|
|
const days = getDaysOfWeek();
|
|
|
|
days.forEach(day => {
|
|
const option = document.createElement('option');
|
|
option.value = day.date;
|
|
option.textContent = `${day.dayName} (${day.displayDate})`;
|
|
weekDaySelect.appendChild(option);
|
|
});
|
|
}
|
|
|
|
// Function to fetch and display report data
|
|
async function loadReportData() {
|
|
loadingSpinner.classList.remove('d-none'); // Show spinner
|
|
errorMessage.classList.add('d-none'); // Hide error message
|
|
reportBody.innerHTML = ''; // Clear previous data
|
|
|
|
const user = userFilterInput.value.trim();
|
|
let apiUrl = `/api/reports/${currentPeriod}`;
|
|
const params = new URLSearchParams();
|
|
|
|
if (user) {
|
|
params.append('user', user);
|
|
}
|
|
|
|
// Add date parameter for daily view
|
|
if (currentPeriod === 'daily') {
|
|
params.append('date', selectedDate);
|
|
}
|
|
|
|
// Add selected day parameter for weekly view if a specific day is selected
|
|
if (currentPeriod === 'weekly' && selectedWeekDay) {
|
|
params.append('day', selectedWeekDay);
|
|
}
|
|
|
|
if (params.toString()) {
|
|
apiUrl += `?${params.toString()}`;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(apiUrl);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const result = await response.json();
|
|
|
|
if (result.success && result.data) {
|
|
populateTable(result.data);
|
|
} else {
|
|
showError(result.message || 'Failed to load data.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching report data:', error);
|
|
showError('Network error or failed to fetch data.');
|
|
} finally {
|
|
loadingSpinner.classList.add('d-none'); // Hide spinner
|
|
}
|
|
}
|
|
|
|
// Function to populate the table with data
|
|
function populateTable(data) {
|
|
// Update table header based on period
|
|
switch (currentPeriod) {
|
|
case 'daily': periodHeader.textContent = 'Day'; break;
|
|
case 'weekly': periodHeader.textContent = 'Week'; break;
|
|
case 'monthly': periodHeader.textContent = 'Month Starting'; break;
|
|
default: periodHeader.textContent = 'Period';
|
|
}
|
|
|
|
// Show/hide First Login Time column based on period and day selection
|
|
const firstLoginHeader = document.querySelector('#reportTable th:nth-child(4)');
|
|
// Show First Login Time for daily view or when a specific day is selected in weekly view
|
|
const hideLoginTime = currentPeriod === 'monthly' ||
|
|
(currentPeriod === 'weekly' && !selectedWeekDay);
|
|
|
|
if (hideLoginTime) {
|
|
// Hide First Login Time column for monthly view and weekly view without day selection
|
|
firstLoginHeader.style.display = 'none';
|
|
} else {
|
|
// Show First Login Time column for daily view or when a specific day is selected
|
|
firstLoginHeader.style.display = '';
|
|
}
|
|
|
|
if (data.length === 0) {
|
|
reportBody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">No data found for the selected filters.</td></tr>';
|
|
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 = periodValue;
|
|
if (periodValue && periodValue.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
const dateParts = periodValue.substring(0, 10).split('-');
|
|
formattedPeriod = `${dateParts[2]}/${dateParts[1]}/${dateParts[0]}`;
|
|
}
|
|
|
|
// Format first login time for display (from ISO to local time)
|
|
let firstLoginTime = 'N/A';
|
|
if (entry.first_login_time) {
|
|
const loginDate = new Date(entry.first_login_time);
|
|
firstLoginTime = loginDate.toLocaleTimeString([], {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: true
|
|
});
|
|
}
|
|
|
|
if (hideLoginTime) {
|
|
// Don't include First Login Time cell in monthly and weekly view without day selection
|
|
row.innerHTML = `
|
|
<td><a class="user-link" data-user="${entry.user}">${entry.user}</a></td>
|
|
<td>${formattedPeriod || 'N/A'}</td>
|
|
<td>${entry.duration_hours !== null ? entry.duration_hours.toFixed(2) : 'N/A'}</td>
|
|
`;
|
|
} else {
|
|
row.innerHTML = `
|
|
<td><a class="user-link" data-user="${entry.user}">${entry.user}</a></td>
|
|
<td>${formattedPeriod || 'N/A'}</td>
|
|
<td>${entry.duration_hours !== null ? entry.duration_hours.toFixed(2) : 'N/A'}</td>
|
|
<td>${firstLoginTime}</td>
|
|
`;
|
|
}
|
|
reportBody.appendChild(row);
|
|
});
|
|
|
|
// Add event listeners to user links
|
|
document.querySelectorAll('.user-link').forEach(link => {
|
|
link.addEventListener('click', function() {
|
|
const username = this.getAttribute('data-user');
|
|
showUserActivityModal(username);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Function to show error messages
|
|
function showError(message) {
|
|
errorMessage.textContent = `Error: ${message}`;
|
|
errorMessage.classList.remove('d-none');
|
|
reportBody.innerHTML = '<tr><td colspan="4" class="text-center text-danger">Failed to load data.</td></tr>';
|
|
}
|
|
|
|
// Function to show user activity modal
|
|
function showUserActivityModal(username) {
|
|
modalUsername.textContent = username;
|
|
userActivityBody.innerHTML = '';
|
|
modalErrorMessage.classList.add('d-none');
|
|
|
|
// Initialize and show modal with Bootstrap 5
|
|
if (!userActivityModalInstance) {
|
|
userActivityModalInstance = new bootstrap.Modal(userActivityModal);
|
|
}
|
|
userActivityModalInstance.show();
|
|
|
|
// Load user activity data
|
|
loadUserActivityData(username);
|
|
}
|
|
|
|
// Function to load user activity data
|
|
async function loadUserActivityData(username) {
|
|
modalLoadingSpinner.classList.remove('d-none');
|
|
userActivityBody.innerHTML = '';
|
|
modalErrorMessage.classList.add('d-none');
|
|
|
|
const startDate = startDateInput.value;
|
|
const endDate = endDateInput.value;
|
|
|
|
try {
|
|
const response = await fetch(`/api/user-activity/${encodeURIComponent(username)}?start_date=${startDate}&end_date=${endDate}`);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.success && result.data) {
|
|
populateUserActivityTable(result.data.activities);
|
|
} else {
|
|
showModalError(result.message || 'Failed to load activity data');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching user activity data:', error);
|
|
showModalError('Network error or failed to fetch activity data');
|
|
} finally {
|
|
modalLoadingSpinner.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
// Function to populate user activity table
|
|
function populateUserActivityTable(activities) {
|
|
if (activities.length === 0) {
|
|
userActivityBody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">No activity found for the selected date range.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
activities.forEach(activity => {
|
|
const row = document.createElement('tr');
|
|
|
|
// Format date and times for display
|
|
const date = new Date(activity.date).toLocaleDateString();
|
|
const startTime = new Date(activity.start_time).toLocaleTimeString([], {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: true
|
|
});
|
|
const endTime = new Date(activity.end_time).toLocaleTimeString([], {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: true
|
|
});
|
|
|
|
row.innerHTML = `
|
|
<td>${date}</td>
|
|
<td>${startTime}</td>
|
|
<td>${endTime}</td>
|
|
<td>${activity.duration_hours.toFixed(2)}</td>
|
|
`;
|
|
userActivityBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
// Function to show modal error message
|
|
function showModalError(message) {
|
|
modalErrorMessage.textContent = `Error: ${message}`;
|
|
modalErrorMessage.classList.remove('d-none');
|
|
userActivityBody.innerHTML = '<tr><td colspan="4" class="text-center text-danger">Failed to load activity data.</td></tr>';
|
|
}
|
|
|
|
// Add event listeners to filter buttons
|
|
filterButtons.forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
// Update active button state
|
|
filterButtons.forEach(btn => btn.classList.remove('active'));
|
|
this.classList.add('active');
|
|
currentPeriod = this.getAttribute('data-period');
|
|
|
|
// Show/hide week day selector based on period
|
|
if (currentPeriod === 'weekly') {
|
|
weekDaySelector.classList.remove('d-none');
|
|
dateNavControls.classList.add('d-none');
|
|
populateWeekDaySelector();
|
|
} else if (currentPeriod === 'daily') {
|
|
weekDaySelector.classList.add('d-none');
|
|
dateNavControls.classList.remove('d-none');
|
|
updateDateDisplay();
|
|
} else {
|
|
weekDaySelector.classList.add('d-none');
|
|
dateNavControls.classList.add('d-none');
|
|
}
|
|
|
|
loadReportData(); // Reload data when period changes
|
|
});
|
|
});
|
|
|
|
// Add event listener for user filter input (typing with debounce)
|
|
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() {
|
|
loadReportData();
|
|
}, 300);
|
|
});
|
|
|
|
// Add event listener for clear user filter button
|
|
clearUserFilterBtn.addEventListener('click', function() {
|
|
userFilterInput.value = '';
|
|
loadReportData();
|
|
});
|
|
|
|
// Add event listener for Apply Date Range button
|
|
applyDateRangeBtn.addEventListener('click', function() {
|
|
const username = modalUsername.textContent;
|
|
loadUserActivityData(username);
|
|
});
|
|
|
|
// Add event listener for week day selector
|
|
weekDaySelect.addEventListener('change', function() {
|
|
selectedWeekDay = this.value;
|
|
loadReportData(); // Reload data when day selection changes
|
|
});
|
|
|
|
// Initialize the dashboard
|
|
function initializeDashboard() {
|
|
// Initialize date navigation elements
|
|
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
|
|
dateSelector.value = selectedDate;
|
|
|
|
// Update the date display
|
|
updateDateDisplay();
|
|
|
|
// Show date navigation controls if daily view is active
|
|
if (currentPeriod === '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);
|
|
|
|
// Load initial report data
|
|
loadReportData();
|
|
}
|
|
|
|
// Initial load
|
|
initializeDashboard();
|
|
});
|