feat: add pagination to file listing and improve UI

This commit is contained in:
2025-09-03 23:07:02 +02:00
parent 75548dab2b
commit 3c07c89f24
4 changed files with 454 additions and 4 deletions
+308 -1
View File
@@ -36,6 +36,8 @@
<p class="lead">Créez des exercices de multiplication et division personnalisés en PDF</p>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">Paramètres des Exercices</h5>
@@ -68,6 +70,36 @@
</div>
</div>
<!-- Existing PDFs Section -->
<div class="card mb-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="card-title mb-0">Fichiers PDF Existants</h5>
<button id="refreshListBtn" class="btn btn-outline-primary btn-sm">
<span id="refreshText">Actualiser</span>
<span id="refreshSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
</button>
</div>
<div class="d-flex gap-2 mb-3">
<button id="downloadSelectedBtn" class="btn btn-success btn-sm" disabled>
Télécharger Sélectionnés
</button>
<button id="deleteSelectedBtn" class="btn btn-danger btn-sm" disabled>
Supprimer Sélectionnés
</button>
<button id="selectAllBtn" class="btn btn-outline-secondary btn-sm">
Tout Sélectionner
</button>
<button id="clearSelectionBtn" class="btn btn-outline-secondary btn-sm" disabled>
Désélectionner
</button>
</div>
<div id="pdfListContainer" class="mt-3">
<p class="text-muted">Cliquez sur "Actualiser" pour charger la liste des fichiers existants.</p>
</div>
</div>
</div>
<div id="resultContainer" class="exercise-result d-none">
<h5>Résultat</h5>
<div id="resultMessage"></div>
@@ -145,8 +177,280 @@
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Current page for pagination
let currentPage = 1;
const pageSize = 10;
// Function to load and display existing PDFs with pagination (sorted from newest to oldest)
async function loadPdfList(page = 1) {
const refreshBtn = document.getElementById('refreshListBtn');
const refreshText = document.getElementById('refreshText');
const refreshSpinner = document.getElementById('refreshSpinner');
const pdfListContainer = document.getElementById('pdfListContainer');
// Show loading state
refreshText.textContent = 'Chargement...';
refreshSpinner.classList.remove('d-none');
refreshBtn.disabled = true;
try {
const response = await fetch(`/list?page=${page}&page_size=${pageSize}`);
const result = await response.json();
if (result.error) {
pdfListContainer.innerHTML = `<div class="alert alert-danger">${result.error}</div>`;
} else {
if (result.files.length === 0) {
pdfListContainer.innerHTML = '<p class="text-muted">Aucun fichier PDF trouvé.</p>';
} else {
let html = '<div class="table-responsive"><table class="table table-striped table-hover">';
html += '<thead><tr><th><input type="checkbox" id="selectAllHeader"></th><th>Nom du fichier</th><th>Date de création</th><th>Taille</th><th>Actions</th></tr></thead><tbody>';
result.files.forEach(file => {
// Format date
const date = new Date(file.LastModified);
const formattedDate = date.toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
// Format size
const sizeInKB = Math.round(file.Size / 1024);
html += '<tr>';
html += `<td><input type="checkbox" class="file-checkbox" data-filename="${file.Key}"></td>`;
html += `<td>${file.Key}</td>`;
html += `<td>${formattedDate}</td>`;
html += `<td>${sizeInKB} KB</td>`;
html += `<td>
<a href="/download/${encodeURIComponent(file.Key)}" class="btn btn-success btn-sm me-2">Télécharger</a>
<button class="btn btn-danger btn-sm delete-btn" data-filename="${file.Key}">Supprimer</button>
</td>`;
html += '</tr>';
});
html += '</tbody></table></div>';
// Add pagination controls if needed
if (result.pagination.total_pages > 1) {
html += '<nav aria-label="Pagination des fichiers"><ul class="pagination justify-content-center">';
// Previous button
if (result.pagination.has_prev) {
html += `<li class="page-item"><a class="page-link" href="#" data-page="${result.pagination.current_page - 1}">Précédent</a></li>`;
} else {
html += '<li class="page-item disabled"><span class="page-link">Précédent</span></li>';
}
// Page numbers
for (let i = 1; i <= result.pagination.total_pages; i++) {
if (i === result.pagination.current_page) {
html += `<li class="page-item active"><span class="page-link">${i}</span></li>`;
} else {
html += `<li class="page-item"><a class="page-link" href="#" data-page="${i}">${i}</a></li>`;
}
}
// Next button
if (result.pagination.has_next) {
html += `<li class="page-item"><a class="page-link" href="#" data-page="${result.pagination.current_page + 1}">Suivant</a></li>`;
} else {
html += '<li class="page-item disabled"><span class="page-link">Suivant</span></li>';
}
html += '</ul></nav>';
// Display total count
html += `<div class="text-center text-muted small">
Affichage ${(result.pagination.current_page - 1) * pageSize + 1}-${Math.min(result.pagination.current_page * pageSize, result.pagination.total_files)}
sur ${result.pagination.total_files} fichiers
</div>`;
}
pdfListContainer.innerHTML = html;
// Add event listeners to pagination links
document.querySelectorAll('.page-link[data-page]').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const page = parseInt(this.getAttribute('data-page'));
loadPdfList(page);
});
});
// Add event listeners to delete buttons
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', async function() {
const filename = this.getAttribute('data-filename');
if (confirm(`Êtes-vous sûr de vouloir supprimer le fichier "${filename}" ?`)) {
try {
const response = await fetch(`/delete/${encodeURIComponent(filename)}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.error) {
alert(`Erreur: ${result.error}`);
} else {
// Refresh the list (stay on current page)
loadPdfList(currentPage);
alert(result.message);
}
} catch (error) {
alert(`Erreur lors de la suppression: ${error.message}`);
}
}
});
});
// Add event listener for select all checkbox in header
document.getElementById('selectAllHeader').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.file-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateBulkButtons();
});
// Add event listeners to individual checkboxes
document.querySelectorAll('.file-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', updateBulkButtons);
});
// Update current page
currentPage = page;
}
}
} catch (error) {
pdfListContainer.innerHTML = `<div class="alert alert-danger">Erreur lors du chargement de la liste: ${error.message}</div>`;
} finally {
// Reset button state
refreshText.textContent = 'Actualiser';
refreshSpinner.classList.add('d-none');
refreshBtn.disabled = false;
}
}
// Event listener for refresh button
document.getElementById('refreshListBtn').addEventListener('click', function() {
loadPdfList(1); // Reset to first page on refresh
});
// Function to update bulk action buttons state
function updateBulkButtons() {
const selectedCount = document.querySelectorAll('.file-checkbox:checked').length;
document.getElementById('downloadSelectedBtn').disabled = selectedCount === 0;
document.getElementById('deleteSelectedBtn').disabled = selectedCount === 0;
document.getElementById('clearSelectionBtn').disabled = selectedCount === 0;
// Update select all header checkbox
const allChecked = selectedCount === document.querySelectorAll('.file-checkbox').length && selectedCount > 0;
document.getElementById('selectAllHeader').checked = allChecked;
}
// Load PDF list on page load
document.addEventListener('DOMContentLoaded', function() {
loadPdfList(currentPage);
});
// Event listener for select all button
document.getElementById('selectAllBtn').addEventListener('click', function() {
document.querySelectorAll('.file-checkbox').forEach(checkbox => {
checkbox.checked = true;
});
updateBulkButtons();
});
// Event listener for clear selection button
document.getElementById('clearSelectionBtn').addEventListener('click', function() {
document.querySelectorAll('.file-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
updateBulkButtons();
});
// Event listener for download selected button
document.getElementById('downloadSelectedBtn').addEventListener('click', async function() {
const selectedFiles = Array.from(document.querySelectorAll('.file-checkbox:checked'))
.map(checkbox => checkbox.getAttribute('data-filename'));
if (selectedFiles.length === 0) {
alert('Veuillez sélectionner au moins un fichier à télécharger.');
return;
}
try {
// Create a form to submit the request (needed for file download)
const formData = new FormData();
formData.append('filenames', JSON.stringify(selectedFiles));
// Create a hidden form and submit it
const form = document.createElement('form');
form.method = 'POST';
form.action = '/bulk-download';
form.style.display = 'none';
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'filenames';
input.value = JSON.stringify(selectedFiles);
form.appendChild(input);
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
} catch (error) {
alert(`Erreur lors du téléchargement: ${error.message}`);
}
});
// Event listener for delete selected button
document.getElementById('deleteSelectedBtn').addEventListener('click', async function() {
const selectedFiles = Array.from(document.querySelectorAll('.file-checkbox:checked'))
.map(checkbox => checkbox.getAttribute('data-filename'));
if (selectedFiles.length === 0) {
alert('Veuillez sélectionner au moins un fichier à supprimer.');
return;
}
if (!confirm(`Êtes-vous sûr de vouloir supprimer ${selectedFiles.length} fichier(s) ?`)) {
return;
}
try {
// Create form data
const formData = new FormData();
formData.append('filenames', JSON.stringify(selectedFiles));
const response = await fetch('/bulk-delete', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.error) {
alert(`Erreur: ${result.error}`);
} else {
// Refresh the list (stay on current page)
loadPdfList(currentPage);
let message = result.message;
if (result.errors && result.errors.length > 0) {
message += `\nErreurs pour les fichiers: ${result.errors.join(', ')}`;
}
alert(message);
}
} catch (error) {
alert(`Erreur lors de la suppression: ${error.message}`);
}
});
document.getElementById('exerciseForm').addEventListener('submit', async function(e) {
e.preventDefault();
@@ -181,6 +485,9 @@
// Automatically trigger download
window.location.href = response.url;
// Refresh the PDF list (reset to first page)
setTimeout(() => loadPdfList(1), 1000);
// Show success message
const resultContainer = document.getElementById('resultContainer');
const resultMessage = document.getElementById('resultMessage');