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

351 lines
19 KiB
HTML

{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% block content %}
<div class="container-fluid py-4" style="background-color: #f8fafc; min-height: 100vh;">
<!-- Header/Breadcrumbs -->
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-2" style="background-color: transparent; padding: 0;">
<li class="breadcrumb-item"><a href="{% url 'assets:trafficlights_index' %}">{% translate "Thématiques" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'assets:trafficlights_intersections_detail' intersection.id %}">{{ intersection.code }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% translate "Importation DXF" %}</li>
</ol>
</nav>
<div class="d-flex align-items-center justify-content-between">
<h2 class="h3 font-weight-bold text-dark mb-0">
<i class="bi bi-file-earmark-arrow-up text-primary me-2"></i>
{% translate "Importation d'un plan DXF — Poteaux" %}
</h2>
<a href="{% url 'assets:trafficlights_intersections_detail' intersection.id %}" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-arrow-left"></i> {% translate "Retour au carrefour" %}
</a>
</div>
</div>
</div>
<!-- Step 1: Select/Upload DXF -->
{% if step == 'select' %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
<div class="card-header bg-gradient-primary text-white py-3" style="background: linear-gradient(135deg, #2563eb, #1d4ed8);">
<h5 class="card-title mb-0 font-weight-semibold">
<i class="bi bi-folder-symlink me-2"></i> {% translate "Choisir un plan DXF" %}
</h5>
</div>
<div class="card-body p-4" style="background-color: #ffffff;">
{% if error %}
<div class="alert alert-danger rounded-3 d-flex align-items-center mb-4" role="alert">
<i class="bi bi-exclamation-triangle-fill fs-5 me-2"></i>
<div>{{ error }}</div>
</div>
{% endif %}
<form method="post" enctype="multipart/form-data" id="dxf-import-form">
{% csrf_token %}
<!-- List files in external_files -->
<div class="mb-4">
<label for="dxf_file" class="form-label font-weight-semibold text-secondary">
{% translate "Sélectionner un fichier existant (déjà importé)" %}
</label>
<select name="dxf_file" id="dxf_file" class="form-select form-select-lg rounded-3">
<option value="">-- {% translate "Sélectionner un fichier" %} --</option>
{% for file in dxf_files %}
<option value="{{ file }}" {% if file == selected_file %}selected{% endif %}>{{ file }}</option>
{% endfor %}
</select>
<div class="form-text mt-1 text-muted">
{% translate "Sélectionnez un fichier DXF parmi les fichiers déjà disponibles sur le serveur." %}
</div>
</div>
<div class="d-flex align-items-center my-4">
<hr class="flex-grow-1" style="border-top: 1px solid #e2e8f0;">
<span class="mx-3 text-secondary font-weight-medium">{% translate "OU" %}</span>
<hr class="flex-grow-1" style="border-top: 1px solid #e2e8f0;">
</div>
<!-- Upload DXF File -->
<div class="mb-4">
<label for="dxf_file_upload" class="form-label font-weight-semibold text-secondary">
{% translate "Téléverser un nouveau fichier DXF" %}
</label>
<input type="file" name="dxf_file_upload" id="dxf_file_upload" class="form-control rounded-3" accept=".dxf">
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary btn-lg rounded-3 shadow-sm font-weight-semibold">
<i class="bi bi-search me-1"></i> {% translate "Analyser le fichier" %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Modal for Layer Selection & Progress -->
<div class="modal fade" id="layerSelectModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="layerSelectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content border-0 shadow rounded-4 bg-white">
<div class="modal-header border-bottom-0 pb-0">
<h5 class="modal-title font-weight-bold text-dark" id="layerSelectModalLabel">
<i class="bi bi-layers-half text-primary me-2"></i>
{% translate "Importer les couches vectorielles" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="modal-btn-close"></button>
</div>
<div class="modal-body py-4">
<!-- Layer Selection Area -->
<div id="modal-selection-area">
<p class="text-secondary small mb-3">
{% translate "Sélectionnez les couches vectorielles à importer depuis le fichier DXF. Les couches non cochées ne seront pas converties ni affichées sur la carte." %}
</p>
<!-- Search Box -->
<div class="mb-3">
<input type="text" id="layer-search-input" class="form-control rounded-3" placeholder="{% translate 'Rechercher un calque...' %}">
</div>
<div class="mb-3 d-flex gap-2">
<button type="button" id="btn-select-all-layers" class="btn btn-sm btn-outline-primary rounded">{% translate "Tout cocher" %}</button>
<button type="button" id="btn-select-none-layers" class="btn btn-sm btn-outline-secondary rounded">{% translate "Tout décocher" %}</button>
</div>
<div id="modal-layers-container" class="row g-2 overflow-auto" style="max-height: 250px; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; background-color: #f8fafc;">
<!-- Checkboxes populated via JS -->
</div>
<div class="d-flex justify-content-end gap-2 mt-4">
<button type="button" class="btn btn-outline-secondary px-4 rounded-3" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
<button type="button" id="btn-start-import" class="btn btn-primary px-4 rounded-3 font-weight-semibold shadow-sm">
<i class="bi bi-play-fill me-1"></i> {% translate "Lancer l'importation" %}
</button>
</div>
</div>
<!-- Progress Bar / Spinner Area -->
<div id="modal-progress-area" style="display: none;" class="text-center py-4">
<div class="spinner-border text-primary mb-3" style="width: 3rem; height: 3rem;" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<h6 id="modal-progress-title" class="font-weight-bold text-dark">{% translate "Génération de l'image géolocalisée et des couches vectorielles..." %}</h6>
<p id="modal-progress-desc" class="text-muted small">{% translate "Cette opération peut prendre un peu de temps..." %}</p>
<div class="progress mt-4 mx-auto" style="height: 10px; max-width: 80%;">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('dxf-import-form');
const fileInput = document.getElementById('dxf_file_upload');
const fileSelect = document.getElementById('dxf_file');
const intersectionId = "{{ intersection.id }}";
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
const dxfFileParam = "{% if selected_file %}{{ selected_file|escapejs }}{% endif %}";
// Modal elements
const modalEl = document.getElementById('layerSelectModal');
const modal = new bootstrap.Modal(modalEl);
const layersContainer = document.getElementById('modal-layers-container');
const selectionArea = document.getElementById('modal-selection-area');
const progressArea = document.getElementById('modal-progress-area');
const btnStartImport = document.getElementById('btn-start-import');
const btnCloseModal = document.getElementById('modal-btn-close');
let selectedFilename = null;
// Select All / Select None
document.getElementById('btn-select-all-layers').addEventListener('click', () => {
document.querySelectorAll('.modal-layer-checkbox').forEach(cb => cb.checked = true);
});
document.getElementById('btn-select-none-layers').addEventListener('click', () => {
document.querySelectorAll('.modal-layer-checkbox').forEach(cb => cb.checked = false);
});
function analyzeFile(filename, isUpload = false, fileObj = null) {
const submitBtn = form.querySelector('button[type="submit"]');
const origHtml = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span> {% translate "Analyse..." %}';
// Update progress titles for the initial upload/analysis step and show modal
const progressTitle = document.getElementById('modal-progress-title');
const progressDesc = document.getElementById('modal-progress-desc');
if (progressTitle && progressDesc) {
if (isUpload) {
progressTitle.innerText = "{% translate 'Téléversement et analyse du fichier DXF...' %}";
progressDesc.innerText = "{% translate 'Veuillez patienter pendant l\'envoi et la lecture des calques du fichier...' %}";
} else {
progressTitle.innerText = "{% translate 'Analyse du fichier DXF...' %}";
progressDesc.innerText = "{% translate 'Lecture des calques disponibles dans le fichier...' %}";
}
}
selectionArea.style.display = 'none';
progressArea.style.display = 'block';
btnCloseModal.style.display = 'none';
modal.show();
let fetchPromise;
if (isUpload && fileObj) {
const formData = new FormData();
formData.append('dxf_file_upload', fileObj);
formData.append('csrfmiddlewaretoken', csrfToken);
fetchPromise = fetch(`/assets/trafficlights/intersections/${intersectionId}/import-dxf/?ajax=true`, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
} else {
fetchPromise = fetch(`/assets/trafficlights/intersections/${intersectionId}/dxf-layers/?file=${encodeURIComponent(filename)}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
}
fetchPromise
.then(response => {
if (!response.ok) {
return response.json().then(data => { throw new Error(data.error || "Server error"); });
}
return response.json();
})
.then(data => {
selectedFilename = data.filename;
const layers = data.layers || [];
// Reset search input
const searchInput = document.getElementById('layer-search-input');
if (searchInput) {
searchInput.value = '';
}
layersContainer.innerHTML = '';
if (layers.length === 0) {
layersContainer.innerHTML = '<div class="col-12 text-muted">{% translate "Aucune couche trouvée dans le fichier." %}</div>';
} else {
layers.forEach(layer => {
const layerName = typeof layer === 'object' ? layer.name : layer;
layersContainer.innerHTML += `
<div class="col-md-6 col-12 modal-layer-item" data-layer="${layerName}">
<div class="form-check">
<input class="form-check-input modal-layer-checkbox" type="checkbox" value="${layerName}" id="layer-${layerName}" data-layer="${layerName}">
<label class="form-check-label text-dark font-monospace small" for="layer-${layerName}">
${layerName}
</label>
</div>
</div>
`;
});
}
selectionArea.style.display = 'block';
progressArea.style.display = 'none';
btnCloseModal.style.display = 'block';
submitBtn.disabled = false;
submitBtn.innerHTML = origHtml;
})
.catch(err => {
modal.hide();
submitBtn.disabled = false;
submitBtn.innerHTML = origHtml;
alert("{% translate 'Erreur lors de l\'analyse du fichier : ' %}" + err.message);
});
}
form.addEventListener('submit', function(e) {
e.preventDefault();
const hasUpload = fileInput.files && fileInput.files.length > 0;
const hasSelect = fileSelect.value !== "";
if (!hasUpload && !hasSelect) {
alert("{% translate 'Veuillez sélectionner un fichier DXF ou en téléverser un.' %}");
return;
}
if (hasUpload) {
analyzeFile("", true, fileInput.files[0]);
} else {
analyzeFile(fileSelect.value);
}
});
btnStartImport.addEventListener('click', function() {
const checkedLayers = [];
document.querySelectorAll('.modal-layer-checkbox:checked').forEach(cb => {
checkedLayers.push(cb.dataset.layer);
});
// Reset progress texts for the final heavy processing step
const progressTitle = document.getElementById('modal-progress-title');
const progressDesc = document.getElementById('modal-progress-desc');
if (progressTitle && progressDesc) {
progressTitle.innerText = "{% translate 'Génération de l\'image géolocalisée et des couches vectorielles...' %}";
progressDesc.innerText = "{% translate 'Cette opération peut prendre un peu de temps...' %}";
}
selectionArea.style.display = 'none';
progressArea.style.display = 'block';
btnCloseModal.style.display = 'none';
const formData = new FormData();
formData.append('csrfmiddlewaretoken', csrfToken);
formData.append('dxf_file', selectedFilename);
formData.append('layers', checkedLayers.join(','));
fetch(`/assets/trafficlights/intersections/${intersectionId}/save-dxf-layers/`, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
window.location.href = `{% url 'assets:trafficlights_intersections_detail' intersection.id %}`;
})
.catch(err => {
console.error("Error during import:", err);
window.location.href = `{% url 'assets:trafficlights_intersections_detail' intersection.id %}`;
});
});
// Bind search filter input listener
const searchInput = document.getElementById('layer-search-input');
if (searchInput) {
searchInput.addEventListener('input', function() {
const query = this.value.toLowerCase().trim();
document.querySelectorAll('.modal-layer-item').forEach(item => {
const layerName = item.dataset.layer.toLowerCase();
if (layerName.includes(query)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
}
// Trigger automatically if file preselected in URL
if (dxfFileParam && dxfFileParam !== "None" && dxfFileParam !== "null" && dxfFileParam !== "") {
analyzeFile(dxfFileParam);
}
});
</script>
</div>
{% endblock %}