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

419 lines
No EOL
16 KiB
HTML

{% extends "assets/base_assets_index.html" %}
{% load static i18n %}
{% block locations_filter_form %}
<form method="get" id="filter-form" class="row g-3 mb-3 me-3" style="width: 41vw;">
<div class="col-md-4">
<label for="status" class="form-label">{% translate "Statut" %}</label>
<select name="status" id="status" class="form-select" multiple>
{% for key, value in status_choices %}
<option value="{{ key }}" {% if key in selected_statuses %}selected{% endif %}>
{{ value }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label for="voltage" class="form-label">{% translate "Voltage" %}</label>
<select name="voltage" id="voltage" class="form-select" multiple>
{% for key, value in voltage_choices %}
<option value="{{ key }}" {% if key in selected_voltages %}selected{% endif %}>
{{ value }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label for="begin_date" class="form-label">{% translate "Date de mise en service (à partir de)" %}</label>
<input type="date" name="begin_date" id="begin_date" class="form-control" value="{{ begin_date }}">
<br>
<label class="form-label">{% translate "Connexion à la centrale" %}</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="connected" id="connected_yes" value="yes" {% if connected == 'yes' %}checked{% endif %}>
<label class="form-check-label" for="connected_yes">{% translate "Oui" %}</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="connected" id="connected_no" value="no" {% if connected == 'no' %}checked{% endif %}>
<label class="form-check-label" for="connected_no">{% translate "Non" %}</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="connected" id="connected_all" value="" {% if not connected %}checked{% endif %}>
<label class="form-check-label" for="connected_all">{% translate "Tous" %}</label>
</div>
</div>
</div>
{% if is_intern %}
<div class="col-md-4">
<label for="controller_maintenance_provider" class="form-label">{% translate "Prestataire maintenance contrôleurs" %}</label>
<select name="controller_maintenance_provider" id="controller_maintenance_provider" class="form-select">
<option value="">{% translate "Tous" %}</option>
{% for contract_id, contract_name in controller_maintenance_contracts %}
<option value="{{ contract_id }}" {% if controller_maintenance_provider == contract_id|stringformat:"s" %}selected{% endif %}>
{{ contract_name }}
</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="col-12">
<button type="button" id="apply-filters" class="btn btn-primary">{% translate "Filtrer" %}</button>
<button type="button" id="reset-filters" class="btn btn-secondary">{% translate "Réinitialiser" %}</button>
</div>
</form>
{% endblock locations_filter_form %}
{% block location_row_actions %}
{% if can_create_location %}
<button type="button" class="btn btn-sm btn-outline-primary" data-location-create
title="{% translate 'Créer une nouvelle intersection' %}">
<i class="bi bi-plus-circle"></i>
</button>
{% endif %}
{% endblock location_row_actions %}
{% block assets_filter_form %}
<form method="get" id="assets-filter-form" class="row g-3 mb-3 me-3" style="width: 600px;">
<div class="col-6">
<label for="category" class="form-label">{% translate "Catégorie" %}</label>
<select name="category" id="category" class="form-select" multiple>
{% for key, value in category_choices %}
<option value="{{ key }}" {% if key in selected_categories %}selected{% endif %}>
{{ value }}
</option>
{% endfor %}
</select>
</div>
<div class="col-6">
<label for="model" class="form-label">{% translate "Modèle" %}</label>
<select name="model" id="model" class="form-select" multiple>
{% for key, value in model_choices %}
<option value="{{ key }}" {% if key in selected_models %}selected{% endif %}>
{{ value }}
</option>
{% endfor %}
</select>
</div>
<div class="col-12 d-flex justify-content-between mt-4" style="width: 200px;">
<button type="button" id="apply-assets-filters" class="btn btn-sm btn-primary">{% translate "Filtrer" %}</button>
<button type="button" id="reset-assets-filters" class="btn btn-sm btn-secondary">{% translate "Réinitialiser" %}</button>
</div>
</form>
{% endblock assets_filter_form %}
{% block const_script %}
<script>
const locationsgeojsonUrl = "{% url 'assets:trafficlights_intersections_geojson' %}";
const assetsgeojsonUrl = "{% url 'assets:trafficlights_assets_geojson' %}";
const locationsurl = "{% url 'assets:trafficlights_intersections' %}";
const assetsurl = "{% url 'assets:trafficlights_index' %}";
var locations_property_fields = ['name', 'voltage_type'];
var locations_columns = [
{ field: 'name', title: '{% translate "Nom" %}' },
{ field: 'voltage_type', title: '{% translate "Type" %}', align: 'center' }
];
var assets_property_fields = ['name', 'category', 'model'];
var assets_columns = [
{ field: 'name', title: '{% translate "Nom" %}' },
{ field: 'category', title: '{% translate "Catégorie" %}' },
{ field: 'model', title: '{% translate "Modèle" %}' }
]
const locations_layer_def = {
id: 'locations',
type: 'circle',
source: 'locations',
paint: {
'circle-radius': [
'interpolate',
['linear'],
['zoom'],
8, 1,
13, 4,
16, 7,
19, 10
],
'circle-stroke-color': [
'case',
['boolean', ['feature-state', 'highlighted'], false], '#ffeb3b',
'#00000000'
],
'circle-stroke-width': [
'case',
['boolean', ['feature-state', 'highlighted'], false], 3,
0
],
'circle-color': [
'match', ['get', 'status'],
'active', '#0094ff',
'in_preparation', '#f59f00',
'under_repair', '#f76707',
'voluntarily_out_of_service', '#e03131',
'inactive', '#e03131',
'archived', '#6c757d',
'#6c757d'
],
'circle-opacity': 0.7
},
minzoom: 7
};
const assets_layer_def = {
id: 'assets',
type: 'symbol',
source: 'assets',
layout: {
'icon-image': [
'match',
['get', 'asset_type'],
'trafficlightpole', 'traffic-light-icon',
'trafficlightradarpole', 'simple-pole-icon',
'trafficlightradar', 'radar-preventif-icon',
'trafficlightelectricalcabinet', 'electrical-panel-icon',
'trafficlightcontroller', 'electrical-panel-icon',
'trafficlightdetector', 'electrical-panel-icon',
'electrical-panel-icon' // default for non-pole trafficlights assets
],
'icon-size': [
'interpolate', ['linear'], ['zoom'],
8, 0.1,
14, 0.5,
22, 1
],
'icon-offset': [
'case',
['all', ['==', ['get', 'asset_type'], 'trafficlightradar'], ['coalesce', ['get', 'has_support'], false]], ['literal', [0, -25]],
['literal', [0, 0]]
],
'icon-allow-overlap': true,
'icon-ignore-placement': true,
'text-field': ['get', 'name'],
'text-font': ['Noto Sans Regular'],
'text-size': [
'interpolate',
['linear'],
['zoom'],
14, 8,
18, 11,
22, 14
],
'text-anchor': 'top',
'text-offset': [0, 1.5],
'text-allow-overlap': false,
'text-optional': true
},
paint: {
'icon-opacity': [
'case',
['boolean', ['feature-state', 'highlighted'], false], 1,
0.85
],
'text-color': '#333333',
'text-halo-color': 'rgba(255, 255, 255, 0.85)',
'text-halo-width': 3,
'text-halo-blur': 1
},
minzoom: 8
};
// Fallback circle layer for assets (shown before icon loads or if icon fails)
const assets_circle_layer_def = {
id: 'assets-circles',
type: 'circle',
source: 'assets',
paint: {
'circle-radius': [
'interpolate',
['linear'],
['zoom'],
8, 1,
22, 10
],
'circle-stroke-color': [
'case',
['boolean', ['feature-state', 'highlighted'], false], '#ffeb3b',
'#00000000'
],
'circle-stroke-width': [
'case',
['boolean', ['feature-state', 'highlighted'], false], 3,
0
],
'circle-color': '#0022ff',
'circle-opacity': 0.7
},
minzoom: 8
};
const assets_layers_defs = [assets_layer_def];
// Normalise an image URL onto a fixed-size canvas so icon-size is
// a true multiplier regardless of the source image's intrinsic dimensions.
function loadNormalisedIcon(url, size = 64) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
const scale = Math.min(size / img.width, size / img.height);
const w = img.width * scale;
const h = img.height * scale;
ctx.drawImage(img, (size - w) / 2, (size - h) / 2, w, h);
resolve(ctx.getImageData(0, 0, size, size));
};
img.onerror = () => reject(new Error('Failed to load icon: ' + url));
img.src = url;
});
}
// Load icons into the map
function loadMapIcons() {
// Load traffic light icon
if (!map.hasImage('traffic-light-icon')) {
loadNormalisedIcon("{% static 'assets/traffic_light_icon.svg' %}", 64).then(canvas => {
if (!map.hasImage('traffic-light-icon')) {
map.addImage('traffic-light-icon', canvas);
}
}).catch(e => console.warn(e));
}
// Load electrical panel icon
if (!map.hasImage('electrical-panel-icon')) {
loadNormalisedIcon("{% static 'assets/electrical-panel.png' %}", 32).then(canvas => {
if (!map.hasImage('electrical-panel-icon')) {
map.addImage('electrical-panel-icon', canvas);
}
}).catch(e => console.warn(e));
}
// Load prevention radar icon
if (!map.hasImage('radar-preventif-icon')) {
loadNormalisedIcon("{% static 'assets/radar_preventif_icon.svg' %}?v=20260710", 64).then(canvas => {
if (!map.hasImage('radar-preventif-icon')) {
map.addImage('radar-preventif-icon', canvas);
}
}).catch(e => console.warn(e));
}
// Load simple pole icon
if (!map.hasImage('simple-pole-icon')) {
loadNormalisedIcon("{% static 'assets/simple_pole_icon.svg' %}", 64).then(canvas => {
if (!map.hasImage('simple-pole-icon')) {
map.addImage('simple-pole-icon', canvas);
}
}).catch(e => console.warn(e));
}
// Register cross SDF icons for radar locations
const sz = 36;
const pad = 6;
if (!map.hasImage('cross-sdf')) {
const canvas = document.createElement('canvas');
canvas.width = sz; canvas.height = sz;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, sz, sz);
ctx.strokeStyle = '#000000';
ctx.lineWidth = 6;
ctx.lineCap = 'round';
ctx.beginPath();
// Horizontal
ctx.moveTo(pad, sz / 2);
ctx.lineTo(sz - pad, sz / 2);
// Vertical
ctx.moveTo(sz / 2, pad);
ctx.lineTo(sz / 2, sz - pad);
ctx.stroke();
map.addImage('cross-sdf', { width: sz, height: sz, data: ctx.getImageData(0, 0, sz, sz).data }, { sdf: true });
}
if (!map.hasImage('cross-sdf-border')) {
const bc = document.createElement('canvas');
bc.width = sz; bc.height = sz;
const bctx = bc.getContext('2d');
bctx.clearRect(0, 0, sz, sz);
bctx.strokeStyle = '#000000';
bctx.lineWidth = 12;
bctx.lineCap = 'round';
bctx.beginPath();
// Horizontal
bctx.moveTo(pad, sz / 2);
bctx.lineTo(sz - pad, sz / 2);
// Vertical
bctx.moveTo(sz / 2, pad);
bctx.lineTo(sz / 2, sz - pad);
bctx.stroke();
map.addImage('cross-sdf-border', { width: sz, height: sz, data: bctx.getImageData(0, 0, sz, sz).data }, { sdf: true });
}
}
const modelsByCategory = {{ category_model_map|safe }};
document.addEventListener("DOMContentLoaded", function () {
const categorySelect = document.getElementById("category");
const modelSelect = document.getElementById("model");
function updateModelOptions() {
const selectedCategories = Array.from(categorySelect.selectedOptions).map(opt => opt.value);
const previouslySelectedModels = Array.from(modelSelect.selectedOptions).map(opt => opt.value);
if (selectedCategories.length === 0) {
return;
}
modelSelect.innerHTML = "";
const allModels = [];
const addedKeys = new Set();
selectedCategories.forEach(cat => {
const models = modelsByCategory[cat] || [];
models.forEach(model => {
if (!addedKeys.has(model.key)) {
allModels.push(model);
addedKeys.add(model.key);
}
});
});
allModels.sort((a, b) => a.label.localeCompare(b.label));
allModels.forEach(model => {
const option = document.createElement("option");
option.value = model.key;
option.text = model.label;
if (previouslySelectedModels.includes(model.key)) {
option.selected = true;
}
modelSelect.appendChild(option);
});
}
categorySelect.addEventListener("change", updateModelOptions);
updateModelOptions();
});
</script>
{% endblock const_script %}
{% block extra_script %}
<script>
// Load icons when map is ready
maploader.addEventListener("mapLoaded", function(event) {
loadMapIcons();
});
</script>
{% if can_create_location %}
{% include "assets/components/location_edit_modals.html" with thematic_code=thematic_code location_type=location_type entry_label="Intersection" %}
{% endif %}
{% endblock extra_script %}