gmao/app_new/wizard/templates/wizard/equipment_wizard.html
2026-08-14 16:02:25 +00:00

363 lines
No EOL
18 KiB
HTML

{% extends "base.html" %}
{% block title %}Wizard Création Équipements — GMAO Collège{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row mb-4">
<div class="col-12">
<h1><i class="bi bi-magic"></i> Création d'équipements par lot</h1>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-1-circle"></i> Étape 1 : Catégorie et Lot</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-12 mb-3">
<label class="form-label">Nom de l'équipement</label>
<input type="text" id="equipment_name" class="form-control" placeholder="Laisser vide pour utiliser le nom de la catégorie">
<small class="text-muted">Optionnel. Si vide, le nom de la catégorie sera utilisé.</small>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 mb-3">
<label class="form-label">Catégorie *</label>
<select id="category_id" class="form-select">
<option value="">-- Sélectionner une catégorie --</option>
{% for cat in categories %}
<option value="{{ cat.id }}">{{ cat.name }}</option>
{% endfor %}
</select>
<small class="text-muted">Type d'équipement (Fenêtre, Porte, etc.)</small>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Lot *</label>
<select id="lot_id" class="form-select" disabled>
<option value="">-- Sélectionner d'abord une catégorie --</option>
</select>
<small class="text-muted" id="lot_hint">
<i class="bi bi-info-circle"></i> Sélectionnez d'abord une catégorie
</small>
<div id="lot_tasks_container" class="mt-3" style="display: none;">
<h6><i class="bi bi-clipboard-check"></i> Maintenances préventives du lot</h6>
<div id="lot_tasks_list" class="table-responsive"></div>
</div>
</div>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-2-circle"></i> Étape 2 : Groupe parent (optionnel)</h5>
</div>
<div class="card-body">
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="create_parent">
<label class="form-check-label" for="create_parent">Créer un groupe parent global</label>
</div>
<div id="parent_options" style="display: none;">
<div class="mb-3">
<label class="form-label">Nom du groupe parent</label>
<input type="text" id="parent_name" class="form-control" placeholder="Ex: Fenêtres du collège">
</div>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-3-circle"></i> Étape 3 : Salles et quantités</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4">
<label class="form-label">Bâtiment</label>
<select id="building_id" class="form-select">
<option value="">-- Tous les bâtiments --</option>
{% for b in buildings %}
<option value="{{ b.id }}">{{ b.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label">Zone (optionnel)</label>
<select id="zone_id" class="form-select">
<option value="">-- Toutes les zones --</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Type de salle</label>
<select id="room_type_id" class="form-select">
<option value="">-- Tous les types --</option>
{% for rt in room_types %}
<option value="{{ rt.id }}">{{ rt.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">Suivi individuel</label>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="tracked_individually">
<label class="form-check-label" for="tracked_individually">
Créer des équipements individuels (chaque fenêtre sera suivie séparément)
</label>
</div>
</div>
<h6>Salles sélectionnées</h6>
<div id="rooms_list" class="list-group mb-3">
<div class="list-group-item text-muted">
<i class="bi bi-info-circle"></i> Sélectionnez des salles dans le panneau de droite
</div>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5><i class="bi bi-4-circle"></i> Étape 4 : Prévisualisation</h5>
</div>
<div class="card-body">
<button type="button" id="btn_preview" class="btn btn-outline-primary mb-3">
<i class="bi bi-eye"></i> Prévisualiser les équipements
</button>
<div id="preview_result" style="display: none;">
<table class="table table-sm">
<thead><tr><th>Équipement</th><th>Salle</th><th>Catégorie</th><th>Lot</th></tr></thead>
<tbody id="preview_body"></tbody>
</table>
</div>
</div>
</div>
<div class="mt-3">
<a href="{{ url_for('equipments.index') }}" class="btn btn-secondary">
<i class="bi bi-x-lg"></i> Annuler
</a>
<button type="button" id="btn_submit" class="btn btn-primary" disabled>
<i class="bi bi-check-lg"></i> Créer les équipements
</button>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5><i class="bi bi-door-open"></i> Salles disponibles</h5>
</div>
<div class="card-body">
<input type="text" id="room_search" class="form-control mb-3" placeholder="Rechercher une salle...">
<div id="rooms_by_building">
{% for building in buildings %}
<div class="building-group" data-building-id="{{ building.id }}">
<h6>{{ building.name }}</h6>
<div class="room-list">
{% for room in building.rooms|sort(attribute='name') %}
<button type="button" class="btn btn-outline-secondary btn-sm mb-1 me-1 room-item"
data-room-id="{{ room.id }}" data-room-name="{{ room.name }}"
data-building-id="{{ building.id }}"
data-zone-id="{{ room.zone_id if room.zone_id else '' }}"
data-zone-name="{{ room.zone.name if room.zone else '' }}"
data-room-type-id="{{ room.room_type_id if room.room_type_id else '' }}">
{{ room.name }}
{% if room.zone %}<small class="text-muted">({{ room.zone.name }})</small>{% endif %}
</button>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
<select id="all_lots" style="display: none;">
{% for lot in lots %}
<option value="{{ lot.id }}">{{ lot.name }}</option>
{% endfor %}
</select>
{% endblock %}
{% block extra_scripts %}
<script>
let selectedRooms = [];
let lotsByCategory = {{ lots_by_category | tojson | safe }};
document.getElementById('create_parent').addEventListener('change', function() {
document.getElementById('parent_options').style.display = this.checked ? 'block' : 'none';
});
document.getElementById('category_id').addEventListener('change', function() {
const categoryId = this.value;
const lotSelect = document.getElementById('lot_id');
lotSelect.innerHTML = '<option value="">-- Sélectionner un lot --</option>';
if (!categoryId) { lotSelect.disabled = true; return; }
const lots = lotsByCategory[categoryId] || [];
if (lots.length === 0) {
const allLots = document.getElementById('all_lots').querySelectorAll('option');
allLots.forEach(opt => { if (opt.value) lotSelect.appendChild(opt.cloneNode(true)); });
} else {
lots.forEach(lot => { lotSelect.appendChild(new Option(lot.name, lot.id)); });
}
lotSelect.disabled = false;
});
document.getElementById('lot_id').addEventListener('change', async function() {
const lotId = this.value;
const container = document.getElementById('lot_tasks_container');
const list = document.getElementById('lot_tasks_list');
if (!lotId) { container.style.display = 'none'; return; }
try {
const response = await fetch('/wizard/api/lot_tasks/' + lotId);
const tasks = await response.json();
if (tasks.length === 0) {
container.style.display = 'block';
list.innerHTML = '<p class="text-muted small">Aucune maintenance préventive définie pour ce lot.</p>';
return;
}
let html = '<table class="table table-sm table-hover"><thead><tr>' +
'<th>N°</th><th>Tâche</th><th>Périodicité</th><th>Contrat</th><th>Gestionnaire</th></tr></thead><tbody>';
tasks.forEach(task => {
html += '<tr><td>' + (task.num_tache || '-') + '</td><td>' + (task.tache || '-') + '</td>' +
'<td>' + (task.periodicite || '-') + '</td>' +
'<td>' + (task.contrat ? '<i class="bi bi-check text-success"></i>' : '-') + '</td>' +
'<td>' + (task.gestionnaire || '-') + '</td></tr>';
});
html += '</tbody></table>';
list.innerHTML = html;
container.style.display = 'block';
} catch (e) { container.style.display = 'none'; }
});
function filterRooms() {
const buildingId = document.getElementById('building_id').value;
const zoneId = document.getElementById('zone_id').value;
const roomTypeId = document.getElementById('room_type_id').value;
document.querySelectorAll('.room-item').forEach(item => {
const show = (!buildingId || item.dataset.buildingId === buildingId) &&
(!zoneId || item.dataset.zoneId === zoneId) &&
(!roomTypeId || item.dataset.roomTypeId === roomTypeId);
item.style.display = show ? '' : 'none';
});
document.querySelectorAll('.building-group').forEach(g => {
const gid = g.dataset.buildingId;
g.style.display = (buildingId && gid !== buildingId) ? 'none' : '';
});
}
document.getElementById('building_id').addEventListener('change', async function() {
const buildingId = this.value;
const zoneSelect = document.getElementById('zone_id');
zoneSelect.innerHTML = '<option value="">-- Toutes les zones --</option>';
if (buildingId) {
const zones = await (await fetch('/wizard/api/zones/' + buildingId)).json();
zones.forEach(z => zoneSelect.appendChild(new Option(z.name, z.id)));
}
filterRooms();
});
document.getElementById('zone_id').addEventListener('change', filterRooms);
document.getElementById('room_type_id').addEventListener('change', filterRooms);
document.getElementById('room_search').addEventListener('input', function() {
const search = this.value.toLowerCase();
document.querySelectorAll('.room-item').forEach(item => {
item.style.display = item.dataset.roomName.toLowerCase().includes(search) ? '' : 'none';
});
});
document.querySelectorAll('.room-item').forEach(btn => {
btn.addEventListener('click', function() {
const roomId = this.dataset.roomId;
const roomName = this.dataset.roomName;
const zoneName = this.dataset.zoneName || '';
const idx = selectedRooms.findIndex(r => r.id == roomId);
if (idx > -1) {
selectedRooms.splice(idx, 1);
this.classList.replace('btn-primary', 'btn-outline-secondary');
} else {
selectedRooms.push({id: roomId, name: roomName, zone: zoneName, count: 1});
this.classList.replace('btn-outline-secondary', 'btn-primary');
}
updateRoomsList();
});
});
function updateRoomsList() {
const list = document.getElementById('rooms_list');
if (selectedRooms.length === 0) {
list.innerHTML = '<div class="list-group-item text-muted"><i class="bi bi-info-circle"></i> Sélectionnez des salles</div>';
document.getElementById('btn_submit').disabled = true;
return;
}
list.innerHTML = selectedRooms.map(r => '<div class="list-group-item d-flex justify-content-between">' +
'<span>' + r.name + (r.zone ? ' <small class="text-muted">(' + r.zone + ')</small>' : '') + '</span>' +
'<input type="number" class="form-control form-control-sm room-count" style="width:80px" data-room-id="' + r.id + '" value="' + (r.count || 1) + '" min="1">' +
'</div>').join('');
document.querySelectorAll('.room-count').forEach(i => i.addEventListener('change', function() {
const room = selectedRooms.find(r => r.id == this.dataset.roomId);
if (room) room.count = parseInt(this.value) || 1;
}));
document.getElementById('btn_submit').disabled = false;
}
document.getElementById('btn_preview').addEventListener('click', function() {
const cat = document.getElementById('category_id');
const lot = document.getElementById('lot_id');
const nameInput = document.getElementById('equipment_name');
if (!cat.value || !lot.value || selectedRooms.length === 0) {
alert('Sélectionnez une catégorie, un lot et des salles.'); return;
}
const equipmentName = nameInput.value.trim() || cat.options[cat.selectedIndex].text;
const catName = cat.options[cat.selectedIndex].text;
const lotName = lot.options[lot.selectedIndex].text;
document.getElementById('preview_body').innerHTML = selectedRooms.flatMap(r => {
const count = r.count || 1;
return Array.from({length: count}, (_, i) => '<tr><td>' + equipmentName + (count > 1 ? ' - ' + (i+1) : '') + '</td><td>' + r.name + '</td><td>' + catName + '</td><td>' + lotName + '</td></tr>');
}).join('');
document.getElementById('preview_result').style.display = 'block';
});
document.getElementById('btn_submit').addEventListener('click', async function() {
const data = {
category_id: document.getElementById('category_id').value,
lot_id: document.getElementById('lot_id').value,
name: document.getElementById('equipment_name').value.trim(),
create_parent: document.getElementById('create_parent').checked,
parent_name: document.getElementById('parent_name').value,
building_id: document.getElementById('building_id').value,
zone_id: document.getElementById('zone_id').value,
rooms: selectedRooms.map(r => ({room_id: r.id, count: r.count || 1})),
tracked_individually: document.getElementById('tracked_individually').checked
};
try {
const resp = await fetch('/wizard/equipments/create', {
method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)
});
const result = await resp.json();
if (result.success) {
alert(result.count + ' équipement(s) créé(s) avec succès !');
window.location.href = '/equipments/';
}
else alert('Erreur: ' + (result.error || 'Erreur inconnue'));
} catch (e) { alert('Erreur lors de la création.'); }
});
</script>
{% endblock %}