351 lines
19 KiB
HTML
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 %}
|