loko/streetup/inspections/templates/inspections/quick_inspection_map.html

470 lines
25 KiB
HTML

{% extends base_template|default:"base.html" %}
{% load i18n static %}
{% block title %}{% translate "Inspection Rapide Carte GPS" %}{% endblock %}
{% block extra_head %}
<link href="{% static 'common/maplibre-gl.css' %}" rel="stylesheet">
<style>
body {
padding-bottom: 0 !important;
{% if is_mobile %}
padding-top: 54px !important;
{% else %}
padding-top: 0 !important;
{% endif %}
}
main.container, main {
margin: 0 !important;
padding: 0 !important;
max-width: 100% !important;
width: 100% !important;
}
footer {
display: none !important;
}
#inspection-map-wrapper {
position: relative;
height: calc(100vh - 54px);
min-height: 500px;
width: 100%;
overflow: hidden;
}
#inspection-map {
min-height: 500px;
height: 100%;
width: 100%;
z-index: 1;
}
.map-controls-overlay {
position: absolute;
top: 6px;
left: 8px;
right: 8px;
z-index: 1000;
pointer-events: none;
}
.map-controls-overlay > * {
pointer-events: auto;
}
/* Transition fluide pour le repositionnement dynamique des contrôles MapLibre */
#inspection-map .maplibregl-ctrl-top-left,
#inspection-map .maplibregl-ctrl-top-right {
transition: margin-top 0.15s ease-out;
}
.map-floating-actions {
position: absolute;
bottom: 24px;
right: 16px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 8px;
}
.user-gps-dot {
width: 16px;
height: 16px;
background-color: #0d6efd;
border: 2px solid #ffffff;
border-radius: 50%;
box-shadow: 0 0 10px rgba(13, 110, 253, 0.8);
}
/* Bottom Sheet Modal Styling for Mobile */
.modal-bottom-sheet .modal-dialog {
margin: 0;
position: fixed;
bottom: 0;
left: 0;
right: 0;
max-width: 100%;
}
.modal-bottom-sheet .modal-content {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-top-left-radius: 1.25rem;
border-top-right-radius: 1.25rem;
border: none;
max-height: 85vh;
display: flex;
flex-direction: column;
}
.modal-bottom-sheet .modal-body {
max-height: 70vh;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
.thematic-tile {
border: 2px solid #e9ecef;
border-radius: 0.85rem;
padding: 0.75rem 1rem;
transition: all 0.2s ease;
cursor: pointer;
background: #ffffff;
}
.thematic-tile.active {
border-color: #0d6efd;
background-color: rgba(13, 110, 253, 0.06);
}
.thematic-tile-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
flex-shrink: 0;
}
.filter-btn-pill {
background-color: #ffffff;
border: 1px solid #dee2e6;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
border-radius: 50rem;
padding: 0.4rem 0.85rem;
font-size: 0.85rem;
transition: all 0.15s ease;
}
.filter-btn-pill:active {
transform: scale(0.97);
}
.maplibregl-popup {
max-width: 330px !important;
}
.maplibregl-popup-content {
border-radius: 0.75rem !important;
padding: 0.75rem !important;
box-shadow: 0 4px 18px rgba(0,0,0,0.18) !important;
}
#mapInspectionPhotosCarousel .carousel-control-prev,
#mapInspectionPhotosCarousel .carousel-control-next,
#fullscreenPhotoCarousel .carousel-control-prev,
#fullscreenPhotoCarousel .carousel-control-next {
width: 10% !important;
pointer-events: none;
}
#mapInspectionPhotosCarousel .carousel-control-prev-icon,
#mapInspectionPhotosCarousel .carousel-control-next-icon,
#fullscreenPhotoCarousel .carousel-control-prev-icon,
#fullscreenPhotoCarousel .carousel-control-next-icon {
pointer-events: auto;
}
</style>
{% endblock %}
{% block content %}
<div id="inspection-map-wrapper">
<!-- Overlay des filtres mémorisés (Boutons tactiles mobiles) -->
<div class="map-controls-overlay">
<div class="card shadow-sm border-0 bg-white opacity-95 rounded-4 overflow-hidden">
<div class="card-body p-2">
<div class="row g-2 align-items-center">
<!-- Bloc titre -->
<div class="col-12 col-md-4 d-flex align-items-center gap-1.5 justify-content-center justify-content-md-start mb-1 mb-md-0">
<i class="bi bi-geo-alt-fill text-primary fs-6"></i>
<span class="fw-bold small me-1">{% translate "Inspection Rapide" %}</span>
<span class="badge bg-light text-muted border small" id="gps-status-text">{% translate "GPS actif" %}</span>
</div>
<!-- Bloc filtres -->
<div class="col-12 col-md-8">
<div class="row g-2 justify-content-md-end">
<div class="col-6 col-md-auto">
<!-- Bouton Déclencheur Thématique -->
<button id="btn-select-thematic" type="button" class="btn filter-btn-pill w-100 d-flex align-items-center justify-content-between gap-1.5" data-bs-toggle="modal" data-bs-target="#thematicChooserModal">
<span class="text-truncate">
<i id="thematic-icon-display" class="bi bi-collection text-primary me-2"></i>
<span id="thematic-name-display" class="fw-semibold">{% translate "Thématique" %}</span>
</span>
<i class="bi bi-chevron-down text-muted ms-1 small"></i>
</button>
</div>
<div class="col-6 col-md-auto">
<!-- Bouton Déclencheur Catégorie -->
<button id="btn-select-category" type="button" class="btn filter-btn-pill w-100 d-flex align-items-center justify-content-between gap-1.5" data-bs-toggle="modal" data-bs-target="#categoryChooserModal">
<span class="text-truncate">
<i class="bi bi-tag-fill text-secondary me-1.5"></i>
<span id="category-name-display" class="fw-semibold">{% translate "Catégorie" %}</span>
</span>
<i class="bi bi-chevron-down text-muted ms-1 small"></i>
</button>
</div>
<div class="col-12 col-md-auto">
<!-- Bouton Déclencheur Historique Inspections -->
<button id="btn-toggle-inspections" type="button" class="btn filter-btn-pill w-100 d-flex align-items-center justify-content-between gap-1.5" data-bs-toggle="modal" data-bs-target="#inspectionsHistoryModal">
<span class="text-truncate">
<i id="inspections-history-icon" class="bi bi-clock-history text-primary me-1"></i>
<span id="inspections-history-label" class="fw-semibold">{% translate "Inspections (3 mois)" %}</span>
</span>
<i class="bi bi-chevron-down text-muted ms-1 small"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Sélecteurs cachés synchronisés avec le JS d'inspection -->
<select id="filter-thematic" class="d-none">
<option value="">{% translate "-- Thématique --" %}</option>
{% for t in thematics %}
<option value="{{ t.code }}" data-icon="{{ t.icon|default:'bi-folder2' }}" data-name="{{ t.name }}" {% if t.code == 'parking' %}selected{% endif %}>{{ t.name }}</option>
{% endfor %}
</select>
<select id="filter-category" class="d-none">
<option value="">{% translate "-- Catégorie --" %}</option>
{% for c in categories %}
<option value="{{ c.id }}" data-thematic="{{ c.thematic.code }}" data-name="{{ c.get_name }}">{{ c.get_name }}</option>
{% endfor %}
</select>
<div id="map-warning-alert" class="alert alert-warning alert-dismissible fade show d-none shadow-sm py-2 px-3 mt-2 mb-0 small" role="alert">
<i class="bi bi-exclamation-triangle-fill me-1 text-warning"></i>
<span id="map-warning-text">{% translate "Veuillez zoomer sur la carte pour afficher les équipements." %}</span>
<button type="button" class="btn-close py-2" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
<!-- Carte MapLibre -->
<div id="inspection-map"></div>
<!-- Actions Flottantes (Bouton GPS) -->
<div class="map-floating-actions">
<button id="btn-gps-locate" class="btn btn-white btn-lg shadow rounded-circle border" title="{% translate 'Ma position GPS' %}">
<i class="bi bi-crosshair text-primary fs-5"></i>
</button>
</div>
</div>
<!-- Modal Choix de Thématique (Style Mobile Bottom Sheet) -->
<div class="modal fade modal-bottom-sheet" id="thematicChooserModal" tabindex="-1" aria-labelledby="thematicChooserModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header border-0 pb-1">
<h6 class="modal-title fw-bold" id="thematicChooserModalLabel">
<i class="bi bi-layers-fill text-primary me-1"></i>{% translate "Choisir une thématique" %}
</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body p-3">
<div class="row g-2" id="thematic-tiles-container">
{% for t in thematics %}
<div class="col-12 col-sm-6">
<div class="thematic-tile d-flex align-items-center gap-3 {% if t.code == 'parking' %}active{% endif %}" data-code="{{ t.code }}" data-icon="{{ t.icon|default:'bi-folder2' }}" data-name="{{ t.name }}" data-bs-dismiss="modal">
<div class="thematic-tile-icon" style="background-color: {{ t.color|default:'#0d6efd' }}15; color: {{ t.color|default:'#0d6efd' }};">
<i class="bi {{ t.icon|default:'bi-folder2' }}"></i>
</div>
<div class="flex-grow-1">
<h6 class="mb-0 fw-bold small text-dark">{{ t.name }}</h6>
<small class="text-muted" style="font-size: 0.75rem;">{{ t.description|default:""|truncatechars:45 }}</small>
</div>
<i class="bi bi-check-circle-fill text-primary fs-5 thematic-check d-none"></i>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<!-- Modal Choix de Catégorie (Style Mobile Bottom Sheet) -->
<div class="modal fade modal-bottom-sheet" id="categoryChooserModal" tabindex="-1" aria-labelledby="categoryChooserModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header border-0 pb-1">
<h6 class="modal-title fw-bold" id="categoryChooserModalLabel">
<i class="bi bi-tags-fill text-primary me-1"></i>{% translate "Filtrer par catégorie" %}
</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body p-3">
<div class="list-group list-group-flush" id="category-list-container">
<button type="button" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2.5 px-3 rounded-3 mb-1 border-0 category-item active" data-id="" data-name="{% translate 'Toutes les catégories' %}" data-bs-dismiss="modal">
<span class="fw-semibold small">{% translate "Toutes les catégories" %}</span>
<i class="bi bi-check-lg text-primary category-check"></i>
</button>
{% for c in categories %}
<button type="button" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2.5 px-3 rounded-3 mb-1 border-0 category-item" data-id="{{ c.id }}" data-thematic="{{ c.thematic.code }}" data-name="{{ c.get_name }}" data-bs-dismiss="modal">
<span class="fw-semibold small text-dark">{{ c.get_name }}</span>
<i class="bi bi-check-lg text-primary category-check d-none"></i>
</button>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<!-- Modal Choix Historique Inspections (Style Mobile Bottom Sheet) -->
<div class="modal fade modal-bottom-sheet" id="inspectionsHistoryModal" tabindex="-1" aria-labelledby="inspectionsHistoryModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header border-0 pb-1">
<h6 class="modal-title fw-bold" id="inspectionsHistoryModalLabel">
<i class="bi bi-clock-history text-primary me-1"></i>{% translate "Historique des inspections sur la carte" %}
</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body p-3">
<p class="text-muted small mb-3">
{% translate "Choisissez la durée d'affichage des contrôles et inspections effectués dans la zone." %}
</p>
<div class="list-group list-group-flush" id="inspections-period-options">
<button type="button" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2.5 px-3 rounded-3 mb-1 border-0 insp-period-item" data-period="disabled" data-bs-dismiss="modal">
<span class="fw-semibold small text-danger"><i class="bi bi-eye-slash me-2"></i>{% translate "Désactiver l'affichage" %}</span>
<i class="bi bi-check-lg text-primary period-check d-none"></i>
</button>
<button type="button" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2.5 px-3 rounded-3 mb-1 border-0 insp-period-item" data-period="1m" data-bs-dismiss="modal">
<span class="fw-semibold small text-dark"><i class="bi bi-calendar-check me-2 text-primary"></i>{% translate "1 mois" %}</span>
<i class="bi bi-check-lg text-primary period-check d-none"></i>
</button>
<button type="button" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2.5 px-3 rounded-3 mb-1 border-0 insp-period-item active" data-period="3m" data-bs-dismiss="modal">
<span class="fw-semibold small text-dark"><i class="bi bi-calendar-check me-2 text-primary"></i>{% translate "3 mois (par défaut)" %}</span>
<i class="bi bi-check-lg text-primary period-check"></i>
</button>
<button type="button" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2.5 px-3 rounded-3 mb-1 border-0 insp-period-item" data-period="6m" data-bs-dismiss="modal">
<span class="fw-semibold small text-dark"><i class="bi bi-calendar-check me-2 text-primary"></i>{% translate "6 mois" %}</span>
<i class="bi bi-check-lg text-primary period-check d-none"></i>
</button>
<button type="button" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2.5 px-3 rounded-3 mb-1 border-0 insp-period-item" data-period="1y" data-bs-dismiss="modal">
<span class="fw-semibold small text-dark"><i class="bi bi-calendar-check me-2 text-primary"></i>{% translate "1 an" %}</span>
<i class="bi bi-check-lg text-primary period-check d-none"></i>
</button>
<button type="button" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-2.5 px-3 rounded-3 mb-1 border-0 insp-period-item" data-period="all" data-bs-dismiss="modal">
<span class="fw-semibold small text-dark"><i class="bi bi-calendar-range me-2 text-primary"></i>{% translate "Tout (historique complet)" %}</span>
<i class="bi bi-check-lg text-primary period-check d-none"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal Détail d'une Inspection depuis la carte -->
<div class="modal fade" id="mapInspectionDetailModal" tabindex="-1" aria-labelledby="mapInspectionDetailModalLabel" aria-hidden="true" style="z-index: 1065;">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold" id="mapInspectionDetailModalLabel">
<i class="bi bi-clock-history me-2 text-primary"></i>{% translate "Détail de l'inspection" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-3">
<!-- Lien vers la fiche de l'asset -->
<div class="mb-3" id="map-insp-asset-link-container">
<a href="#" id="map-insp-asset-link" class="btn btn-primary w-100 fw-semibold">
<i class="bi bi-box-arrow-up-right me-2"></i>{% translate "Consulter la fiche de l'asset" %}
</a>
</div>
<ul class="list-group list-group-flush mb-3">
<li class="list-group-item d-flex justify-content-between align-items-center px-0 py-2">
<span class="text-muted"><i class="bi bi-geo-alt me-1"></i>{% translate "Cible" %}</span>
<span class="fw-bold text-end ms-2" id="map-insp-target-display">-</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-0 py-2">
<span class="text-muted"><i class="bi bi-calendar-event me-1"></i>{% translate "Date & Heure" %}</span>
<span class="fw-semibold" id="map-insp-date">-</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-0 py-2">
<span class="text-muted"><i class="bi bi-shield-check me-1"></i>{% translate "Résultat du contrôle" %}</span>
<span class="badge bg-primary" id="map-insp-status-badge">-</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-0 py-2">
<span class="text-muted"><i class="bi bi-eye me-1"></i>{% translate "Nature" %}</span>
<span class="badge bg-light text-dark border" id="map-insp-nature">-</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-0 py-2">
<span class="text-muted"><i class="bi bi-person me-1"></i>{% translate "Inspecteur" %}</span>
<span class="fw-semibold" id="map-insp-inspector">-</span>
</li>
</ul>
<!-- Remarques & Constats -->
<div class="card bg-light border-0 p-3 mb-3">
<div class="fw-semibold small text-dark mb-1"><i class="bi bi-card-text me-1 text-primary"></i>{% translate "Constats & Remarques" %}</div>
<div class="small text-muted" id="map-insp-summary">-</div>
<div class="small text-dark mt-2 d-none" id="map-insp-notes"></div>
</div>
<!-- Carrousel Photos si images présentés -->
<div id="mapInspectionPhotosCarouselContainer" class="d-none mb-3">
<div class="fw-semibold small text-dark mb-2">
<i class="bi bi-images me-1 text-primary"></i>{% translate "Galerie photos" %} (<span id="map-insp-photos-count">0</span>)
</div>
<div id="mapInspectionPhotosCarousel" class="carousel slide rounded overflow-hidden shadow-sm bg-black" data-bs-ride="false">
<div class="carousel-indicators mb-1" id="map-insp-carousel-indicators"></div>
<div class="carousel-inner" id="map-insp-carousel-inner"></div>
<button class="carousel-control-prev" type="button" data-bs-target="#mapInspectionPhotosCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon p-2 rounded-circle bg-dark bg-opacity-50" aria-hidden="true"></span>
<span class="visually-hidden">Précédent</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#mapInspectionPhotosCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon p-2 rounded-circle bg-dark bg-opacity-50" aria-hidden="true"></span>
<span class="visually-hidden">Suivant</span>
</button>
</div>
</div>
<!-- Section Documents non-images -->
<div id="map-insp-docs-section" class="d-none mb-2">
<div class="fw-semibold small text-dark mb-2">
<i class="bi bi-file-earmark-text me-1 text-primary"></i>{% translate "Documents joints" %}
</div>
<div class="d-flex flex-wrap gap-2" id="map-insp-docs-container"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Overlay Carrousel Plein Écran pour Photos (avec bouton fermer) -->
<div id="fullscreenPhotoOverlay" class="d-none position-fixed top-0 start-0 w-100 h-100 bg-black" style="z-index: 999999;">
<!-- Bouton Fermer -->
<button type="button" class="btn-close btn-close-white position-absolute top-0 end-0 m-3 fs-4 shadow p-2" id="btn-close-photo-overlay" style="z-index: 1000005; cursor: pointer;" aria-label="Fermer"></button>
<!-- Conteneur Carrousel -->
<div id="fullscreenPhotoCarousel" class="carousel slide w-100 h-100" data-bs-ride="false">
<div class="carousel-indicators mb-3" id="fullscreen-carousel-indicators" style="z-index: 1000001;"></div>
<div class="carousel-inner w-100 h-100" id="fullscreen-carousel-inner"></div>
<button class="carousel-control-prev" type="button" data-bs-target="#fullscreenPhotoCarousel" data-bs-slide="prev" style="z-index: 1000000;">
<span class="carousel-control-prev-icon p-3 rounded-circle bg-dark bg-opacity-75" aria-hidden="true"></span>
<span class="visually-hidden">Précédent</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#fullscreenPhotoCarousel" data-bs-slide="next" style="z-index: 1000000;">
<span class="carousel-control-next-icon p-3 rounded-circle bg-dark bg-opacity-75" aria-hidden="true"></span>
<span class="visually-hidden">Suivant</span>
</button>
</div>
</div>
<!-- Inclure la Modale d'inspection -->
{% include "inspections/components/inspection_form_modal.html" %}
{% endblock %}
{% block footer %}{% endblock %}
{% block extra_js %}
<script src="{% static 'common/layer-switcher-control.js' %}"></script>
<script src="{% static 'common/address_search_control.js' %}"></script>
<script>
window.AUTOCOMPLETE_STREET_URL = "{% url 'common:autocomplete_street' %}";
window.PARKING_SPOT_CT_ID = {{ parking_spot_ct_id|default:"null" }};
window.PRESELECT_CONTENT_TYPE_ID = {{ preselect_ct_id|safe }};
window.PRESELECT_OBJECT_ID = {{ preselect_obj_id|safe }};
window.PRESELECT_LON = {{ preselect_lon|safe }};
window.PRESELECT_LAT = {{ preselect_lat|safe }};
window.PRESELECT_THEMATIC_CODE = {{ preselect_thematic_code|safe }};
window.PRESELECT_CATEGORY_ID = {{ preselect_category_id|safe }};
</script>
<script src="{% static 'assets/inspection_map.js' %}"></script>
{% endblock %}