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

425 lines
24 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"/>
<link rel="stylesheet" href="{% static 'assets/assets_index.css' %}">
<link rel="stylesheet" href="{% static 'assets/info-panel.css' %}">
<style>
#assets-search-input:focus {
outline: none;
box-shadow: none;
border-color: #dee2e6;
}
</style>
{% endblock head %}
{% block content %}
<div class="d-flex justify-content-between align-items-center gap-4 flex-wrap {% if preventive_draft_id %} d-none {% endif %}">
<div class="d-flex align-items-center gap-2">
<label for="theme-select" class="form-label mb-0">{% translate "Thématique :" %}</label>
<div class="dropdown">
<button
class="btn btn-light dropdown-toggle"
type="button"
id="thematicDropdown"
data-bs-toggle="dropdown"
aria-expanded="false">
{% if selected_thematic %}
<i class="bi {{ selected_thematic.icon }} me-2"></i>{{ selected_thematic.name }}
{% else %}
{% translate "Sélectionnez une thématique" %}
{% endif %}
</button>
<ul class="dropdown-menu" aria-labelledby="thematicDropdown">
{% for thematic in thematics %}
<li>
<a
class="dropdown-item {% if selected_thematic and thematic.code == selected_thematic.code %}active{% endif %}"
href="/assets/{{ thematic.code }}/">
<i class="bi {{ thematic.icon }} me-2"></i> {{ thematic.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<hr class="{% if preventive_draft_id %} d-none {% endif %}">
<!-- Formulaire de filtre pour les localisations -->
<div id="filter-dropdown-wrapper" class="float-left mt-2 d-none">
<div class="dropdown me-2">
<button class="btn btn-light dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
{% translate "Filtre" %}
</button>
<div class="dropdown-menu p-2 filter-dropdown">
{% block locations_filter_form %}
{% endblock locations_filter_form %}
</div>
</div>
</div>
<!-- Formulaire de filtre pour les assets -->
<div id="filter-assets-dropdown-wrapper" class="float-left mt-2 d-none">
<div class="dropdown me-2" data-bs-auto-close="outside">
<button class="btn btn-light dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
{% translate "Filtre" %}
</button>
<div class="dropdown-menu p-2 filter-dropdown">
{% block assets_filter_form %}
{% endblock assets_filter_form %}
</div>
</div>
</div>
<div class="position-relative">
<!-- Layout selection buttons -->
<div class="position-absolute top-0 end-0 mt-1" 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 request.user.config.assets_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 not request.user.config.assets_list_layout or request.user.config.assets_list_layout == 'both' %} 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 request.user.config.assets_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="assets-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="assets-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 'Code, nom…' %}">
<button type="button" id="assets-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>
<!-- AND/OR toggle -->
<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="assets-search-mode" id="assets-search-mode-and" value="and" checked>
<label class="btn btn-outline-secondary py-0 px-2" for="assets-search-mode-and" title="{% translate 'Tous les mots doivent correspondre' %}">AND</label>
<input type="radio" class="btn-check" name="assets-search-mode" id="assets-search-mode-or" value="or">
<label class="btn btn-outline-secondary py-0 px-2" for="assets-search-mode-or" title="{% translate 'Au moins un mot doit correspondre' %}">OR</label>
</div>
</span>
<!-- Bouton recherche -->
<button class="btn btn-sm" type="button" id="assets-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 - 200px);">
<div id="col-table" class="{% if request.user.config.assets_list_layout == 'map_only' %}d-none{% elif request.user.config.assets_list_layout == 'table_only' %}col-12{% else %}col-md-6{% endif %}" style="max-height: calc(100vh - 200px); overflow: auto;">
<div class="d-flex">
</div>
{% with default_tab_value=default_tab|default:'locations' %}
<ul class="nav nav-tabs" id="tablesTab" role="tablist">
<li class="nav-item mt-1" role="presentation">
<button class="nav-link {% if default_tab_value != 'assets' %}active{% endif %}" id="locations-table-tab" data-bs-toggle="tab" data-bs-target="#locations-table-content" type="button" role="tab" aria-controls="locations-table" aria-selected="{% if default_tab_value != 'assets' %}true{% else %}false{% endif %}">{{ locations_tab_title }}</button>
</li>
<li class="nav-item mt-1 {% if preventive_draft_id and preventive_element_type == 'locations' %} d-none {% endif %}" role="presentation">
<button class="nav-link {% if default_tab_value == 'assets' %}active{% endif %}" id="assets-table-tab" data-bs-toggle="tab" data-bs-target="#assets-table-content" type="button" role="tab" aria-controls="assets-table" aria-selected="{% if default_tab_value == 'assets' %}true{% else %}false{% endif %}">{{ locations_assets_title }}</button>
</li>
{% block tab_actions %}{% endblock %}
<div class="d-flex ms-auto">
</div>
</ul>
<div class="tab-content my-2" id="tablesTabContent">
<div class="tab-pane fade {% if default_tab_value != 'assets' %} show active {% endif %}" id="locations-table-content" role="tabpanel" aria-labelledby="locations-table-tab">
<!-- Locations selection -->
<div class="d-flex gap-2 flex-wrap justify-content-start align-items-center">
<button type="button" class="btn btn-sm btn-outline-secondary" id="locations-select-all">{% translate "Tout sélectionner" %}</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="locations-deselect-all">{% translate "Tout désélectionner" %}</button>
{% if preventive_draft_id and preventive_element_type == 'locations' %}
<button type="submit" class="btn btn-success" form="preventive-select-form">{% translate "Valider la sélection" %}</button>
{% endif %}
<div id="locations-selection-status" class="text-muted small mb-0">
{% blocktranslate count counter=selected_locations_ids|length %}
{{ counter }} localisation sélectionnée
{% plural %}
{{ counter }} localisations sélectionnées
{% endblocktranslate %}
</div>
<div class="ms-auto">{% block location_row_actions %}{% endblock %}</div>
</div>
<div id="location-alert" class="alert alert-warning d-none" role="alert"></div>
<!-- Spinner -->
<div id="locations-spinner" class="table-spinner">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">{% translate "Loading..." %}</span>
</div>
</div>
<!-- Container of streets table -->
<div id="locations-table-container">
<table id="locations-table" class="table"
data-height="550"
data-pagination="true"
data-page-size="100"
data-click-to-select="true">
</table>
</div>
</div>
<div class="tab-pane fade {% if not preventive_draft_id or preventive_element_type != 'locations' %} show {% endif %} {% if default_tab_value == 'assets' %} active {% endif %} {% if preventive_draft_id and preventive_element_type == 'locations' %} d-none{% endif %}" id="assets-table-content" role="tabpanel" aria-labelledby="assets-table-tab">
<!-- Assets selection -->
<div class="d-flex gap-2 flex-wrap justify-content-start align-items-center">
<button type="button" class="btn btn-sm btn-outline-secondary" id="assets-select-all">{% translate "Tout sélectionner" %}</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="assets-deselect-all">{% translate "Tout désélectionner" %}</button>
{% if preventive_draft_id and preventive_element_type == 'assets' %}
<button type="submit" class="btn btn-success" form="preventive-select-form">{% translate "Valider la sélection" %}</button>
{% endif %}
<div id="assets-selection-status" class="text-muted small mb-0">
{% blocktranslate count counter=selected_assets_ids|length %}
{{ counter }} asset sélectionné
{% plural %}
{{ counter }} assets sélectionnés
{% endblocktranslate %}
</div>
<div class="d-flex gap-2 align-items-center ms-auto">
{% block asset_row_actions %}{% endblock %}
{% if not preventive_draft_id %}
<div class="dropdown" id="assets-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 dropdown-menu-end">
<li>
<a class="dropdown-item" href="#" id="assets-action-export"
data-bs-toggle="modal" data-bs-target="#assetsExportModal">
<i class="bi bi-download me-2"></i>{% translate "Exporter" %}
</a>
</li>
</ul>
</div>
{% endif %}
</div><!-- /.ms-auto wrapper -->
</div>
<div id="asset-alert" class="alert alert-warning d-none" role="alert"></div>
<!-- Spinner -->
<div id="assets-spinner" class="table-spinner">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">{% translate "Loading..." %}</span>
</div>
</div>
<!-- Container of assets table -->
<div id="assets-table-container">
<table id="assets-table"
data-height="550"
data-pagination="true"
data-page-size="100"
data-click-to-select="true">
</table>
</div>
</div>
</div>
{% endwith %}
</div>
<div id="col-map" class="{% if request.user.config.assets_list_layout == 'table_only' %}d-none{% elif request.user.config.assets_list_layout == 'map_only' %}col-12{% else %}col-md-6{% endif %}" style="position: relative; margin-top: 50px;">
<!-- Panneau d'information pour les commentaires et assets -->
<div id="info-panel">
<div class="panel-header">
<button id="info-panel-back" class="btn btn-sm btn-outline-secondary me-2" style="display: none;">
<i class="bi bi-arrow-left"></i>
</button>
<h5 id="info-panel-title" class="mb-0 flex-grow-1"></h5>
<span class="panel-close" id="info-panel-close">&times;</span>
</div>
<div id="info-panel-content" class="panel-content">
<!-- Le contenu sera injecté ici dynamiquement -->
</div>
</div>
<div class="d-flex">
<div class="dropdown flex-grow-1">
<div class="d-flex flex-row-reverse">
<button class="btn btn-light dropdown-toggle m-1 d-none" type="button" data-bs-toggle="dropdown" aria-expanded="false">
{% translate "Carte" %}
</button>
<!-- Filtre carte -->
</div>
</div>
<div id="auto-zone-wrapper" class="form-check form-switch form-check-reverse d-none mt-2">
<label class="form-check-label mb-0" for="autoZoneCheck">
{% translate "Filtrer automatiquement sur la zone visible" %}
</label>
<input class="form-check-input" type="checkbox" id="autoZoneCheck" checked>
</div>
</div>
<div id="map" style="position: relative;">
<!-- Overlay Totaux -->
<div id="totalsOverlay" class="totals-overlay">
<div class="totals-toolbar">
<strong>{% translate "Totaux des assets" %}</strong>
<button id="totalsClose" type="button" class="btn btn-sm btn-outline-secondary">
{% translate "Fermer" %}
</button>
</div>
<div id="totalsContent" class="totals-content">
<!-- La synthèse sera injectée ici -->
</div>
</div>
<!-- Panneau d'information déplacé avant le div map -->
</div>
<div>
<button id="totals_button" class="btn btn-light mt-2 mb-1" type="button">{% translate "Totaux" %}</button>
</div>
</div>
</div><!-- /.row -->
</div><!-- /.position-relative -->
<!-- Modal Export Assets -->
<div class="modal fade" id="assetsExportModal" tabindex="-1" aria-labelledby="assetsExportModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="assetsExportModalLabel">{% translate "Exporter les assets sélectionnés" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
</div>
<div class="modal-body">
<p class="text-muted small">{% translate "Choisissez le format d'export :" %}</p>
<div class="form-check">
<input class="form-check-input" type="radio" name="assets-export-format" id="assets-export-format-csv" value="csv" checked>
<label class="form-check-label" for="assets-export-format-csv">CSV</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="assets-export-format" id="assets-export-format-xlsx" value="xlsx">
<label class="form-check-label" for="assets-export-format-xlsx">Excel (XLSX)</label>
</div>
<div id="assets-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="assets-confirm-export-btn">{% translate "Exporter" %}</button>
</div>
</div>
</div>
</div>
{% if preventive_draft_id %}
<form method="post" id="preventive-select-form" action="{% url 'interventions:intervention_preventive_link_elements' thematic_code=selected_thematic.code %}">
{% csrf_token %}
<input type="hidden" name="preventive_draft_id" value="{{ preventive_draft_id }}">
<input type="hidden" name="preventive_locations_ids" id="input-preventive-selected-locations-ids" value="{{ selected_locations_ids|join:',' }}">
<input type="hidden" name="assets" id="input-preventive-selected-assets">
<input type="hidden" name="preventive_element_type" value="{{ preventive_element_type }}">
</form>
{% endif %}
{% endblock content%}
{% block script %}
<!-- Mapbox GL Draw 1.5.0 (local) -->
<script src="{% static 'common/mapbox-gl-draw.js' %}"></script>
<script>
const preventive_draft_id = "{{ preventive_draft_id }}";
var selected_locations_set = new Set({{ selected_locations_ids|safe }});
const assetKey = (asset_type, id) => `${asset_type}:${id}`;
const initial_selected_assets = {{ selected_assets_json|safe }};
var selected_assets_set = new Set();
if (initial_selected_assets) {
selected_assets_set = new Set(
initial_selected_assets.map(a => assetKey(a.asset_type, a.object_id))
);
}
const preventive_element_type = "{{ preventive_element_type }}";
const saveAssetLayoutUrl = "{% url 'common:save_assets_list_layout' %}";
const INITIAL_ASSET_LAYOUT = '{{ request.user.config.assets_list_layout|default:"both" }}';
const exportAssetsUrl = "{% url 'assets:export_assets' %}";
</script>
{% block const_script %}
{% endblock const_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 'assets/assets_index.js' %}"></script>
{% block extra_script %}
{% endblock extra_script %}
<script>
// Export assets
(function () {
const exportConfirmBtn = document.getElementById('assets-confirm-export-btn');
const exportError = document.getElementById('assets-export-error');
if (!exportConfirmBtn) return;
exportConfirmBtn.addEventListener('click', function () {
const ids = Array.from(window.selected_assets_set || []);
if (!ids.length) {
if (exportError) {
exportError.textContent = gettext("Veuillez sélectionner au moins un asset.");
exportError.classList.remove('d-none');
}
return;
}
if (exportError) exportError.classList.add('d-none');
const fmt = (document.querySelector('input[name="assets-export-format"]:checked')?.value || 'csv');
const form = document.createElement('form');
form.method = 'POST';
form.action = exportAssetsUrl;
form.style.display = 'none';
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrfmiddlewaretoken';
csrfInput.value = getCookie('csrftoken');
form.appendChild(csrfInput);
const formatInput = document.createElement('input');
formatInput.type = 'hidden';
formatInput.name = 'format';
formatInput.value = fmt;
form.appendChild(formatInput);
const idsInput = document.createElement('input');
idsInput.type = 'hidden';
idsInput.name = 'ids';
idsInput.value = JSON.stringify(ids);
form.appendChild(idsInput);
document.body.appendChild(form);
const modalEl = document.getElementById('assetsExportModal');
if (modalEl) {
bootstrap.Modal.getOrCreateInstance(modalEl).hide();
}
form.submit();
setTimeout(() => document.body.removeChild(form), 1000);
});
})();
</script>
{% endblock script %}