loko/streetup/assets/templates/assets/trafficlights_intersection_detail.html
2026-07-22 14:48:40 +02:00

1387 lines
95 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% load static %}
{% load tz %}
{% load i18n %}
{% load documents_tags %}
{% load asset_tags %}
{% load document_viewer_tags %}
{% block content %}
<style>
/* Pull top-right controls to the top and leave margin below orthophotos button */
#map .maplibregl-ctrl-top-right {
top: 10px !important;
}
.layer-switcher-ctrl {
margin-bottom: 15px !important;
}
</style>
<div class="row mt-2" style="height: calc(100vh - 200px);">
<div class="col-md-8" style="max-height: calc(100vh - 200px); overflow: auto;">
<div class="d-flex align-items-center justify-content-between mb-0">
<h3 class="h3 mb-0">
<i class="bi bi-sign-stop-lights"></i><span class="mx-2">{{ intersection.code}}</span> - <span class="mx-2">{{ intersection.get_name }}</span>
</h3>
{% if can_edit_location %}
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-primary"
data-location-edit="{{ intersection.pk }}"
title="{% translate 'Modifier cette intersection' %}">
<i class="bi bi-pencil-square"></i>
</button>
</div>
{% endif %}
</div>
<hr>
<div class="row mb-4">
<div class="col-md-8">
<label class="mx-2">{% translate "Statut" %}:</label><i class="bi bi-circle-fill status_{{ intersection.status }}"></i> {{ intersection.get_status_display }}<br>
<label class="mx-2">{% translate "Tension (Voltage)" %}:</label>{{ intersection.get_voltage_type_display }}<br>
<div class="my-2">
<label class="mx-2">{% translate "Connexion centrale" %}:</label>
{% if intersection.is_connected_to_control_center %}
<i class="bi bi-check-square"></i>
{% elif intersection.is_connected_to_control_center == False %}
<i class="bi bi-x-square"></i>
{% else %}
<i class="bi bi-question-square"></i>
{% endif %}
<br>
<label class="mx-2">{% translate "Index" %}:</label>{{ intersection.current_index|default:""}}
{% if intersection.master %}
<br>
<label class="mx-2">{% translate "Carrefour principal" %}:</label><a href="{% url 'assets:trafficlights_intersections_detail' intersection.master.id %}">{{ intersection.master.code|default:"/" }}</a>
{% endif %}
</div>
{% if can_edit_location %}
<div class="my-2 d-flex align-items-start flex-wrap gap-1">
<label class="mx-2 me-1">{% translate "Contrats" %}:</label>
<span class="contract-badges-container d-flex flex-wrap gap-1 align-items-center">
{% if trafficlight_contracts %}
{% for tlc in trafficlight_contracts %}
<span class="badge bg-secondary me-1" title="{{ tlc.get_maintenance_type_display }} — {{ tlc.get_status_display }}">
{{ tlc.contract.contract_number }}
<span class="text-light opacity-75 ms-1">({{ tlc.get_maintenance_type_display }})</span>
</span>
{% endfor %}
{% else %}
<span class="text-muted fst-italic">{% translate "Aucun" %}</span>
{% endif %}
</span>
<button class="btn btn-sm btn-link p-0 ms-1" data-bs-toggle="modal" data-bs-target="#contractsEditModal"
title="{% translate 'Modifier les contrats' %}">
<i class="bi bi-pencil-square text-primary"></i>
</button>
</div>
{% endif %}
</div>
<div class="col-md-4">
<label class="me-2 mb-2">{% translate "Commune" %}:</label>{{ intersection.municipality }}<br>
{% if intersection.lon %}
<a href="https://www.google.com/maps?cbll={{ intersection.lat|floatformat:'6u'}},{{ intersection.lon|floatformat:'6u'}}&cbp=12,0,0,0,5&layer=c&panoid=" target="_blank" style="text-decoration: none; font-size: 16px; display: flex; align-items: center;">
<img src="{% static 'common/google-street-view-logo.png' %}" alt="{% translate 'Street View' %}" height="20" style="margin-right: 8px;">
{% translate "View in Google Street View" %}
</a>
{% endif %}
</div>
</div>
{% if staged_assets_count > 0 %}
<div class="card border-warning mb-4 shadow-sm" style="background: linear-gradient(135deg, #fffbeb, #fef3c7); border-left: 5px solid #f59e0b;">
<div class="card-body">
<h5 class="card-title text-warning-emphasis d-flex align-items-center gap-2 font-weight-bold">
<i class="bi bi-exclamation-triangle-fill text-warning fs-4"></i>
{% translate "Assets importés par DXF en attente de validation" %}
</h5>
<p class="card-text text-secondary mb-3">
{% blocktranslate count count=staged_assets_count %}
Il y a <strong>{{ count }}</strong> poteau importé depuis un plan DXF qui est en attente de validation pour ce carrefour.
{% plural %}
Il y a <strong>{{ count }}</strong> poteaux importés depuis un plan DXF qui sont en attente de validation pour ce carrefour.
{% endblocktranslate %}
</p>
<!-- Date of installation selection -->
<div class="mb-3 d-flex align-items-center gap-2" style="max-width: 320px;">
<label for="installation_date_picker" class="form-label font-weight-semibold text-dark small mb-0 flex-shrink-0">
<i class="bi bi-calendar-event text-primary me-1"></i>
{% translate "Date d'installation :" %}
</label>
<input type="date" id="installation_date_picker" class="form-control form-control-sm" value="{{ today_date }}">
</div>
<div class="d-flex flex-wrap gap-2 align-items-center">
<form method="post" action="{% url 'assets:integrate_dxf' intersection.id %}" class="d-inline integrate-form">
{% csrf_token %}
<input type="hidden" name="choice" value="choice_a">
<input type="hidden" name="installation_date" class="form-installation-date" value="{{ today_date }}">
<button type="submit" class="btn btn-warning btn-sm font-weight-semibold shadow-sm text-dark px-3 py-1.5 rounded-2">
<i class="bi bi-arrow-repeat me-1"></i> {% translate "Option A : Remplacer par code et créer les nouveaux" %}
</button>
</form>
<form method="post" action="{% url 'assets:integrate_dxf' intersection.id %}" class="d-inline integrate-form">
{% csrf_token %}
<input type="hidden" name="choice" value="choice_b">
<input type="hidden" name="installation_date" class="form-installation-date" value="{{ today_date }}">
<button type="submit" class="btn btn-outline-danger btn-sm font-weight-semibold px-3 py-1.5 rounded-2" onclick="return confirm('{% translate "Êtes-vous sûr de vouloir archiver TOUS les poteaux existants de ce carrefour et d\'activer uniquement les nouveaux poteaux importés ?" %}');">
<i class="bi bi-trash-fill me-1"></i> {% translate "Option B : Remplacement complet (Archiver tout le reste)" %}
</button>
</form>
<a href="{% url 'assets:import_dxf' intersection.id %}" class="btn btn-outline-secondary btn-sm ms-auto px-3 py-1.5 rounded-2">
<i class="bi bi-pencil-square"></i> {% translate "Réimporter / Corriger" %}
</a>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const datePicker = document.getElementById('installation_date_picker');
if (datePicker) {
datePicker.addEventListener('change', function() {
document.querySelectorAll('.form-installation-date').forEach(input => {
input.value = datePicker.value;
});
});
}
});
</script>
{% endif %}
<!-- Documents -->
<div class="card mb-4" style="border-color: #b2d8d1;">
<div class="card-header d-flex justify-content-between align-items-center" style="background: linear-gradient(to left, #d0eeea, #f5fcfb); border-color: #b2d8d1;">
<h4>{% translate "Documents" %}</h4>
<button onclick="" class="btn btn-light border-0" title="{% translate 'Ajouter' %}" data-bs-toggle="modal" data-bs-target="#addDocumentModal">
<i class="bi bi-plus-circle text-primary"></i>
</button>
</div>
<div class="card-body">
{% if can_browse_folder and document_folder %}
<div class="row">
<div class="col-md-12 mb-3">
<label class="mx-2">{% translate "Répertoire principal" %}:</label>
<span class="mx-2"><a href="/documents/?folder={{ document_folder.slug }}">{{ intersection.code }}</a></span>
</div>
</div>
{% endif %}
{# ── Document grid ── #}
{% if use_managed_documents %}
{# Nouveau système : le composant gère sa propre grille + modale visionneuse #}
{% render_document_viewer main_documents modal_id="tliDocViewerModal" show_tags=True show_versions=True location_id=intersection.id %}
{% else %}
{# Ancien système - TrafficLightIntersectionDocument #}
<div class="row">
{% for document in main_documents %}
{% if document %}
<div class="col-md-3 mb-3">
<div class="card h-100 shadow-sm border rounded-3">
<div class="d-flex">
<!-- Colonne gauche : contenu document -->
<div class="flex-grow-1 p-2" style="min-width:0; overflow:hidden;">
<div class="d-flex flex-column align-items-center mb-2">
<!-- Badge centré -->
<div class="text-muted" style="font-size: 0.85em;">
<span class="badge bg-primary">{{ document.get_document_type_display }}</span>
</div>
<!-- Image -->
<a href="{{ document.file.url }}" target="_blank" class="text-decoration-none w-100" title="{{ document.filename }}">
<div class="img-thumbnail d-flex justify-content-center align-items-center mt-1" style="width: 100%; height: 100px; overflow: hidden; background-color: #f8f9fa;">
{% if document.thumbnail %}
<img src="{{ document.thumbnail.url }}" alt="{{ document.filename }}"
style="max-height: 100%; max-width: 100%; object-fit: contain;">
{% else %}
<i class="{{ document.filename|file_icon_class }}" style="font-size: 2.5rem;"></i>
{% endif %}
</div>
</a>
</div>
<div class="text-muted text-start" style="font-size: 0.85em;">
{{ document.upload_date|timezone:"Europe/Paris"|date:"d/m/Y" }}<br>
{{ document.uploaded_by.username }}
</div>
</div>
<!-- Colonne droite : icônes -->
<div class="d-flex flex-column justify-content-end align-items-center p-2" style="width: 40px; min-height: 100%;">
<!-- Historique -->
<button class="btn btn-sm p-0 border-0 bg-transparent mb-2"
data-bs-toggle="modal"
data-bs-target="#documentHistoryModal"
data-document-type="{{ document.document_type }}"
data-location-id="{{ intersection.id }}"
title="{% translate 'Historique' %}">
<i class="bi bi-clock-history text-primary"></i>
</button>
</div>
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
<!-- Données JSON pour JavaScript -->
<script type="application/json" id="documentConfigData">
{
"use_managed_documents": {% if use_managed_documents %}true{% else %}false{% endif %},
"can_add_unique_tag_documents": {% if can_add_unique_tag_documents %}true{% else %}false{% endif %},
"config_url": "{% url 'assets:trafficlights_intersections_document_config' intersection.id %}"
}
</script>
<script type="application/json" id="georeferencedDocumentsData">
{{ georeferenced_documents_json|safe }}
</script>
<!-- Modal form add document -->
<div class="modal fade" id="addDocumentModal" tabindex="-1" aria-labelledby="addDocumentModalLabel" aria-hidden="true" style="z-index: 10050;">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addDocumentModalLabel">{% translate "Ajouter des documents" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
</div>
<div class="modal-body">
<form id="addDocumentForm" method="post" enctype="multipart/form-data" action="{% url 'assets:trafficlights_intersections_add_document' intersection.id %}">
{% csrf_token %}
<div class="mb-3">
<label for="fileInput" class="form-label">{% translate "Sélectionner des fichiers" %}</label>
<input type="file" id="fileInput" name="files" multiple class="form-control">
</div>
<div id="fileFieldsContainer">
<!-- Champs dynamiques injectés ici -->
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" id="documentSubmitButton">
{% translate "Ajouter" %}
<span class="spinner-border spinner-border-sm ms-2 d-none" role="status" aria-hidden="true" id="documentSubmitSpinner"></span>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Modal form delete document -->
<div class="modal fade" id="documentDeleteModal" tabindex="-1" aria-labelledby="documentDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="documentDeleteModalLabel">{% translate "Confirmer la suppression" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
</div>
<div class="modal-body">
<p>{% translate "Voulez-vous vraiment supprimer ce document ?" %}</p>
<p><strong id="deleteDocumentFileName">{% translate "Nom du fichier" %}</strong></p>
</div>
<div class="modal-footer">
<form method="post" id="deleteDocumentForm" action="">
{% csrf_token %}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
<button type="submit" class="btn btn-danger">{% translate "Supprimer" %}</button>
</form>
</div>
</div>
</div>
</div>
<!-- Modal for document history -->
<div class="modal fade" id="documentHistoryModal" tabindex="-1" aria-labelledby="documentHistoryModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="documentHistoryModalLabel">{% translate "Historique des documents" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
</div>
<div class="modal-body">
<div id="documentHistoryContent" class="row g-3">
<!-- Contenu dynamique injecté ici -->
</div>
</div>
</div>
</div>
</div>
<div class="card mb-4" id="location-detail-container"
style="border-color: #cce0ff;"
data-thematic-code="{{ thematic_code }}"
data-location-type="{{ location_type }}"
data-location-id="{{ intersection.id }}">
<div class="card-header d-flex justify-content-between align-items-center" style="background: linear-gradient(to left, #c8dffe, #f0f8ff); border-color: #cce0ff;">
<h4 data-bs-toggle="collapse" href="#assets_card_body" role="button" class="mb-0" style="color: #1e40af;"><i class="bi bi-boxes me-2"></i>{% translate "Assets" %}</h4>
{% if allowed_location_actions %}
<div class="d-flex gap-2">
{% if 'create' in allowed_location_actions %}
<button id="add-asset-btn" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#addAssetModal" title="{% translate 'Ajouter un asset' %}">
<i class="bi bi-plus-circle"></i> {% translate "Ajouter" %}
</button>
{% endif %}
{% if can_edit_location %}
<a href="{% url 'assets:import_dxf' intersection.id %}" class="btn btn-sm btn-outline-success" title="{% translate 'Importer plan DXF' %}">
<i class="bi bi-file-earmark-arrow-up"></i> {% translate "Importer plan DXF" %}
</a>
<a href="{% url 'assets:import_cross_plan' intersection.id %}" class="btn btn-sm btn-outline-info" title="{% translate 'Importer plan de croix' %}">
<i class="bi bi-file-earmark-excel"></i> {% translate "Importer plan de croix" %}
</a>
{% endif %}
{% if 'bulk_archive' in allowed_location_actions %}
<button id="bulk-archive-btn" class="btn btn-sm btn-outline-danger" title="{% translate 'Archiver tous les assets' %}">
<i class="bi bi-archive"></i> {% translate "Tout archiver" %}
</button>
{% endif %}
</div>
{% endif %}
</div>
<div class="card-body collapse show" id="assets_card_body" style="background-color: #f7fbff;">
<style>
#assetsTab .nav-link {
color: #495057;
padding: 0.6rem 1.1rem;
border-radius: 0.5rem 0.5rem 0 0;
border: 1px solid #dee2e6;
border-bottom: none;
background: #f8f9fa;
font-weight: 500;
transition: background 0.15s, box-shadow 0.15s;
}
#assetsTab .nav-link:hover:not(.active) {
background: #e9ecef;
color: #212529;
}
#assetsTab .nav-link.active {
color: #0d6efd;
background: #fff;
border-color: #dee2e6 #dee2e6 #fff;
box-shadow: 0 -2px 6px rgba(0,0,0,0.08);
font-weight: 600;
}
/* Assets inactifs / archivés */
.asset-archived {
opacity: 0.55;
}
.asset-archived a {
text-decoration: line-through !important;
color: #6c757d !important;
}
.asset-inactive {
opacity: 0.7;
}
.asset-inactive a {
color: #6c757d !important;
}
</style>
<ul class="nav nav-tabs" id="assetsTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active d-flex align-items-center gap-2"
id="assets-outdoor-tab" data-bs-toggle="tab" data-bs-target="#assets-outdoor"
type="button" role="tab" aria-controls="assets-outdoor" aria-selected="true">
<i class="bi bi-sign-stop-lights"></i>
<span>{% translate "Installation extérieure" %}</span>
</button>
</li>
{% if not intersection.is_radar %}
<li class="nav-item" role="presentation">
<button class="nav-link d-flex align-items-center gap-2"
id="assets-controller-tab" data-bs-toggle="tab" data-bs-target="#assets-controller"
type="button" role="tab" aria-controls="assets-controller" aria-selected="false">
<i class="bi bi-cpu"></i>
<span>{% translate "Contrôleur" %}</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link d-flex align-items-center gap-2"
id="assets-programmation-tab" data-bs-toggle="tab" data-bs-target="#assets-programmation"
type="button" role="tab" aria-controls="assets-programmation" aria-selected="false">
<i class="bi bi-kanban"></i>
<span>{% translate "Programmation" %}</span>
</button>
</li>
{% endif %}
</ul>
<div class="tab-content border border-top-0 rounded-bottom p-3" id="assetsTabContent" style="background-color: #fff;">
{# ── Onglet Installation extérieure ── #}
<div class="tab-pane fade show active" id="assets-outdoor" role="tabpanel" aria-labelledby="assets-outdoor-tab">
<div class="row g-3">
{# ── Colonne Poteaux ── #}
<div class="col-md-6">
<div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-light d-flex align-items-center gap-2 py-2">
<i class="bi bi-sign-stop-lights text-secondary"></i>
<span class="fw-semibold">{% translate "Poteaux" %}</span>
<span class="badge bg-secondary ms-auto">{{ assets.poles|length }}</span>
<button id="expandAllPoles" class="btn btn-sm btn-link p-0 text-secondary border-0 flex-shrink-0"
type="button"
title="{% translate 'Tout déplier / replier' %}">
<i class="bi bi-chevron-expand small"></i>
</button>
</div>
<div class="card-body p-0">
{% if assets.poles %}
<ul class="list-group list-group-flush" id="accordionPoles">
{% for pole in assets.poles %}
<li class="list-group-item p-0{% if pole.status == 'archived' %} asset-archived{% elif pole.status == 'inactive' %} asset-inactive{% endif %}">
{# ── En-tête du poteau ── #}
<div class="d-flex align-items-center px-3 py-2 gap-2">
{% if pole.sorted_radars and not pole.sorted_lanterns and not pole.sorted_accessories %}
<i class="bi bi-radar text-secondary small flex-shrink-0"></i>
{% else %}
<i class="bi bi-feather2 text-secondary small flex-shrink-0"></i>
{% endif %}
<a href="{{ pole.get_absolute_url }}" class="fw-medium text-decoration-none flex-grow-0">{{ pole.code }}</a>
{% if pole.name %}<span class="text-muted small">{{ pole.name }}</span>{% endif %}
{% if pole.status != 'active' %}<span class="badge {% if pole.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-1" title="{{ pole.get_status_display }}">{{ pole.get_status_display }}</span>{% endif %}
{% if pole.sorted_lanterns or pole.sorted_accessories or pole.sorted_radars or pole.sorted_detectors or pole.sorted_signpanels %}
<div class="d-flex align-items-center gap-1 ms-auto">
{% if pole.sorted_accessories %}
<span class="badge bg-success bg-opacity-10 text-success" title="{% translate 'Accessoires' %}">{{ pole.sorted_accessories|length }} <i class="bi bi-align-middle"></i></span>
{% endif %}
{% if pole.sorted_lanterns %}
<span class="badge bg-primary bg-opacity-10 text-primary" title="{% translate 'Lanternes' %}">{{ pole.sorted_lanterns|length }} <i class="bi bi-stoplights"></i></span>
{% endif %}
{% if pole.sorted_radars %}
<span class="badge bg-info bg-opacity-10 text-info" title="{% translate 'Radars' %}">{{ pole.sorted_radars|length }} <i class="bi bi-radar"></i></span>
{% endif %}
{% if pole.sorted_detectors %}
<span class="badge bg-secondary bg-opacity-10 text-secondary" title="{% translate 'Détecteurs' %}">{{ pole.sorted_detectors|length }} <i class="bi bi-radar"></i></span>
{% endif %}
{% if pole.sorted_signpanels %}
<span class="badge bg-warning bg-opacity-10 text-warning" title="{% translate 'Panneaux de signalisation' %}">{{ pole.sorted_signpanels|length }} <i class="bi bi-signpost"></i></span>
{% endif %}
</div>
<button class="btn btn-sm btn-link p-0 text-secondary border-0 flex-shrink-0"
type="button"
data-bs-toggle="collapse"
data-bs-target="#poleCollapse{{ pole.id }}"
aria-expanded="false"
aria-controls="poleCollapse{{ pole.id }}"
title="{% translate 'Afficher les lanternes, accessoires, radars, détecteurs et panneaux de signalisation' %}">
<i class="bi bi-chevron-down small"></i>
</button>
{% endif %}
</div>
{# ── Lanternes + Accessoires + Radars + Détecteurs + Panneaux ── #}
{% if pole.sorted_lanterns or pole.sorted_accessories or pole.sorted_radars or pole.sorted_detectors or pole.sorted_signpanels %}
<div id="poleCollapse{{ pole.id }}" class="collapse">
<ul class="list-group list-group-flush border-top">
{% for lantern in pole.sorted_lanterns %}
<li class="list-group-item py-1 ps-5 pe-3 d-flex align-items-center gap-2 bg-light{% if lantern.status == 'archived' %} asset-archived{% elif lantern.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-stoplights text-warning small"></i>
<a href="{{ lantern.get_absolute_url }}" class="text-decoration-none small fw-medium">{{ lantern.code }}</a>
{% if lantern.get_name %}<span class="text-muted small fst-italic">{{ lantern.get_name }}</span>{% endif %}
{% if lantern.status != 'active' %}<span class="badge {% if lantern.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-auto">{{ lantern.get_status_display }}</span>{% endif %}
</li>
{% endfor %}
{% for accessory in pole.sorted_accessories %}
<li class="list-group-item py-1 ps-5 pe-3 d-flex align-items-center gap-2 bg-light{% if accessory.status == 'archived' %} asset-archived{% elif accessory.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-align-middle text-success small"></i>
<a href="{{ accessory.get_absolute_url }}" class="text-decoration-none small fw-medium">{{ accessory.code }}</a>
{% if accessory.get_name %}<span class="text-muted small fst-italic">{{ accessory.get_name }}</span>{% endif %}
{% if accessory.status != 'active' %}<span class="badge {% if accessory.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-auto">{{ accessory.get_status_display }}</span>{% endif %}
</li>
{% endfor %}
{% for radar in pole.sorted_radars %}
<li class="list-group-item py-1 ps-5 pe-3 d-flex align-items-center gap-2 bg-light{% if radar.status == 'archived' %} asset-archived{% elif radar.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-radar text-info small"></i>
<a href="{{ radar.get_absolute_url }}" class="text-decoration-none small fw-medium">{{ radar.code }}</a>
{% if radar.get_name %}<span class="text-muted small fst-italic">{{ radar.get_name }}</span>{% endif %}
{% if radar.status != 'active' %}<span class="badge {% if radar.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-auto">{{ radar.get_status_display }}</span>{% endif %}
</li>
{% endfor %}
{% for detector in pole.sorted_detectors %}
<li class="list-group-item py-1 ps-5 pe-3 d-flex align-items-center gap-2 bg-light{% if detector.status == 'archived' %} asset-archived{% elif detector.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-radar text-secondary small"></i>
<a href="{{ detector.get_absolute_url }}" class="text-decoration-none small fw-medium">{{ detector.code }}</a>
{% if detector.get_name %}<span class="text-muted small fst-italic">{{ detector.get_name }}</span>{% endif %}
{% if detector.status != 'active' %}<span class="badge {% if detector.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-auto">{{ detector.get_status_display }}</span>{% endif %}
</li>
{% endfor %}
{% for panel in pole.sorted_signpanels %}
<li class="list-group-item py-1 ps-5 pe-3 d-flex align-items-center gap-2 bg-light{% if panel.status == 'archived' %} asset-archived{% elif panel.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-signpost text-warning small"></i>
<a href="{{ panel.get_absolute_url }}" class="text-decoration-none small fw-medium">{{ panel.code }}</a>
{% if panel.signpanel_type %}<span class="text-muted small">{{ panel.signpanel_type }}</span>{% endif %}
{% if panel.signpanel_text %}<span class="text-muted small fst-italic">"{{ panel.signpanel_text }}"</span>{% endif %}
{% if panel.status != 'active' %}<span class="badge {% if panel.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-auto">{{ panel.get_status_display }}</span>{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small p-3 mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucun poteau" %}</p>
{% endif %}
</div>
</div>
</div>
{# ── Colonne Armoires + Câbles ── #}
<div class="col-md-6 d-flex flex-column gap-3">
{# Armoires électriques #}
<div class="card border-0 shadow-sm">
<div class="card-header bg-light d-flex align-items-center gap-2 py-2">
<i class="bi bi-lightning-charge text-secondary"></i>
<span class="fw-semibold">{% translate "Armoires électriques" %}</span>
<span class="badge bg-secondary ms-auto">{{ assets.electrical_cabinets|length }}</span>
</div>
{% if assets.electrical_cabinets %}
<ul class="list-group list-group-flush">
{% for cabinet in assets.electrical_cabinets %}
<li class="list-group-item d-flex align-items-center gap-2 py-2{% if cabinet.status == 'archived' %} asset-archived{% elif cabinet.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-box text-secondary small"></i>
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightelectricalcabinet' cabinet.id %}" class="fw-medium text-decoration-none">{{ cabinet.code }}</a>
<span class="text-muted small ms-1">{{ cabinet.get_name }}</span>
{% if cabinet.status != 'active' %}<span class="badge {% if cabinet.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-auto">{{ cabinet.get_status_display }}</span>{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<div class="card-body py-2">
<p class="text-muted small mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucune armoire électrique" %}</p>
</div>
{% endif %}
</div>
{# Câbles #}
<div class="card border-0 shadow-sm">
<div class="card-header bg-light d-flex align-items-center gap-2 py-2">
<i class="bi bi-ethernet text-secondary"></i>
<span class="fw-semibold">{% translate "Câbles" %}</span>
<span class="badge bg-secondary ms-auto">{{ assets.cables|length }}</span>
</div>
{% if assets.cables %}
<ul class="list-group list-group-flush">
{% for cable in assets.cables %}
<li class="list-group-item d-flex align-items-center gap-2 py-2{% if cable.status == 'archived' %} asset-archived{% elif cable.status == 'inactive' %} asset-inactive{% endif %}">
<svg width="24" height="12" class="shadow-sm rounded-1" style="vertical-align: middle; background-color: rgba(255,255,255,0.7); border: 1px solid rgba(0,0,0,0.08);" title="{% translate 'Couleur du câble sur la carte' %}">
<line x1="2" y1="6" x2="22" y2="6" stroke="{{ cable.color|default:'#e53935' }}" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="4,3" />
</svg>
<i class="bi bi-plug text-secondary small"></i>
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightcable' cable.id %}" class="fw-medium text-decoration-none">{{ cable.code }}</a>
{% if cable.name %}<span class="text-muted small ms-1">{{ cable.name }}</span>{% endif %}
{% if cable.status != 'active' %}<span class="badge {% if cable.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-auto">{{ cable.get_status_display }}</span>{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<div class="card-body py-2">
<p class="text-muted small mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucun câble" %}</p>
</div>
{% endif %}
</div>
{# Radars #}
<div class="card border-0 shadow-sm">
<div class="card-header bg-light d-flex align-items-center gap-2 py-2">
<i class="bi bi-radar text-secondary"></i>
<span class="fw-semibold">{% translate "Radars préventifs" %}</span>
<span class="badge bg-secondary ms-auto">{{ assets.radars|length }}</span>
</div>
{% if assets.radars %}
<ul class="list-group list-group-flush">
{% for radar in assets.radars %}
<li class="list-group-item d-flex align-items-center gap-2 py-2{% if radar.status == 'archived' %} asset-archived{% elif radar.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-broadcast text-secondary small"></i>
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightradar' radar.id %}" class="fw-medium text-decoration-none">{{ radar.code }}</a>
{% if radar.get_name %}<span class="text-muted small ms-1">{{ radar.get_name }}</span>{% endif %}
{% if radar.status != 'active' %}<span class="badge {% if radar.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %} ms-auto">{{ radar.get_status_display }}</span>{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<div class="card-body py-2">
<p class="text-muted small mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucun radar préventif" %}</p>
</div>
{% endif %}
</div>
</div>{# /col armoires+câbles #}
</div>{# /row #}
</div>
{% if not intersection.is_radar %}
{# ── Onglet Contrôleur ── #}
<div class="tab-pane fade" id="assets-controller" role="tabpanel" aria-labelledby="assets-controller-tab">
{% if assets.controllers.first %}
{% with ctrl=assets.controllers.first %}
{# ── Carte contrôleur ── #}
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-light d-flex align-items-center gap-2 py-2">
<i class="bi bi-cpu text-secondary"></i>
<span class="fw-semibold">{% translate "Contrôleur de trafic" %}</span>
</div>
<div class="card-body">
<div class="d-flex align-items-center gap-3 mb-3">
<i class="bi bi-cpu-fill fs-2 text-secondary opacity-50"></i>
<div>
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightcontroller' ctrl.id %}" class="fw-semibold fs-6 text-decoration-none">{{ ctrl.code }}</a>
{% if ctrl.name %}<div class="text-muted small">{{ ctrl.name }}</div>{% endif %}
</div>
</div>
<div class="row g-2 small">
<div class="col-sm-6">
<span class="text-muted">{% translate "Modèle" %} :</span>
<span class="ms-1">{% if ctrl.model %}{{ ctrl.model.get_name }}{% else %}<span class="text-muted fst-italic">—</span>{% endif %}</span>
</div>
<div class="col-sm-6">
<span class="text-muted">{% translate "Type" %} :</span>
<span class="ms-1">{{ ctrl.cabinet_type|default:"—" }}</span>
</div>
<div class="col-sm-6">
<span class="text-muted">{% translate "Plan" %} :</span>
<span class="ms-1">{{ ctrl.plan|default:"—" }}</span>
</div>
<div class="col-sm-6">
<span class="text-muted">{% translate "Puissance" %} :</span>
<span class="ms-1">{{ ctrl.power|default:"—" }}</span>
</div>
<div class="col-sm-6">
<span class="text-muted">{% translate "Date de mise en service" %} :</span>
<span class="ms-1">{% if ctrl.installation_date %}{{ ctrl.installation_date|date:"d/m/Y" }}{% else %}<span class="fst-italic">—</span>{% endif %}</span>
</div>
</div>
</div>
</div>
{# ── Accordéons liés au contrôleur ── #}
<div class="accordion" id="controllerAccordion{{ ctrl.id }}">
{# ── Block 1 : Hardwares du contrôleur ── #}
<div class="accordion-item">
<h2 class="accordion-header" id="headingHardware{{ ctrl.id }}">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseHardware{{ ctrl.id }}"
aria-expanded="false"
aria-controls="collapseHardware{{ ctrl.id }}">
<i class="bi bi-motherboard me-2 text-secondary"></i>
{% translate "Hardwares du contrôleur" %}
<span class="badge bg-secondary ms-2">{{ ctrl.hardwares.count }}</span>
</button>
</h2>
<div id="collapseHardware{{ ctrl.id }}" class="accordion-collapse collapse"
aria-labelledby="headingHardware{{ ctrl.id }}"
data-bs-parent="#controllerAccordion{{ ctrl.id }}">
<div class="accordion-body p-0">
{% with hw_list=ctrl.sorted_hardwares %}
{% if hw_list %}
<ul class="list-group list-group-flush">
{% for hw in hw_list %}
<li class="list-group-item d-flex align-items-center gap-2 py-2{% if hw.status == 'archived' %} asset-archived{% elif hw.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-motherboard text-secondary small"></i>
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightcontrollerhardware' hw.id %}" class="fw-medium text-decoration-none">{{ hw.code }}</a>
{% if hw.name %}<span class="text-muted small ms-1">{{ hw.name }}</span>{% endif %}
<span class="ms-auto d-flex gap-1 align-items-center">
{% if hw.hardware %}<span class="badge bg-light text-dark border">{{ hw.hardware }}</span>{% endif %}
{% if hw.status != 'active' %}<span class="badge {% if hw.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %}">{{ hw.get_status_display }}</span>{% endif %}
</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small p-3 mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucun hardware" %}</p>
{% endif %}
{% endwith %}
</div>
</div>
</div>
{# ── Block 2 : Détecteurs ── #}
<div class="accordion-item">
<h2 class="accordion-header" id="headingDetectors{{ ctrl.id }}">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseDetectors{{ ctrl.id }}"
aria-expanded="false"
aria-controls="collapseDetectors{{ ctrl.id }}">
<i class="bi bi-radar me-2 text-secondary"></i>
{% translate "Détecteurs" %}
<span class="badge bg-secondary ms-2">{{ ctrl.detectors.count }}</span>
</button>
</h2>
<div id="collapseDetectors{{ ctrl.id }}" class="accordion-collapse collapse"
aria-labelledby="headingDetectors{{ ctrl.id }}"
data-bs-parent="#controllerAccordion{{ ctrl.id }}">
<div class="accordion-body p-0">
{% with det_list=ctrl.sorted_detectors %}
{% if det_list %}
<ul class="list-group list-group-flush">
{% for det in det_list %}
<li class="list-group-item d-flex align-items-center gap-2 py-2{% if det.status == 'archived' %} asset-archived{% elif det.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-radar text-secondary small"></i>
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightdetector' det.id %}" class="fw-medium text-decoration-none">{{ det.code }}</a>
{% if det.name %}<span class="text-muted small ms-1">{{ det.name }}</span>{% endif %}
<span class="ms-auto d-flex gap-1 align-items-center">
{% if det.hardware %}<span class="badge bg-light text-dark border">{{ det.hardware }}</span>{% endif %}
{% if det.status != 'active' %}<span class="badge {% if det.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %}">{{ det.get_status_display }}</span>{% endif %}
</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small p-3 mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucun détecteur" %}</p>
{% endif %}
{% endwith %}
</div>
</div>
</div>
{# ── Block 3 : Hardware réseau ── #}
<div class="accordion-item">
<h2 class="accordion-header" id="headingNetwork{{ ctrl.id }}">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseNetwork{{ ctrl.id }}"
aria-expanded="false"
aria-controls="collapseNetwork{{ ctrl.id }}">
<i class="bi bi-hdd-network me-2 text-secondary"></i>
{% translate "Hardware réseau" %}
<span class="badge bg-secondary ms-2">{{ ctrl.network_hardwares.count }}</span>
</button>
</h2>
<div id="collapseNetwork{{ ctrl.id }}" class="accordion-collapse collapse"
aria-labelledby="headingNetwork{{ ctrl.id }}"
data-bs-parent="#controllerAccordion{{ ctrl.id }}">
<div class="accordion-body p-0">
{% with net_list=ctrl.sorted_network_hardwares %}
{% if net_list %}
<ul class="list-group list-group-flush">
{% for net in net_list %}
<li class="list-group-item d-flex align-items-center gap-2 py-2{% if net.status == 'archived' %} asset-archived{% elif net.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-hdd-network text-secondary small"></i>
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightnetworkhardware' net.id %}" class="fw-medium text-decoration-none">{{ net.code }}</a>
{% if net.name %}<span class="text-muted small ms-1">{{ net.name }}</span>{% endif %}
<span class="ms-auto d-flex gap-1 align-items-center">
{% if net.hardware %}<span class="badge bg-light text-dark border">{{ net.hardware }}</span>{% endif %}
{% if net.status != 'active' %}<span class="badge {% if net.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %}">{{ net.get_status_display }}</span>{% endif %}
</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small p-3 mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucun hardware réseau" %}</p>
{% endif %}
{% endwith %}
</div>
</div>
</div>
{# ── Block 4 : Hardware Tension ── #}
<div class="accordion-item">
<h2 class="accordion-header" id="headingVoltage{{ ctrl.id }}">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseVoltage{{ ctrl.id }}"
aria-expanded="false"
aria-controls="collapseVoltage{{ ctrl.id }}">
<i class="bi bi-lightning-charge me-2 text-secondary"></i>
{% translate "Hardware Tension" %}
<span class="badge bg-secondary ms-2">{{ ctrl.voltage_hardwares.count }}</span>
</button>
</h2>
<div id="collapseVoltage{{ ctrl.id }}" class="accordion-collapse collapse"
aria-labelledby="headingVoltage{{ ctrl.id }}"
data-bs-parent="#controllerAccordion{{ ctrl.id }}">
<div class="accordion-body p-0">
{% with volt_list=ctrl.sorted_voltage_hardwares %}
{% if volt_list %}
<ul class="list-group list-group-flush">
{% for volt in volt_list %}
<li class="list-group-item d-flex align-items-center gap-2 py-2{% if volt.status == 'archived' %} asset-archived{% elif volt.status == 'inactive' %} asset-inactive{% endif %}">
<i class="bi bi-lightning-charge text-secondary small"></i>
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightvoltagehardware' volt.id %}" class="fw-medium text-decoration-none">{{ volt.code }}</a>
{% if volt.name %}<span class="text-muted small ms-1">{{ volt.name }}</span>{% endif %}
<span class="ms-auto d-flex gap-1 align-items-center">
{% if volt.hardware %}<span class="badge bg-light text-dark border">{{ volt.hardware }}</span>{% endif %}
{% if volt.status != 'active' %}<span class="badge {% if volt.status == 'archived' %}bg-secondary{% else %}bg-warning text-dark{% endif %}">{{ volt.get_status_display }}</span>{% endif %}
</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small p-3 mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucun hardware tension" %}</p>
{% endif %}
{% endwith %}
</div>
</div>
</div>
</div>{# /accordion #}
{% endwith %}
{% else %}
<div class="text-center text-muted py-4">
<i class="bi bi-cpu fs-3 opacity-50 d-block mb-2"></i>
{% translate "Aucun contrôleur" %}
</div>
{% endif %}
</div>
{% endif %}
{% if not intersection.is_radar %}
{# ── Onglet Programmation ── #}
<div class="tab-pane fade" id="assets-programmation" role="tabpanel" aria-labelledby="assets-programmation-tab">
{# ── Programmations ── #}
{% with prog_list=intersection.programmations.all %}
{% if prog_list %}
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-light d-flex align-items-center gap-2 py-2">
<i class="bi bi-kanban text-secondary"></i>
<span class="fw-semibold">{% translate "Programmations" %}</span>
<span class="badge bg-secondary ms-auto">{{ prog_list|length }}</span>
</div>
<ul class="list-group list-group-flush">
{% for prog in prog_list %}
<li class="list-group-item d-flex align-items-center gap-2 py-2">
<i class="bi bi-kanban text-secondary small flex-shrink-0"></i>
{# Index - colonne fixe #}
<span style="width:3.5rem; flex-shrink:0;">
{% if prog.index %}<span class="badge bg-light text-dark border">{{ prog.index }}</span>{% endif %}
</span>
{# Statut - colonne fixe #}
<span style="width:7rem; flex-shrink:0;">
<span class="badge {% if prog.status == 'active' %}bg-success{% elif prog.status == 'removed' %}bg-secondary{% elif prog.status == 'damaged' or prog.status == 'to_replace' %}bg-warning text-dark{% else %}bg-info text-dark{% endif %}">{{ prog.get_status_display }}</span>
</span>
{# Code + nom #}
<a href="{% url 'assets:trafficlights_assets_detail' 'trafficlightprogramming' prog.id %}" class="fw-medium text-decoration-none">{{ prog.code }}</a>
{% if prog.get_name %}<span class="text-muted small">{{ prog.get_name }}</span>{% endif %}
{# Date alignée à droite #}
{% if prog.commissioning_date %}<span class="text-muted small ms-auto flex-shrink-0">{{ prog.commissioning_date|date:"d/m/Y" }}</span>{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="text-center text-muted py-4">
<i class="bi bi-kanban fs-3 opacity-50 d-block mb-2"></i>
{% translate "Aucune programmation" %}
</div>
{% endif %}
{% endwith %}
{# ── Log files du contrôleur ── #}
{% with ctrl=assets.controllers.first %}
{% if ctrl %}
{% with log_list=ctrl.logs.all|dictsortreversed:"filedate" %}
<div class="accordion" id="progLogAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingLogs">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseLogs"
aria-expanded="false"
aria-controls="collapseLogs">
<i class="bi bi-file-earmark-text me-2 text-secondary"></i>
{% translate "Fichiers de log" %}
<span class="badge bg-secondary ms-2">{{ log_list|length }}</span>
</button>
</h2>
<div id="collapseLogs" class="accordion-collapse collapse"
aria-labelledby="headingLogs"
data-bs-parent="#progLogAccordion">
<div class="accordion-body p-0">
{% if log_list %}
<ul class="list-group list-group-flush">
{% for log in log_list %}
<li class="list-group-item d-flex align-items-center gap-2 py-2">
<i class="bi bi-file-earmark-text text-secondary small"></i>
{% private_file_url log 'file' as log_url %}
<a href="{{ log_url }}" target="_blank" class="fw-medium text-decoration-none text-truncate" style="max-width: 60%;">{{ log.file.name|slice:"42:" }}</a>
{% if log.filedate %}<span class="text-muted small ms-auto">{{ log.filedate|date:"d/m/Y" }}</span>{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted small p-3 mb-0"><i class="bi bi-info-circle me-1"></i>{% translate "Aucun fichier de log" %}</p>
{% endif %}
</div>
</div>
</div>
</div>
{% endwith %}
{% endif %}
{% endwith %}
</div>
{% endif %}
</div>{# /tab-content #}
</div>
</div>
<div class="card mb-4">
<div class="card-header" style="background: linear-gradient(to left, #e2e6ea, #f8f9fa);">
<h4>{% translate "Interventions" %}</h4>
</div>
<div class="card-body">
<table id="interventions-table" class="table"
data-height="550"
data-pagination="true"
data-page-size="25"
data-click-to-select="true" >
<thead>
<tr>
<th data-field="actions" data-width="100"></th>
<th data-field="code">{% translate "Code" %}</th>
<th data-field="title">{% translate "Titre" %}</th>
<th data-field="begin_date">{% translate "Début" %}</th>
<th data-field="statut">{% translate "Statut" %}</th>
</tr>
<tbody>
{% for interv in interventions %}
<tr>
<td>
<a href="{% url 'interventions:interventions_index' %}{{ interv.id }}" type="button" class="btn btn-sm btn-light detail-btn" data-id="${row.id}" data-bs-toggle="tooltip" title="{% translate 'Détail' %}">
<i class="bi bi-info-circle"></i>
</a>
</td>
<td>{{ interv.code }}</td>
<td>{{ interv.title }}</td>
<td>{{ interv.begin_time|date:"d-m-Y H:i" }}</td>
<td>{{ interv.get_status_display }}</td>
</tr>
{% endfor %}
</tbody>
</thead>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0 font-weight-bold" style="color: #2c3e50;"><i class="bi bi-map-fill me-1"></i> {% translate "Plan" %}</h6>
</div>
<div id="map" class="shadow-sm rounded-3 border">
</div>
</div>
</div>
<!-- Add Asset Modal -->
{% if 'create' in allowed_location_actions %}
<div class="modal fade" id="addAssetModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% translate "Ajouter un asset" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="addAssetForm">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">{% translate "Catégorie" %} <span class="text-danger">*</span></label>
<select name="category_id" id="addAssetCategory" class="form-select" required>
<option value="">-- {% translate "Sélectionner une catégorie" %} --</option>
</select>
</div>
{# Type d'asset (dynamique) #}
<div class="mb-3 d-none" id="addAssetTypeContainer">
<label for="addAssetType" class="form-label">
{% translate "Type d'équipement" %} <span class="text-danger">*</span>
</label>
<select class="form-select" id="addAssetType" name="asset_type">
<option value="">{% translate "Sélectionner un type..." %}</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">{% translate "Modèle" %} <span class="text-danger" id="addAssetModelRequiredStar" style="display: none;">*</span></label>
<select name="model_id" id="addAssetModel" class="form-select" disabled>
<option value="">{% translate "Sélectionner d'abord une catégorie..." %}</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
<button type="submit" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> {% translate "Créer" %}
</button>
</div>
</form>
</div>
</div>
</div>
{% endif %}
<!-- Georeferenced Plans Selection Modal -->
<div class="modal fade" id="georefPlanModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-3 border-0 shadow-lg">
<div class="modal-header bg-light border-bottom-0 py-3">
<h5 class="modal-title font-weight-bold text-dark"><i class="bi bi-geo-alt-fill text-primary me-2"></i>{% translate "Plans géoréférencés" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body py-3">
<!-- Nav Tabs -->
<ul class="nav nav-tabs nav-fill mb-3" id="georefPlanTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active font-weight-bold" id="tab-image-plan" data-bs-toggle="tab" data-bs-target="#panel-image-plan" type="button" role="tab" aria-controls="panel-image-plan" aria-selected="true">
<i class="bi bi-file-earmark-image me-1"></i>{% translate "Plan image" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link font-weight-bold" id="tab-vector-plan" data-bs-toggle="tab" data-bs-target="#panel-vector-plan" type="button" role="tab" aria-controls="panel-vector-plan" aria-selected="false">
<i class="bi bi-file-earmark-code me-1"></i>{% translate "Plan CAO" %}
</button>
</li>
</ul>
<!-- Tab Content -->
<div class="tab-content pt-2" id="georefPlanTabContent">
<!-- Tab 1: Image Plan -->
<div class="tab-pane fade show active" id="panel-image-plan" role="tabpanel" aria-labelledby="tab-image-plan">
<div id="image-plan-selection-container">
<div id="image-plans-list" class="d-flex flex-column gap-3 mb-2">
<!-- Dynamically populated list of images with switches and sliders -->
</div>
<div id="no-georef-images-alert" class="text-center py-4" style="display: none;">
<div class="text-muted mb-3"><i class="bi bi-info-circle" style="font-size: 2.5rem;"></i></div>
<p class="text-dark font-weight-semibold mb-1">{% translate "Aucun plan d'image géoréférencé disponible" %}</p>
<p class="text-muted small">{% translate "Géoréférencez d'abord un plan PDF ou image dans l'onglet Documents de la fiche." %}</p>
</div>
</div>
</div>
<!-- Tab 2: Vector Plan (DXF) -->
<div class="tab-pane fade" id="panel-vector-plan" role="tabpanel" aria-labelledby="tab-vector-plan">
<div id="dxf-selection-container">
{% if not dxf_files %}
<div class="text-center py-4">
<div class="text-muted mb-3"><i class="bi bi-exclamation-triangle" style="font-size: 2.5rem;"></i></div>
<p class="text-dark font-weight-semibold mb-1">{% translate "Aucun fichier DXF disponible pour ce carrefour" %}</p>
<p class="text-muted small">{% translate "Veuillez d'abord en importer un." %}</p>
<a href="{% url 'assets:import_dxf' intersection.id %}" class="btn btn-primary rounded-3 btn-sm mt-2 px-3">
<i class="bi bi-upload me-1"></i>{% translate "Importer un plan DXF" %}
</a>
</div>
{% else %}
<!-- File Selection -->
<div class="mb-3">
<label class="form-label font-weight-semibold text-muted small uppercase">{% translate "Sélectionner le plan DXF" %}</label>
{% if dxf_files|length == 1 %}
<div class="p-3 bg-light border rounded-3 font-weight-semibold text-dark mb-3">
<i class="bi bi-file-earmark-check-fill text-success me-2"></i>{{ dxf_files.0 }}
<input type="hidden" id="select-dxf-file" value="{{ dxf_files.0 }}">
</div>
{% else %}
<select id="select-dxf-file" class="form-select rounded-3 p-3 mb-3">
<option value="">{% translate "-- Sélectionner un plan CAO (DXF) --" %}</option>
{% for f in dxf_files %}
<option value="{{ f }}">{{ f }}</option>
{% endfor %}
</select>
{% endif %}
</div>
<!-- Layers selection checklist -->
<div id="dxf-detail-layers-section" style="display: none;" class="mb-3">
<label class="form-label font-weight-semibold text-muted small uppercase d-flex justify-content-between align-items-center mb-2">
<span>{% translate "Couches vectorielles CAO" %}</span>
<button type="button" id="dxf-detail-toggle-all-layers" class="btn btn-link btn-sm p-0 text-decoration-none" style="font-size: 0.75rem;">
{% translate "Décocher tout" %}
</button>
</label>
<div id="dxf-detail-layers-spinner" class="text-center py-4" style="display: none;">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
<div class="text-muted small mt-2">{% translate "Lecture des couches du plan..." %}</div>
</div>
<div id="dxf-detail-layers-list" class="border rounded-3 p-3 bg-white" style="max-height: 250px; overflow-y: auto;">
<!-- Checkboxes will be populated dynamically -->
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="modal-footer border-top-0 pt-0 pb-4 justify-content-between px-4">
<button type="button" id="btn-disable-all-plans" class="btn btn-outline-danger px-3 rounded-3">
<i class="bi bi-eye-slash-fill me-1"></i>{% translate "Désactiver tout" %}
</button>
<button type="button" class="btn btn-secondary px-4 rounded-3 font-weight-semibold shadow-sm" data-bs-dismiss="modal">{% translate "Fermer" %}</button>
</div>
</div>
</div>
</div>
{% endblock content%}
{% block script %}
{{ intersection.lon|json_script:"lon_value" }}
{{ intersection.lat|json_script:"lat_value" }}
{{ intersection.status|json_script:"status_value" }}
{{ intersection.is_radar|json_script:"is_radar_value" }}
{{ assets_geojson|json_script:"assets_geojson" }}
<script>
const trafficlightintersectionsurl = "{% url 'assets:trafficlights_intersections' %}";
const document_history_url = "{% url 'assets:trafficlights_intersections_document_history' %}";
const feature_lon = JSON.parse(document.getElementById("lon_value").textContent);
const feature_lat = JSON.parse(document.getElementById("lat_value").textContent);
const feature_status = JSON.parse(document.getElementById("status_value").textContent);
const feature_is_radar = JSON.parse(document.getElementById("is_radar_value").textContent);
const assets_geojson = JSON.parse(document.getElementById("assets_geojson").textContent);
const traffic_light_icon_url = "{% static 'assets/traffic_light_icon.svg' %}";
const simple_pole_icon_url = "{% static 'assets/simple_pole_icon.svg' %}";
const electrical_cabinet_icon_url = "{% static 'assets/electrical-panel.png' %}";
const radar_preventif_icon_url = "{% static 'assets/radar_preventif_icon.svg' %}?v=20260710";
const thematicCode = "{{ thematic_code }}";
const intersectionId = "{{ intersection.id }}";
const dxfFiles = {% if dxf_files %}{{ dxf_files|safe }}{% else %}[]{% endif %};
</script>
<script src="{% static 'common/layer-switcher-control.js' %}"></script>
<script src="{% static 'assets/trafficlights_intersection_detail.js' %}"></script>
{% if allowed_location_actions %}
<script src="{% static 'assets/asset_cms_edit_mode.js' %}"></script>
{% endif %}
{# ── Location editing modals (edit / delete) ── #}
{% if can_edit_location %}
{% include "assets/components/location_edit_modals.html" with thematic_code=thematic_code location_type=location_type entry_label="Intersection" %}
{# ── Contracts management modal ── #}
<div class="modal fade" id="contractsEditModal" tabindex="-1" aria-labelledby="contractsEditModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="contractsEditModalLabel">
<i class="bi bi-file-earmark-text me-2"></i>{% translate "Gérer les contrats" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
</div>
<div class="modal-body">
{# Current contracts list #}
<h6 class="mb-3">{% translate "Contrats associés" %}</h6>
<div id="contractsList">
{% if trafficlight_contracts %}
<table class="table table-sm table-bordered">
<thead class="table-light">
<tr>
<th>{% translate "Numéro de contrat" %}</th>
<th>{% translate "Entreprise" %}</th>
<th>{% translate "Type de maintenance" %}</th>
<th>{% translate "Statut" %}</th>
<th></th>
</tr>
</thead>
<tbody id="contractsTableBody">
{% for tlc in trafficlight_contracts %}
<tr id="contract-row-{{ tlc.pk }}">
<td>{{ tlc.contract.contract_number }}</td>
<td>{{ tlc.contract.company.name }}</td>
<td>{{ tlc.get_maintenance_type_display }}</td>
<td>{{ tlc.get_status_display }}</td>
<td>
<button type="button" class="btn btn-sm btn-outline-danger contract-remove-btn"
data-tl-contract-id="{{ tlc.pk }}"
data-url="{% url 'assets:trafficlights_intersections_contracts_remove' intersection.id tlc.pk %}"
title="{% translate 'Retirer ce contrat' %}">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-muted fst-italic" id="contractsEmptyMsg">{% translate "Aucun contrat associé." %}</p>
{% endif %}
</div>
<hr>
{# Add contract form #}
<h6 class="mb-3">{% translate "Ajouter un contrat" %}</h6>
<form id="addContractForm">
{% csrf_token %}
<div class="row g-2 align-items-end">
<div class="col-md-5">
<label class="form-label">{% translate "Contrat" %}</label>
<select id="contractSelect" name="contract_id" class="form-select form-select-sm" required>
<option value="">— {% translate "Sélectionner" %} —</option>
{% for contract in available_contracts %}
<option value="{{ contract.pk }}">{{ contract.contract_number }} — {{ contract.company.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label">{% translate "Type" %}</label>
<select id="maintenanceTypeSelect" name="maintenance_type" class="form-select form-select-sm">
<option value="infra">{% translate "Infrastructure" %}</option>
<option value="controller">{% translate "Contrôleur de traffic" %}</option>
<option value="other" selected>{% translate "Autre" %}</option>
</select>
</div>
<input type="hidden" name="status" value="active">
<div class="col-md-2">
<button type="submit" class="btn btn-sm btn-primary w-100">
<i class="bi bi-plus-circle me-1"></i>{% translate "Ajouter" %}
</button>
</div>
</div>
<div id="addContractAlert" class="alert d-none mt-2" role="alert"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% translate "Fermer" %}</button>
</div>
</div>
</div>
</div>
<script>
(function () {
const addUrl = "{% url 'assets:trafficlights_intersections_contracts_add' intersection.id %}";
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value || '{{ csrf_token }}';
function getTableBody() {
return document.getElementById('contractsTableBody');
}
function ensureTable() {
// If there was an empty message, replace it with a table
let emptyMsg = document.getElementById('contractsEmptyMsg');
if (emptyMsg) {
emptyMsg.remove();
}
let existing = document.getElementById('contractsTableBody');
if (existing) return existing;
const listDiv = document.getElementById('contractsList');
listDiv.innerHTML = `<table class="table table-sm table-bordered">
<thead class="table-light"><tr>
<th>{% translate "Numéro de contrat" %}</th>
<th>{% translate "Entreprise" %}</th>
<th>{% translate "Type de maintenance" %}</th>
<th>{% translate "Statut" %}</th>
<th></th>
</tr></thead>
<tbody id="contractsTableBody"></tbody>
</table>`;
return document.getElementById('contractsTableBody');
}
function bindRemoveButtons() {
document.querySelectorAll('.contract-remove-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
const url = this.dataset.url;
const row = document.getElementById('contract-row-' + this.dataset.tlContractId);
fetch(url, {
method: 'POST',
headers: {'X-CSRFToken': csrfToken, 'X-Requested-With': 'XMLHttpRequest'},
})
.then(r => r.json())
.then(data => {
if (data.success && row) {
row.remove();
// Refresh badges in header
refreshContractBadges();
}
});
});
});
}
function refreshContractBadges() {
const badgesContainer = document.querySelector('.contract-badges-container');
if (!badgesContainer) return;
const rows = document.querySelectorAll('#contractsTableBody tr');
badgesContainer.innerHTML = '';
if (rows.length === 0) {
badgesContainer.innerHTML = '<span class="text-muted fst-italic">{% translate "Aucun" %}</span>';
return;
}
rows.forEach(function(row) {
const cells = row.querySelectorAll('td');
const num = cells[0]?.textContent.trim();
const type = cells[2]?.textContent.trim();
const badge = document.createElement('span');
badge.className = 'badge bg-secondary me-1';
badge.title = type;
badge.innerHTML = num + ' <span class="text-light opacity-75 ms-1">(' + type + ')</span>';
badgesContainer.appendChild(badge);
});
}
document.getElementById('addContractForm').addEventListener('submit', function(e) {
e.preventDefault();
const alertEl = document.getElementById('addContractAlert');
alertEl.className = 'alert d-none mt-2';
const formData = new FormData(this);
fetch(addUrl, {
method: 'POST',
headers: {'X-CSRFToken': csrfToken, 'X-Requested-With': 'XMLHttpRequest'},
body: formData,
})
.then(r => r.json())
.then(data => {
if (data.success) {
const tbody = ensureTable();
const removeUrl = "{% url 'assets:trafficlights_intersections_contracts_remove' intersection.id 0 %}".replace('/0/', '/' + data.id + '/');
const tr = document.createElement('tr');
tr.id = 'contract-row-' + data.id;
tr.innerHTML = `<td>${data.contract_number}</td>
<td>${data.company}</td>
<td>${data.maintenance_type}</td>
<td>${data.status}</td>
<td><button type="button" class="btn btn-sm btn-outline-danger contract-remove-btn"
data-tl-contract-id="${data.id}"
data-url="${removeUrl}"
title="{% translate 'Retirer ce contrat' %}">
<i class="bi bi-trash"></i></button></td>`;
tbody.appendChild(tr);
bindRemoveButtons();
refreshContractBadges();
document.getElementById('contractSelect').value = '';
} else {
alertEl.textContent = data.error || "{% translate 'Erreur lors de l’ajout.' %}";
alertEl.className = 'alert alert-danger mt-2';
}
});
});
bindRemoveButtons();
})();
</script>
{% endif %}
<script>
// Expand / collapse all pole accordions
(function () {
const btn = document.getElementById('expandAllPoles');
if (!btn) return;
let expanded = false;
btn.addEventListener('click', function () {
expanded = !expanded;
document.querySelectorAll('#accordionPoles .collapse').forEach(function (el) {
const bsCollapse = bootstrap.Collapse.getOrCreateInstance(el, { toggle: false });
expanded ? bsCollapse.show() : bsCollapse.hide();
});
btn.querySelector('i').className = expanded
? 'bi bi-chevron-contract small'
: 'bi bi-chevron-expand small';
});
})();
</script>
{% endblock script %}