1248 lines
52 KiB
JavaScript
1248 lines
52 KiB
JavaScript
/**
|
|
* Inspection Map JS - Module de carte d'inspection rapide optimisé MapLibre GL, GPS & Mobile
|
|
*/
|
|
(function() {
|
|
let map = null;
|
|
let userMarker = null;
|
|
let loadedAssets = [];
|
|
|
|
const STORAGE_THEMATIC_KEY = 'streetup_insp_thematic';
|
|
const STORAGE_CATEGORY_KEY = 'streetup_insp_category';
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initMap();
|
|
initFilterStorage();
|
|
initFormHandler();
|
|
setupGeolocation();
|
|
});
|
|
|
|
function initMap() {
|
|
const mapContainer = document.getElementById('inspection-map');
|
|
if (!mapContainer || typeof maplibregl === 'undefined') return;
|
|
if (mapContainer.dataset.mapInitialized === 'true') return;
|
|
mapContainer.dataset.mapInitialized = 'true';
|
|
|
|
// Centrage et style depuis les settings globaux (window.MAP_STYLE_LIGHT / window.DEFAULT_MAP_CENTER)
|
|
const defaultCenter = window.DEFAULT_MAP_CENTER || [4.3517, 50.8503];
|
|
const defaultZoom = window.DEFAULT_MAP_ZOOM || 15;
|
|
const styleUrl = window.MAP_STYLE_LIGHT || 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';
|
|
|
|
map = new maplibregl.Map({
|
|
container: 'inspection-map',
|
|
style: styleUrl,
|
|
center: defaultCenter,
|
|
zoom: defaultZoom,
|
|
minzoom: 0,
|
|
maxzoom: 24
|
|
});
|
|
|
|
map.addControl(new maplibregl.NavigationControl({ showCompass: true }), 'top-right');
|
|
|
|
if (typeof LayerSwitcherControl !== 'undefined') {
|
|
const layerSwitcher = new LayerSwitcherControl({
|
|
layers: [{
|
|
id: 'ortho',
|
|
name: 'Orthophotos',
|
|
icon: 'satellite',
|
|
group: 'ortho',
|
|
beforeLayer: 'assets-polygons',
|
|
layer: { type: 'raster', paint: { 'raster-opacity': 1 } },
|
|
sublayers: [
|
|
{
|
|
id: 'urbis',
|
|
name: window.MAP_ORTHO_WMS_NAME || 'Urbis (Bruxelles)',
|
|
default: true,
|
|
source: {
|
|
type: 'raster',
|
|
tiles: [
|
|
window.MAP_ORTHO_WMS_URL || 'https://geoservices-urbis.irisnet.be/geoserver/urbisgrid/wms?service=WMS&version=1.3.0&request=GetMap&layers=Ortho&styles=&format=image/png&transparent=true&width=256&height=256&crs=EPSG:3857&bbox={bbox-epsg-3857}'
|
|
],
|
|
tileSize: 256,
|
|
attribution: window.MAP_ORTHO_WMS_ATTRIBUTION || '© Brussels UrbIS',
|
|
},
|
|
},
|
|
{
|
|
id: 'vlaanderen',
|
|
name: 'Vlaanderen 2023',
|
|
source: {
|
|
type: 'raster',
|
|
tiles: ['https://geo.api.vlaanderen.be/OMW/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=omwrgb23vl&STYLE=&FORMAT=image/png&TILEMATRIXSET=GoogleMapsVL&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}'],
|
|
tileSize: 256,
|
|
attribution: '© Digitaal Vlaanderen - Orthofoto 2023',
|
|
},
|
|
},
|
|
],
|
|
}],
|
|
});
|
|
map.addControl(layerSwitcher, 'top-right');
|
|
}
|
|
|
|
if (typeof AddressSearchControl !== 'undefined') {
|
|
const autocompleteUrl = window.AUTOCOMPLETE_STREET_URL || '/common/autocomplete-street/';
|
|
const searchCtrl = new AddressSearchControl({
|
|
autocompleteUrl: autocompleteUrl,
|
|
placeholder: 'Rechercher une adresse...'
|
|
});
|
|
map.addControl(searchCtrl, 'top-left');
|
|
}
|
|
|
|
map.on('load', function() {
|
|
// Source GeoJSON pour les assets
|
|
map.addSource('assets-source', {
|
|
type: 'geojson',
|
|
data: { type: 'FeatureCollection', features: [] }
|
|
});
|
|
|
|
// Couche Polygones (ex: ParkingSpot polygon)
|
|
map.addLayer({
|
|
id: 'assets-polygons',
|
|
type: 'fill',
|
|
source: 'assets-source',
|
|
filter: ['==', '$type', 'Polygon'],
|
|
paint: {
|
|
'fill-color': '#0d6efd',
|
|
'fill-opacity': 0.35,
|
|
'fill-outline-color': '#0d6efd'
|
|
}
|
|
});
|
|
|
|
// Couche Lignes pour le contour des polygones
|
|
map.addLayer({
|
|
id: 'assets-lines',
|
|
type: 'line',
|
|
source: 'assets-source',
|
|
filter: ['==', '$type', 'Polygon'],
|
|
paint: {
|
|
'line-color': '#0d6efd',
|
|
'line-width': 2
|
|
}
|
|
});
|
|
|
|
// Couche Points (ex: Poteaux, Armoires)
|
|
map.addLayer({
|
|
id: 'assets-points',
|
|
type: 'circle',
|
|
source: 'assets-source',
|
|
filter: ['==', '$type', 'Point'],
|
|
paint: {
|
|
'circle-radius': 8,
|
|
'circle-color': '#0d6efd',
|
|
'circle-stroke-width': 2,
|
|
'circle-stroke-color': '#ffffff'
|
|
}
|
|
});
|
|
|
|
// Clic sur un asset
|
|
map.on('click', ['assets-polygons', 'assets-points'], function(e) {
|
|
// Si un marker d'inspection est présent à cet endroit, ne pas ouvrir la modale directement
|
|
if (map.getLayer('inspections-history-points')) {
|
|
const inspFeatures = map.queryRenderedFeatures(e.point, { layers: ['inspections-history-points'] });
|
|
if (inspFeatures && inspFeatures.length > 0) {
|
|
return;
|
|
}
|
|
}
|
|
if (e.features && e.features.length > 0) {
|
|
const feature = e.features[0];
|
|
handleAssetClick(feature);
|
|
}
|
|
});
|
|
|
|
// Survol curseur
|
|
map.on('mouseenter', ['assets-polygons', 'assets-points'], function() {
|
|
map.getCanvas().style.cursor = 'pointer';
|
|
});
|
|
map.on('mouseleave', ['assets-polygons', 'assets-points'], function() {
|
|
map.getCanvas().style.cursor = '';
|
|
});
|
|
|
|
// Source GeoJSON pour l'historique des inspections
|
|
map.addSource('inspections-history-source', {
|
|
type: 'geojson',
|
|
data: { type: 'FeatureCollection', features: [] }
|
|
});
|
|
|
|
// Placer la couche d'inspections EN DESSOUS des couches d'assets
|
|
let beforeLayerId = undefined;
|
|
if (map.getLayer('assets-polygons')) {
|
|
beforeLayerId = 'assets-polygons';
|
|
} else if (map.getLayer('assets-points')) {
|
|
beforeLayerId = 'assets-points';
|
|
}
|
|
|
|
// Couche Points pour les inspections réalisées sur la carte
|
|
map.addLayer({
|
|
id: 'inspections-history-points',
|
|
type: 'circle',
|
|
source: 'inspections-history-source',
|
|
paint: {
|
|
'circle-radius': 9,
|
|
'circle-color': [
|
|
'match',
|
|
['get', 'latest_result_status'],
|
|
'compliant', '#198754',
|
|
'non_compliant', '#fd7e14',
|
|
'damaged', '#dc3545',
|
|
'missing', '#212529',
|
|
'updated', '#0dcaf0',
|
|
'#6c757d'
|
|
],
|
|
'circle-stroke-width': 2,
|
|
'circle-stroke-color': '#ffffff',
|
|
'circle-opacity': 0.85
|
|
}
|
|
}, beforeLayerId);
|
|
|
|
// Clic sur un marker d'inspection de la carte
|
|
map.on('click', 'inspections-history-points', function(e) {
|
|
if (e.originalEvent) {
|
|
e.originalEvent.stopPropagation();
|
|
if (e.originalEvent.preventDefault) e.originalEvent.preventDefault();
|
|
}
|
|
if (!e.features || e.features.length === 0) return;
|
|
|
|
const feat = e.features[0];
|
|
let props = feat.properties;
|
|
let inspectionsList = [];
|
|
if (typeof props.inspections === 'string') {
|
|
try { inspectionsList = JSON.parse(props.inspections); } catch(err){}
|
|
} else if (Array.isArray(props.inspections)) {
|
|
inspectionsList = props.inspections;
|
|
}
|
|
|
|
const targetDisplay = props.target_display || 'Équipement';
|
|
const assetProps = {
|
|
content_type_id: props.content_type_id,
|
|
object_id: props.object_id,
|
|
model_name: props.model_name,
|
|
spot_type: props.spot_type,
|
|
target_display: targetDisplay
|
|
};
|
|
|
|
function escapeHtml(str) {
|
|
if (!str) return '';
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
let html = `
|
|
<div class="p-1" style="width: 300px; max-width: 310px; font-size: 0.85rem;">
|
|
<div class="fw-bold text-dark border-bottom pb-1 mb-2 d-flex align-items-center justify-content-between">
|
|
<span class="text-truncate me-2">${escapeHtml(targetDisplay)}</span>
|
|
<span class="badge bg-secondary">${props.inspections_count || inspectionsList.length}</span>
|
|
</div>
|
|
|
|
<button type="button" class="btn btn-sm btn-primary w-100 mb-2 py-1.5 fw-semibold d-flex align-items-center justify-content-center gap-1 shadow-sm" id="btn-popup-create-inspection">
|
|
<i class="bi bi-plus-circle-fill"></i>
|
|
<span>Nouvelle inspection</span>
|
|
</button>
|
|
|
|
<div class="fw-semibold text-muted mb-1 extra-small">
|
|
<i class="bi bi-clock-history me-1 text-primary"></i>Inspections (${inspectionsList.length}) :
|
|
</div>
|
|
<div class="list-group list-group-flush overflow-auto pe-1" style="max-height: 190px;">
|
|
`;
|
|
|
|
inspectionsList.forEach((insp, index) => {
|
|
html += `
|
|
<div class="list-group-item px-2 py-1.5 border-bottom bg-light rounded mb-1">
|
|
<div class="d-flex justify-content-between align-items-center mb-1 gap-2">
|
|
<span class="badge ${insp.status_badge_class || 'bg-primary'} extra-small text-truncate flex-shrink-1">${escapeHtml(insp.result_status_display || '')}</span>
|
|
<a href="#" class="insp-date-modal-link text-primary text-decoration-underline fw-bold extra-small text-nowrap flex-shrink-0" style="font-size:0.75rem;" data-insp-index="${index}">
|
|
<i class="bi bi-calendar-event me-1"></i>${escapeHtml(insp.date || '')}
|
|
</a>
|
|
</div>
|
|
${insp.details_summary ? `<div class="extra-small text-dark mb-1 fw-medium" style="font-size:0.75rem;">${escapeHtml(insp.details_summary)}</div>` : ''}
|
|
<div class="d-flex justify-content-between align-items-center extra-small text-muted" style="font-size:0.7rem;">
|
|
<span><i class="bi bi-person me-1"></i>${escapeHtml(insp.inspector_name || '-')}</span>
|
|
${insp.mobile_asset_url ? `<a href="${insp.mobile_asset_url}" class="text-primary text-decoration-none fw-semibold"><i class="bi bi-box-arrow-up-right me-1"></i>Fiche asset</a>` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const popup = new maplibregl.Popup({ closeButton: true, closeOnClick: true })
|
|
.setLngLat(e.lngLat)
|
|
.setHTML(html)
|
|
.addTo(map);
|
|
|
|
setTimeout(function() {
|
|
const btnNew = document.getElementById('btn-popup-create-inspection');
|
|
if (btnNew) {
|
|
btnNew.addEventListener('click', function(ev) {
|
|
if (ev) ev.stopPropagation();
|
|
popup.remove();
|
|
openInspectionModal(assetProps);
|
|
});
|
|
}
|
|
|
|
const dateLinks = document.querySelectorAll('.insp-date-modal-link');
|
|
dateLinks.forEach(link => {
|
|
link.addEventListener('click', function(ev) {
|
|
if (ev) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}
|
|
const idx = parseInt(this.getAttribute('data-insp-index'), 10);
|
|
const inspData = inspectionsList[idx];
|
|
if (inspData) {
|
|
popup.remove();
|
|
showInspectionDetailModal(inspData);
|
|
}
|
|
});
|
|
});
|
|
}, 50);
|
|
});
|
|
|
|
map.on('mouseenter', 'inspections-history-points', function() {
|
|
map.getCanvas().style.cursor = 'pointer';
|
|
});
|
|
map.on('mouseleave', 'inspections-history-points', function() {
|
|
map.getCanvas().style.cursor = '';
|
|
});
|
|
|
|
// Charger les assets et l'historique d'inspections au premier affichage
|
|
loadAssets();
|
|
loadInspectionsHistory();
|
|
setupOverlayResizeObserver();
|
|
});
|
|
|
|
map.on('moveend', onMapMoveEnd);
|
|
|
|
setTimeout(function() {
|
|
if (map) map.resize();
|
|
adjustTopControlsMargin();
|
|
}, 300);
|
|
|
|
window.addEventListener('resize', function() {
|
|
if (map) map.resize();
|
|
adjustTopControlsMargin();
|
|
});
|
|
}
|
|
|
|
function adjustTopControlsMargin() {
|
|
const overlay = document.querySelector('.map-controls-overlay');
|
|
if (!overlay) return;
|
|
|
|
const overlayHeight = overlay.offsetHeight || 0;
|
|
const overlayTop = overlay.offsetTop || 6;
|
|
const marginValue = (overlayTop + overlayHeight + 8) + 'px';
|
|
|
|
const leftCtrl = document.querySelector('#inspection-map .maplibregl-ctrl-top-left');
|
|
const rightCtrl = document.querySelector('#inspection-map .maplibregl-ctrl-top-right');
|
|
|
|
if (leftCtrl) leftCtrl.style.marginTop = marginValue;
|
|
if (rightCtrl) rightCtrl.style.marginTop = marginValue;
|
|
}
|
|
|
|
function setupOverlayResizeObserver() {
|
|
const overlay = document.querySelector('.map-controls-overlay');
|
|
if (!overlay) return;
|
|
|
|
adjustTopControlsMargin();
|
|
|
|
if (window.ResizeObserver) {
|
|
const observer = new ResizeObserver(function() {
|
|
adjustTopControlsMargin();
|
|
});
|
|
observer.observe(overlay);
|
|
} else {
|
|
window.addEventListener('resize', adjustTopControlsMargin);
|
|
}
|
|
}
|
|
|
|
function getMapBbox() {
|
|
if (!map || typeof map.getBounds !== 'function') return null;
|
|
const b = map.getBounds();
|
|
const sw = b.getSouthWest(), ne = b.getNorthEast();
|
|
return {
|
|
type: 'Polygon',
|
|
coordinates: [[
|
|
[sw.lng, sw.lat], [ne.lng, sw.lat],
|
|
[ne.lng, ne.lat], [sw.lng, ne.lat],
|
|
[sw.lng, sw.lat],
|
|
]],
|
|
};
|
|
}
|
|
|
|
let moveendTimeout = null;
|
|
function onMapMoveEnd() {
|
|
if (moveendTimeout) clearTimeout(moveendTimeout);
|
|
moveendTimeout = setTimeout(function() {
|
|
loadAssets();
|
|
loadInspectionsHistory();
|
|
}, 400);
|
|
}
|
|
|
|
function setupGeolocation() {
|
|
const gpsBtn = document.getElementById('btn-gps-locate');
|
|
if (gpsBtn) {
|
|
gpsBtn.addEventListener('click', locateUser);
|
|
}
|
|
|
|
locateUser();
|
|
}
|
|
|
|
function locateUser() {
|
|
if (!navigator.geolocation) return;
|
|
|
|
const statusText = document.getElementById('gps-status-text');
|
|
if (statusText) statusText.textContent = 'Géolocalisation...';
|
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
function(position) {
|
|
const lng = position.coords.longitude;
|
|
const lat = position.coords.latitude;
|
|
|
|
if (typeof maplibregl !== 'undefined' && maplibregl.Marker && map) {
|
|
if (userMarker) {
|
|
userMarker.setLngLat([lng, lat]);
|
|
} else {
|
|
const el = document.createElement('div');
|
|
el.className = 'user-gps-dot';
|
|
|
|
userMarker = new maplibregl.Marker({ element: el })
|
|
.setLngLat([lng, lat])
|
|
.addTo(map);
|
|
}
|
|
|
|
map.flyTo({ center: [lng, lat], zoom: 17 });
|
|
}
|
|
if (statusText) statusText.textContent = 'Position actualisée';
|
|
},
|
|
function(err) {
|
|
console.warn('GPS error:', err.message);
|
|
if (statusText) statusText.textContent = 'GPS indisponible';
|
|
},
|
|
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 5000 }
|
|
);
|
|
}
|
|
|
|
function updateUIElements(thematicCode, categoryId) {
|
|
const thematicSelect = document.getElementById('filter-thematic');
|
|
if (thematicSelect) {
|
|
thematicSelect.value = thematicCode || '';
|
|
}
|
|
|
|
const thematicTiles = document.querySelectorAll('.thematic-tile');
|
|
let selectedIcon = 'bi-folder2';
|
|
let selectedThematicName = 'Thématique';
|
|
|
|
thematicTiles.forEach(tile => {
|
|
const code = tile.getAttribute('data-code');
|
|
const icon = tile.getAttribute('data-icon');
|
|
const name = tile.getAttribute('data-name');
|
|
const check = tile.querySelector('.thematic-check');
|
|
|
|
if (code === thematicCode) {
|
|
tile.classList.add('active');
|
|
if (check) check.classList.remove('d-none');
|
|
selectedIcon = icon || selectedIcon;
|
|
selectedThematicName = name || selectedThematicName;
|
|
} else {
|
|
tile.classList.remove('active');
|
|
if (check) check.classList.add('d-none');
|
|
}
|
|
});
|
|
|
|
const iconDisp = document.getElementById('thematic-icon-display');
|
|
const nameDisp = document.getElementById('thematic-name-display');
|
|
if (iconDisp) iconDisp.className = `bi ${selectedIcon} text-primary me-2`;
|
|
if (nameDisp) nameDisp.textContent = selectedThematicName;
|
|
|
|
const categorySelect = document.getElementById('filter-category');
|
|
if (categorySelect) {
|
|
categorySelect.value = categoryId || '';
|
|
}
|
|
|
|
const categoryItems = document.querySelectorAll('.category-item');
|
|
let selectedCategoryName = 'Toutes les catégories';
|
|
|
|
categoryItems.forEach(item => {
|
|
const id = item.getAttribute('data-id');
|
|
const itemThematic = item.getAttribute('data-thematic');
|
|
const name = item.getAttribute('data-name');
|
|
const check = item.querySelector('.category-check');
|
|
|
|
if (!id) {
|
|
item.classList.remove('d-none');
|
|
} else if (!thematicCode || itemThematic === thematicCode) {
|
|
item.classList.remove('d-none');
|
|
} else {
|
|
item.classList.add('d-none');
|
|
}
|
|
|
|
if ((id || '') === (categoryId || '')) {
|
|
item.classList.add('active');
|
|
if (check) check.classList.remove('d-none');
|
|
selectedCategoryName = name || selectedCategoryName;
|
|
} else {
|
|
item.classList.remove('active');
|
|
if (check) check.classList.add('d-none');
|
|
}
|
|
});
|
|
|
|
const catNameDisp = document.getElementById('category-name-display');
|
|
if (catNameDisp) catNameDisp.textContent = selectedCategoryName;
|
|
}
|
|
|
|
function initFilterStorage() {
|
|
const savedThematic = localStorage.getItem(STORAGE_THEMATIC_KEY) || 'parking';
|
|
const savedCategory = localStorage.getItem(STORAGE_CATEGORY_KEY) || '';
|
|
|
|
updateUIElements(savedThematic, savedCategory);
|
|
|
|
const thematicTiles = document.querySelectorAll('.thematic-tile');
|
|
thematicTiles.forEach(tile => {
|
|
tile.addEventListener('click', function() {
|
|
const code = this.getAttribute('data-code');
|
|
localStorage.setItem(STORAGE_THEMATIC_KEY, code);
|
|
localStorage.removeItem(STORAGE_CATEGORY_KEY);
|
|
|
|
updateUIElements(code, '');
|
|
loadAssets();
|
|
loadInspectionsHistory();
|
|
});
|
|
});
|
|
|
|
const categoryItems = document.querySelectorAll('.category-item');
|
|
categoryItems.forEach(item => {
|
|
item.addEventListener('click', function() {
|
|
const id = this.getAttribute('data-id');
|
|
const thematicSelect = document.getElementById('filter-thematic');
|
|
const currentThematic = thematicSelect ? thematicSelect.value : '';
|
|
|
|
localStorage.setItem(STORAGE_CATEGORY_KEY, id || '');
|
|
|
|
updateUIElements(currentThematic, id || '');
|
|
loadAssets();
|
|
loadInspectionsHistory();
|
|
});
|
|
});
|
|
|
|
const thematicSelect = document.getElementById('filter-thematic');
|
|
if (thematicSelect) {
|
|
thematicSelect.addEventListener('change', function() {
|
|
localStorage.setItem(STORAGE_THEMATIC_KEY, thematicSelect.value);
|
|
updateUIElements(thematicSelect.value, '');
|
|
localStorage.removeItem(STORAGE_CATEGORY_KEY);
|
|
loadAssets();
|
|
loadInspectionsHistory();
|
|
});
|
|
}
|
|
|
|
const categorySelect = document.getElementById('filter-category');
|
|
if (categorySelect) {
|
|
categorySelect.addEventListener('change', function() {
|
|
const thematic = thematicSelect ? thematicSelect.value : '';
|
|
localStorage.setItem(STORAGE_CATEGORY_KEY, categorySelect.value);
|
|
updateUIElements(thematic, categorySelect.value);
|
|
loadAssets();
|
|
loadInspectionsHistory();
|
|
});
|
|
}
|
|
|
|
// Initialisation de la période d'historique des inspections
|
|
const periodItems = document.querySelectorAll('.insp-period-item');
|
|
periodItems.forEach(item => {
|
|
item.addEventListener('click', function() {
|
|
const period = this.getAttribute('data-period') || '3m';
|
|
localStorage.setItem(STORAGE_HISTORY_PERIOD_KEY, period);
|
|
updateHistoryUIElements(period);
|
|
loadInspectionsHistory();
|
|
});
|
|
});
|
|
|
|
const savedPeriod = localStorage.getItem(STORAGE_HISTORY_PERIOD_KEY) || '3m';
|
|
updateHistoryUIElements(savedPeriod);
|
|
}
|
|
|
|
const STORAGE_HISTORY_PERIOD_KEY = 'streetup_insp_history_period';
|
|
|
|
function getHistoryPeriodLabels() {
|
|
return {
|
|
'disabled': 'Inspections (Désactivé)',
|
|
'1m': 'Inspections (1 mois)',
|
|
'3m': 'Inspections (3 mois)',
|
|
'6m': 'Inspections (6 mois)',
|
|
'1y': 'Inspections (1 an)',
|
|
'all': 'Inspections (Tout)'
|
|
};
|
|
}
|
|
|
|
function updateHistoryUIElements(period) {
|
|
const periodItems = document.querySelectorAll('.insp-period-item');
|
|
const labels = getHistoryPeriodLabels();
|
|
const iconEl = document.getElementById('inspections-history-icon');
|
|
const labelEl = document.getElementById('inspections-history-label');
|
|
|
|
periodItems.forEach(item => {
|
|
const p = item.getAttribute('data-period');
|
|
const check = item.querySelector('.period-check');
|
|
if (p === period) {
|
|
item.classList.add('active');
|
|
if (check) check.classList.remove('d-none');
|
|
} else {
|
|
item.classList.remove('active');
|
|
if (check) check.classList.add('d-none');
|
|
}
|
|
});
|
|
|
|
if (labelEl) labelEl.textContent = labels[period] || 'Inspections (3 mois)';
|
|
if (iconEl) {
|
|
if (period === 'disabled') {
|
|
iconEl.className = 'bi bi-eye-slash text-muted me-1';
|
|
} else {
|
|
iconEl.className = 'bi bi-clock-history text-primary me-1';
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadInspectionsHistory() {
|
|
if (!map) return;
|
|
const period = localStorage.getItem(STORAGE_HISTORY_PERIOD_KEY) || '3m';
|
|
|
|
updateHistoryUIElements(period);
|
|
|
|
const source = map.getSource('inspections-history-source');
|
|
if (!source) return;
|
|
|
|
if (period === 'disabled') {
|
|
source.setData({ type: 'FeatureCollection', features: [] });
|
|
return;
|
|
}
|
|
|
|
const currentZoom = (typeof map.getZoom === 'function') ? map.getZoom() : 0;
|
|
if (currentZoom < MIN_ZOOM_FOR_GEOJSON) {
|
|
source.setData({ type: 'FeatureCollection', features: [] });
|
|
return;
|
|
}
|
|
|
|
const thematicSelect = document.getElementById('filter-thematic');
|
|
const thematic = thematicSelect ? thematicSelect.value : '';
|
|
|
|
const categorySelect = document.getElementById('filter-category');
|
|
const categoryId = categorySelect ? categorySelect.value : '';
|
|
|
|
const payload = {
|
|
thematic: thematic,
|
|
category: categoryId,
|
|
period: period,
|
|
filter_geom: getMapBbox()
|
|
};
|
|
|
|
fetch('/assets/inspections/geojson/', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': getCsrfToken()
|
|
}
|
|
})
|
|
.then(res => res.ok ? res.json() : null)
|
|
.then(data => {
|
|
if (data && data.features) {
|
|
source.setData(data);
|
|
}
|
|
})
|
|
.catch(err => console.warn('Erreur chargement GeoJSON inspections:', err));
|
|
}
|
|
|
|
const MIN_ZOOM_FOR_GEOJSON = 16;
|
|
|
|
function loadAssets() {
|
|
if (!map) return;
|
|
|
|
const warningAlert = document.getElementById('map-warning-alert');
|
|
const currentZoom = (typeof map.getZoom === 'function') ? map.getZoom() : 0;
|
|
|
|
if (currentZoom < MIN_ZOOM_FOR_GEOJSON) {
|
|
if (warningAlert) warningAlert.classList.remove('d-none');
|
|
const source = map.getSource('assets-source');
|
|
if (source) {
|
|
source.setData({ type: 'FeatureCollection', features: [] });
|
|
}
|
|
loadedAssets = [];
|
|
return;
|
|
}
|
|
|
|
const thematicSelect = document.getElementById('filter-thematic');
|
|
const thematic = thematicSelect ? thematicSelect.value : '';
|
|
|
|
const categorySelect = document.getElementById('filter-category');
|
|
const categoryId = categorySelect ? categorySelect.value : '';
|
|
|
|
let fetchUrl = '/assets/parking/assets/geojson/';
|
|
if (thematic === 'parking') {
|
|
fetchUrl = '/assets/parking/assets/geojson/';
|
|
} else if (thematic === 'trafficlights') {
|
|
fetchUrl = '/assets/trafficlights/assets/geojson/';
|
|
} else if (thematic === 'publiclighting') {
|
|
fetchUrl = '/assets/publiclighting/assets/geojson/';
|
|
}
|
|
|
|
const payload = {};
|
|
if (categoryId) {
|
|
payload.category = [categoryId];
|
|
}
|
|
|
|
const bbox = getMapBbox();
|
|
if (bbox) {
|
|
payload.filter_geom = { type: 'Feature', geometry: bbox };
|
|
}
|
|
|
|
fetch(fetchUrl, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': getCsrfToken()
|
|
}
|
|
})
|
|
.then(res => {
|
|
if (res.status === 413) {
|
|
if (warningAlert) warningAlert.classList.remove('d-none');
|
|
return null;
|
|
}
|
|
if (!res.ok) {
|
|
if (warningAlert) warningAlert.classList.remove('d-none');
|
|
return null;
|
|
}
|
|
return res.json();
|
|
})
|
|
.then(data => {
|
|
if (!data) return;
|
|
|
|
if (data.error) {
|
|
if (warningAlert) warningAlert.classList.remove('d-none');
|
|
return;
|
|
}
|
|
|
|
if (warningAlert) warningAlert.classList.add('d-none');
|
|
|
|
if (data.features) {
|
|
loadedAssets = data.features;
|
|
const source = map.getSource('assets-source');
|
|
if (source) {
|
|
source.setData(data);
|
|
}
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('Error loading asset GeoJSON:', err);
|
|
if (warningAlert) warningAlert.classList.remove('d-none');
|
|
});
|
|
}
|
|
|
|
function fitAllAssets() {
|
|
if (!loadedAssets || loadedAssets.length === 0 || !map) return;
|
|
|
|
const bounds = new maplibregl.LngLatBounds();
|
|
loadedAssets.forEach(f => {
|
|
if (f.geometry) {
|
|
if (f.geometry.type === 'Point') {
|
|
bounds.extend(f.geometry.coordinates);
|
|
} else if (f.geometry.type === 'Polygon') {
|
|
f.geometry.coordinates[0].forEach(coord => bounds.extend(coord));
|
|
} else if (f.geometry.type === 'MultiPolygon') {
|
|
f.geometry.coordinates.forEach(poly => poly[0].forEach(coord => bounds.extend(coord)));
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!bounds.isEmpty()) {
|
|
map.fitBounds(bounds, { padding: 40 });
|
|
}
|
|
}
|
|
|
|
function handleAssetClick(feature) {
|
|
const props = feature.properties || {};
|
|
openInspectionModal(props);
|
|
}
|
|
|
|
function openFullscreenPhotoCarousel(images, startIndex) {
|
|
const overlay = document.getElementById('fullscreenPhotoOverlay');
|
|
const fsInner = document.getElementById('fullscreen-carousel-inner');
|
|
const fsIndicators = document.getElementById('fullscreen-carousel-indicators');
|
|
if (!overlay || !fsInner) return;
|
|
|
|
fsInner.innerHTML = '';
|
|
if (fsIndicators) fsIndicators.innerHTML = '';
|
|
|
|
images.forEach((doc, idx) => {
|
|
const activeClass = idx === startIndex ? 'active' : '';
|
|
const item = document.createElement('div');
|
|
item.className = `carousel-item ${activeClass} w-100 h-100`;
|
|
item.innerHTML = `
|
|
<div class="w-100 h-100 d-flex flex-column align-items-center justify-content-center p-3">
|
|
<img src="${doc.url}" class="img-fluid mh-100 mw-100 shadow-lg" style="object-fit: contain; max-height: 85vh;" alt="${doc.filename}">
|
|
<div class="text-white small mt-2 bg-dark bg-opacity-75 px-3 py-1 rounded-pill">${doc.filename}</div>
|
|
</div>
|
|
`;
|
|
fsInner.appendChild(item);
|
|
|
|
if (fsIndicators && images.length > 1) {
|
|
const ind = document.createElement('button');
|
|
ind.type = 'button';
|
|
ind.setAttribute('data-bs-target', '#fullscreenPhotoCarousel');
|
|
ind.setAttribute('data-bs-slide-to', idx.toString());
|
|
if (idx === startIndex) {
|
|
ind.className = 'active';
|
|
ind.setAttribute('aria-current', 'true');
|
|
}
|
|
ind.setAttribute('aria-label', `Photo ${idx + 1}`);
|
|
fsIndicators.appendChild(ind);
|
|
}
|
|
});
|
|
|
|
overlay.classList.remove('d-none');
|
|
|
|
const btnClose = document.getElementById('btn-close-photo-overlay');
|
|
if (btnClose) {
|
|
const closeHandler = function(e) {
|
|
if (e) e.preventDefault();
|
|
overlay.classList.add('d-none');
|
|
btnClose.removeEventListener('click', closeHandler);
|
|
};
|
|
btnClose.addEventListener('click', closeHandler);
|
|
}
|
|
|
|
const escHandler = function(e) {
|
|
if (e.key === 'Escape') {
|
|
overlay.classList.add('d-none');
|
|
document.removeEventListener('keydown', escHandler);
|
|
}
|
|
};
|
|
document.addEventListener('keydown', escHandler);
|
|
}
|
|
|
|
function showInspectionDetailModal(insp) {
|
|
const modalEl = document.getElementById('mapInspectionDetailModal');
|
|
if (!modalEl) return;
|
|
|
|
const assetLink = document.getElementById('map-insp-asset-link');
|
|
const assetLinkContainer = document.getElementById('map-insp-asset-link-container');
|
|
if (insp.mobile_asset_url && assetLink) {
|
|
assetLink.href = insp.mobile_asset_url;
|
|
if (assetLinkContainer) assetLinkContainer.classList.remove('d-none');
|
|
} else if (assetLinkContainer) {
|
|
assetLinkContainer.classList.add('d-none');
|
|
}
|
|
|
|
document.getElementById('map-insp-target-display').textContent = insp.target_display || '-';
|
|
document.getElementById('map-insp-date').textContent = insp.date || '-';
|
|
|
|
const badgeEl = document.getElementById('map-insp-status-badge');
|
|
if (badgeEl) {
|
|
badgeEl.className = 'badge ' + (insp.status_badge_class || 'bg-primary');
|
|
badgeEl.textContent = insp.result_status_display || '-';
|
|
}
|
|
|
|
document.getElementById('map-insp-nature').textContent = insp.nature_display || '-';
|
|
const inspText = (insp.inspector_name || '-') + (insp.inspector_role_display ? ' (' + insp.inspector_role_display + ')' : '');
|
|
document.getElementById('map-insp-inspector').textContent = inspText;
|
|
|
|
document.getElementById('map-insp-summary').textContent = insp.details_summary || 'Aucun constat spécifique.';
|
|
const notesEl = document.getElementById('map-insp-notes');
|
|
if (notesEl) {
|
|
if (insp.notes) {
|
|
notesEl.textContent = insp.notes;
|
|
notesEl.classList.remove('d-none');
|
|
} else {
|
|
notesEl.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
const carouselContainer = document.getElementById('mapInspectionPhotosCarouselContainer');
|
|
const carouselInner = document.getElementById('map-insp-carousel-inner');
|
|
const carouselIndicators = document.getElementById('map-insp-carousel-indicators');
|
|
const photosCount = document.getElementById('map-insp-photos-count');
|
|
|
|
const docsSection = document.getElementById('map-insp-docs-section');
|
|
const docsContainer = document.getElementById('map-insp-docs-container');
|
|
|
|
if (insp.documents && insp.documents.length > 0) {
|
|
const images = insp.documents.filter(d => d.is_image);
|
|
const otherDocs = insp.documents.filter(d => !d.is_image);
|
|
|
|
if (images.length > 0 && carouselContainer && carouselInner) {
|
|
carouselInner.innerHTML = '';
|
|
if (carouselIndicators) carouselIndicators.innerHTML = '';
|
|
if (photosCount) photosCount.textContent = images.length;
|
|
|
|
images.forEach((doc, idx) => {
|
|
const activeClass = idx === 0 ? 'active' : '';
|
|
const item = document.createElement('div');
|
|
item.className = `carousel-item ${activeClass}`;
|
|
item.innerHTML = `
|
|
<div class="d-flex align-items-center justify-content-center bg-black position-relative" style="height: 280px;">
|
|
<img src="${doc.url}" class="d-block img-fluid mh-100 mw-100 insp-carousel-trigger" style="object-fit: contain; max-height: 280px; cursor: pointer;" alt="${doc.filename}" data-img-index="${idx}">
|
|
<button type="button" class="btn btn-sm btn-dark bg-opacity-75 text-white rounded-circle p-1.5 shadow insp-carousel-fullscreen-btn position-absolute bottom-0 end-0 m-2" style="z-index: 10; cursor: pointer;" data-img-index="${idx}" title="Plein écran">
|
|
<i class="bi bi-arrows-fullscreen"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
carouselInner.appendChild(item);
|
|
|
|
if (carouselIndicators) {
|
|
const ind = document.createElement('button');
|
|
ind.type = 'button';
|
|
ind.setAttribute('data-bs-target', '#mapInspectionPhotosCarousel');
|
|
ind.setAttribute('data-bs-slide-to', idx.toString());
|
|
if (idx === 0) {
|
|
ind.className = 'active';
|
|
ind.setAttribute('aria-current', 'true');
|
|
}
|
|
ind.setAttribute('aria-label', `Photo ${idx + 1}`);
|
|
carouselIndicators.appendChild(ind);
|
|
}
|
|
});
|
|
|
|
setTimeout(function() {
|
|
const triggers = carouselInner.querySelectorAll('.insp-carousel-trigger, .insp-carousel-fullscreen-btn');
|
|
triggers.forEach(trig => {
|
|
trig.addEventListener('click', function(e) {
|
|
if (e) e.preventDefault();
|
|
const idx = parseInt(this.getAttribute('data-img-index'), 10) || 0;
|
|
openFullscreenPhotoCarousel(images, idx);
|
|
});
|
|
});
|
|
}, 50);
|
|
|
|
carouselContainer.classList.remove('d-none');
|
|
} else if (carouselContainer) {
|
|
carouselContainer.classList.add('d-none');
|
|
}
|
|
|
|
if (otherDocs.length > 0 && docsSection && docsContainer) {
|
|
docsContainer.innerHTML = '';
|
|
otherDocs.forEach(doc => {
|
|
const btn = document.createElement('a');
|
|
btn.href = doc.url;
|
|
btn.target = '_blank';
|
|
btn.className = 'btn btn-sm btn-outline-secondary d-inline-flex align-items-center gap-1';
|
|
btn.innerHTML = `<i class="bi bi-file-earmark-arrow-down text-primary"></i><span class="text-truncate" style="max-width: 180px;">${doc.filename}</span>`;
|
|
docsContainer.appendChild(btn);
|
|
});
|
|
docsSection.classList.remove('d-none');
|
|
} else if (docsSection) {
|
|
docsSection.classList.add('d-none');
|
|
}
|
|
} else {
|
|
if (carouselContainer) carouselContainer.classList.add('d-none');
|
|
if (docsSection) docsSection.classList.add('d-none');
|
|
}
|
|
|
|
const modal = new bootstrap.Modal(modalEl);
|
|
modal.show();
|
|
}
|
|
|
|
window.openInspectionModal = function(props) {
|
|
const modalEl = document.getElementById('quickInspectionModal');
|
|
if (!modalEl) return;
|
|
|
|
let ctId = props.content_type_id || props.ct_id;
|
|
if (!ctId && (props.model_name === 'parkingspot' || props.spot_type || props.asset_type === 'parkingspot' || props.thematic === 'parking')) {
|
|
ctId = window.PARKING_SPOT_CT_ID;
|
|
}
|
|
const objId = props.object_id || props.id || props.pk || '';
|
|
document.getElementById('insp_content_type_id').value = ctId || '';
|
|
document.getElementById('insp_object_id').value = objId;
|
|
|
|
document.getElementById('insp_target_type').textContent = props.asset_type_display || props.model_name || 'Asset';
|
|
document.getElementById('insp_target_code').textContent = props.target_display || props.code || (objId ? ('#' + objId) : '-');
|
|
document.getElementById('insp_target_name').textContent = props.name || props.name_fr || props.target_display || '-';
|
|
document.getElementById('insp_target_status').textContent = props.latest_result_status_display || props.status_display || props.status || '-';
|
|
|
|
const parkingFields = document.getElementById('parkingSpotSpecificFields');
|
|
if (parkingFields) {
|
|
if (props.model_name === 'parkingspot' || props.spot_type) {
|
|
parkingFields.classList.remove('d-none');
|
|
document.getElementById('insp_std_stands').value = '';
|
|
document.getElementById('insp_cargo_stands').value = '';
|
|
const markingSel = document.getElementById('insp_marking_quality');
|
|
if (markingSel) markingSel.value = '';
|
|
} else {
|
|
parkingFields.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
// Réinitialisation de l'accumulateur de photos/fichiers
|
|
accumulatedInspectionFiles = [];
|
|
const previewContainer = document.getElementById('insp_photos_preview_container');
|
|
if (previewContainer) previewContainer.innerHTML = '';
|
|
|
|
const modal = new bootstrap.Modal(modalEl);
|
|
modal.show();
|
|
};
|
|
|
|
let accumulatedInspectionFiles = [];
|
|
let inspCameraStream = null;
|
|
|
|
function initInspectionPhotoHandler() {
|
|
const btnCamera = document.getElementById('insp_btn_continuous_camera');
|
|
const btnGallery = document.getElementById('insp_btn_gallery_files');
|
|
const inputCamera = document.getElementById('insp_camera_input');
|
|
const inputGallery = document.getElementById('insp_gallery_input');
|
|
|
|
const cameraModalEl = document.getElementById('inspCameraModal');
|
|
const cameraPreview = document.getElementById('inspCameraPreview');
|
|
const cameraCanvas = document.getElementById('inspCameraCanvas');
|
|
const cameraFlashOverlay = document.getElementById('inspCameraFlashOverlay');
|
|
const cameraStatus = document.getElementById('inspCameraStatus');
|
|
const cameraCaptureBtn = document.getElementById('inspCameraCaptureBtn');
|
|
const cameraStopBtn = document.getElementById('inspCameraStopBtn');
|
|
|
|
let cameraModal = null;
|
|
if (cameraModalEl && typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
|
cameraModal = bootstrap.Modal.getOrCreateInstance(cameraModalEl);
|
|
}
|
|
|
|
if (!btnCamera || !btnGallery || !inputCamera || !inputGallery) return;
|
|
|
|
function updateCameraStatus(msg, isError) {
|
|
if (!cameraStatus) return;
|
|
cameraStatus.textContent = msg || '';
|
|
cameraStatus.className = isError ? 'small text-center text-danger my-2 flex-shrink-0' : 'small text-center text-info my-2 flex-shrink-0';
|
|
}
|
|
|
|
function triggerFlash() {
|
|
if (!cameraFlashOverlay) return;
|
|
cameraFlashOverlay.style.opacity = '1';
|
|
setTimeout(() => { cameraFlashOverlay.style.opacity = '0'; }, 150);
|
|
}
|
|
|
|
async function startCameraStream() {
|
|
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
|
|
return false;
|
|
}
|
|
try {
|
|
updateCameraStatus('Initialisation de la caméra...', false);
|
|
inspCameraStream = await navigator.mediaDevices.getUserMedia({
|
|
video: { facingMode: 'environment', width: { ideal: 1920 }, height: { ideal: 1080 } },
|
|
audio: false
|
|
});
|
|
if ('srcObject' in cameraPreview) {
|
|
cameraPreview.srcObject = inspCameraStream;
|
|
} else {
|
|
cameraPreview.src = URL.createObjectURL(inspCameraStream);
|
|
}
|
|
cameraPreview.play();
|
|
updateCameraStatus('Caméra prête. Appuyez sur Capturer pour enregistrer une photo.', false);
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('Erreur caméra canvas:', err);
|
|
stopCameraStream();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function stopCameraStream() {
|
|
if (inspCameraStream) {
|
|
inspCameraStream.getTracks().forEach(t => t.stop());
|
|
inspCameraStream = null;
|
|
}
|
|
if (cameraPreview) cameraPreview.srcObject = null;
|
|
updateCameraStatus('', false);
|
|
}
|
|
|
|
function captureFrame() {
|
|
if (!cameraPreview || !cameraCanvas) return;
|
|
const w = cameraPreview.videoWidth;
|
|
const h = cameraPreview.videoHeight;
|
|
if (!w || !h) {
|
|
updateCameraStatus('La caméra n\'est pas encore prête.', true);
|
|
return;
|
|
}
|
|
cameraCanvas.width = w;
|
|
cameraCanvas.height = h;
|
|
const ctx = cameraCanvas.getContext('2d');
|
|
ctx.drawImage(cameraPreview, 0, 0, w, h);
|
|
|
|
triggerFlash();
|
|
|
|
cameraCanvas.toBlob(function(blob) {
|
|
if (!blob) return;
|
|
const filename = `inspection_photo_${Date.now()}.jpg`;
|
|
const file = new File([blob], filename, { type: 'image/jpeg' });
|
|
accumulatedInspectionFiles.push(file);
|
|
renderPhotoPreviews();
|
|
updateCameraStatus('Photo capturée avec succès !', false);
|
|
}, 'image/jpeg', 0.9);
|
|
}
|
|
|
|
btnCamera.addEventListener('click', async function() {
|
|
if (cameraModal) {
|
|
const ok = await startCameraStream();
|
|
if (ok) {
|
|
cameraModal.show();
|
|
return;
|
|
}
|
|
}
|
|
// Fallback si le flux caméra (getUserMedia) n'est pas supporté
|
|
inputCamera.value = '';
|
|
inputCamera.click();
|
|
});
|
|
|
|
if (cameraCaptureBtn) {
|
|
cameraCaptureBtn.addEventListener('click', captureFrame);
|
|
}
|
|
|
|
if (cameraStopBtn) {
|
|
cameraStopBtn.addEventListener('click', function() {
|
|
if (cameraModal) cameraModal.hide();
|
|
});
|
|
}
|
|
|
|
if (cameraModalEl) {
|
|
cameraModalEl.addEventListener('hidden.bs.modal', function() {
|
|
stopCameraStream();
|
|
});
|
|
}
|
|
|
|
btnGallery.addEventListener('click', function() {
|
|
inputGallery.value = '';
|
|
inputGallery.click();
|
|
});
|
|
|
|
function handleFileSelection(files) {
|
|
Array.from(files).forEach(file => {
|
|
accumulatedInspectionFiles.push(file);
|
|
});
|
|
renderPhotoPreviews();
|
|
}
|
|
|
|
inputCamera.addEventListener('change', function(e) {
|
|
if (e.target.files && e.target.files.length > 0) {
|
|
handleFileSelection(e.target.files);
|
|
}
|
|
});
|
|
|
|
inputGallery.addEventListener('change', function(e) {
|
|
if (e.target.files && e.target.files.length > 0) {
|
|
handleFileSelection(e.target.files);
|
|
}
|
|
});
|
|
|
|
function renderPhotoPreviews() {
|
|
const previewContainer = document.getElementById('insp_photos_preview_container');
|
|
if (!previewContainer) return;
|
|
previewContainer.innerHTML = '';
|
|
|
|
accumulatedInspectionFiles.forEach((file, index) => {
|
|
const col = document.createElement('div');
|
|
col.className = 'col-4 col-sm-3 position-relative mt-2';
|
|
|
|
const card = document.createElement('div');
|
|
card.className = 'border rounded overflow-hidden shadow-sm bg-white text-center p-1 position-relative';
|
|
card.style.height = '75px';
|
|
|
|
if (file.type.startsWith('image/')) {
|
|
const img = document.createElement('img');
|
|
img.src = URL.createObjectURL(file);
|
|
img.style.width = '100%';
|
|
img.style.height = '100%';
|
|
img.style.objectFit = 'cover';
|
|
card.appendChild(img);
|
|
} else {
|
|
const icon = document.createElement('div');
|
|
icon.className = 'd-flex flex-column align-items-center justify-content-center h-100 text-muted';
|
|
icon.innerHTML = '<i class="bi bi-file-earmark fs-4 text-primary"></i><span class="extra-small text-truncate w-100 px-1">' + file.name + '</span>';
|
|
card.appendChild(icon);
|
|
}
|
|
|
|
const removeBtn = document.createElement('button');
|
|
removeBtn.type = 'button';
|
|
removeBtn.className = 'btn btn-danger btn-sm rounded-circle position-absolute top-0 end-0 p-0 d-flex align-items-center justify-content-center';
|
|
removeBtn.style.width = '20px';
|
|
removeBtn.style.height = '20px';
|
|
removeBtn.style.fontSize = '12px';
|
|
removeBtn.style.transform = 'translate(30%, -30%)';
|
|
removeBtn.innerHTML = '×';
|
|
removeBtn.onclick = function(ev) {
|
|
ev.stopPropagation();
|
|
accumulatedInspectionFiles.splice(index, 1);
|
|
renderPhotoPreviews();
|
|
};
|
|
|
|
card.appendChild(removeBtn);
|
|
col.appendChild(card);
|
|
previewContainer.appendChild(col);
|
|
});
|
|
}
|
|
}
|
|
|
|
function initFormHandler() {
|
|
initInspectionPhotoHandler();
|
|
|
|
const form = document.getElementById('createInspectionForm') || document.getElementById('quickInspectionForm');
|
|
if (!form) return;
|
|
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const alertDiv = document.getElementById('quickInspectionAlert');
|
|
const submitBtn = document.getElementById('submitQuickInspectionBtn');
|
|
const spinner = document.getElementById('submitQuickInspectionSpinner');
|
|
const icon = document.getElementById('submitQuickInspectionIcon');
|
|
|
|
alertDiv.classList.add('d-none');
|
|
alertDiv.textContent = '';
|
|
submitBtn.disabled = true;
|
|
if (spinner) spinner.classList.remove('d-none');
|
|
if (icon) icon.classList.add('d-none');
|
|
|
|
const formData = new FormData(form);
|
|
|
|
// Ajout des fichiers accumulés en mode continu
|
|
accumulatedInspectionFiles.forEach(file => {
|
|
formData.append('photos', file);
|
|
});
|
|
|
|
fetch(form.action, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const modalEl = document.getElementById('quickInspectionModal');
|
|
const modal = bootstrap.Modal.getInstance(modalEl);
|
|
if (modal) modal.hide();
|
|
|
|
let msg = data.message || 'Inspection enregistrée !';
|
|
if (data.created_intervention_code) {
|
|
msg += ' Intervention ' + data.created_intervention_code + ' créée.';
|
|
}
|
|
alert(msg);
|
|
loadAssets();
|
|
} else {
|
|
alertDiv.textContent = data.error || 'Erreur lors de la validation.';
|
|
alertDiv.classList.remove('d-none');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
alertDiv.textContent = 'Erreur réseau.';
|
|
alertDiv.classList.remove('d-none');
|
|
})
|
|
.finally(() => {
|
|
submitBtn.disabled = false;
|
|
if (spinner) spinner.classList.add('d-none');
|
|
if (icon) icon.classList.remove('d-none');
|
|
});
|
|
});
|
|
}
|
|
|
|
function getCsrfToken() {
|
|
const cookieValue = document.cookie
|
|
.split('; ')
|
|
.find(row => row.startsWith('csrftoken='))
|
|
?.split('=')[1];
|
|
return cookieValue || '';
|
|
}
|
|
})();
|