loko/loko/assets/templates/assets/management_zones/management_zones_index.html

340 lines
18 KiB
HTML

{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% translate "Zones de gestion" %} | {{ product_name }}{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Breadcrumb & Header -->
<div class="d-flex justify-content-between align-items-center mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-1">
<li class="breadcrumb-item"><a href="/">{% translate "Accueil" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'assets:index' %}">{% translate "Assets" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% translate "Zones de gestion" %}</li>
</ol>
</nav>
{% if can_create %}
<a href="{% url 'assets:management_zone_create' %}{% if selected_thematic_code %}?thematic={{ selected_thematic_code }}{% endif %}" class="btn btn-success">
<i class="bi bi-plus-lg me-1"></i>{% translate "Nouvelle zone de gestion" %}
</a>
{% endif %}
</div>
<!-- Title Bar -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1">
<i class="bi bi-diagram-3-fill text-success me-2"></i>{% translate "Zones de gestion" %}
</h1>
<p class="text-muted mb-0">
{% translate "Regroupements géographiques et fonctionnels d'équipements pour l'entretien récurrent, les plans de fauche et l'exploitation." %}
</p>
</div>
<div class="text-end">
<span class="badge bg-light text-dark border fs-6 px-3 py-2">
<strong>{{ total_count }}</strong> {% translate "zone(s) répertoriée(s)" %}
</span>
</div>
</div>
<!-- Filter Card -->
<div class="card shadow-sm mb-4">
<div class="card-body">
<form method="get" class="row g-3 align-items-end">
<!-- Search text -->
<div class="col-md-3">
<label class="form-label small text-muted">{% translate "Recherche" %}</label>
<div class="input-group input-group-sm">
<span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="text" name="q" class="form-control" placeholder="{% translate 'Code, nom, consignes...' %}" value="{{ search_query }}">
</div>
</div>
<!-- Thematic -->
<div class="col-md-3">
<label class="form-label small text-muted">{% translate "Thématique" %}</label>
<select name="thematic" id="filterThematic" class="form-select form-select-sm">
<option value="">-- {% translate "Toutes les thématiques" %} --</option>
{% for th in accessible_thematics %}
<option value="{{ th.code }}" {% if selected_thematic_code == th.code %}selected{% endif %}>
{{ th.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Zone Type -->
<div class="col-md-3">
<label class="form-label small text-muted">{% translate "Type de zone" %}</label>
<select name="zone_type" id="filterZoneType" class="form-select form-select-sm">
<option value="">-- {% translate "Tous les types" %} --</option>
{% for zt in available_types %}
<option value="{{ zt.id }}" {% if selected_type_id == zt.id|stringformat:"s" %}selected{% endif %}>
{{ zt.get_name }} ({{ zt.thematic.name }})
</option>
{% endfor %}
</select>
</div>
<!-- Status -->
<div class="col-md-2">
<label class="form-label small text-muted">{% translate "Statut" %}</label>
<select name="status" class="form-select form-select-sm">
<option value="">-- {% translate "Tous les statuts" %} --</option>
<option value="active" {% if selected_status == 'active' %}selected{% endif %}>{% translate "Actives uniquement" %}</option>
<option value="inactive" {% if selected_status == 'inactive' %}selected{% endif %}>{% translate "Inactives" %}</option>
</select>
</div>
<!-- Submit / Reset buttons -->
<div class="col-md-1 d-flex gap-1">
<button type="submit" class="btn btn-sm btn-primary w-100" title="{% translate 'Filtrer' %}">
<i class="bi bi-funnel"></i>
</button>
<a href="{% url 'assets:management_zones_index' %}" class="btn btn-sm btn-outline-secondary" title="{% translate 'Réinitialiser' %}">
<i class="bi bi-x-lg"></i>
</a>
</div>
</form>
</div>
</div>
<!-- Map Overview Card -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-light d-flex justify-content-between align-items-center py-2">
<span class="fw-semibold small">
<i class="bi bi-map me-1 text-success"></i>{% translate "Aperçu cartographique des périmètres" %}
</span>
<small class="text-muted" id="mapLoadingStatus">{% translate "Chargement de la carte..." %}</small>
</div>
<div class="card-body p-0">
<div id="zonesOverviewMap" style="height: 320px; width: 100%;"></div>
</div>
</div>
<!-- Zones Table Card -->
<div class="card shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light small text-uppercase text-muted">
<tr>
<th style="width: 140px;">{% translate "Code" %}</th>
<th>{% translate "Nom de la zone" %}</th>
<th>{% translate "Thématique" %}</th>
<th>{% translate "Type de zone" %}</th>
<th>{% translate "Fréquence cible" %}</th>
<th>{% translate "Saisonnalité" %}</th>
<th>{% translate "Statut" %}</th>
<th class="text-end" style="width: 160px;">{% translate "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for item in zones_list %}
{% with zone=item.zone can_edit=item.can_edit %}
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<span style="display:inline-block; width: 14px; height: 14px; border-radius: 3px; background-color: {{ zone.get_color }}; border: 1px solid rgba(0,0,0,0.2); flex-shrink: 0;"></span>
<a href="{% url 'assets:management_zone_detail' zone.pk %}" class="fw-bold text-decoration-none">
{{ zone.code }}
</a>
</div>
</td>
<td>
<div class="fw-semibold">{{ zone.get_name }}</div>
{% if zone.instructions %}
<div class="text-muted small text-truncate" style="max-width: 320px;" title="{{ zone.instructions }}">
<i class="bi bi-exclamation-triangle text-warning me-1"></i>{{ zone.instructions }}
</div>
{% endif %}
</td>
<td>
<span class="badge bg-secondary-subtle text-secondary border">
{{ zone.thematic.name }}
</span>
</td>
<td>
{% if zone.zone_type %}
<span class="badge" style="background-color: {{ zone.zone_type.default_color }}; color: #fff;">
{{ zone.zone_type.get_name }}
</span>
{% else %}
<span class="text-muted small">-</span>
{% endif %}
</td>
<td>
{% if zone.target_frequency_days %}
<span class="badge bg-info-subtle text-info border">
<i class="bi bi-arrow-repeat me-1"></i>{{ zone.target_frequency_days }} {% translate "jours" %}
</span>
{% else %}
<span class="text-muted small">-</span>
{% endif %}
</td>
<td>
{% if zone.seasonality_notes %}
<span class="small text-muted"><i class="bi bi-calendar3 me-1"></i>{{ zone.seasonality_notes }}</span>
{% else %}
<span class="text-muted small">-</span>
{% endif %}
</td>
<td>
{% if zone.is_active %}
<span class="badge bg-success-subtle text-success border">{% translate "Active" %}</span>
{% else %}
<span class="badge bg-danger-subtle text-danger border">{% translate "Inactive" %}</span>
{% endif %}
</td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<a href="{% url 'assets:management_zone_detail' zone.pk %}" class="btn btn-outline-primary" title="{% translate 'Détails' %}">
<i class="bi bi-eye"></i>
</a>
{% if can_edit %}
<a href="{% url 'assets:management_zone_edit' zone.pk %}" class="btn btn-outline-secondary" title="{% translate 'Modifier' %}">
<i class="bi bi-pencil"></i>
</a>
<a href="{% url 'assets:management_zone_delete' zone.pk %}" class="btn btn-outline-danger" title="{% translate 'Supprimer' %}">
<i class="bi bi-trash"></i>
</a>
{% endif %}
</div>
</td>
</tr>
{% endwith %}
{% empty %}
<tr>
<td colspan="8" class="text-center py-5 text-muted">
<i class="bi bi-diagram-3 fs-1 d-block mb-2 text-muted"></i>
<p class="mb-2">{% translate "Aucune zone de gestion ne correspond aux critères sélectionnés." %}</p>
{% if can_create %}
<a href="{% url 'assets:management_zone_create' %}{% if selected_thematic_code %}?thematic={{ selected_thematic_code }}{% endif %}" class="btn btn-sm btn-success">
<i class="bi bi-plus-lg me-1"></i>{% translate "Créer une première zone de gestion" %}
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block script %}
<script>
document.addEventListener("DOMContentLoaded", function () {
const mapDiv = document.getElementById("zonesOverviewMap");
if (!mapDiv || typeof maplibregl === "undefined") return;
const thematicParam = "{{ selected_thematic_code|default:'' }}";
const geojsonUrl = "{% url 'assets:management_zones_geojson' %}" + (thematicParam ? ("?thematic=" + encodeURIComponent(thematicParam)) : "");
const map = new maplibregl.Map({
container: mapDiv,
style: window.MAP_STYLE_LIGHT,
center: window.MAP_DEFAULT_CENTER || [4.3572, 50.8477],
zoom: 11
});
map.addControl(new maplibregl.NavigationControl(), 'top-right');
map.on('load', async function () {
try {
const resp = await fetch(geojsonUrl, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
if (!resp.ok) {
document.getElementById("mapLoadingStatus").textContent = "";
return;
}
const data = await resp.json();
const features = data.features || [];
document.getElementById("mapLoadingStatus").textContent = `${features.length} {% translate "périmètre(s) affiché(s)" %}`;
map.addSource('management-zones-overview', {
type: 'geojson',
data: data
});
map.addLayer({
id: 'management-zones-fill',
type: 'fill',
source: 'management-zones-overview',
paint: {
'fill-color': ['coalesce', ['get', 'color'], '#198754'],
'fill-opacity': 0.25
}
});
map.addLayer({
id: 'management-zones-line',
type: 'line',
source: 'management-zones-overview',
paint: {
'line-color': ['coalesce', ['get', 'color'], '#198754'],
'line-width': 2,
'line-dasharray': [3, 1.5]
}
});
const popup = new maplibregl.Popup({ closeButton: true, closeOnClick: true, maxWidth: '300px' });
map.on('click', 'management-zones-fill', function (e) {
if (!e.features || !e.features[0]) return;
const p = e.features[0].properties;
const detailUrl = "/assets/management-zones/" + p.id + "/";
const html = `
<div style="font-size: 12px; line-height: 1.4;">
<div style="display:flex; align-items:center; gap:6px; margin-bottom:4px;">
<span style="display:inline-block; width:12px; height:12px; border-radius:3px; background-color:${p.color || '#198754'};"></span>
<strong>${p.code}</strong>
</div>
<div class="fw-semibold mb-1">${p.name}</div>
${p.zone_type_name ? `<span class="badge bg-secondary mb-1">${p.zone_type_name}</span>` : ''}
${p.target_frequency_days ? `<div class="text-muted small"><i class="bi bi-arrow-repeat me-1"></i>${p.target_frequency_days} {% translate "jours" %}</div>` : ''}
<div class="mt-2 text-end">
<a href="${detailUrl}" class="btn btn-sm btn-outline-primary py-0 px-2" style="font-size: 11px;">
{% translate "Consulter la fiche" %} &rarr;
</a>
</div>
</div>
`;
popup.setLngLat(e.lngLat).setHTML(html).addTo(map);
});
map.on('mouseenter', 'management-zones-fill', () => { map.getCanvas().style.cursor = 'pointer'; });
map.on('mouseleave', 'management-zones-fill', () => { map.getCanvas().style.cursor = ''; });
// Fit bounds to features
if (features.length > 0) {
const bounds = new maplibregl.LngLatBounds();
features.forEach(f => {
const coords = f.geometry?.coordinates;
if (!coords) return;
const addCoords = (arr) => {
if (typeof arr[0] === 'number') {
bounds.extend(arr);
} else {
arr.forEach(addCoords);
}
};
addCoords(coords);
});
if (!bounds.isEmpty()) {
map.fitBounds(bounds, { padding: 40, maxZoom: 15 });
}
}
} catch (err) {
console.error("Error loading management zones overview map:", err);
document.getElementById("mapLoadingStatus").textContent = "";
}
});
});
</script>
{% endblock script %}