Skip to content

Commit 8337efb

Browse files
committed
[FIX] Prevent data overlap when navigating months rapidly
- Implement AbortController to cancel pending requests - Add loading indicators and disable controls during data fetch - Implement throttling to prevent rapid consecutive clicks - Improve error handling and user feedback - Make navigation more robust in both Time Summary and Logs views
1 parent c466c65 commit 8337efb

2 files changed

Lines changed: 259 additions & 76 deletions

File tree

app/static/js/time-log.js

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ document.addEventListener('DOMContentLoaded', function() {
99
const prevYearBtn = document.getElementById('prevYear');
1010
const nextYearBtn = document.getElementById('nextYear');
1111

12+
// Variable para controlar solicitudes simultáneas
13+
let currentRequest = null;
14+
let isLoading = false;
15+
1216
// Initialize select elements if they exist
1317
if (!yearSelect || !monthSelect || !logTable) {
1418
console.error('Required DOM elements not found');
@@ -49,15 +53,67 @@ document.addEventListener('DOMContentLoaded', function() {
4953
}
5054
yearSelect.value = currentYear;
5155

56+
// Function to show loading state
57+
function showLoading() {
58+
isLoading = true;
59+
// Disable navigation controls
60+
prevMonthBtn.disabled = true;
61+
nextMonthBtn.disabled = true;
62+
prevYearBtn.disabled = true;
63+
nextYearBtn.disabled = true;
64+
yearSelect.disabled = true;
65+
monthSelect.disabled = true;
66+
67+
// Change cursor to indicate loading
68+
document.body.style.cursor = 'wait';
69+
70+
// Show loading indicator
71+
logTable.innerHTML = '<tr><td colspan="4" class="text-center">Loading data...</td></tr>';
72+
}
73+
74+
// Function to hide loading state
75+
function hideLoading() {
76+
isLoading = false;
77+
// Enable navigation controls
78+
prevMonthBtn.disabled = false;
79+
nextMonthBtn.disabled = false;
80+
prevYearBtn.disabled = false;
81+
nextYearBtn.disabled = false;
82+
yearSelect.disabled = false;
83+
monthSelect.disabled = false;
84+
85+
// Reset cursor
86+
document.body.style.cursor = 'default';
87+
}
88+
5289
// Function to load monthly logs
5390
async function loadMonthlyLogs() {
91+
// Prevent multiple simultaneous requests
92+
if (isLoading) {
93+
console.log('Already loading data, request ignored');
94+
return;
95+
}
96+
97+
showLoading();
98+
5499
console.log('Loading monthly logs');
55100
const year = yearSelect.value;
56101
const month = monthSelect.value;
57102

58103
try {
59104
console.log(`Fetching logs for ${year}/${month}`);
60-
const response = await fetch(`/logs/monthly/${year}/${month}`);
105+
106+
// Cancel any existing request
107+
if (currentRequest) {
108+
currentRequest.abort();
109+
}
110+
111+
// Use fetch with AbortController
112+
const controller = new AbortController();
113+
const signal = controller.signal;
114+
currentRequest = controller;
115+
116+
const response = await fetch(`/logs/monthly/${year}/${month}`, { signal });
61117

62118
if (!response.ok) {
63119
throw new Error(`HTTP error! status: ${response.status}`);
@@ -66,15 +122,11 @@ document.addEventListener('DOMContentLoaded', function() {
66122
const logsData = await response.json();
67123
console.log('Logs data:', logsData);
68124

69-
// Clear the table
70-
logTable.innerHTML = '';
71-
72125
// Display log entries
73126
if (logsData.length === 0) {
74-
const row = document.createElement('tr');
75-
row.innerHTML = '<td colspan="4" class="text-center">No time entries found for this month</td>';
76-
logTable.appendChild(row);
127+
logTable.innerHTML = '<tr><td colspan="4" class="text-center">No time entries found for this month</td></tr>';
77128
} else {
129+
logTable.innerHTML = '';
78130
logsData.forEach(log => {
79131
const row = document.createElement('tr');
80132

@@ -105,13 +157,34 @@ document.addEventListener('DOMContentLoaded', function() {
105157
});
106158
}
107159
} catch (error) {
108-
console.error('Error loading logs:', error);
109-
logTable.innerHTML = `<tr><td colspan="4" class="text-center text-danger">Error loading data: ${error.message}</td></tr>`;
160+
if (error.name === 'AbortError') {
161+
console.log('Fetch aborted');
162+
} else {
163+
console.error('Error loading logs:', error);
164+
logTable.innerHTML = `<tr><td colspan="4" class="text-center text-danger">Error loading data: ${error.message}</td></tr>`;
165+
}
166+
} finally {
167+
hideLoading();
168+
currentRequest = null;
110169
}
111170
}
112171

113-
// Navigation functions
172+
// Navigation functions with throttling
173+
function throttle(func, delay) {
174+
let lastCall = 0;
175+
return function(...args) {
176+
const now = new Date().getTime();
177+
if (now - lastCall < delay) {
178+
return;
179+
}
180+
lastCall = now;
181+
return func(...args);
182+
};
183+
}
184+
114185
function navigateToPreviousMonth() {
186+
if (isLoading) return;
187+
115188
let month = parseInt(monthSelect.value);
116189
let year = parseInt(yearSelect.value);
117190

@@ -129,6 +202,8 @@ document.addEventListener('DOMContentLoaded', function() {
129202
}
130203

131204
function navigateToNextMonth() {
205+
if (isLoading) return;
206+
132207
let month = parseInt(monthSelect.value);
133208
let year = parseInt(yearSelect.value);
134209

@@ -146,6 +221,8 @@ document.addEventListener('DOMContentLoaded', function() {
146221
}
147222

148223
function navigateToPreviousYear() {
224+
if (isLoading) return;
225+
149226
let year = parseInt(yearSelect.value);
150227
year--;
151228

@@ -156,6 +233,8 @@ document.addEventListener('DOMContentLoaded', function() {
156233
}
157234

158235
function navigateToNextYear() {
236+
if (isLoading) return;
237+
159238
let year = parseInt(yearSelect.value);
160239
year++;
161240

@@ -165,11 +244,11 @@ document.addEventListener('DOMContentLoaded', function() {
165244
}
166245
}
167246

168-
// Add navigation button event listeners
169-
prevMonthBtn.addEventListener('click', navigateToPreviousMonth);
170-
nextMonthBtn.addEventListener('click', navigateToNextMonth);
171-
prevYearBtn.addEventListener('click', navigateToPreviousYear);
172-
nextYearBtn.addEventListener('click', navigateToNextYear);
247+
// Add navigation button event listeners with throttling
248+
prevMonthBtn.addEventListener('click', throttle(navigateToPreviousMonth, 300));
249+
nextMonthBtn.addEventListener('click', throttle(navigateToNextMonth, 300));
250+
prevYearBtn.addEventListener('click', throttle(navigateToPreviousYear, 300));
251+
nextYearBtn.addEventListener('click', throttle(navigateToNextYear, 300));
173252

174253
// Load data when changing year or month manually
175254
yearSelect.addEventListener('change', loadMonthlyLogs);

0 commit comments

Comments
 (0)