Files
math-tables/app/templates/index.html

927 lines
48 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Générateur d'Exercices de Mathématiques</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
--bs-body-bg: #ffffff;
--bs-body-color: #212529;
--bs-border-color: #dee2e6;
--bs-secondary-bg: #f8f9fa;
--bs-tertiary-bg: #ffffff;
}
[data-bs-theme="dark"] {
--bs-body-bg: #121212;
--bs-body-color: #e9ecef;
--bs-border-color: #495057;
--bs-secondary-bg: #212529;
--bs-tertiary-bg: #343a40;
}
body {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.card {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transition: 0.3s;
margin-bottom: 20px;
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-border-color);
}
.card:hover {
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
.exercise-result {
background-color: var(--bs-secondary-bg);
border-radius: 5px;
padding: 15px;
margin-top: 20px;
}
.feature-icon {
width: 3rem;
height: 3rem;
margin-right: 0.75rem;
}
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
border-radius: 50%;
width: 50px;
height: 50px;
border: none;
background-color: var(--bs-tertiary-bg);
color: var(--bs-body-color);
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.dark-mode-toggle:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
[data-bs-theme="dark"] .bg-light {
background-color: var(--bs-secondary-bg) !important;
}
[data-bs-theme="dark"] .text-muted {
color: var(--bs-body-color) !important;
opacity: 0.7;
}
[data-bs-theme="dark"] .table {
--bs-table-bg: var(--bs-tertiary-bg);
--bs-table-striped-bg: var(--bs-secondary-bg);
--bs-table-hover-bg: var(--bs-border-color);
}
[data-bs-theme="dark"] .alert {
border-color: var(--bs-border-color);
}
[data-bs-theme="dark"] .form-control,
[data-bs-theme="dark"] .form-select {
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-border-color);
color: var(--bs-body-color);
}
[data-bs-theme="dark"] .form-control:focus,
[data-bs-theme="dark"] .form-select:focus {
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-primary);
color: var(--bs-body-color);
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
[data-bs-theme="dark"] .form-check-input {
background-color: var(--bs-tertiary-bg);
border-color: var(--bs-border-color);
}
[data-bs-theme="dark"] .form-check-input:checked {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
.transition-all {
transition: all 0.3s ease;
}
</style>
</head>
<body>
<!-- Dark Mode Toggle Button -->
<button class="dark-mode-toggle" id="darkModeToggle" title="Basculer le mode sombre" aria-label="Basculer le mode sombre">
<i class="bi bi-moon-fill" id="darkModeIcon"></i>
</button>
<div class="container mt-5 mb-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="text-center mb-5">
<h1 class="display-4 fw-bold">Générateur d'Exercices de Mathématiques</h1>
<p class="lead">Créez des exercices de multiplication, division et opérations complexes personnalisés en PDF</p>
</div>
<!-- Multiplication/Division Exercises Section -->
<div class="card">
<div class="card-body">
<h5 class="card-title">Paramètres des Exercices (Multiplications/Divisions)</h5>
<form id="exerciseForm">
<div class="mb-3">
<label for="minTable" class="form-label">Table minimale</label>
<input type="number" class="form-control" id="minTable" min="1" max="12" value="1" required>
<div class="form-text">La plus petite table de multiplication à inclure</div>
</div>
<div class="mb-3">
<label for="maxTable" class="form-label">Table maximale</label>
<input type="number" class="form-control" id="maxTable" min="1" max="12" value="10" required>
<div class="form-text">La plus grande table de multiplication à inclure</div>
</div>
<div class="mb-3">
<label for="numExercises" class="form-label">Nombre d'exercices</label>
<input type="number" class="form-control" id="numExercises" min="1" max="100" value="15" required>
<div class="form-text">Nombre total d'exercices à générer (entre 1 et 100)</div>
</div>
<div class="mb-3">
<label class="form-label">Types d'exercices</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="exerciseType" id="multiplicationOnly" value="multiplication" checked>
<label class="form-check-label" for="multiplicationOnly">
Multiplications uniquement
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="exerciseType" id="multiplicationAndDivision" value="both">
<label class="form-check-label" for="multiplicationAndDivision">
Multiplications ET divisions
</label>
</div>
</div>
<div class="mb-3">
<label for="maxFirstOperand" class="form-label">Maximum du premier opérande</label>
<select class="form-select" id="maxFirstOperand">
<option value="9">9 (Tables jusqu'à 9)</option>
<option value="10" selected>10 (Tables jusqu'à 10)</option>
<option value="12">12 (Tables jusqu'à 12)</option>
</select>
<div class="form-text">La valeur maximale pour le premier opérande dans les multiplications</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg" id="generateBtn">
<span id="buttonText">Générer le PDF (Mult/Div)</span>
<span id="spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
</button>
</div>
</form>
</div>
</div>
<!-- Operation Exercises Section -->
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Paramètres des Exercices d'Opérations</h5>
<form id="operationExerciseForm">
<div class="mb-3">
<label for="numOperationExercises" class="form-label">Nombre d'exercices</label>
<input type="number" class="form-control" id="numOperationExercises" min="1" max="100" value="20" required>
<div class="form-text">Nombre total d'exercices d'opérations à générer (entre 1 et 100)</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success btn-lg" id="generateOperationBtn">
<span id="operationButtonText">Générer le PDF (Opérations)</span>
<span id="operationSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
</button>
</div>
</form>
</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>
<div id="downloadLinkContainer" class="mt-3 d-none">
<a id="downloadLink" class="btn btn-success" href="#" download>Télécharger le PDF</a>
</div>
</div>
<div class="row mt-5">
<div class="col-md-6">
<div class="d-flex align-items-start">
<div class="feature-icon bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-x-lg text-primary" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z"/>
</svg>
</div>
<div>
<h5 class="fw-bold">Multiplications</h5>
<p>Exercices de multiplication avec des opérateurs aléatoires de la table spécifiée.</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start">
<div class="feature-icon bg-success bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-slash-lg text-success" viewBox="0 0 16 16">
<path d="M3.5 1.5l9 13"/>
</svg>
</div>
<div>
<h5 class="fw-bold">Divisions</h5>
<p>Exercices de division correspondants aux tables de multiplication sélectionnées.</p>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<div class="d-flex align-items-start">
<div class="feature-icon bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-file-earmark-pdf text-info" viewBox="0 0 16 16">
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
</svg>
</div>
<div>
<h5 class="fw-bold">Format PDF</h5>
<p>Générez des fichiers PDF prêts à imprimer avec une mise en page organisée.</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start">
<div class="feature-icon bg-warning bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-columns-gap text-warning" viewBox="0 0 16 16">
<path d="M6 1H1v3h5V1zM1 0a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1H1zm14 12h-5v3h5v-3zm-5-1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-5zM6 8H1v7h5V8zm-1 1a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H5zm9-9h-5v5h5V1zm-1 1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1h-5z"/>
</svg>
</div>
<div>
<h5 class="fw-bold">Colonnes Variables</h5>
<p>Les exercices de multiplication/division sont présentés en trois colonnes, les opérations complexes en deux colonnes.</p>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<div class="d-flex align-items-start">
<div class="feature-icon bg-danger bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-calculator text-danger" viewBox="0 0 16 16">
<path d="M12 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h8zM4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H4z"/>
<path d="M4 2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-7a.5.5 0 0 1-.5-.5v-2zm0 4a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V6.5zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V6.5zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V6.5zm-6 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V9.5zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V9.5zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V9.5zm-6 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/>
</svg>
</div>
<div>
<h5 class="fw-bold">Opérations Complexes</h5>
<p>Générez des exercices avec des expressions mathématiques complexes incluant parenthèses, additions, soustractions et multiplications.</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start">
<div class="feature-icon bg-success bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-cloud-arrow-down text-success" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M7.646 10.854a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 9.293V5.5a.5.5 0 0 0-1 0v3.793L6.354 8.146a.5.5 0 1 0-.708.708l2 2z"/>
<path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383zm.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/>
</svg>
</div>
<div>
<h5 class="fw-bold">Stockage Cloud</h5>
<p>Tous les fichiers générés sont automatiquement stockés dans le cloud et peuvent être téléchargés ultérieurement.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="bg-light text-center text-muted py-4 mt-5 transition-all">
<div class="container">
<p>Générateur d'Exercices de Mathématiques &copy; 2025</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Dark Mode Theme Management
class ThemeManager {
constructor() {
this.init();
}
init() {
// Load saved theme or detect system preference
const savedTheme = localStorage.getItem('theme');
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const theme = savedTheme || systemTheme;
this.setTheme(theme);
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
this.setTheme(e.matches ? 'dark' : 'light');
}
});
// Setup toggle button
this.setupToggleButton();
}
setTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
localStorage.setItem('theme', theme);
this.updateToggleIcon(theme);
}
toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-bs-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
this.setTheme(newTheme);
}
updateToggleIcon(theme) {
const icon = document.getElementById('darkModeIcon');
if (theme === 'dark') {
icon.className = 'bi bi-sun-fill';
} else {
icon.className = 'bi bi-moon-fill';
}
}
setupToggleButton() {
const toggleButton = document.getElementById('darkModeToggle');
toggleButton.addEventListener('click', () => {
this.toggleTheme();
this.animateToggle();
});
}
animateToggle() {
const button = document.getElementById('darkModeToggle');
button.style.transform = 'scale(0.9)';
setTimeout(() => {
button.style.transform = '';
}, 150);
}
}
// Initialize theme manager
const themeManager = new ThemeManager();
// Current page for pagination
let currentPage = 1;
const pageSize = 10;
// Save form values to localStorage
function saveFormValues() {
const exerciseFormValues = {
minTable: document.getElementById('minTable').value,
maxTable: document.getElementById('maxTable').value,
numExercises: document.getElementById('numExercises').value,
exerciseType: document.querySelector('input[name="exerciseType"]:checked').value,
maxFirstOperand: document.getElementById('maxFirstOperand').value
};
const operationFormValues = {
numOperationExercises: document.getElementById('numOperationExercises').value
};
localStorage.setItem('exerciseFormValues', JSON.stringify(exerciseFormValues));
localStorage.setItem('operationFormValues', JSON.stringify(operationFormValues));
}
// Restore form values from localStorage
function restoreFormValues() {
// Restore exercise form values
const savedExerciseValues = localStorage.getItem('exerciseFormValues');
if (savedExerciseValues) {
const exerciseValues = JSON.parse(savedExerciseValues);
document.getElementById('minTable').value = exerciseValues.minTable || 1;
document.getElementById('maxTable').value = exerciseValues.maxTable || 10;
document.getElementById('numExercises').value = exerciseValues.numExercises || 15;
// Restore radio button selection
if (exerciseValues.exerciseType) {
const radio = document.querySelector(`input[name="exerciseType"][value="${exerciseValues.exerciseType}"]`);
if (radio) {
radio.checked = true;
}
} else if (exerciseValues.multiplicationOnly !== undefined) {
// Handle legacy storage format
document.getElementById('multiplicationOnly').checked = exerciseValues.multiplicationOnly;
}
document.getElementById('maxFirstOperand').value = exerciseValues.maxFirstOperand || 10;
}
// Restore operation form values
const savedOperationValues = localStorage.getItem('operationFormValues');
if (savedOperationValues) {
const operationValues = JSON.parse(savedOperationValues);
document.getElementById('numOperationExercises').value = operationValues.numOperationExercises || 20;
}
}
// 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() {
// Ensure theme is initialized first
setTimeout(() => {
restoreFormValues();
loadPdfList(currentPage);
}, 100);
});
// Add event listeners to save form values on change
document.getElementById('minTable').addEventListener('change', saveFormValues);
document.getElementById('maxTable').addEventListener('change', saveFormValues);
document.getElementById('numExercises').addEventListener('change', saveFormValues);
document.getElementById('maxFirstOperand').addEventListener('change', saveFormValues);
document.getElementById('numOperationExercises').addEventListener('change', saveFormValues);
// Add event listeners for radio buttons
document.querySelectorAll('input[name="exerciseType"]').forEach(radio => {
radio.addEventListener('change', saveFormValues);
});
// 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();
// Save form values to localStorage
saveFormValues();
const minTable = parseInt(document.getElementById('minTable').value);
const maxTable = parseInt(document.getElementById('maxTable').value);
const numExercises = parseInt(document.getElementById('numExercises').value);
const exerciseType = document.querySelector('input[name="exerciseType"]:checked').value;
const multiplicationOnly = (exerciseType === 'multiplication');
const maxFirstOperand = parseInt(document.getElementById('maxFirstOperand').value);
const generateBtn = document.getElementById('generateBtn');
const buttonText = document.getElementById('buttonText');
const spinner = document.getElementById('spinner');
// Show loading state
buttonText.textContent = 'Génération en cours...';
spinner.classList.remove('d-none');
generateBtn.disabled = true;
try {
const response = await fetch('/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
min_table: minTable,
max_table: maxTable,
num_exercises: numExercises,
multiplication_only: multiplicationOnly,
max_first_operand: maxFirstOperand
})
});
// Check if it's a redirect response (for automatic download)
if (response.redirected) {
// 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');
const exerciseType = multiplicationOnly ? "multiplications uniquement" : "multiplications et divisions";
resultMessage.innerHTML = `
<div class="alert alert-success">
<h6>PDF généré avec succès!</h6>
<p>Le téléchargement devrait commencer automatiquement.</p>
<ul class="mb-0">
<li>Tables de ${minTable} à ${maxTable}</li>
<li>${numExercises} exercices générés (${exerciseType})</li>
</ul>
</div>
`;
resultContainer.classList.remove('d-none');
} else {
// Handle JSON response (error case)
const result = await response.json();
const resultContainer = document.getElementById('resultContainer');
const resultMessage = document.getElementById('resultMessage');
if (result.error) {
resultMessage.innerHTML = `<div class="alert alert-danger">${result.error}</div>`;
}
resultContainer.classList.remove('d-none');
}
} catch (error) {
const resultContainer = document.getElementById('resultContainer');
const resultMessage = document.getElementById('resultMessage');
resultMessage.innerHTML = `<div class="alert alert-danger">Erreur: ${error.message}</div>`;
resultContainer.classList.remove('d-none');
} finally {
// Reset button state
buttonText.textContent = 'Générer le PDF (Mult/Div)';
spinner.classList.add('d-none');
generateBtn.disabled = false;
}
});
// Handle operation exercises form submission
document.getElementById('operationExerciseForm').addEventListener('submit', async function(e) {
e.preventDefault();
// Save form values to localStorage
saveFormValues();
const numExercises = parseInt(document.getElementById('numOperationExercises').value);
const generateBtn = document.getElementById('generateOperationBtn');
const buttonText = document.getElementById('operationButtonText');
const spinner = document.getElementById('operationSpinner');
// Show loading state
buttonText.textContent = 'Génération en cours...';
spinner.classList.remove('d-none');
generateBtn.disabled = true;
try {
const response = await fetch('/generate-operations', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
num_exercises: numExercises
})
});
// Check if it's a redirect response (for automatic download)
if (response.redirected) {
// 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');
resultMessage.innerHTML = `
<div class="alert alert-success">
<h6>PDF d'opérations généré avec succès!</h6>
<p>Le téléchargement devrait commencer automatiquement.</p>
<ul class="mb-0">
<li>${numExercises} exercices d'opérations générés</li>
</ul>
</div>
`;
resultContainer.classList.remove('d-none');
} else {
// Handle JSON response (error case)
const result = await response.json();
const resultContainer = document.getElementById('resultContainer');
const resultMessage = document.getElementById('resultMessage');
if (result.error) {
resultMessage.innerHTML = `<div class="alert alert-danger">${result.error}</div>`;
}
resultContainer.classList.remove('d-none');
}
} catch (error) {
const resultContainer = document.getElementById('resultContainer');
const resultMessage = document.getElementById('resultMessage');
resultMessage.innerHTML = `<div class="alert alert-danger">Erreur: ${error.message}</div>`;
resultContainer.classList.remove('d-none');
} finally {
// Reset button state
buttonText.textContent = 'Générer le PDF (Opérations)';
spinner.classList.add('d-none');
generateBtn.disabled = false;
}
});
</script>
</body>
</html>