1210 lines
68 KiB
HTML
1210 lines
68 KiB
HTML
{% extends "base.html" %}
|
||
{% load static %}
|
||
{% load i18n %}
|
||
|
||
{% block head %}
|
||
|
||
<!-- Mapbox GL Draw 1.5.0 (local) -->
|
||
<link href="{% static 'common/mapbox-gl-draw.css' %}" rel="stylesheet"/>
|
||
<!-- <style>
|
||
.mapbox-gl-draw_ctrl-group .mapbox-gl-draw_ctrl-draw-btn {
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
}
|
||
.mapbox-gl-draw_polygon:before {
|
||
content: '';
|
||
width: 18px; height:18px;
|
||
background-image: url('data:image/svg+xml;utf8,<svg fill="%23007cbf" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2L2 7l10 5 10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>');
|
||
}
|
||
.mapbox-gl-draw_rectangle:before {
|
||
content: '';
|
||
width: 18px; height:18px;
|
||
background-image: url('data:image/svg+xml;utf8,<svg fill="%23faa" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><rect x="3" y="6" width="18" height="12" rx="2"/></svg>');
|
||
}
|
||
.mapbox-gl-draw_circle:before {
|
||
content: '';
|
||
width: 18px; height:18px;
|
||
background-image: url('data:image/svg+xml;utf8,<svg fill="%23afa" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="8"/></svg>');
|
||
}
|
||
</style> -->
|
||
|
||
<style>
|
||
i.small-icon {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
#interventions-table-container {
|
||
position: relative;
|
||
}
|
||
|
||
#filter-dropdown-wrapper {
|
||
position: absolute;
|
||
top: 0.5rem;
|
||
left: 0.5rem;
|
||
z-index: 10;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.popup-scrollable {
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* jQuery UI autocomplete above Bootstrap modal */
|
||
.ui-autocomplete {
|
||
z-index: 1060 !important;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
/* Supprime le contour bleu Bootstrap au focus sur l'input de recherche */
|
||
#interventions-search-input:focus {
|
||
outline: none;
|
||
box-shadow: none;
|
||
border-color: #dee2e6;
|
||
}
|
||
|
||
|
||
/* multi-select compacte et scrollable */
|
||
.form-select[multiple].bs-iconized {
|
||
min-height: 3.25rem;
|
||
max-height: 10rem;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* tenter d’enlever le fond du système pour l’état sélectionné */
|
||
.form-select[multiple].bs-iconized option:checked {
|
||
background-color: #e1f6ff !important;
|
||
color: inherit;
|
||
}
|
||
|
||
/* focus clair */
|
||
.form-select[multiple].bs-iconized:focus {
|
||
border-color: var(--bs-primary);
|
||
box-shadow: 0 0 0 .2rem rgba(13,110,253,.25);
|
||
}
|
||
|
||
/* petite ligne de synthèse (facultative) */
|
||
.bs-summary {
|
||
margin-top: .35rem;
|
||
color: var(--bs-secondary-color);
|
||
font-size: .875rem;
|
||
}
|
||
|
||
.location-item {
|
||
background-color: #e1f6ff;
|
||
}
|
||
|
||
/* Styles pour la modale de filtre */
|
||
#filterModal .modal-dialog {
|
||
max-width: 95%;
|
||
}
|
||
|
||
#filterModal .modal-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: 90vh;
|
||
}
|
||
|
||
#filterModal .modal-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
#filterModal .modal-footer {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.filter-section {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
/* Default badge styling */
|
||
.filter-option label {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 0;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
background-color: #e7f1ff;
|
||
color: #0c5cc9;
|
||
border-radius: 1rem;
|
||
padding: 0.4rem 0.5rem;
|
||
font-size: 0.75rem;
|
||
border: 1px solid #b8d4f1;
|
||
transition: all 0.2s ease;
|
||
width: 140px;
|
||
flex: 1 1 auto;
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* Contextual color badges - inactive state (outline) */
|
||
.filter-option label.context-badge {
|
||
border-color: currentColor;
|
||
color: inherit;
|
||
background-color: transparent;
|
||
}
|
||
|
||
/* Bootstrap outline color mappings */
|
||
.filter-option label.btn-outline-primary {
|
||
border-color: #0c5cc9;
|
||
color: #0c5cc9;
|
||
}
|
||
.filter-option label.btn-outline-danger {
|
||
border-color: #dc3545;
|
||
color: #dc3545;
|
||
}
|
||
.filter-option label.btn-outline-warning {
|
||
border-color: #ffc107;
|
||
color: #ffc107;
|
||
}
|
||
.filter-option label.btn-outline-success {
|
||
border-color: #28a745;
|
||
color: #28a745;
|
||
}
|
||
.filter-option label.btn-outline-info {
|
||
border-color: #17a2b8;
|
||
color: #17a2b8;
|
||
}
|
||
.filter-option label.btn-outline-dark {
|
||
border-color: #343a40;
|
||
color: #343a40;
|
||
}
|
||
.filter-option label.btn-outline-secondary {
|
||
border-color: #6c757d;
|
||
color: #6c757d;
|
||
}
|
||
|
||
/* Default badges - checked state */
|
||
.filter-option input[type="checkbox"]:checked + label:not(.context-badge) {
|
||
background-color: #0c5cc9;
|
||
color: white;
|
||
border-color: #0c5cc9;
|
||
}
|
||
|
||
/* Contextual badges - checked state (filled/enfoncé) */
|
||
.filter-option input[type="checkbox"]:checked + label.context-badge {
|
||
color: white;
|
||
}
|
||
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-primary {
|
||
background-color: #0c5cc9;
|
||
border-color: #0c5cc9;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-danger {
|
||
background-color: #dc3545;
|
||
border-color: #dc3545;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-warning {
|
||
background-color: #ffc107;
|
||
border-color: #ffc107;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-success {
|
||
background-color: #28a745;
|
||
border-color: #28a745;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-info {
|
||
background-color: #17a2b8;
|
||
border-color: #17a2b8;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-dark {
|
||
background-color: #343a40;
|
||
border-color: #343a40;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-secondary {
|
||
background-color: #6c757d;
|
||
border-color: #6c757d;
|
||
}
|
||
|
||
/* Default badges - hover */
|
||
.filter-option input[type="checkbox"]:hover + label:not(.context-badge) {
|
||
background-color: #cfe2f3;
|
||
}
|
||
|
||
/* Contextual badges - hover */
|
||
.filter-option input[type="checkbox"]:hover + label.context-badge {
|
||
background-color: inherit;
|
||
}
|
||
|
||
.filter-section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 0.75rem;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.filter-section-header label {
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin: 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.filter-section-buttons {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-left: auto;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.filter-section-buttons button {
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.75rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.filter-options {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
min-height: 2.5rem;
|
||
padding: 0.5rem;
|
||
background-color: #f8f9fa;
|
||
border-radius: 0.375rem;
|
||
border: 1px solid #dee2e6;
|
||
}
|
||
|
||
.filter-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.filter-option.subcategory-option {
|
||
font-size: 0.875em;
|
||
color: #555;
|
||
}
|
||
.filter-option-break {
|
||
flex-basis: 100%;
|
||
height: 0;
|
||
}
|
||
.filter-subcat-controls {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
.filter-option input[type="checkbox"] {
|
||
position: absolute;
|
||
opacity: 0;
|
||
width: 0;
|
||
height: 0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Réduire la taille de police pour tout le tableau des interventions */
|
||
#interventions-table {
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
/* Style pour la colonne titre - limité à 2 lignes avec ellipsis */
|
||
.intervention-title-cell {
|
||
font-size: 0.85rem;
|
||
max-height: 2.8em;
|
||
line-height: 1.4;
|
||
overflow: hidden;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
/* Tooltip only on truncated text */
|
||
.intervention-title-cell[data-truncated="true"] {
|
||
cursor: help;
|
||
}
|
||
|
||
/* Harmoniser la taille de police du header du tableau */
|
||
#interventions-table thead th,
|
||
#interventions-table thead .th-inner,
|
||
.fixed-table-header,
|
||
.fixed-table-header .th-inner {
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
/* Styled restore button when in expanded mode */
|
||
.restore-btn-styled {
|
||
border-radius: 10px !important;
|
||
border: 1px solid rgba(0, 0, 0, 0.15) !important;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12) !important;
|
||
transition: all 0.2s ease !important;
|
||
}
|
||
|
||
.restore-btn-styled:hover {
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18) !important;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.restore-btn-styled i {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
/* Priority gradients & custom styles */
|
||
.filter-option label.btn-outline-priority-1 {
|
||
border-color: #dc3545;
|
||
color: #dc3545;
|
||
}
|
||
.filter-option label.btn-outline-priority-2 {
|
||
border-color: #fd7e14;
|
||
color: #fd7e14;
|
||
}
|
||
.filter-option label.btn-outline-priority-3 {
|
||
border-color: #0d6efd;
|
||
color: #0d6efd;
|
||
}
|
||
.filter-option label.btn-outline-priority-4 {
|
||
border-color: #6c757d;
|
||
color: #6c757d;
|
||
}
|
||
|
||
.filter-option label[class*="btn-outline-priority-"] {
|
||
transition: all 0.2s ease-in-out;
|
||
}
|
||
|
||
.filter-option input[type="checkbox"]:hover + label[class*="btn-outline-priority-"] {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 8px rgba(0,0,0,0.08);
|
||
}
|
||
|
||
.filter-option input[type="checkbox"]:hover + label.btn-outline-priority-1:not(:checked) {
|
||
background-color: rgba(220, 53, 69, 0.05);
|
||
}
|
||
.filter-option input[type="checkbox"]:hover + label.btn-outline-priority-2:not(:checked) {
|
||
background-color: rgba(253, 126, 20, 0.05);
|
||
}
|
||
.filter-option input[type="checkbox"]:hover + label.btn-outline-priority-3:not(:checked) {
|
||
background-color: rgba(13, 110, 253, 0.05);
|
||
}
|
||
.filter-option input[type="checkbox"]:hover + label.btn-outline-priority-4:not(:checked) {
|
||
background-color: rgba(108, 117, 125, 0.05);
|
||
}
|
||
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-priority-1 {
|
||
background: linear-gradient(135deg, #dc3545 0%, #ff5e62 100%) !important;
|
||
border-color: #dc3545 !important;
|
||
color: white !important;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-priority-2 {
|
||
background: linear-gradient(135deg, #fd7e14 0%, #ffb703 100%) !important;
|
||
border-color: #fd7e14 !important;
|
||
color: white !important;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-priority-3 {
|
||
background: linear-gradient(135deg, #0d6efd 0%, #00b4d8 100%) !important;
|
||
border-color: #0d6efd !important;
|
||
color: white !important;
|
||
}
|
||
.filter-option input[type="checkbox"]:checked + label.btn-outline-priority-4 {
|
||
background: linear-gradient(135deg, #6c757d 0%, #adb5bd 100%) !important;
|
||
border-color: #6c757d !important;
|
||
color: white !important;
|
||
}
|
||
</style>
|
||
{% endblock head %}
|
||
|
||
|
||
{% block content %}
|
||
|
||
<!-- Modal for choosing intervention type -->
|
||
<div class="modal fade" id="interventionTypeModal" tabindex="-1" aria-labelledby="interventionTypeModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<div class="modal-content">
|
||
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="interventionTypeModalLabel">{% translate "Choisir le type d’intervention" %}</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||
</div>
|
||
|
||
<div class="modal-body d-flex flex-column gap-2">
|
||
|
||
<a href="{% url 'interventions:intervention_add_choose_thematic' %}?type=corrective" class="btn btn-outline-danger w-100">
|
||
<i class="bi bi-gear"></i> {% translate "Intervention corrective" %}<br>
|
||
<small class="text-muted">{% translate "Réparer ou corriger un dysfonctionnement (avarie, panne, défaut, ...)" %}</small>
|
||
</a>
|
||
|
||
{% if can_create_preventive %}
|
||
<a href="{% url 'interventions:intervention_add_choose_thematic' %}?type=preventive" class="btn btn-outline-warning w-100">
|
||
<i class="bi bi-gear"></i> {% translate "Interventions préventives" %}<br>
|
||
<small class="text-muted">{% translate "Anticiper un problème potentiel (entretien cyclique, ...)" %} </small>
|
||
</a>
|
||
{% endif %}
|
||
|
||
{% if can_create_ameliorative %}
|
||
<a href="{% url 'interventions:intervention_add_choose_thematic' %}?type=ameliorative" class="btn btn-outline-success w-100">
|
||
<i class="bi bi-gear"></i> {% translate "Intervention améliorative" %}<br>
|
||
<small class="text-muted">{% translate "Optimiser ou améliorer une situation existante (projet, fonctionnel, ...)" %}</small>
|
||
</a>
|
||
{% endif %}
|
||
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modale pour le filtre des interventions -->
|
||
<div class="modal fade" id="filterModal" tabindex="-1" aria-labelledby="filterModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="filterModalLabel">{% translate "Filtrer les interventions" %}</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- Formulaire de filtre -->
|
||
<form method="get" id="filter-form" class="mb-4">
|
||
<!-- Thématique, Statut et Catégorie -->
|
||
<div class="row">
|
||
<!-- Thématique -->
|
||
<div class="col-12 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Thématique" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="thematic">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="thematic">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="thematic">
|
||
{% for key, value, icon in thematic_choices %}
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="thematic" id="thematic_{{ key }}" value="{{ key }}" {% if key in selected_thematics %}checked{% endif %}>
|
||
<label for="thematic_{{ key }}">
|
||
{% if icon %}<i class="bi {{ icon }} me-2"></i>{% endif %}{{ value }}
|
||
</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Statut -->
|
||
<div class="col-12 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Statut" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="status">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="status">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="status">
|
||
{% for key, value, css_class in status_choices %}
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="status" id="status_{{ key }}" value="{{ key }}" {% if key in selected_statuses %}checked{% endif %}>
|
||
<label for="status_{{ key }}" class="context-badge status-{{ key }} btn-outline-{{ css_class }}">{{ value }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Raisons de mise en pause -->
|
||
<div class="col-md-6 filter-section {% if not pause_reason_filter_enabled %}d-none{% endif %}" id="pause-reason-filter">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Raisons de la mise en pause" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="pause_reason" {% if not pause_reason_filter_enabled %}disabled{% endif %}>{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="pause_reason" {% if not pause_reason_filter_enabled %}disabled{% endif %}>{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="pause_reason">
|
||
{% for key, value in pause_reason_choices %}
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="pause_reason" id="pause_reason_{{ key }}" value="{{ key }}" {% if key in selected_pause_reasons %}checked{% endif %} {% if not pause_reason_filter_enabled %}disabled{% endif %}>
|
||
<label for="pause_reason_{{ key }}">{{ value }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Catégorie d'intervention (full width) -->
|
||
<div class="col-12 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Catégorie" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="category">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="category">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="category">
|
||
{% for cat_id, cat_name, cat_thematic, cat_parent_id in category_choices %}
|
||
{% if cat_parent_id %}{% ifchanged cat_parent_id %}<div class="filter-option-break"></div>{% endifchanged %}{% endif %}
|
||
<div class="filter-option{% if cat_parent_id %} subcategory-option{% endif %}" data-thematic="{{ cat_thematic|default:'' }}" data-category-id="{{ cat_id }}" data-parent-id="{{ cat_parent_id|default:'' }}">
|
||
<input type="checkbox" name="category" id="category_{{ cat_id }}" value="{{ cat_id }}" {% if cat_id|stringformat:"s" in selected_categories %}checked{% endif %}>
|
||
<label for="category_{{ cat_id }}">{% if cat_parent_id %}<i class="bi bi-arrow-return-right me-1 text-muted small"></i>{% endif %}{{ cat_name }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Autres filtres en deux colonnes -->
|
||
<div class="row mt-3">
|
||
|
||
<!-- Type de maintenance -->
|
||
<div class="col-md-6 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Type de maintenance" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="maintain_type">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="maintain_type">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="maintain_type">
|
||
{% for key, value, css_class in maintain_type_choices %}
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="maintain_type" id="maintain_type_{{ key }}" value="{{ key }}" {% if key in selected_maintain_types %}checked{% endif %}>
|
||
<label for="maintain_type_{{ key }}" class="context-badge btn-outline-{{ css_class }}">{{ value }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Type d'intervention -->
|
||
<div class="col-md-6 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Type d'intervention" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="type">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="type">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="type">
|
||
{% for key, value in type_choices %}
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="type" id="type_{{ key }}" value="{{ key }}" {% if key in selected_types %}checked{% endif %}>
|
||
<label for="type_{{ key }}">{{ value }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bon de préparation -->
|
||
<div class="col-md-6 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Bon de préparation (stock)" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="preparation_state">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="preparation_state">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="preparation_state">
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="preparation_state" id="preparation_state_processed" value="processed" {% if "processed" in selected_preparation_states %}checked{% endif %}>
|
||
<label for="preparation_state_processed" class="context-badge btn-outline-success">
|
||
<i class="bi bi-check-circle me-1"></i>{% translate "Prêt" %}
|
||
</label>
|
||
</div>
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="preparation_state" id="preparation_state_not_processed" value="not_processed" {% if "not_processed" in selected_preparation_states %}checked{% endif %}>
|
||
<label for="preparation_state_not_processed" class="context-badge btn-outline-danger">
|
||
<i class="bi bi-hourglass me-1"></i>{% translate "En attente" %}
|
||
</label>
|
||
</div>
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="preparation_state" id="preparation_state_none" value="none" {% if "none" in selected_preparation_states %}checked{% endif %}>
|
||
<label for="preparation_state_none" class="context-badge btn-outline-secondary">
|
||
<i class="bi bi-question-circle me-1"></i>{% translate "Aucun bon" %}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Priorité -->
|
||
<div class="col-md-6 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Priorité" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="priority">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="priority">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="priority">
|
||
{% for key, value in priority_choices %}
|
||
<div class="filter-option">
|
||
<input type="checkbox" name="priority" id="priority_{{ key }}" value="{{ key }}" {% if key in selected_priorities %}checked{% endif %}>
|
||
<label for="priority_{{ key }}" class="context-badge btn-outline-priority-{{ key }}">{{ value }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Prestataires / Contrats -->
|
||
{% if show_provider_filters %}
|
||
<div class="row mt-3">
|
||
<div class="col-md-6 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Prestataire" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="provider">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="provider">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="provider">
|
||
{% for pid, pname, pt_thematics in provider_choices %}
|
||
<div class="filter-option" data-thematic-codes="{{ pt_thematics|join:',' }}">
|
||
<input type="checkbox" name="provider" id="provider_{{ pid }}" value="{{ pid }}" {% if pid|stringformat:"s" in selected_providers %}checked{% endif %}>
|
||
<label for="provider_{{ pid }}">{{ pname }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Contrat" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="contract">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="contract">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="contract">
|
||
{% for cid, cnumber, cprovider, cthematics, cuses_mgr, cmanagers_json in contract_choices %}
|
||
<div class="filter-option" data-provider="{{ cprovider }}" data-thematic-codes="{{ cthematics|join:',' }}" data-uses-manager="{{ cuses_mgr|yesno:"true,false" }}" data-managers="{{ cmanagers_json }}">
|
||
<input type="checkbox" name="contract" id="contract_{{ cid }}" value="{{ cid }}" {% if cid|stringformat:"s" in selected_contracts %}checked{% endif %}>
|
||
<label for="contract_{{ cid }}">{{ cnumber }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Gestionnaires (dynamic) -->
|
||
<div class="row mt-3 d-none" id="intervention-manager-filter-row">
|
||
<div class="col-md-12 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Gestionnaire d'intervention" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="intervention_manager">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="intervention_manager">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="intervention_manager" id="intervention-manager-options-container">
|
||
<!-- Dynamic options will be inserted here by JS -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Équipes / Membres assignés -->
|
||
<div class="row mt-3">
|
||
<div class="col-md-6 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Équipe assignée" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="assigned_team">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="assigned_team">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="assigned_team">
|
||
{% for tid, tlabel, tcompany, tthematics in team_choices %}
|
||
<div class="filter-option" data-company="{{ tcompany }}" data-thematic-codes="{{ tthematics|join:',' }}">
|
||
<input type="checkbox" name="assigned_team" id="assigned_team_{{ tid }}" value="{{ tid }}" {% if tid|stringformat:"s" in selected_teams %}checked{% endif %}>
|
||
<label for="assigned_team_{{ tid }}">{{ tlabel }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 filter-section">
|
||
<div class="filter-section-header">
|
||
<label>{% translate "Membre assigné" %}</label>
|
||
<div class="filter-section-buttons">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary select-all-btn" data-field="assigned_member">{% translate "Tous" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary deselect-all-btn" data-field="assigned_member">{% translate "Aucun" %}</button>
|
||
</div>
|
||
</div>
|
||
<div class="filter-options" data-field="assigned_member">
|
||
{% for mid, mlabel, mteam, mcompany, mthematics in member_choices %}
|
||
<div class="filter-option" data-team="{{ mteam }}" data-company="{{ mcompany }}" data-thematic-codes="{{ mthematics|join:',' }}">
|
||
<input type="checkbox" name="assigned_member" id="assigned_member_{{ mid }}" value="{{ mid }}" {% if mid|stringformat:"s" in selected_members %}checked{% endif %}>
|
||
<label for="assigned_member_{{ mid }}">{{ mlabel }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Localisations (full width) -->
|
||
<div class="mt-3 mb-3">
|
||
<label class="form-label">{% translate "Localisations" %}</label>
|
||
<div id="location-autocomplete-container" class="position-relative">
|
||
<input id="location-autocomplete" type="text" class="form-control"
|
||
placeholder="{% translate 'Chercher une localisation (min. 2 caractères)…' %}">
|
||
</div>
|
||
<div id="selected-locations-chips" class="mt-2 d-flex flex-wrap gap-2"></div>
|
||
<button id="clear-locations" type="button" class="btn btn-sm btn-outline-secondary mt-2">
|
||
{% translate "Effacer les localisations" %}
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Dates -->
|
||
<div class="row mt-3">
|
||
<div class="col-6">
|
||
<div>
|
||
<label class="form-label">{% translate "Date de début" %}</label>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<input type="date" name="begin_date_min" id="begin_date_min" class="form-control" placeholder="Min" value="{{ begin_date_min }}">
|
||
<span class="mx-1">→</span>
|
||
<input type="date" name="begin_date_max" id="begin_date_max" class="form-control" placeholder="Max" value="{{ begin_date_max }}">
|
||
</div>
|
||
</div>
|
||
<div class="mt-3">
|
||
<label class="form-label">{% translate "Date de fin" %}</label>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<input type="date" name="end_date_min" id="end_date_min" class="form-control" placeholder="Min" value="{{ end_date_min }}">
|
||
<span class="mx-1">→</span>
|
||
<input type="date" name="end_date_max" id="end_date_max" class="form-control" placeholder="Max" value="{{ end_date_max }}">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-check mt-3">
|
||
<input
|
||
class="form-check-input"
|
||
type="checkbox"
|
||
name="overdue"
|
||
id="overdue"
|
||
value="1"
|
||
{% if overdue == '1' %}checked{% endif %}
|
||
>
|
||
<label class="form-check-label" for="overdue">
|
||
{% translate "Interventions non traitées (en retard)" %}
|
||
</label>
|
||
</div>
|
||
<div class="form-check mt-2">
|
||
<input
|
||
class="form-check-input"
|
||
type="checkbox"
|
||
name="followed_only"
|
||
id="followed_only"
|
||
value="1"
|
||
{% if followed_only == '1' %}checked{% endif %}
|
||
>
|
||
<label class="form-check-label" for="followed_only">
|
||
{% translate "Interventions suivies uniquement" %}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-6"></div>
|
||
</div>
|
||
|
||
{% if to_be_planned_soon %}
|
||
<input type="hidden" name="to_be_planned_soon" value="{{ to_be_planned_soon }}">
|
||
{% endif %}
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" id="reset-filters" class="btn btn-secondary">{% translate "Réinitialiser" %}</button>
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
|
||
<button type="button" id="apply-filters" class="btn btn-primary">{% translate "Filtrer" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="position-relative">
|
||
<!-- Layout selection buttons -->
|
||
<div class="position-absolute top-0 end-0 mt-2" style="z-index: 10;">
|
||
<div class="btn-group" role="group" aria-label="{% translate 'Sélection de la vue' %}">
|
||
<button id="layout-btn-table" class="btn btn-light{% if intervention_list_layout == 'table_only' %} text-primary{% endif %}" type="button" title="{% translate 'Vue tableau uniquement' %}">
|
||
<i class="bi bi-table"></i>
|
||
</button>
|
||
<button id="layout-btn-both" class="btn btn-light{% if intervention_list_layout == 'both' or not intervention_list_layout %} text-primary{% endif %}" type="button" title="{% translate 'Vue partagée' %}">
|
||
<i class="bi bi-layout-split"></i>
|
||
</button>
|
||
<button id="layout-btn-map" class="btn btn-light{% if intervention_list_layout == 'map_only' %} text-primary{% endif %}" type="button" title="{% translate 'Vue carte uniquement' %}">
|
||
<i class="bi bi-globe-europe-africa"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Widget de recherche — centré sur toute la largeur (tableau + carte) -->
|
||
<div id="interventions-search-widget"
|
||
style="position:absolute; top:4px; left:50%; transform:translateX(-50%); z-index:10; pointer-events:none;">
|
||
<div class="input-group" style="pointer-events:auto; flex-wrap:nowrap;">
|
||
<!-- Input + bouton X superposé à l'intérieur -->
|
||
<div style="position:relative; display:flex;">
|
||
<input type="text" id="interventions-search-input" class="form-control form-control-sm"
|
||
style="width:320px; border-radius:20px 0 0 20px; border-color:#dee2e6; padding-right:1.6rem;"
|
||
placeholder="{% translate 'Titre, code, localisation, contrat…' %}">
|
||
<button type="button" id="interventions-search-clear"
|
||
style="position:absolute; right:4px; top:50%; transform:translateY(-50%);
|
||
background:none; border:none; padding:0 2px; line-height:1;
|
||
color:#adb5bd; cursor:pointer; visibility:hidden;"
|
||
title="{% translate 'Effacer la recherche' %}">
|
||
<i class="bi bi-x-lg" style="font-size:0.7rem;"></i>
|
||
</button>
|
||
</div>
|
||
<span class="input-group-text bg-white px-1" style="border-left:0; border-right:0; border-color:#dee2e6;">
|
||
<div class="btn-group btn-group-sm" role="group" aria-label="{% translate 'Mode' %}">
|
||
<input type="radio" class="btn-check" name="search-mode" id="search-mode-and" value="and" checked>
|
||
<label class="btn btn-outline-secondary py-0 px-2" for="search-mode-and" title="{% translate 'Tous les mots doivent correspondre' %}">{% translate "AND" %}</label>
|
||
<input type="radio" class="btn-check" name="search-mode" id="search-mode-or" value="or">
|
||
<label class="btn btn-outline-secondary py-0 px-2" for="search-mode-or" title="{% translate 'Au moins un mot doit correspondre' %}">{% translate "OR" %}</label>
|
||
</div>
|
||
</span>
|
||
<button class="btn btn-sm" type="button" id="interventions-search-btn"
|
||
style="border-radius:0 20px 20px 0; border:1px solid #dee2e6; border-left:0; background:white; color:#495057;"
|
||
title="{% translate 'Rechercher' %}">
|
||
<i class="bi bi-search"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row mt-2" style="height: calc(100vh - 110px);">
|
||
<div class="{% if intervention_list_layout == 'map_only' %}d-none{% elif intervention_list_layout == 'table_only' %}col-12{% else %}col-md-6{% endif %}" id="col-left" style="max-height: calc(100vh - 110px); ">
|
||
<ul class="nav nav-tabs" id="tablesTab" role="tablist">
|
||
<li class="nav-item" role="presentation">
|
||
<button class="nav-link active" id="interventions-table-tab" data-bs-toggle="tab" data-bs-target="#interventions-table-content" type="button" role="tab" aria-controls="interventions-table" aria-selected="true">{% translate "Interventions" %}</button>
|
||
</li>
|
||
|
||
<div class="my-1 ms-2">
|
||
<button class="btn btn-light" type="button" data-bs-toggle="modal" data-bs-target="#interventionTypeModal">
|
||
<i class="bi bi-plus-circle"></i>
|
||
</button>
|
||
</div>
|
||
</ul>
|
||
<div class="tab-content d-flex flex-column" id="tablesTabContent" style="flex: 1; min-height: 0;">
|
||
<div class="tab-pane fade show active d-flex flex-column" id="interventions-table-content" role="tabpanel" aria-labelledby="interventions-table-tab" style="flex: 1; min-height: 0;">
|
||
|
||
<div class="d-flex justify-content-between flex-wrap align-items-center gap-2 mb-2 mt-2">
|
||
|
||
<!-- Bloc gauche : selection -->
|
||
<div class="d-flex gap-2 flex-wrap align-items-center">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" id="select-all">{% translate "Tout sélectionner" %}</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" id="deselect-all">{% translate "Tout désélectionner" %}</button>
|
||
<div id="selection-status" class="text-muted small mb-0">
|
||
{% blocktranslate count count=selected_interventions_ids|length %}
|
||
{{ count }} intervention sélectionnée
|
||
{% plural %}
|
||
{{ count }} interventions sélectionnées
|
||
{% endblocktranslate %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bloc droite : action -->
|
||
<div class="dropdown" id="bulk-action-dropdown">
|
||
<button class="btn btn-light btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||
{% translate "Action" %}
|
||
</button>
|
||
<ul class="dropdown-menu">
|
||
<li>
|
||
<a class="dropdown-item" href="#" data-action="status-change">
|
||
<i class="bi bi-fast-forward-circle me-2"></i></i>{% translate "Modifier le statut" %}
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a class="dropdown-item" href="#" data-action="associate-order">
|
||
<i class="bi bi-link-45deg me-2"></i>{% translate "Associer à une commande" %}
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a class="dropdown-item" href="#" data-action="export" data-bs-toggle="modal" data-bs-target="#exportModal">
|
||
<i class="bi bi-download me-2"></i>{% translate "Exporter" %}
|
||
</a>
|
||
</li>
|
||
{% if is_admin %}
|
||
<li>
|
||
<a class="dropdown-item" href="#" data-action="export-sheets-zip">
|
||
<i class="bi bi-file-earmark-zip me-2"></i>{% translate "Exporter les fiches (ZIP)" %}
|
||
</a>
|
||
</li>
|
||
{% endif %}
|
||
<li><hr class="dropdown-divider"></li>
|
||
<li>
|
||
<a class="dropdown-item" href="#" data-action="execution-report" data-bs-toggle="modal" data-bs-target="#executionReportModal">
|
||
<i class="bi bi-file-earmark-text me-2"></i>{% translate "Rapport d'exécution (PDF)" %}
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Spinner -->
|
||
<div id="interventions-spinner" class="table-spinner">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">{% translate "Chargement..." %}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="interventions-table-container" style="display: none; flex: 1; min-height: 0; overflow: hidden;">
|
||
|
||
<!-- Bouton de filtre -->
|
||
<div id="filter-dropdown-wrapper" class="float-left mb-2">
|
||
<button class="btn btn-light position-relative" type="button" data-bs-toggle="modal" data-bs-target="#filterModal">
|
||
<i class="bi bi-funnel"></i> {% translate "Filtre" %}
|
||
<span id="filter-badge" class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-black d-none">
|
||
<span id="filter-count">0</span>
|
||
</span>
|
||
</button>
|
||
<!-- Alerte limite atteinte (cachée par défaut) -->
|
||
<span id="filter-limit-alert" class="alert alert-info py-1 px-2 ms-2 d-none align-middle" role="alert">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
{% translate "Contenu limité à 5 000 éléments." %}"
|
||
</span>
|
||
</div>
|
||
|
||
<table id="interventions-table" class="table" style="margin-bottom: 0;"
|
||
data-pagination="true"
|
||
data-page-size="50"
|
||
data-click-to-select="true"
|
||
data-show-columns="true"
|
||
data-unique-id="id" >
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="{% if intervention_list_layout == 'table_only' %}d-none{% elif intervention_list_layout == 'map_only' %}col-12{% else %}col-md-6{% endif %}" id="col-right">
|
||
<div id="map" class="rounded-2" style="height: calc(100vh - 210px); margin-top: 50px;">
|
||
<button id="filterzone" class="btn btn-light btn-sm d-flex mx-auto mt-1 " type="button">
|
||
{% translate "Filtrer sur cette zone" %}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modale pour le changement de staut -->
|
||
<div class="modal fade" id="statusChangeModal" tabindex="-1" aria-labelledby="statusChangeModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="statusChangeModalLabel">{% translate "Changer le statut des interventions" %}</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="status-change-selection-info" class="alert alert-info d-none mb-3" role="alert">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
{% translate "Aucune intervention sélectionnée : le changement de statut s'appliquera à toutes les interventions filtrées." %}
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="new-status" class="form-label">{% translate "Nouveau statut" %}</label>
|
||
<select class="form-select" id="new-status">
|
||
<option value="">{% translate "-- Choisir un statut --" %}</option>
|
||
{% if is_admin %}
|
||
{% for key, value, css_class in status_choices %}
|
||
<option value="{{ key }}">{{ value }}</option>
|
||
{% endfor %}
|
||
{% else %}
|
||
<option value="to_be_approved">{% translate "À approuver" %}</option>
|
||
<option value="to_be_planned">{% translate "À planifier" %}</option>
|
||
<option value="to_be_processed">{% translate "À traiter" %}</option>
|
||
<option value="validated">{% translate "Validé" %}</option>
|
||
<option value="canceled">{% translate "Annulé" %}</option>
|
||
{% endif %}
|
||
</select>
|
||
</div>
|
||
<div id="cancellation-reason-group" class="mb-3 d-none">
|
||
<label for="bulk-cancellation-type" class="form-label">{% translate "Type d'annulation" %} <span class="text-danger">*</span></label>
|
||
<select class="form-select mb-2" id="bulk-cancellation-type">
|
||
<option value="" selected disabled>{% translate "Choisissez un type" %}</option>
|
||
<option value="duplicate">{% translate "Doublon" %}</option>
|
||
<option value="to_redirect">{% translate "A rediriger" %}</option>
|
||
<option value="not_our_assets">{% translate "Pas sous notre gestion" %}</option>
|
||
<option value="non_relevant">{% translate "Non-relevant" %}</option>
|
||
<option value="other">{% translate "Autre" %}</option>
|
||
</select>
|
||
<label for="bulk-cancellation-reason" class="form-label">{% translate "Commentaire" %} <span class="text-danger d-none" id="bulkCancellationReasonRequired">*</span></label>
|
||
<textarea class="form-control" id="bulk-cancellation-reason" rows="3" maxlength="1000" placeholder="{% translate 'Commentaire libre...' %}"></textarea>
|
||
<small class="form-text text-muted">{% translate "Obligatoire pour le motif \"Autre\". Sinon, le motif choisi sera enregistré comme note." %}</small>
|
||
</div>
|
||
<div id="status-error-message" class="alert alert-danger d-none" role="alert"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
|
||
<button type="button" class="btn btn-primary" id="confirm-status-change">{% translate "Confirmer" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modale pour associer à une commande -->
|
||
<div class="modal fade" id="associateOrderModal" tabindex="-1" aria-labelledby="associateOrderModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="associateOrderModalLabel">{% translate "Associer les interventions à une commande" %}</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="associate-order-selection-info" class="alert alert-info d-none mb-3" role="alert">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
{% translate "Aucune intervention sélectionnée : l'association s'appliquera à toutes les interventions filtrées." %}
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="order-search" class="form-label">{% translate "Commande" %}</label>
|
||
<input type="text" id="order-search" class="form-control" placeholder="{% translate 'Rechercher une commande...' %}">
|
||
<input type="hidden" id="order-id" value="">
|
||
<div class="form-text">{% translate "Seules les commandes au statut 'En préparation' ou 'Envoyé' et auxquelles vous avez accès sont proposées." %}</div>
|
||
</div>
|
||
<div id="associate-order-error" class="alert alert-danger d-none" role="alert"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
|
||
<button type="button" class="btn btn-primary" id="confirm-associate-order">{% translate "Associer" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Modal Export -->
|
||
<div class="modal fade" id="exportModal" tabindex="-1" aria-labelledby="exportModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="exportModalLabel">{% translate "Exporter les interventions sélectionnées" %}</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="export-selection-info" class="alert alert-info d-none mb-3" role="alert">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
{% translate "Aucune intervention sélectionnée : l'export inclura toutes les interventions filtrées." %}
|
||
</div>
|
||
<p class="small text-muted">{% translate "Choisissez le format de fichier à générer." %}</p>
|
||
<div class="d-flex gap-3">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="radio" name="export-format" id="export-format-csv" value="csv" checked>
|
||
<label class="form-check-label" for="export-format-csv">CSV</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="radio" name="export-format" id="export-format-xlsx" value="xlsx">
|
||
<label class="form-check-label" for="export-format-xlsx">Excel (XLSX)</label>
|
||
</div>
|
||
</div>
|
||
<div id="export-error" class="alert alert-danger d-none mt-3" role="alert"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
|
||
<button type="button" class="btn btn-primary" id="confirm-export-btn">{% translate "Exporter" %}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Modal Rapport d'exécution -->
|
||
<div class="modal fade" id="executionReportModal" tabindex="-1" aria-labelledby="executionReportModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="executionReportModalLabel"><i class="bi bi-file-earmark-text me-2"></i>{% translate "Rapport d'exécution" %}</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="execution-report-selection-info" class="alert alert-info d-none mb-3" role="alert">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
{% translate "Aucune intervention sélectionnée : toutes les interventions filtrées seront incluses dans le rapport." %}
|
||
</div>
|
||
<div id="execution-report-too-many" class="alert alert-warning d-none mb-3" role="alert">
|
||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||
{% translate "Vous avez sélectionné plus de 50 interventions. Veuillez réduire votre sélection avant de générer le rapport." %}
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="execution-report-title" class="form-label fw-semibold">{% translate "Titre du rapport" %}</label>
|
||
<input type="text" class="form-control" id="execution-report-title"
|
||
placeholder="{% translate 'Ex : Rapport mensuel – Voirie avril 2026' %}" maxlength="120">
|
||
<div class="form-text">{% translate "Optionnel. Apparaîtra en tête avant le sommaire." %}</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold">{% translate "Période (changement de statut)" %}</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text">{% translate "Du" %}</span>
|
||
<input type="date" class="form-control" id="execution-report-date-from">
|
||
<span class="input-group-text">{% translate "Au" %}</span>
|
||
<input type="date" class="form-control" id="execution-report-date-to">
|
||
</div>
|
||
<div class="form-text">{% translate "Optionnel. Filtre sur la date de changement de statut de l'intervention." %}</div>
|
||
</div>
|
||
<p class="small text-muted mb-0">{% translate "Le rapport reprendra les interventions correspondant aux filtres actifs." %}</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
|
||
<button type="button" class="btn btn-primary" id="confirm-execution-report-btn">
|
||
<i class="bi bi-file-earmark-pdf me-1"></i>{% translate "Télécharger le PDF" %}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Overlay de progression pour la génération du rapport d'exécution -->
|
||
<div id="execution-report-progress-overlay"
|
||
class="position-fixed top-0 start-0 w-100 h-100"
|
||
style="display:none; background:rgba(0,0,0,.55); z-index:9050; align-items:center; justify-content:center;">
|
||
<div class="card shadow-lg border-0 rounded-3" style="max-width:440px; width:90%;">
|
||
<div class="card-body text-center p-4 p-md-5">
|
||
<i class="bi bi-file-earmark-pdf text-primary mb-3" style="font-size:2.75rem;"></i>
|
||
<h6 class="fw-semibold mb-1">{% translate "Génération du rapport en cours…" %}</h6>
|
||
<p id="execution-report-progress-label" class="text-muted small mb-3"> </p>
|
||
<div class="progress rounded-pill mb-1" style="height:12px;">
|
||
<div id="execution-report-progress-bar"
|
||
class="progress-bar bg-primary"
|
||
role="progressbar"
|
||
style="width:0%; transition:width .4s ease;"
|
||
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||
</div>
|
||
<p id="execution-report-progress-pct" class="text-muted small mb-0">0 %</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 1100">
|
||
<div id="app-toast" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="polite" aria-atomic="true">
|
||
<div class="d-flex">
|
||
<div id="app-toast-body" class="toast-body">
|
||
<!-- Message injecté dynamiquement -->
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Fermer"></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock content%}
|
||
|
||
|
||
{% block script %}
|
||
<!-- Mapbox GL Draw 1.5.0 (local) -->
|
||
<script src="{% static 'common/mapbox-gl-draw.js' %}"></script>
|
||
<!-- Turf.js (local) -->
|
||
<script src="{% static 'common/turf.min.js' %}"></script>
|
||
|
||
<!-- jQuery UI 1.14.1 (local) -->
|
||
<link rel="stylesheet" href="{% static 'common/jquery-ui.min.css' %}">
|
||
<script src="{% static 'common/jquery-ui.min.js' %}"></script>
|
||
|
||
<script>
|
||
const interventionsgeojsonUrl = "{% url 'interventions:interventions_geojson' %}";
|
||
const interventionsurl = "{% url 'interventions:interventions_index' %}";
|
||
const locationsAutocompleteUrl = "{% url 'assets:locations_autocomplete' %}";
|
||
const availableOrdersUrl = "{% url 'interventions:available_orders' %}";
|
||
const bulkAssociateOrderUrl = "{% url 'interventions:bulk_associate_order' %}";
|
||
const exportInterventionsUrl = "{% url 'interventions:export_interventions' %}";
|
||
const exportSheetsZipUrl = "{% url 'interventions:export_intervention_sheets_zip' %}";
|
||
const executionReportUrl = "{% url 'interventions:bulk_execution_report_pdf' %}";
|
||
const executionReportStartUrl = "{% url 'interventions:start_bulk_execution_report' %}";
|
||
const executionReportPollBaseUrl = "{% url 'interventions:poll_bulk_execution_report' token='__TOKEN__' %}";
|
||
const executionReportDownloadBaseUrl = "{% url 'interventions:download_bulk_execution_report' token='__TOKEN__' %}";
|
||
const saveLayoutUrl = "{% url 'common:save_intervention_list_layout' %}";
|
||
const savePreferencesUrl = "{% url 'common:save_intervention_list_preferences' %}";
|
||
|
||
var selected_interventions_set = new Set(JSON.parse('{{ selected_interventions_ids|default:"[]"|escapejs }}'));
|
||
const PRESELECTED_LOCATIONS = JSON.parse('{{ preselected_locations|default:"[]"|escapejs }}');
|
||
const INITIAL_LAYOUT = '{{ intervention_list_layout|default:"both" }}';
|
||
const SAVED_SORT = {{ saved_sort|default:"{}"|safe }};
|
||
const SAVED_COLUMNS = {{ saved_columns|default:"null"|safe }};
|
||
const subcatNoneLabel = "{% translate '(aucune sous-catégorie)' %}";
|
||
</script>
|
||
|
||
|
||
<script src="{% url 'javascript-catalog' %}"></script>
|
||
<script src="{% static 'common/base_script.js' %}"></script>
|
||
<script src="{% static 'common/base_map.js' %}"></script>
|
||
<script src="{% static 'interventions/status_colors.js' %}"></script>
|
||
<script src="{% static 'interventions/interventions_index.js' %}"></script>
|
||
{% endblock script %}
|
||
|