feat: update exceptional transport widget with refined UI, new status filters, and improved structure detail display
This commit is contained in:
parent
1ee9164faf
commit
90323a5013
2 changed files with 158 additions and 105 deletions
|
|
@ -723,19 +723,21 @@ def structures_edit_exceptional_transport(request, structure_id):
|
||||||
|
|
||||||
@require_GET
|
@require_GET
|
||||||
def structures_exceptional_transport_geojson(request):
|
def structures_exceptional_transport_geojson(request):
|
||||||
"""Retourne l'ensemble des ouvrages avec données de transport exceptionnel au format GeoJSON."""
|
"""Retourne l'ensemble des ouvrages pertinents (ponts, viaducs, tunnels, passerelles...) au format GeoJSON."""
|
||||||
if not request.user.is_authenticated or not can_view_exceptional_transport(request.user):
|
if not request.user.is_authenticated or not can_view_exceptional_transport(request.user):
|
||||||
return JsonResponse({'type': 'FeatureCollection', 'features': []})
|
return JsonResponse({'type': 'FeatureCollection', 'features': []})
|
||||||
|
|
||||||
te_list = (
|
structures = (
|
||||||
StructureExceptionalTransport.objects
|
Structure.objects
|
||||||
.select_related('structure', 'structure__main_location')
|
.exclude(structure_type='gallery')
|
||||||
|
.exclude(name_fr__icontains='galerie technique')
|
||||||
|
.exclude(name_nl__icontains='technische koker')
|
||||||
|
.select_related('exceptional_transport_info', 'main_location')
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
features = []
|
features = []
|
||||||
for te in te_list:
|
for structure in structures:
|
||||||
structure = te.structure
|
|
||||||
loc = structure.main_location
|
loc = structure.main_location
|
||||||
if not loc:
|
if not loc:
|
||||||
continue
|
continue
|
||||||
|
|
@ -760,9 +762,10 @@ def structures_exceptional_transport_geojson(request):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
detail_url = reverse('assets:structures_locations_detail', args=[loc.id])
|
detail_url = reverse('assets:structures_locations_detail', args=[loc.id])
|
||||||
|
te = getattr(structure, 'exceptional_transport_info', None)
|
||||||
|
|
||||||
h_str = ""
|
h_str = ""
|
||||||
if te.clearance_height_left_m or te.clearance_height_center_m or te.clearance_height_right_m:
|
if te and (te.clearance_height_left_m or te.clearance_height_center_m or te.clearance_height_right_m):
|
||||||
h_left = f"{te.clearance_height_left_m}m" if te.clearance_height_left_m else "-"
|
h_left = f"{te.clearance_height_left_m}m" if te.clearance_height_left_m else "-"
|
||||||
h_center = f"{te.clearance_height_center_m}m" if te.clearance_height_center_m else "-"
|
h_center = f"{te.clearance_height_center_m}m" if te.clearance_height_center_m else "-"
|
||||||
h_right = f"{te.clearance_height_right_m}m" if te.clearance_height_right_m else "-"
|
h_right = f"{te.clearance_height_right_m}m" if te.clearance_height_right_m else "-"
|
||||||
|
|
@ -775,33 +778,35 @@ def structures_exceptional_transport_geojson(request):
|
||||||
'coordinates': [lon, lat]
|
'coordinates': [lon, lat]
|
||||||
},
|
},
|
||||||
'properties': {
|
'properties': {
|
||||||
'id': te.id,
|
'id': te.id if te else None,
|
||||||
|
'has_te_info': bool(te),
|
||||||
'structure_id': structure.id,
|
'structure_id': structure.id,
|
||||||
'structure_code': structure.code or '',
|
'structure_code': structure.code or '',
|
||||||
'name': structure.get_name(),
|
'name': structure.get_name(),
|
||||||
'name_fr': structure.name_fr or '',
|
'name_fr': structure.name_fr or '',
|
||||||
'name_nl': structure.name_nl or '',
|
'name_nl': structure.name_nl or '',
|
||||||
'structure_type': structure.get_structure_type_display() if hasattr(structure, 'get_structure_type_display') else (structure.structure_type or ''),
|
'structure_type': structure.get_structure_type_display() if hasattr(structure, 'get_structure_type_display') else (structure.structure_type or ''),
|
||||||
|
'structure_type_code': structure.structure_type or '',
|
||||||
'location_id': loc.id,
|
'location_id': loc.id,
|
||||||
'location_code': loc.code or '',
|
'location_code': loc.code or '',
|
||||||
'detail_url': detail_url,
|
'detail_url': detail_url,
|
||||||
'fod_vnr': te.fod_vnr,
|
'fod_vnr': te.fod_vnr if te else None,
|
||||||
'national_id': te.national_id or '',
|
'national_id': te.national_id if te and te.national_id else '',
|
||||||
'bridge_class': te.bridge_class or '',
|
'bridge_class': te.bridge_class if te and te.bridge_class else '',
|
||||||
'passage_status': te.passage_status,
|
'passage_status': te.passage_status if te else 'not_evaluated',
|
||||||
'passage_status_display': str(te.get_passage_status_display()),
|
'passage_status_display': str(te.get_passage_status_display()) if te else str(_("Non évalué")),
|
||||||
'max_authorized_weight_tonnes': float(te.max_authorized_weight_tonnes) if te.max_authorized_weight_tonnes is not None else None,
|
'max_authorized_weight_tonnes': float(te.max_authorized_weight_tonnes) if te and te.max_authorized_weight_tonnes is not None else None,
|
||||||
'min_guaranteed_capacity_tonnes': float(te.min_guaranteed_capacity_tonnes) if te.min_guaranteed_capacity_tonnes is not None else None,
|
'min_guaranteed_capacity_tonnes': float(te.min_guaranteed_capacity_tonnes) if te and te.min_guaranteed_capacity_tonnes is not None else None,
|
||||||
'max_calculated_weight_tonnes': float(te.max_calculated_weight_tonnes) if te.max_calculated_weight_tonnes is not None else None,
|
'max_calculated_weight_tonnes': float(te.max_calculated_weight_tonnes) if te and te.max_calculated_weight_tonnes is not None else None,
|
||||||
'usable_width_m': float(te.usable_width_m) if te.usable_width_m is not None else None,
|
'usable_width_m': float(te.usable_width_m) if te and te.usable_width_m is not None else None,
|
||||||
'clearance_heights_str': h_str,
|
'clearance_heights_str': h_str,
|
||||||
'max_speed_kmh': te.max_speed_kmh,
|
'max_speed_kmh': te.max_speed_kmh if te else None,
|
||||||
'is_braking_forbidden': te.is_braking_forbidden,
|
'is_braking_forbidden': te.is_braking_forbidden if te else False,
|
||||||
'is_isolated_crossing_only': te.is_isolated_crossing_only,
|
'is_isolated_crossing_only': te.is_isolated_crossing_only if te else False,
|
||||||
'safety_distance_m': float(te.safety_distance_m) if te.safety_distance_m is not None else None,
|
'safety_distance_m': float(te.safety_distance_m) if te and te.safety_distance_m is not None else None,
|
||||||
'prescriptions_fr': te.prescriptions_fr or '',
|
'prescriptions_fr': te.prescriptions_fr if te and te.prescriptions_fr else '',
|
||||||
'prescriptions_nl': te.prescriptions_nl or '',
|
'prescriptions_nl': te.prescriptions_nl if te and te.prescriptions_nl else '',
|
||||||
'last_study_ref': te.last_study_ref or '',
|
'last_study_ref': te.last_study_ref if te and te.last_study_ref else '',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,14 @@
|
||||||
{% translate "Transport Exceptionnel & Convois Lourds" %}
|
{% translate "Transport Exceptionnel & Convois Lourds" %}
|
||||||
</h5>
|
</h5>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
{% translate "Cartographie du réseau d'ouvrages d'art et conditions d'admissibilité" %}
|
{% translate "Cartographie du réseau d'ouvrages d'art (ponts, viaducs, tunnels, passerelles) et conditions de passage" %}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span class="badge bg-primary-subtle text-primary border border-primary-subtle px-3 py-2 rounded-pill fw-semibold" id="te-total-badge">
|
<span class="badge bg-primary-subtle text-primary border border-primary-subtle px-3 py-2 rounded-pill fw-semibold" id="te-total-badge">
|
||||||
<i class="bi bi-geo-alt-fill me-1"></i><span id="te-total-count">0</span> {% translate "ouvrages répertoriés" %}
|
<i class="bi bi-geo-alt-fill me-1"></i><span id="te-total-count">0</span> {% translate "ouvrages sur la carte" %}
|
||||||
</span>
|
</span>
|
||||||
<a href="{% url 'assets:structures_index' %}" class="btn btn-outline-secondary btn-sm rounded-pill px-3 py-1 fw-medium" title="{% translate 'Voir tous les ouvrages d\'art' %}">
|
<a href="{% url 'assets:structures_index' %}" class="btn btn-outline-secondary btn-sm rounded-pill px-3 py-1 fw-medium" title="{% translate 'Voir tous les ouvrages d\'art' %}">
|
||||||
<i class="bi bi-arrow-right me-1"></i>{% translate "Tous les ouvrages" %}
|
<i class="bi bi-arrow-right me-1"></i>{% translate "Tous les ouvrages" %}
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
<!-- Controls Bar: Search & Status Filters -->
|
<!-- Controls Bar: Search & Status Filters -->
|
||||||
<div class="row g-2 align-items-center mb-3">
|
<div class="row g-2 align-items-center mb-3">
|
||||||
<!-- Search Autocomplete -->
|
<!-- Search Autocomplete -->
|
||||||
<div class="col-12 col-lg-6 position-relative">
|
<div class="col-12 col-lg-5 position-relative">
|
||||||
<div class="input-group shadow-sm rounded-pill overflow-hidden border">
|
<div class="input-group shadow-sm rounded-pill overflow-hidden border">
|
||||||
<span class="input-group-text bg-white border-0 ps-3 text-muted">
|
<span class="input-group-text bg-white border-0 ps-3 text-muted">
|
||||||
<i class="bi bi-search"></i>
|
<i class="bi bi-search"></i>
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="te-search-input"
|
id="te-search-input"
|
||||||
class="form-control border-0 py-2 shadow-none"
|
class="form-control border-0 py-2 shadow-none"
|
||||||
placeholder="{% translate 'Rechercher un ouvrage (code ex: OA120, nom, n° FOD, BKL...)' %}"
|
placeholder="{% translate 'Rechercher (ex: OA120, pont, tunnel, passerelle, VNR...)' %}"
|
||||||
autocomplete="off">
|
autocomplete="off">
|
||||||
<button class="btn btn-white border-0 pe-3 text-muted d-none" type="button" id="te-search-clear" title="{% translate 'Effacer la recherche' %}">
|
<button class="btn btn-white border-0 pe-3 text-muted d-none" type="button" id="te-search-clear" title="{% translate 'Effacer la recherche' %}">
|
||||||
<i class="bi bi-x-circle-fill"></i>
|
<i class="bi bi-x-circle-fill"></i>
|
||||||
|
|
@ -50,12 +50,12 @@
|
||||||
<!-- Autocomplete Dropdown List -->
|
<!-- Autocomplete Dropdown List -->
|
||||||
<div id="te-autocomplete-dropdown"
|
<div id="te-autocomplete-dropdown"
|
||||||
class="list-group position-absolute w-100 shadow-lg rounded-3 mt-1 overflow-auto border-0 d-none"
|
class="list-group position-absolute w-100 shadow-lg rounded-3 mt-1 overflow-auto border-0 d-none"
|
||||||
style="max-height: 320px; z-index: 1060; left: 0; background: rgba(255, 255, 255, 0.98); backdrop-filter: blur(10px);">
|
style="max-height: 340px; z-index: 1060; left: 0; background: rgba(255, 255, 255, 0.98); backdrop-filter: blur(10px);">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status Filter Chips -->
|
<!-- Status Filter Chips -->
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-7">
|
||||||
<div class="d-flex flex-wrap gap-1 justify-content-lg-end" id="te-status-filters">
|
<div class="d-flex flex-wrap gap-1 justify-content-lg-end" id="te-status-filters">
|
||||||
<button type="button" class="btn btn-sm btn-dark rounded-pill px-3 py-1 te-filter-btn active" data-status="all">
|
<button type="button" class="btn btn-sm btn-dark rounded-pill px-3 py-1 te-filter-btn active" data-status="all">
|
||||||
{% translate "Tous" %} (<span id="te-count-all">0</span>)
|
{% translate "Tous" %} (<span id="te-count-all">0</span>)
|
||||||
|
|
@ -66,15 +66,18 @@
|
||||||
<button type="button" class="btn btn-sm btn-outline-warning rounded-pill px-2 py-1 te-filter-btn text-dark" data-status="conditional">
|
<button type="button" class="btn btn-sm btn-outline-warning rounded-pill px-2 py-1 te-filter-btn text-dark" data-status="conditional">
|
||||||
<i class="bi bi-exclamation-triangle-fill me-1 text-warning"></i>{% translate "Sous conditions" %} (<span id="te-count-conditional">0</span>)
|
<i class="bi bi-exclamation-triangle-fill me-1 text-warning"></i>{% translate "Sous conditions" %} (<span id="te-count-conditional">0</span>)
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill px-2 py-1 te-filter-btn" data-status="other">
|
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill px-2 py-1 te-filter-btn" data-status="not_evaluated">
|
||||||
{% translate "Autres" %} (<span id="te-count-other">0</span>)
|
<i class="bi bi-clock-history me-1"></i>{% translate "Non évalué" %} (<span id="te-count-not-evaluated">0</span>)
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger rounded-pill px-2 py-1 te-filter-btn" data-status="restricted">
|
||||||
|
<i class="bi bi-slash-circle me-1"></i>{% translate "Restreint/Interdit" %} (<span id="te-count-restricted">0</span>)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Map Container -->
|
<!-- Map Container -->
|
||||||
<div class="position-relative rounded-4 overflow-hidden border shadow-inner" style="height: 540px;">
|
<div class="position-relative rounded-4 overflow-hidden border shadow-inner" style="height: 550px;">
|
||||||
<div id="te-map-container" style="width: 100%; height: 100%;"></div>
|
<div id="te-map-container" style="width: 100%; height: 100%;"></div>
|
||||||
|
|
||||||
<!-- Floating Map Controls Overlay (Top Right) -->
|
<!-- Floating Map Controls Overlay (Top Right) -->
|
||||||
|
|
@ -86,7 +89,7 @@
|
||||||
|
|
||||||
<!-- Floating Legend (Bottom Left) -->
|
<!-- Floating Legend (Bottom Left) -->
|
||||||
<div class="position-absolute bottom-0 start-0 m-3 p-2 px-3 rounded-3 shadow-sm border bg-white bg-opacity-95 text-start"
|
<div class="position-absolute bottom-0 start-0 m-3 p-2 px-3 rounded-3 shadow-sm border bg-white bg-opacity-95 text-start"
|
||||||
style="z-index: 5; font-size: 0.8rem; max-width: 280px; backdrop-filter: blur(8px);">
|
style="z-index: 5; font-size: 0.8rem; max-width: 300px; backdrop-filter: blur(8px);">
|
||||||
<div class="fw-bold mb-1 text-secondary text-uppercase" style="font-size: 0.7rem; letter-spacing: 0.5px;">
|
<div class="fw-bold mb-1 text-secondary text-uppercase" style="font-size: 0.7rem; letter-spacing: 0.5px;">
|
||||||
{% translate "Statut de passage" %}
|
{% translate "Statut de passage" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -100,16 +103,12 @@
|
||||||
<span>{% translate "Sous conditions / prescriptions" %}</span>
|
<span>{% translate "Sous conditions / prescriptions" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span class="rounded-circle d-inline-block" style="width: 12px; height: 12px; background-color: #fd7e14; border: 2px solid white; box-shadow: 0 0 2px rgba(0,0,0,0.5);"></span>
|
<span class="rounded-circle d-inline-block" style="width: 12px; height: 12px; background-color: #6c757d; border: 2px solid white; box-shadow: 0 0 2px rgba(0,0,0,0.5);"></span>
|
||||||
<span>{% translate "Étude préalable requise" %}</span>
|
<span>{% translate "Non évalué (fiche à compléter)" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span class="rounded-circle d-inline-block" style="width: 12px; height: 12px; background-color: #dc3545; border: 2px solid white; box-shadow: 0 0 2px rgba(0,0,0,0.5);"></span>
|
<span class="rounded-circle d-inline-block" style="width: 12px; height: 12px; background-color: #dc3545; border: 2px solid white; box-shadow: 0 0 2px rgba(0,0,0,0.5);"></span>
|
||||||
<span>{% translate "Interdit" %}</span>
|
<span>{% translate "Interdit / Étude requise" %}</span>
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
|
||||||
<span class="rounded-circle d-inline-block" style="width: 12px; height: 12px; background-color: #6c757d; border: 2px solid white; box-shadow: 0 0 2px rgba(0,0,0,0.5);"></span>
|
|
||||||
<span>{% translate "Non évalué" %}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -218,13 +217,14 @@
|
||||||
|
|
||||||
function updateCounts() {
|
function updateCounts() {
|
||||||
let total = teFeatures.length;
|
let total = teFeatures.length;
|
||||||
let auth = 0, cond = 0, other = 0;
|
let auth = 0, cond = 0, notEval = 0, restricted = 0;
|
||||||
|
|
||||||
teFeatures.forEach(f => {
|
teFeatures.forEach(f => {
|
||||||
const st = f.properties.passage_status;
|
const st = f.properties.passage_status;
|
||||||
if (st === 'authorized') auth++;
|
if (st === 'authorized') auth++;
|
||||||
else if (st === 'conditional') cond++;
|
else if (st === 'conditional') cond++;
|
||||||
else other++;
|
else if (st === 'not_evaluated' || !f.properties.has_te_info) notEval++;
|
||||||
|
else restricted++;
|
||||||
});
|
});
|
||||||
|
|
||||||
const elTotal = document.getElementById('te-total-count');
|
const elTotal = document.getElementById('te-total-count');
|
||||||
|
|
@ -235,8 +235,10 @@
|
||||||
if (elAuth) elAuth.textContent = auth;
|
if (elAuth) elAuth.textContent = auth;
|
||||||
const elCond = document.getElementById('te-count-conditional');
|
const elCond = document.getElementById('te-count-conditional');
|
||||||
if (elCond) elCond.textContent = cond;
|
if (elCond) elCond.textContent = cond;
|
||||||
const elOther = document.getElementById('te-count-other');
|
const elNotEval = document.getElementById('te-count-not-evaluated');
|
||||||
if (elOther) elOther.textContent = other;
|
if (elNotEval) elNotEval.textContent = notEval;
|
||||||
|
const elRestricted = document.getElementById('te-count-restricted');
|
||||||
|
if (elRestricted) elRestricted.textContent = restricted;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMapLayers() {
|
function renderMapLayers() {
|
||||||
|
|
@ -291,7 +293,11 @@
|
||||||
],
|
],
|
||||||
'circle-stroke-width': 2,
|
'circle-stroke-width': 2,
|
||||||
'circle-stroke-color': '#ffffff',
|
'circle-stroke-color': '#ffffff',
|
||||||
'circle-opacity': 0.95
|
'circle-opacity': [
|
||||||
|
'case',
|
||||||
|
['==', ['get', 'passage_status'], 'not_evaluated'], 0.7,
|
||||||
|
0.95
|
||||||
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -352,63 +358,88 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSingleStructurePopupHtml(p) {
|
function buildSingleStructurePopupHtml(p) {
|
||||||
|
const hasTe = p.has_te_info && p.passage_status !== 'not_evaluated';
|
||||||
const statusBadge = getStatusBadgeHtml(p.passage_status, p.passage_status_display);
|
const statusBadge = getStatusBadgeHtml(p.passage_status, p.passage_status_display);
|
||||||
|
|
||||||
let metricsHtml = '<div class="row g-2 mb-2">';
|
let typeBadge = p.structure_type ? `<span class="badge bg-light text-secondary border ms-1">${p.structure_type}</span>` : '';
|
||||||
metricsHtml += `
|
let bodyContent = '';
|
||||||
<div class="col-6">
|
|
||||||
<div class="te-metric-box">
|
if (hasTe) {
|
||||||
<div class="small text-muted" style="font-size: 0.7rem;">Poids max autorisé</div>
|
let metricsHtml = '<div class="row g-2 mb-2">';
|
||||||
<div class="fw-bold text-dark">${p.max_authorized_weight_tonnes ? p.max_authorized_weight_tonnes + ' t' : (p.max_calculated_weight_tonnes ? p.max_calculated_weight_tonnes + ' t (calc)' : '-')}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<div class="te-metric-box">
|
|
||||||
<div class="small text-muted" style="font-size: 0.7rem;">Classe de pont (BKL)</div>
|
|
||||||
<div class="fw-bold text-dark text-truncate" title="${p.bridge_class || '-'}">${p.bridge_class || '-'}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
if (p.usable_width_m || p.clearance_heights_str) {
|
|
||||||
metricsHtml += `
|
metricsHtml += `
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="te-metric-box">
|
<div class="te-metric-box">
|
||||||
<div class="small text-muted" style="font-size: 0.7rem;">Largeur utile</div>
|
<div class="small text-muted" style="font-size: 0.7rem;">Poids max autorisé</div>
|
||||||
<div class="fw-bold text-dark">${p.usable_width_m ? p.usable_width_m + ' m' : '-'}</div>
|
<div class="fw-bold text-dark">${p.max_authorized_weight_tonnes ? p.max_authorized_weight_tonnes + ' t' : (p.max_calculated_weight_tonnes ? p.max_calculated_weight_tonnes + ' t (calc)' : '-')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="te-metric-box">
|
<div class="te-metric-box">
|
||||||
<div class="small text-muted" style="font-size: 0.7rem;">Hauteurs (G/C/D)</div>
|
<div class="small text-muted" style="font-size: 0.7rem;">Classe de pont (BKL)</div>
|
||||||
<div class="fw-bold text-dark text-truncate" title="${p.clearance_heights_str || '-'}">${p.clearance_heights_str || '-'}</div>
|
<div class="fw-bold text-dark text-truncate" title="${p.bridge_class || '-'}">${p.bridge_class || '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
if (p.usable_width_m || p.clearance_heights_str) {
|
||||||
metricsHtml += '</div>';
|
metricsHtml += `
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="te-metric-box">
|
||||||
|
<div class="small text-muted" style="font-size: 0.7rem;">Largeur utile</div>
|
||||||
|
<div class="fw-bold text-dark">${p.usable_width_m ? p.usable_width_m + ' m' : '-'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="te-metric-box">
|
||||||
|
<div class="small text-muted" style="font-size: 0.7rem;">Hauteurs (G/C/D)</div>
|
||||||
|
<div class="fw-bold text-dark text-truncate" title="${p.clearance_heights_str || '-'}">${p.clearance_heights_str || '-'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
metricsHtml += '</div>';
|
||||||
|
|
||||||
// Restrictions badges
|
// Restrictions badges
|
||||||
let restrictionsBadges = '';
|
let restrictionsBadges = '';
|
||||||
if (p.max_speed_kmh) {
|
if (p.max_speed_kmh) {
|
||||||
restrictionsBadges += `<span class="badge bg-warning text-dark me-1 mb-1"><i class="bi bi-speedometer2 me-1"></i>Max ${p.max_speed_kmh} km/h</span>`;
|
restrictionsBadges += `<span class="badge bg-warning text-dark me-1 mb-1"><i class="bi bi-speedometer2 me-1"></i>Max ${p.max_speed_kmh} km/h</span>`;
|
||||||
}
|
}
|
||||||
if (p.is_braking_forbidden) {
|
if (p.is_braking_forbidden) {
|
||||||
restrictionsBadges += `<span class="badge bg-danger me-1 mb-1"><i class="bi bi-slash-circle me-1"></i>Freinage interdit</span>`;
|
restrictionsBadges += `<span class="badge bg-danger me-1 mb-1"><i class="bi bi-slash-circle me-1"></i>Freinage interdit</span>`;
|
||||||
}
|
}
|
||||||
if (p.is_isolated_crossing_only) {
|
if (p.is_isolated_crossing_only) {
|
||||||
restrictionsBadges += `<span class="badge bg-info text-dark me-1 mb-1"><i class="bi bi-person-slash me-1"></i>Convoi seul</span>`;
|
restrictionsBadges += `<span class="badge bg-info text-dark me-1 mb-1"><i class="bi bi-person-slash me-1"></i>Convoi seul</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prescriptions excerpt
|
// Prescriptions excerpt
|
||||||
let prescText = p.prescriptions_fr || p.prescriptions_nl || '';
|
let prescText = p.prescriptions_fr || p.prescriptions_nl || '';
|
||||||
let prescHtml = '';
|
let prescHtml = '';
|
||||||
if (prescText) {
|
if (prescText) {
|
||||||
let shortPresc = prescText.length > 130 ? prescText.substring(0, 130) + '...' : prescText;
|
let shortPresc = prescText.length > 130 ? prescText.substring(0, 130) + '...' : prescText;
|
||||||
prescHtml = `
|
prescHtml = `
|
||||||
<div class="alert alert-light border p-2 mb-2 text-start" style="font-size: 0.75rem; max-height: 80px; overflow-y: auto;">
|
<div class="alert alert-light border p-2 mb-2 text-start" style="font-size: 0.75rem; max-height: 80px; overflow-y: auto;">
|
||||||
<div class="fw-bold text-secondary mb-1"><i class="bi bi-card-text me-1"></i>Prescriptions :</div>
|
<div class="fw-bold text-secondary mb-1"><i class="bi bi-card-text me-1"></i>Prescriptions :</div>
|
||||||
${shortPresc}
|
${shortPresc}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyContent = `
|
||||||
|
${metricsHtml}
|
||||||
|
${restrictionsBadges ? `<div class="mb-2 text-start">${restrictionsBadges}</div>` : ''}
|
||||||
|
${prescHtml}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
// Not evaluated yet
|
||||||
|
let overpassNote = (p.structure_type_code === 'overpass')
|
||||||
|
? `<div class="text-primary small mb-2"><i class="bi bi-info-circle me-1"></i><strong>Passerelle / Ouvrage piétonnier</strong> : gabarit de hauteur libre à renseigner.</div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
bodyContent = `
|
||||||
|
<div class="alert alert-secondary border-0 p-3 mb-2 text-start rounded-3" style="font-size: 0.8rem; background-color: #f3f4f6;">
|
||||||
|
<div class="fw-bold text-dark mb-1"><i class="bi bi-clock-history me-1 text-secondary"></i>Non évalué pour le transport exceptionnel</div>
|
||||||
|
<div class="text-muted">Cet ouvrage n'a pas encore de fiche technique TE enregistrée.</div>
|
||||||
</div>
|
</div>
|
||||||
|
${overpassNote}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -416,7 +447,10 @@
|
||||||
<div class="card border-0 shadow-none">
|
<div class="card border-0 shadow-none">
|
||||||
<div class="card-header bg-light border-bottom p-3 text-start">
|
<div class="card-header bg-light border-bottom p-3 text-start">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
<span class="badge bg-secondary">${p.structure_code}</span>
|
<div>
|
||||||
|
<span class="badge bg-secondary">${p.structure_code}</span>
|
||||||
|
${typeBadge}
|
||||||
|
</div>
|
||||||
${p.fod_vnr ? `<span class="small text-muted">FOD n°${p.fod_vnr}</span>` : ''}
|
${p.fod_vnr ? `<span class="small text-muted">FOD n°${p.fod_vnr}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="fw-bold text-dark" style="font-size: 0.95rem; line-height: 1.2;">
|
<div class="fw-bold text-dark" style="font-size: 0.95rem; line-height: 1.2;">
|
||||||
|
|
@ -425,9 +459,7 @@
|
||||||
<div class="mt-2">${statusBadge}</div>
|
<div class="mt-2">${statusBadge}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
${metricsHtml}
|
${bodyContent}
|
||||||
${restrictionsBadges ? `<div class="mb-2 text-start">${restrictionsBadges}</div>` : ''}
|
|
||||||
${prescHtml}
|
|
||||||
<a href="${p.detail_url}" class="btn btn-primary btn-sm w-100 rounded-pill fw-semibold mt-1">
|
<a href="${p.detail_url}" class="btn btn-primary btn-sm w-100 rounded-pill fw-semibold mt-1">
|
||||||
<i class="bi bi-box-arrow-up-right me-1"></i>{% translate "Ouvrir la fiche de l'ouvrage" %}
|
<i class="bi bi-box-arrow-up-right me-1"></i>{% translate "Ouvrir la fiche de l'ouvrage" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -441,15 +473,19 @@
|
||||||
features.forEach((f, idx) => {
|
features.forEach((f, idx) => {
|
||||||
const p = f.properties;
|
const p = f.properties;
|
||||||
const statusBadge = getStatusBadgeHtml(p.passage_status, p.passage_status_display);
|
const statusBadge = getStatusBadgeHtml(p.passage_status, p.passage_status_display);
|
||||||
|
let typeBadge = p.structure_type ? `<span class="badge bg-light text-secondary border ms-1">${p.structure_type}</span>` : '';
|
||||||
itemsHtml += `
|
itemsHtml += `
|
||||||
<div class="border-bottom pb-2 mb-2 text-start ${idx > 0 ? 'pt-2' : ''}">
|
<div class="border-bottom pb-2 mb-2 text-start ${idx > 0 ? 'pt-2' : ''}">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<span class="badge bg-secondary me-1">${p.structure_code}</span>
|
<div>
|
||||||
|
<span class="badge bg-secondary me-1">${p.structure_code}</span>
|
||||||
|
${typeBadge}
|
||||||
|
</div>
|
||||||
${statusBadge}
|
${statusBadge}
|
||||||
</div>
|
</div>
|
||||||
<div class="fw-bold text-dark small mt-1">${p.name || p.name_fr}</div>
|
<div class="fw-bold text-dark small mt-1">${p.name || p.name_fr}</div>
|
||||||
<div class="d-flex justify-content-between align-items-center mt-2">
|
<div class="d-flex justify-content-between align-items-center mt-2">
|
||||||
<span class="small text-muted">Poids: <strong>${p.max_authorized_weight_tonnes ? p.max_authorized_weight_tonnes + 't' : '-'}</strong></span>
|
<span class="small text-muted">${p.max_authorized_weight_tonnes ? 'Poids: <strong>' + p.max_authorized_weight_tonnes + 't</strong>' : '<em>Non évalué</em>'}</span>
|
||||||
<a href="${p.detail_url}" class="btn btn-sm btn-outline-primary py-0 px-2 rounded-pill" style="font-size: 0.75rem;">
|
<a href="${p.detail_url}" class="btn btn-sm btn-outline-primary py-0 px-2 rounded-pill" style="font-size: 0.75rem;">
|
||||||
{% translate "Détail" %} <i class="bi bi-arrow-right"></i>
|
{% translate "Détail" %} <i class="bi bi-arrow-right"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -487,6 +523,9 @@
|
||||||
} else if (status === 'forbidden') {
|
} else if (status === 'forbidden') {
|
||||||
colorClass = 'bg-danger';
|
colorClass = 'bg-danger';
|
||||||
icon = 'bi-slash-circle-fill';
|
icon = 'bi-slash-circle-fill';
|
||||||
|
} else if (status === 'not_evaluated') {
|
||||||
|
colorClass = 'bg-secondary bg-opacity-75 text-white';
|
||||||
|
icon = 'bi-clock-history';
|
||||||
}
|
}
|
||||||
return `<span class="badge ${colorClass} py-1 px-2"><i class="bi ${icon} me-1"></i>${display || status}</span>`;
|
return `<span class="badge ${colorClass} py-1 px-2"><i class="bi ${icon} me-1"></i>${display || status}</span>`;
|
||||||
}
|
}
|
||||||
|
|
@ -512,21 +551,24 @@
|
||||||
|
|
||||||
// Filter features
|
// Filter features
|
||||||
const matches = teFeatures.filter(f => {
|
const matches = teFeatures.filter(f => {
|
||||||
|
const p = f.properties;
|
||||||
|
const st = p.passage_status;
|
||||||
|
|
||||||
if (activeFilter !== 'all') {
|
if (activeFilter !== 'all') {
|
||||||
const st = f.properties.passage_status;
|
|
||||||
if (activeFilter === 'authorized' && st !== 'authorized') return false;
|
if (activeFilter === 'authorized' && st !== 'authorized') return false;
|
||||||
if (activeFilter === 'conditional' && st !== 'conditional') return false;
|
if (activeFilter === 'conditional' && st !== 'conditional') return false;
|
||||||
if (activeFilter === 'other' && (st === 'authorized' || st === 'conditional')) return false;
|
if (activeFilter === 'not_evaluated' && st !== 'not_evaluated') return false;
|
||||||
|
if (activeFilter === 'restricted' && (st === 'authorized' || st === 'conditional' || st === 'not_evaluated')) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const p = f.properties;
|
|
||||||
const code = (p.structure_code || '').toLowerCase();
|
const code = (p.structure_code || '').toLowerCase();
|
||||||
const nameFr = (p.name_fr || '').toLowerCase();
|
const nameFr = (p.name_fr || '').toLowerCase();
|
||||||
const nameNl = (p.name_nl || '').toLowerCase();
|
const nameNl = (p.name_nl || '').toLowerCase();
|
||||||
const vnr = p.fod_vnr ? String(p.fod_vnr) : '';
|
const vnr = p.fod_vnr ? String(p.fod_vnr) : '';
|
||||||
const bkl = (p.bridge_class || '').toLowerCase();
|
const bkl = (p.bridge_class || '').toLowerCase();
|
||||||
|
const typeStr = (p.structure_type || '').toLowerCase();
|
||||||
|
|
||||||
return code.includes(query) || nameFr.includes(query) || nameNl.includes(query) || vnr.includes(query) || bkl.includes(query);
|
return code.includes(query) || nameFr.includes(query) || nameNl.includes(query) || vnr.includes(query) || bkl.includes(query) || typeStr.includes(query);
|
||||||
}).slice(0, 8);
|
}).slice(0, 8);
|
||||||
|
|
||||||
if (matches.length === 0) {
|
if (matches.length === 0) {
|
||||||
|
|
@ -539,16 +581,20 @@
|
||||||
matches.forEach(f => {
|
matches.forEach(f => {
|
||||||
const p = f.properties;
|
const p = f.properties;
|
||||||
const statusBadge = getStatusBadgeHtml(p.passage_status, p.passage_status_display);
|
const statusBadge = getStatusBadgeHtml(p.passage_status, p.passage_status_display);
|
||||||
|
let typeBadge = p.structure_type ? `<span class="badge bg-light text-secondary border ms-1">${p.structure_type}</span>` : '';
|
||||||
html += `
|
html += `
|
||||||
<button type="button" class="list-group-item list-group-item-action te-autocomplete-item p-2 px-3 border-bottom text-start" data-structure-id="${p.structure_id}">
|
<button type="button" class="list-group-item list-group-item-action te-autocomplete-item p-2 px-3 border-bottom text-start" data-structure-id="${p.structure_id}">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<span class="badge bg-secondary">${p.structure_code}</span>
|
<div>
|
||||||
|
<span class="badge bg-secondary">${p.structure_code}</span>
|
||||||
|
${typeBadge}
|
||||||
|
</div>
|
||||||
${statusBadge}
|
${statusBadge}
|
||||||
</div>
|
</div>
|
||||||
<div class="fw-bold text-dark small mt-1 text-truncate">${p.name || p.name_fr}</div>
|
<div class="fw-bold text-dark small mt-1 text-truncate">${p.name || p.name_fr}</div>
|
||||||
<div class="small text-muted d-flex justify-content-between mt-1" style="font-size: 0.75rem;">
|
<div class="small text-muted d-flex justify-content-between mt-1" style="font-size: 0.75rem;">
|
||||||
<span>${p.fod_vnr ? 'FOD: ' + p.fod_vnr : ''}</span>
|
<span>${p.fod_vnr ? 'FOD: ' + p.fod_vnr : (p.structure_type || '')}</span>
|
||||||
<span>Poids: ${p.max_authorized_weight_tonnes ? p.max_authorized_weight_tonnes + 't' : (p.max_calculated_weight_tonnes ? p.max_calculated_weight_tonnes + 't' : '-')}</span>
|
<span>${p.max_authorized_weight_tonnes ? 'Poids: ' + p.max_authorized_weight_tonnes + 't' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
@ -608,10 +654,12 @@
|
||||||
filterExpr = ['==', ['get', 'passage_status'], 'authorized'];
|
filterExpr = ['==', ['get', 'passage_status'], 'authorized'];
|
||||||
} else if (activeFilter === 'conditional') {
|
} else if (activeFilter === 'conditional') {
|
||||||
filterExpr = ['==', ['get', 'passage_status'], 'conditional'];
|
filterExpr = ['==', ['get', 'passage_status'], 'conditional'];
|
||||||
} else if (activeFilter === 'other') {
|
} else if (activeFilter === 'not_evaluated') {
|
||||||
filterExpr = ['all',
|
filterExpr = ['==', ['get', 'passage_status'], 'not_evaluated'];
|
||||||
['!=', ['get', 'passage_status'], 'authorized'],
|
} else if (activeFilter === 'restricted') {
|
||||||
['!=', ['get', 'passage_status'], 'conditional']
|
filterExpr = ['any',
|
||||||
|
['==', ['get', 'passage_status'], 'forbidden'],
|
||||||
|
['==', ['get', 'passage_status'], 'prior_study_required']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue