work-tracing/static/js/dashboard.js
ilia-gurielidze-autstand 9e6d0a6911 first commit
2025-05-05 12:12:46 +04:00

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