gmao/app_new/templates/setup_wizard/index.html

1148 lines
52 KiB
HTML
Raw Normal View History

2026-08-14 18:02:25 +02:00
{% extends "base.html" %}
{% block title %}Configuration initiale — GMAO{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4 class="mb-0"><i class="bi bi-gear-fill"></i> Configuration initiale GMAO</h4>
</div>
<div class="card-body">
<!-- Indicateurs d'étapes -->
<div class="d-flex justify-content-between mb-4 flex-wrap">
{% for i in range(1, 11) %}
<div class="text-center flex-fill" id="step{{ i }}-indicator" style="min-width: 60px;">
<i class="bi bi-{% if i <= progress.step %}check-circle-fill text-success{% else %}{{ i }}-circle{% if i == progress.step + 1 %} text-primary{% else %} text-secondary{% endif %}{% endif %} fs-3"></i>
<div class="small mt-1">
{% if i == 1 %}Admin
{% elif i == 2 %}Collège
{% elif i == 3 %}Bâtiments
{% elif i == 4 %}Lots
{% elif i == 5 %}Vacances
{% elif i == 6 %}ENT
{% elif i == 7 %}Pronote
{% elif i == 8 %}Salles
{% elif i == 9 %}Personnels
{% else %}Profs
{% endif %}
</div>
</div>
{% endfor %}
</div>
<!-- Étape 1: Administrateur -->
<div id="step1" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-person-fill"></i> Compte administrateur</h5>
<p class="text-muted mb-3">Créez le compte administrateur principal de l'application.</p>
<form id="adminForm">
<div class="mb-3">
<label for="adminUsername" class="form-label">Nom d'utilisateur</label>
<input type="text" class="form-control" id="adminUsername" name="username"
placeholder="Ex: admin" required minlength="3"
value="{{ progress['data'].get('admin_username', '') }}">
</div>
<div class="mb-3">
<label for="adminPassword" class="form-label">Mot de passe</label>
<div class="input-group">
<input type="password" class="form-control" id="adminPassword" name="password"
placeholder="Mot de passe" required minlength="4">
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword()">
<i class="bi bi-eye" id="passwordIcon"></i>
</button>
</div>
</div>
</form>
</div>
<!-- Étape 2: Collège -->
<div id="step2" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-building"></i> Informations du collège</h5>
<p class="text-muted mb-3">Renseignez les informations de votre établissement.</p>
<form id="collegeForm">
<div class="mb-3">
<label for="collegeName" class="form-label">Nom du collège</label>
<input type="text" class="form-control" id="collegeName" name="name"
placeholder="Ex: Collège Jean Moulin" required
value="{{ (progress['data'].get('college', {}) or {}).get('name', '') }}">
</div>
<div class="mb-3">
<label for="collegeAddress" class="form-label">Adresse</label>
<input type="text" class="form-control" id="collegeAddress" name="address"
placeholder="Ex: 12 rue de la République" required
value="{{ (progress['data'].get('college', {}) or {}).get('address', '') }}">
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="collegeZip" class="form-label">Code postal</label>
<input type="text" class="form-control" id="collegeZip" name="zip"
placeholder="77000" required pattern="[0-9]{5}"
value="{{ (progress['data'].get('college', {}) or {}).get('zip', '') }}">
</div>
<div class="col-md-8 mb-3">
<label for="collegeCity" class="form-label">Ville</label>
<input type="text" class="form-control" id="collegeCity" name="city"
placeholder="Ex: Melun" required
value="{{ (progress['data'].get('college', {}) or {}).get('city', '') }}">
</div>
</div>
</form>
</div>
<!-- Étape 3: Bâtiments -->
<div id="step3" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-buildings"></i> Bâtiments</h5>
<p class="text-muted mb-3">Ajoutez les bâtiments de votre établissement.</p>
<div class="row mb-2 fw-bold">
<div class="col-md-5">Nom</div>
<div class="col-md-6">Description</div>
<div class="col-md-1"></div>
</div>
<div id="buildingsList">
<!-- Les bâtiments seront ajoutés ici dynamiquement -->
</div>
<button type="button" class="btn btn-outline-primary mb-3" onclick="addBuilding()">
<i class="bi bi-plus-circle"></i> Ajouter un bâtiment
</button>
</div>
<!-- Étape 4: Lots de maintenance -->
<div id="step4" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-list-check"></i> Catégories de maintenance par lot</h5>
<p class="text-muted mb-3">Sélectionnez les catégories actives pour chaque lot de maintenance.</p>
<div class="mb-2">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="toggleAllCategories(true)">
<i class="bi bi-check-all"></i> Tout cocher
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="toggleAllCategories(false)">
<i class="bi bi-x"></i> Tout décocher
</button>
</div>
<div class="border rounded" style="max-height: 500px; overflow-y: auto;">
<div id="lotsCategoriesList">
<!-- Les lots avec leurs catégories seront ajoutés ici -->
</div>
</div>
<div class="mt-3">
<span class="badge bg-primary"><span id="selectedCategoriesCount">0</span> catégorie(s) sélectionnée(s)</span>
</div>
</div>
<!-- Étape 5: Vacances et horaires -->
<div id="step5" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-calendar-week"></i> Vacances et horaires</h5>
<!-- Zone académique -->
<div class="card mb-3">
<div class="card-header">
<strong><i class="bi bi-geo-alt"></i> Zone académique</strong>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label class="form-label">Votre zone académique</label>
<select class="form-select" id="academiqueZone" onchange="loadVacancesPreview()">
<option value="">-- Sélectionner --</option>
<option value="A">Zone A (Lyon, Grenoble, etc.)</option>
<option value="B">Zone B (Paris, Créteil, Versailles, etc.)</option>
<option value="C">Zone C (Bordeaux, Toulouse, etc.)</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Année scolaire</label>
<select class="form-select" id="anneeScolaire" onchange="loadVacancesPreview()">
<option value="2024">2024-2025</option>
<option value="2025">2025-2026</option>
</select>
</div>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="importAutoVacances" checked>
<label class="form-check-label" for="importAutoVacances">
Importer automatiquement les vacances scolaires officielles
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="importAutoFeries" checked>
<label class="form-check-label" for="importAutoFeries">
Importer automatiquement les jours fériés
</label>
</div>
</div>
</div>
<!-- Horaires de travail -->
<div class="card mb-3">
<div class="card-header">
<strong><i class="bi bi-clock"></i> Horaires de travail</strong>
</div>
<div class="card-body">
<div id="workScheduleGrid">
<!-- Rempli dynamiquement -->
</div>
</div>
</div>
</div>
<!-- Étape 6: ENT77 -->
<div id="step6" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-envelope"></i> Configuration ENT77</h5>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> Les identifiants ENT77 permettront de récupérer automatiquement les messages reçus pour créer des tâches intelligentes.
</div>
<div class="card mb-3">
<div class="card-body">
<div class="row mb-3">
<div class="col-md-12">
<label class="form-label">Nom d'utilisateur ENT77</label>
<input type="text" class="form-control" id="entUsername" placeholder="prenom.nom">
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<label class="form-label">Mot de passe ENT77</label>
<input type="password" class="form-control" id="entPassword" placeholder="••••••••">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="entIsActive" checked>
<label class="form-check-label" for="entIsActive">
Activer la récupération automatique
</label>
</div>
</div>
<div class="col-md-6 text-end">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="testEntConnection()">
<i class="bi bi-wifi"></i> Tester la connexion
</button>
<span id="entTestResult" class="ms-2"></span>
</div>
</div>
<div id="entValidationMessage" class="alert" style="display:none;"></div>
</div>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="skipEnt">
<label class="form-check-label text-muted" for="skipEnt">
Passer cette étape (configuration possible plus tard dans /ent/credentials)
</label>
</div>
</div>
<!-- Étape 7: Pronote -->
<div id="step7" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-calendar-week"></i> Configuration Pronote</h5>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> La connexion Pronote permet de récupérer les emplois du temps pour une meilleure planification, et pré-ajoute les salles, personnels et professeurs.
</div>
<div class="card mb-3">
<div class="card-body">
<div class="row mb-3">
<div class="col-md-12">
<label class="form-label">QR Code JSON</label>
<small class="text-muted d-block mb-1">Utilisez l'extension Chrome "Lecteur QR Code" pour extraire le JSON du QR code Pronote</small>
<textarea class="form-control" id="pronoteQrCode" rows="3" placeholder='{"url": "...", "_nom": "..."}'></textarea>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Code attribué au QR Code</label>
<input type="text" class="form-control" id="pronoteQrCodeId" placeholder="Code affiché sous le QR code">
<small class="text-muted">Le code numérique affiché sous le QR code dans Pronote</small>
</div>
<div class="col-md-6">
<label class="form-label">Code PIN</label>
<input type="password" class="form-control" id="pronotePin" placeholder="••••">
<small class="text-muted">Votre code PIN personnel (4 chiffres)</small>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="pronoteSyncSalles" checked>
<label class="form-check-label" for="pronoteSyncSalles">
Synchroniser les salles automatiquement
</label>
</div>
</div>
<div class="col-md-6 text-end">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="testPronoteConnection()">
<i class="bi bi-wifi"></i> Tester la connexion
</button>
<span id="pronoteTestResult" class="ms-2"></span>
</div>
</div>
<div id="pronoteValidationMessage" class="alert" style="display:none;"></div>
</div>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="skipPronote">
<label class="form-check-label text-muted" for="skipPronote">
Passer cette étape (configuration possible plus tard dans /pronote/connect)
</label>
</div>
</div>
<!-- Étape 8: Salles (à venir) -->
<div id="step8" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-door-open"></i> Salles</h5>
<div class="alert alert-secondary">
<i class="bi bi-clock"></i> <strong>Étape à venir</strong>
</div>
<div class="card mb-3">
<div class="card-body text-center py-5">
<i class="bi bi-door-open" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-3 text-muted">La configuration des salles sera disponible dans une prochaine version.</p>
<p class="text-muted small">
Cette étape permettra de :<br>
• Importer les salles depuis Pronote<br>
• Ajouter des salles manuellement<br>
• Définir les étages et bâtiments
</p>
</div>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="skipSalles" checked>
<label class="form-check-label text-muted" for="skipSalles">
Passer cette étape pour l'instant
</label>
</div>
</div>
<!-- Étape 9: Personnels de l'établissement (à venir) -->
<div id="step9" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-people"></i> Personnels de l'établissement</h5>
<div class="alert alert-secondary">
<i class="bi bi-clock"></i> <strong>Étape à venir</strong>
</div>
<div class="card mb-3">
<div class="card-body text-center py-5">
<i class="bi bi-people" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-3 text-muted">La configuration des personnels sera disponible dans une prochaine version.</p>
<p class="text-muted small">
Cette étape permettra de :<br>
• Importer les personnels depuis Pronote<br>
• Ajouter des personnels manuellement<br>
• Définir les rôles et responsabilités
</p>
</div>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="skipPersonnels" checked>
<label class="form-check-label text-muted" for="skipPersonnels">
Passer cette étape pour l'instant
</label>
</div>
</div>
<!-- Étape 10: Professeurs (à venir) -->
<div id="step10" class="step-panel" style="display:none;">
<h5 class="mb-3"><i class="bi bi-mortarboard"></i> Professeurs</h5>
<div class="alert alert-secondary">
<i class="bi bi-clock"></i> <strong>Étape à venir</strong>
</div>
<div class="card mb-3">
<div class="card-body text-center py-5">
<i class="bi bi-mortarboard" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-3 text-muted">La configuration des professeurs sera disponible dans une prochaine version.</p>
<p class="text-muted small">
Cette étape permettra de :<br>
• Importer les professeurs depuis Pronote<br>
• Ajouter des professeurs manuellement<br>
• Associer les professeurs aux matières
</p>
</div>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="skipProfesseurs" checked>
<label class="form-check-label text-muted" for="skipProfesseurs">
Passer cette étape pour l'instant
</label>
</div>
</div>
<!-- Boutons de navigation -->
<div class="d-flex justify-content-between mt-4">
<button type="button" id="prevBtn" class="btn btn-secondary" onclick="prevStep()" style="display:none;">
<i class="bi bi-arrow-left"></i> Précédent
</button>
<button type="button" id="nextBtn" class="btn btn-primary" onclick="nextStep()">
Suivant <i class="bi bi-arrow-right"></i>
</button>
<button type="button" id="finishBtn" class="btn btn-success" onclick="finishSetup()" style="display:none;">
<i class="bi bi-check-lg"></i> Terminer
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// currentStep = dernière étape validée (stockée en base)
// displayStep = étape actuellement affichée à l'écran
const totalSteps = 10;
let currentStep = {{ progress.step | default(0) }};
let displayStep = Math.min(currentStep + 1, totalSteps);
// Données du setup
const setupData = {{ progress['data'] | tojson | safe }};
let buildings = setupData.buildings || [];
let selectedCategories = setupData.categories || [];
let workSchedule = setupData.workSchedule || {};
let customClosures = setupData.customClosures || [];
let personalLeaves = setupData.personalLeaves || [];
// Lots avec leurs catégories (depuis le JSON)
const lotsData = {{ lots_data | tojson | safe }};
// Jours de la semaine
const weekdays = [
{id: 1, name: "Lundi"},
{id: 2, name: "Mardi"},
{id: 3, name: "Mercredi"},
{id: 4, name: "Jeudi"},
{id: 5, name: "Vendredi"},
{id: 6, name: "Samedi"},
{id: 7, name: "Dimanche"}
];
// Horaires par défaut
const defaultSchedule = {
am_start: "08:00",
am_end: "12:00",
pm_start: "13:00",
pm_end: "17:00"
};
function showStep(step) {
displayStep = step;
// Masquer toutes les étapes
document.querySelectorAll('.step-panel').forEach(el => el.style.display = 'none');
// Afficher l'étape courante
document.getElementById('step' + step).style.display = 'block';
// Mettre à jour les indicateurs
for (let i = 1; i <= totalSteps; i++) {
const indicator = document.getElementById('step' + i + '-indicator');
const icon = indicator.querySelector('i');
if (i < step) {
icon.className = 'bi bi-check-circle-fill fs-3 text-success';
} else if (i === step) {
icon.className = 'bi bi-' + i + '-circle fs-3 text-primary';
} else {
icon.className = 'bi bi-' + i + '-circle fs-3 text-secondary';
}
}
// Gérer les boutons
document.getElementById('prevBtn').style.display = step > 1 ? 'block' : 'none';
document.getElementById('nextBtn').style.display = step < totalSteps ? 'block' : 'none';
document.getElementById('finishBtn').style.display = step === totalSteps ? 'block' : 'none';
// Charger les données spécifiques à l'étape
if (step === 3 && document.getElementById('buildingsList').children.length === 0) {
loadBuildings();
}
if (step === 4 && document.getElementById('lotsCategoriesList').children.length === 0) {
loadLotsCategories();
}
if (step === 5 && document.getElementById('workScheduleGrid').children.length === 0) {
loadWorkSchedule();
}
}
function togglePassword() {
const input = document.getElementById('adminPassword');
const icon = document.getElementById('passwordIcon');
if (input.type === 'password') {
input.type = 'text';
icon.className = 'bi bi-eye-slash';
} else {
input.type = 'password';
icon.className = 'bi bi-eye';
}
}
// === Gestion des bâtiments ===
function addBuildingRow(name = '', description = '') {
const index = buildings.length;
buildings.push({name: name, description: description});
const div = document.createElement('div');
div.className = 'row mb-2 building-item';
div.id = 'building_' + index;
div.innerHTML = `
<div class="col-md-5">
<input type="text" class="form-control form-control-sm"
placeholder="Nom du bâtiment"
value="${name}"
onchange="updateBuilding(${index}, 'name', this.value)">
</div>
<div class="col-md-6">
<input type="text" class="form-control form-control-sm"
placeholder="Description (optionnel)"
value="${description}"
onchange="updateBuilding(${index}, 'description', this.value)">
</div>
<div class="col-md-1">
<button type="button" class="btn btn-sm btn-danger" onclick="removeBuilding(${index}, this)">
<i class="bi bi-trash"></i>
</button>
</div>
`;
document.getElementById('buildingsList').appendChild(div);
}
function addBuilding() {
addBuildingRow('', '');
}
function updateBuilding(index, field, value) {
if (buildings[index]) {
buildings[index][field] = value.trim();
}
}
function removeBuilding(index, button) {
buildings[index] = null;
const div = button.closest('.building-item');
if (div) {
div.remove();
}
}
function loadBuildings() {
const list = document.getElementById('buildingsList');
list.innerHTML = '';
buildings.forEach(b => {
if (b) addBuildingRow(b.name || '', b.description || '');
});
}
// === Gestion des catégories par lot ===
function loadLotsCategories() {
const container = document.getElementById('lotsCategoriesList');
container.innerHTML = '';
const sortedLots = Object.keys(lotsData).sort();
sortedLots.forEach(lotName => {
const categories = lotsData[lotName];
const sortedCategories = Object.keys(categories).sort();
if (sortedCategories.length === 0) return;
// Créer l'accordéon pour le lot
const lotDiv = document.createElement('div');
lotDiv.className = 'border-bottom';
// En-tête du lot
const headerDiv = document.createElement('div');
headerDiv.className = 'd-flex justify-content-between align-items-center p-2 bg-light';
headerDiv.style.cursor = 'pointer';
headerDiv.innerHTML = `
<div class="d-flex align-items-center">
<i class="bi bi-chevron-right me-2 lot-chevron"></i>
<strong>${lotName}</strong>
<span class="badge bg-secondary ms-2">${sortedCategories.length} catégorie(s)</span>
</div>
<div class="form-check">
<input class="form-check-input lot-check-all" type="checkbox"
id="lot_all_${lotName.replace(/\s/g, '_')}"
onchange="toggleAllLotCategories('${lotName}', this.checked)">
<label class="form-check-label small" for="lot_all_${lotName.replace(/\s/g, '_')}">Tout le lot</label>
</div>
`;
// Contenu des catégories
const contentDiv = document.createElement('div');
contentDiv.className = 'p-2';
contentDiv.style.display = 'none';
sortedCategories.forEach(catName => {
const catKey = `${lotName}|||${catName}`;
const checked = selectedCategories.includes(catKey);
const taches = categories[catName];
const catDiv = document.createElement('div');
catDiv.className = 'form-check ms-4 mb-1';
catDiv.innerHTML = `
<input class="form-check-input category-checkbox" type="checkbox"
id="cat_${lotName.replace(/\s/g, '_')}_${catName.replace(/\s/g, '_')}"
value="${catKey}" ${checked ? 'checked' : ''}
data-lot="${lotName}"
onchange="toggleCategory('${catKey}', this.checked)">
<label class="form-check-label" for="cat_${lotName.replace(/\s/g, '_')}_${catName.replace(/\s/g, '_')}">
${catName} <small class="text-muted">(${taches.length} tâche${taches.length > 1 ? 's' : ''})</small>
</label>
`;
contentDiv.appendChild(catDiv);
});
// Toggle accordéon
headerDiv.addEventListener('click', function(e) {
if (e.target.type === 'checkbox' || e.target.tagName === 'LABEL') return;
const chevron = headerDiv.querySelector('.lot-chevron');
if (contentDiv.style.display === 'none') {
contentDiv.style.display = 'block';
chevron.className = 'bi bi-chevron-down me-2 lot-chevron';
} else {
contentDiv.style.display = 'none';
chevron.className = 'bi bi-chevron-right me-2 lot-chevron';
}
});
lotDiv.appendChild(headerDiv);
lotDiv.appendChild(contentDiv);
container.appendChild(lotDiv);
});
updateSelectedCategoriesCount();
}
function toggleCategory(catKey, checked) {
if (checked) {
if (!selectedCategories.includes(catKey)) {
selectedCategories.push(catKey);
}
} else {
selectedCategories = selectedCategories.filter(c => c !== catKey);
}
updateSelectedCategoriesCount();
updateLotCheckAll();
}
function toggleAllLotCategories(lotName, checked) {
const checkboxes = document.querySelectorAll(`.category-checkbox[data-lot="${lotName}"]`);
checkboxes.forEach(cb => {
cb.checked = checked;
const catKey = cb.value;
if (checked) {
if (!selectedCategories.includes(catKey)) {
selectedCategories.push(catKey);
}
} else {
selectedCategories = selectedCategories.filter(c => c !== catKey);
}
});
updateSelectedCategoriesCount();
}
function toggleAllCategories(checked) {
const checkboxes = document.querySelectorAll('.category-checkbox');
checkboxes.forEach(cb => {
cb.checked = checked;
const catKey = cb.value;
if (checked) {
if (!selectedCategories.includes(catKey)) {
selectedCategories.push(catKey);
}
} else {
selectedCategories = selectedCategories.filter(c => c !== catKey);
}
});
// Mettre à jour les checkboxes "tout le lot"
document.querySelectorAll('.lot-check-all').forEach(cb => {
cb.checked = checked;
});
updateSelectedCategoriesCount();
}
function updateSelectedCategoriesCount() {
document.getElementById('selectedCategoriesCount').textContent = selectedCategories.length;
}
function updateLotCheckAll() {
document.querySelectorAll('.lot-check-all').forEach(cb => {
const lotName = cb.id.replace('lot_all_', '').replace(/_/g, ' ');
const catCheckboxes = document.querySelectorAll(`.category-checkbox[data-lot="${lotName}"]`);
const allChecked = Array.from(catCheckboxes).every(c => c.checked);
cb.checked = allChecked;
});
}
// === Gestion de l'étape 5 : Vacances et horaires ===
function loadWorkSchedule() {
const container = document.getElementById('workScheduleGrid');
container.innerHTML = '';
// En-têtes
const headerRow = document.createElement('div');
headerRow.className = 'row fw-bold mb-2 pb-2 border-bottom';
headerRow.innerHTML = `
<div class="col-md-2">Jour</div>
<div class="col-md-2 text-center">Travail</div>
<div class="col-md-4">Matin</div>
<div class="col-md-4">Après-midi</div>
`;
container.appendChild(headerRow);
// Lignes pour chaque jour
weekdays.forEach(day => {
const schedule = workSchedule[day.id] || {
is_working: day.id <= 5, // Lundi-Vendredi par défaut
am_start: defaultSchedule.am_start,
am_end: defaultSchedule.am_end,
pm_start: defaultSchedule.pm_start,
pm_end: defaultSchedule.pm_end,
am_working: true,
pm_working: true
};
const row = document.createElement('div');
row.className = 'row mb-2 align-items-center';
row.id = `schedule_row_${day.id}`;
row.innerHTML = `
<div class="col-md-2">
<strong>${day.name}</strong>
</div>
<div class="col-md-2 text-center">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="working_${day.id}"
${schedule.is_working ? 'checked' : ''}
onchange="toggleWorkingDay(${day.id})">
</div>
</div>
<div class="col-md-2">
<input type="time" class="form-control form-control-sm" id="am_start_${day.id}"
value="${schedule.am_start}" ${!schedule.is_working ? 'disabled' : ''}>
</div>
<div class="col-md-2">
<input type="time" class="form-control form-control-sm" id="am_end_${day.id}"
value="${schedule.am_end}" ${!schedule.is_working ? 'disabled' : ''}>
</div>
<div class="col-md-2">
<input type="time" class="form-control form-control-sm" id="pm_start_${day.id}"
value="${schedule.pm_start}" ${!schedule.is_working ? 'disabled' : ''}>
</div>
<div class="col-md-2">
<input type="time" class="form-control form-control-sm" id="pm_end_${day.id}"
value="${schedule.pm_end}" ${!schedule.is_working ? 'disabled' : ''}>
</div>
`;
container.appendChild(row);
});
}
function toggleWorkingDay(dayId) {
const checkbox = document.getElementById(`working_${dayId}`);
const inputs = document.querySelectorAll(`#schedule_row_${dayId} input[type="time"]`);
inputs.forEach(input => {
input.disabled = !checkbox.checked;
});
}
function getWorkScheduleData() {
const schedule = {};
weekdays.forEach(day => {
schedule[day.id] = {
is_working: document.getElementById(`working_${day.id}`).checked,
am_start: document.getElementById(`am_start_${day.id}`).value,
am_end: document.getElementById(`am_end_${day.id}`).value,
pm_start: document.getElementById(`pm_start_${day.id}`).value,
pm_end: document.getElementById(`pm_end_${day.id}`).value
};
});
return schedule;
}
// === Navigation ===
function nextStep() {
if (displayStep === 1) {
// Valider le formulaire admin
const username = document.getElementById('adminUsername').value.trim();
const password = document.getElementById('adminPassword').value;
if (username.length < 3) {
alert('Le nom d\'utilisateur doit faire au moins 3 caractères');
return;
}
if (password.length < 4) {
alert('Le mot de passe doit faire au moins 4 caractères');
return;
}
saveStep(1, {username, password}).then(success => {
if (success) {
currentStep = 1;
showStep(2);
}
});
} else if (displayStep === 2) {
// Valider le formulaire collège
const name = document.getElementById('collegeName').value.trim();
const address = document.getElementById('collegeAddress').value.trim();
const zip = document.getElementById('collegeZip').value.trim();
const city = document.getElementById('collegeCity').value.trim();
if (!name) {
alert('Le nom du collège est requis');
return;
}
if (!address) {
alert('L\'adresse est requise');
return;
}
if (!/^\d{5}$/.test(zip)) {
alert('Le code postal doit contenir 5 chiffres');
return;
}
if (!city) {
alert('La ville est requise');
return;
}
saveStep(2, {name, address, zip, city}).then(success => {
if (success) {
currentStep = 2;
showStep(3);
}
});
} else if (displayStep === 3) {
// Valider les bâtiments
const validBuildings = buildings.filter(b => b && b.name && b.name.trim());
if (validBuildings.length === 0) {
alert('Ajoutez au moins un bâtiment');
return;
}
saveStep(3, {buildings: validBuildings}).then(success => {
if (success) {
currentStep = 3;
showStep(4);
}
});
} else if (displayStep === 4) {
// Valider les catégories
if (selectedCategories.length === 0) {
alert('Sélectionnez au moins une catégorie');
return;
}
saveStep(4, {categories: selectedCategories}).then(success => {
if (success) {
currentStep = 4;
showStep(5);
}
});
} else if (displayStep === 5) {
// Étape 5 : Vacances et horaires
const zone = document.getElementById('academiqueZone').value;
const annee = document.getElementById('anneeScolaire').value;
const importVacances = document.getElementById('importAutoVacances').checked;
const importFeries = document.getElementById('importAutoFeries').checked;
const workData = getWorkScheduleData();
const stepData = {
zone: zone,
annee: annee,
import_vacances: importVacances,
import_feries: importFeries,
work_schedule: workData
};
saveStep(5, stepData).then(success => {
if (success) {
currentStep = 5;
showStep(6);
}
});
} else if (displayStep === 6) {
// Étape 6 : ENT77
const skipEnt = document.getElementById('skipEnt').checked;
const entUsername = document.getElementById('entUsername').value.trim();
const entPassword = document.getElementById('entPassword').value;
const entIsActive = document.getElementById('entIsActive').checked;
if (skipEnt) {
// Passer l'étape ENT
currentStep = 6;
showStep(7);
return;
}
if (!entUsername || !entPassword) {
alert('Remplissez les identifiants ENT77 ou cochez "Passer cette étape"');
return;
}
// Tester la connexion avant de sauvegarder
testEntAndSave(entUsername, entPassword, entIsActive);
} else if (displayStep === 7) {
// Étape 7 : Pronote
const skipPronote = document.getElementById('skipPronote').checked;
const qrCode = document.getElementById('pronoteQrCode').value.trim();
const qrCodeId = document.getElementById('pronoteQrCodeId').value.trim();
const pin = document.getElementById('pronotePin').value;
const syncSalles = document.getElementById('pronoteSyncSalles').checked;
if (skipPronote) {
// Passer l'étape Pronote
currentStep = 7;
showStep(8);
return;
}
if (!qrCode || !qrCodeId || !pin) {
alert('Remplissez tous les champs Pronote ou cochez "Passer cette étape"');
return;
}
// Tester la connexion avant de sauvegarder
testPronoteAndSave(qrCode, qrCodeId, pin, syncSalles);
} else if (displayStep === 8) {
// Étape 8 : Salles (à venir) - passer automatiquement
currentStep = 8;
showStep(9);
} else if (displayStep === 9) {
// Étape 9 : Personnels (à venir) - passer automatiquement
currentStep = 9;
showStep(10);
} else if (displayStep === 10) {
// Étape 10 : Professeurs (à venir) - terminer
finishSetup();
} else {
}
}
function prevStep() {
if (displayStep > 1) {
showStep(displayStep - 1);
}
}
async function saveStep(step, data) {
try {
const response = await fetch('/setup-wizard/api/step/' + step, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.error) {
alert(result.error);
return false;
}
if (result.success) {
// Mettre à jour les données locales
if (step === 1) {
setupData.admin_username = data.username;
} else if (step === 2) {
setupData.college = data;
} else if (step === 3) {
setupData.buildings = data.buildings;
} else if (step === 4) {
setupData.categories = data.categories;
} else if (step === 5) {
setupData.workSchedule = data.work_schedule;
setupData.customClosures = data.custom_closures;
setupData.personalLeaves = data.personal_leaves;
}
return true;
}
return false;
} catch (e) {
alert('Erreur: ' + e.message);
return false;
}
}
// === Fonctions pour l'étape 6: ENT77 ===
async function testEntConnection() {
const username = document.getElementById('entUsername').value.trim();
const password = document.getElementById('entPassword').value;
if (!username || !password) {
showEntMessage('Remplissez les identifiants pour tester', 'warning');
return;
}
document.getElementById('entTestResult').innerHTML = '<i class="bi bi-hourglass-split"></i> Test...';
try {
const response = await fetch('/setup-wizard/api/test-ent', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username, password})
});
const result = await response.json();
if (result.success) {
document.getElementById('entTestResult').innerHTML = '<span class="text-success"><i class="bi bi-check-circle"></i> OK</span>';
showEntMessage(result.message || 'Connexion réussie', 'success');
} else {
document.getElementById('entTestResult').innerHTML = '<span class="text-danger"><i class="bi bi-x-circle"></i> Échec</span>';
showEntMessage(result.error || 'Échec de connexion', 'danger');
}
} catch (e) {
document.getElementById('entTestResult').innerHTML = '<span class="text-danger"><i class="bi bi-x-circle"></i> Erreur</span>';
showEntMessage('Erreur de connexion au serveur', 'danger');
}
}
async function testEntAndSave(username, password, isActive) {
document.getElementById('entTestResult').innerHTML = '<i class="bi bi-hourglass-split"></i> Test...';
try {
const response = await fetch('/setup-wizard/api/test-ent', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username, password})
});
const result = await response.json();
if (result.success) {
document.getElementById('entTestResult').innerHTML = '<span class="text-success"><i class="bi bi-check-circle"></i> OK</span>';
showEntMessage(result.message || 'Connexion réussie ! Enregistrement...', 'success');
// Sauvegarder et passer à l'étape suivante
await saveStep(6, {username, password, is_active: isActive});
currentStep = 6;
showStep(7);
} else {
document.getElementById('entTestResult').innerHTML = '<span class="text-danger"><i class="bi bi-x-circle"></i> Échec</span>';
showEntMessage(result.error || 'Échec de connexion', 'danger');
}
} catch (e) {
document.getElementById('entTestResult').innerHTML = '<span class="text-danger"><i class="bi bi-x-circle"></i> Erreur</span>';
showEntMessage('Erreur de connexion au serveur', 'danger');
}
}
function showEntMessage(message, type) {
const div = document.getElementById('entValidationMessage');
div.className = 'alert alert-' + type;
div.textContent = message;
div.style.display = 'block';
}
// === Fonctions pour l'étape 7: Pronote ===
async function testPronoteConnection() {
const qrCode = document.getElementById('pronoteQrCode').value.trim();
const qrCodeId = document.getElementById('pronoteQrCodeId').value.trim();
const pin = document.getElementById('pronotePin').value;
if (!qrCode || !qrCodeId || !pin) {
showPronoteMessage('Remplissez tous les champs pour tester', 'warning');
return;
}
document.getElementById('pronoteTestResult').innerHTML = '<i class="bi bi-hourglass-split"></i> Test...';
try {
const response = await fetch('/setup-wizard/api/test-pronote', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({qr_code: qrCode, qr_code_id: qrCodeId, pin})
});
const result = await response.json();
if (result.success) {
document.getElementById('pronoteTestResult').innerHTML = '<span class="text-success"><i class="bi bi-check-circle"></i> OK</span>';
showPronoteMessage(result.message || 'Connexion réussie', 'success');
} else {
document.getElementById('pronoteTestResult').innerHTML = '<span class="text-danger"><i class="bi bi-x-circle"></i> Échec</span>';
showPronoteMessage(result.error || 'Échec de connexion', 'danger');
}
} catch (e) {
document.getElementById('pronoteTestResult').innerHTML = '<span class="text-danger"><i class="bi bi-x-circle"></i> Erreur</span>';
showPronoteMessage('Erreur de connexion au serveur', 'danger');
}
}
async function testPronoteAndSave(qrCode, qrCodeId, pin, syncSalles) {
document.getElementById('pronoteTestResult').innerHTML = '<i class="bi bi-hourglass-split"></i> Test...';
try {
const response = await fetch('/setup-wizard/api/test-pronote', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({qr_code: qrCode, qr_code_id: qrCodeId, pin})
});
const result = await response.json();
if (result.success) {
document.getElementById('pronoteTestResult').innerHTML = '<span class="text-success"><i class="bi bi-check-circle"></i> OK</span>';
showPronoteMessage(result.message || 'Connexion réussie ! Enregistrement...', 'success');
// Sauvegarder les tokens
await saveStep(7, {qr_code: qrCode, qr_code_id: qrCodeId, pin, sync_salles: syncSalles});
// Passer à l'étape suivante
showStep(8);
} else {
document.getElementById('pronoteTestResult').innerHTML = '<span class="text-danger"><i class="bi bi-x-circle"></i> Échec</span>';
showPronoteMessage(result.error || 'Échec de connexion', 'danger');
}
} catch (e) {
document.getElementById('pronoteTestResult').innerHTML = '<span class="text-danger"><i class="bi bi-x-circle"></i> Erreur</span>';
showPronoteMessage('Erreur de connexion au serveur', 'danger');
}
}
function showPronoteMessage(message, type) {
const div = document.getElementById('pronoteValidationMessage');
div.className = 'alert alert-' + type;
div.textContent = message;
div.style.display = 'block';
}
function finishSetup() {
// Appeler l'API pour finaliser le setup
fetch('/setup-wizard/api/finish', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Configuration terminée ! L\'application va redémarrer.');
window.location.href = '/';
} else {
alert('Erreur: ' + (data.error || 'Erreur inconnue'));
}
})
.catch(e => {
alert('Erreur de connexion au serveur');
});
}
// Initialiser
document.addEventListener('DOMContentLoaded', function() {
displayStep = Math.min(currentStep + 1, totalSteps);
showStep(displayStep);
});
</script>
{% endblock %}