28b1c80b50
Moved gen-op.py to app/gen_op.py and integrated it with the FastAPI application Added a new section in the UI for generating operation exercises Updated the PDF generation to work in memory and upload to S3 Bumped version to 1.0.9
659 lines
36 KiB
HTML
659 lines
36 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">
|
|
<style>
|
|
.card {
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
transition: 0.3s;
|
|
margin-bottom: 20px;
|
|
}
|
|
.card:hover {
|
|
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
|
|
}
|
|
.exercise-result {
|
|
background-color: #f8f9fa;
|
|
border-radius: 5px;
|
|
padding: 15px;
|
|
margin-top: 20px;
|
|
}
|
|
.feature-icon {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
margin-right: 0.75rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<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="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">
|
|
<div class="container">
|
|
<p>Générateur d'Exercices de Mathématiques © 2025</p>
|
|
</div>
|
|
</footer>
|
|
|
|
<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();
|
|
|
|
const minTable = parseInt(document.getElementById('minTable').value);
|
|
const maxTable = parseInt(document.getElementById('maxTable').value);
|
|
const numExercises = parseInt(document.getElementById('numExercises').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
|
|
})
|
|
});
|
|
|
|
// 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 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</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();
|
|
|
|
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> |