var center = [4.3572, 50.8477]; var zoom = 11; if (feature_lon && feature_lat) { center = [feature_lon, feature_lat]; zoom = 17 } const map = new maplibregl.Map({ style: window.MAP_STYLE_LIGHT, center: center, zoom: zoom, pitch: 45, bearing: -17.6, container: 'map', antialias: true }); map.on('load', () => { if (geomGeoJSON && geomGeoJSON.features.length > 0) { map.addSource('project', { type: 'geojson', data: geomGeoJSON }); map.addLayer({ id: 'project-layer', type: 'fill', source: 'project', paint: { 'fill-color': '#007cbf', 'fill-opacity': 0.5, 'fill-outline-color': '#000' } }); // Zoom sur le bounding box du projet const bounds = new maplibregl.LngLatBounds(); geomGeoJSON.features.forEach(feature => { const geom = feature.geometry; if (!geom) return; const rings = geom.type === 'Polygon' ? geom.coordinates : geom.type === 'MultiPolygon' ? geom.coordinates.flat(1) : []; rings.forEach(ring => ring.forEach(coord => bounds.extend(coord))); }); if (!bounds.isEmpty()) { map.fitBounds(bounds, { padding: 40 }); } } }); // Initialization after page load $( document ).ready(function() { $('.circle.action').on('click', function () { //alert("Action pour passer au statut " + $(this).attr('status')); }); }); function showAlert(message, alertContainer, type="success") { const alertBox = document.createElement("div"); alertBox.className = `alert alert-${type} alert-dismissible fade show`; alertBox.role = "alert"; alertBox.innerHTML = ` ${message} `; alertContainer.appendChild(alertBox); // Auto-dismiss after 5 seconds // setTimeout(() => { // alertBox.classList.remove("show"); // alertBox.classList.add("hide"); // }, 5000); } const addMemberForm = document.getElementById("addMemberForm"); if (addMemberForm) { addMemberForm.addEventListener("submit", function(e) { e.preventDefault(); submitUserMemberForm(); }); } function submitAccessForm() { const teamTabActive = document.getElementById('team-tab')?.classList.contains('active'); if (teamTabActive) { submitTeamAccessForm(); } else { submitUserMemberForm(); } } function submitUserMemberForm() { const form = document.getElementById("addMemberForm"); if (!form) return; const alertContainer = document.getElementById("membersAlertContainer"); const userId = document.getElementById("userAccessUserId").value; if (!userId) { showAlert("❌ " + gettext("Veuillez sélectionner un utilisateur."), alertContainer, "danger"); return; } const formData = new FormData(form); fetch(`/projects/${project_id}/add_member/`, { method: "POST", headers: { "X-CSRFToken": form.querySelector('[name=csrfmiddlewaretoken]').value, }, body: formData }) .then(response => response.json()) .then(data => { if (data.success) { showAlert("✅ Membre ajouté !", alertContainer, "success"); resetUserSearch(); form.reset(); const modal = bootstrap.Modal.getInstance(document.getElementById('addMemberModal')); modal.hide(); const member = data.member; const container = document.querySelector("#membersContainer"); const wrapper = document.createElement("div"); wrapper.id = `member-container-${member.id}`; wrapper.className = "border-bottom p-1 d-flex justify-content-between align-items-center"; const roleLabel = member.can_edit ? ` - ${gettext('Éditeur')}` : ''; wrapper.innerHTML = ` ${member.first_name} ${member.last_name}${roleLabel} `; container.appendChild(wrapper); } else { let errorMsg = data.errors ? Object.values(data.errors).flat().join("
") : data.error; showAlert("❌ " + errorMsg, alertContainer, "danger"); } }); } function submitTeamAccessForm() { const form = document.getElementById("addTeamAccessForm"); if (!form) return; const alertContainer = document.getElementById("membersAlertContainer"); const teamId = document.getElementById("teamAccessTeamId").value; if (!teamId) { showAlert("❌ " + gettext("Veuillez sélectionner une équipe."), alertContainer, "danger"); return; } const formData = new FormData(form); fetch(`/projects/${project_id}/add-team-access/`, { method: "POST", headers: { "X-CSRFToken": form.querySelector('[name=csrfmiddlewaretoken]').value, }, body: formData }) .then(response => response.json()) .then(data => { if (data.success) { showAlert("✅ Équipe ajoutée !", alertContainer, "success"); resetTeamSearch(); form.reset(); const modal = bootstrap.Modal.getInstance(document.getElementById('addMemberModal')); modal.hide(); const ta = data.team_access; const container = document.querySelector("#membersContainer"); const wrapper = document.createElement("div"); wrapper.id = `team-access-container-${ta.id}`; wrapper.className = "border-bottom p-1 d-flex justify-content-between align-items-center"; const roleLabel = ta.can_edit ? ` - ${gettext('Éditeur')}` : ''; const displayName = `${ta.team_name} (${ta.team_company})`; wrapper.innerHTML = ` ${displayName}${roleLabel} `; container.appendChild(wrapper); } else { let errorMsg = data.errors ? Object.values(data.errors).flat().join("
") : data.error; showAlert("❌ " + errorMsg, alertContainer, "danger"); } }); } function resetTeamSearch() { const input = document.getElementById("teamSearchInput"); const results = document.getElementById("teamSearchResults"); const display = document.getElementById("teamSelectedDisplay"); const hiddenId = document.getElementById("teamAccessTeamId"); if (input) input.value = ''; if (results) results.innerHTML = ''; if (display) { display.textContent = ''; display.classList.add('d-none'); } if (hiddenId) hiddenId.value = ''; } function resetUserSearch() { const input = document.getElementById("userSearchInput"); const results = document.getElementById("userSearchResults"); const display = document.getElementById("userSelectedDisplay"); const hiddenId = document.getElementById("userAccessUserId"); if (input) input.value = ''; if (results) results.innerHTML = ''; if (display) { display.textContent = ''; display.classList.add('d-none'); } if (hiddenId) hiddenId.value = ''; } function renderUserResults(users) { const results = document.getElementById("userSearchResults"); if (!results) return; results.innerHTML = ''; users.slice(0, 20).forEach(u => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'list-group-item list-group-item-action py-1 px-2'; btn.textContent = u.text; btn.addEventListener('click', function () { document.getElementById("userAccessUserId").value = u.id; const display = document.getElementById("userSelectedDisplay"); display.textContent = `✔ ${u.text}`; display.classList.remove('d-none'); document.getElementById("userSearchInput").value = ''; results.innerHTML = ''; }); results.appendChild(btn); }); } function fetchUsers(term) { const url = `${user_search_url}?q=${encodeURIComponent(term)}&project_id=${project_id}`; fetch(url) .then(r => r.json()) .then(data => renderUserResults(data.results || [])); } // Team + User search logic document.addEventListener('DOMContentLoaded', function () { // ---- Team search ---- const teamSearchInput = document.getElementById("teamSearchInput"); if (teamSearchInput) { function renderTeamResults(term) { const results = document.getElementById("teamSearchResults"); results.innerHTML = ''; const filtered = (typeof allTeams !== 'undefined' ? allTeams : []).filter(t => !term || t.name.toLowerCase().includes(term) || t.company.toLowerCase().includes(term) ); filtered.slice(0, 20).forEach(t => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'list-group-item list-group-item-action py-1 px-2'; btn.textContent = `${t.name} (${t.company})`; btn.addEventListener('click', function () { document.getElementById("teamAccessTeamId").value = t.id; const display = document.getElementById("teamSelectedDisplay"); display.textContent = `✔ ${t.name} (${t.company})`; display.classList.remove('d-none'); teamSearchInput.value = ''; results.innerHTML = ''; }); results.appendChild(btn); }); } teamSearchInput.addEventListener('focus', function () { renderTeamResults(this.value.toLowerCase().trim()); }); teamSearchInput.addEventListener('input', function () { renderTeamResults(this.value.toLowerCase().trim()); }); } // ---- User search ---- const userSearchInput = document.getElementById("userSearchInput"); if (userSearchInput) { let userSearchTimer = null; userSearchInput.addEventListener('focus', function () { fetchUsers(this.value.trim()); }); userSearchInput.addEventListener('input', function () { clearTimeout(userSearchTimer); const term = this.value.trim(); userSearchTimer = setTimeout(() => fetchUsers(term), 200); }); } // Reset both searches when modal is hidden const addMemberModal = document.getElementById('addMemberModal'); if (addMemberModal) { addMemberModal.addEventListener('hidden.bs.modal', function () { resetTeamSearch(); resetUserSearch(); }); } // Accès strict toggle const strictAccessToggle = document.getElementById('strictAccessToggle'); if (strictAccessToggle) { strictAccessToggle.addEventListener('change', function () { fetch(`/projects/${project_id}/toggle-strict-access/`, { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/x-www-form-urlencoded', }, body: `strict_access=${this.checked ? 'true' : 'false'}`, }) .then(r => r.json()) .then(data => { if (!data.success) { this.checked = !this.checked; } }) .catch(() => { this.checked = !this.checked; }); }); } }); // Remove Select2 initialisation (replaced by custom search) let memberToDeleteId = null; let memberToDeleteType = null; let memberToDeleteName = null; let memberDivToDelete = null; // Quand on clique sur la corbeille function openDeleteModal(element) { memberToDeleteId = element.dataset.memberId; memberToDeleteType = element.dataset.memberType; memberToDeleteName = element.dataset.memberName; const prefix = memberToDeleteType === 'team' ? 'team-access-container' : 'member-container'; memberDivToDelete = document.getElementById(`${prefix}-${memberToDeleteId}`); const memberNameContainer = document.getElementById("memberNameToDeleteContainer"); memberNameContainer.innerHTML = `${memberToDeleteName}`; const modalEl = document.getElementById('deleteMemberModal'); const modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); } function deleteMember(element) { if (!memberToDeleteId) return; const memberId = memberToDeleteId; const memberDiv = memberDivToDelete; const alertContainer = document.getElementById("membersAlertContainer"); const url = memberToDeleteType === 'team' ? `/projects/${project_id}/team-access/${memberId}/delete/` : `/projects/${project_id}/members/${memberId}/delete/`; fetch(url, { method: "POST", headers: { "X-CSRFToken": getCookie("csrftoken") } }) .then(response => response.json()) .then(data => { if (data.success) { memberDiv.remove(); showAlert(`✅ ${gettext("Membre supprimé")} !`, alertContainer, "success"); } else { showAlert(`❌ ${gettext("Erreur")} : ` + data.error, alertContainer, "danger"); } // Réinitialiser et fermer la modale memberToDeleteId = null; memberToDeleteType = null; memberDivToDelete = null; //const memberNameContainer = document.getElementById("memberNameToDeleteContainer"); //memberNameContainer.innerHTML = ''; const modal = bootstrap.Modal.getInstance(document.getElementById('deleteMemberModal')); if (modal) modal.hide(); }); } $(document).ready(function () { // Event listener pour les boutons de dissociation de commandes $(document).on('click', '.dissociate-order-btn', function(e) { e.preventDefault(); openDissociateOrderModal(this); }); // Initialisation de Select2 pour le champ de sélection des commandes $('#id_order_select').select2({ dropdownParent: $('#associateOrderModal'), theme: 'bootstrap-5', placeholder: gettext('Rechercher une commande...'), allowClear: true, width: '100%' }); // Event listener pour les boutons de suppression de phases $(document).on('click', '.delete-phase-btn', function(e) { e.preventDefault(); openDeletePhaseModal(this); }); // Event listener pour confirmer la suppression de phase $('#confirmDeletePhaseBtn').on('click', function() { deletePhase(); }); }); function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } // Gestion de la dissociation de commandes let orderToDissociateId = null; let orderToDissociateCode = null; function openDissociateOrderModal(element) { orderToDissociateId = element.dataset.orderId; orderToDissociateCode = element.dataset.orderCode; const orderCodeContainer = document.getElementById("orderCodeToDissociateContainer"); orderCodeContainer.innerHTML = `${orderToDissociateCode}`; const modalEl = document.getElementById('dissociateOrderModal'); const modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); } function dissociateOrder() { if (!orderToDissociateId) return; const orderId = orderToDissociateId; fetch(`/projects/${project_id}/dissociate-order/${orderId}/`, { method: "POST", headers: { "X-CSRFToken": getCookie("csrftoken") } }) .then(response => response.json()) .then(data => { if (data.success) { // Recharger la page pour afficher les changements location.reload(); } else { alert(`${gettext("Erreur")} : ` + data.error); } // Réinitialiser et fermer la modale orderToDissociateId = null; const modal = bootstrap.Modal.getInstance(document.getElementById('dissociateOrderModal')); if (modal) modal.hide(); }); } // Gestion de la suppression de phases let phaseToDeleteId = null; let phaseToDeleteName = null; function openDeletePhaseModal(element) { phaseToDeleteId = element.dataset.phaseId; phaseToDeleteName = element.dataset.phaseName; const phaseNameContainer = document.getElementById("phaseNameToDeleteContainer"); phaseNameContainer.innerHTML = `${phaseToDeleteName}`; const modalEl = document.getElementById('deletePhaseModal'); const modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); } function deletePhase() { if (!phaseToDeleteId) return; const phaseId = phaseToDeleteId; fetch(`/projects/${project_id}/phases/${phaseId}/delete/`, { method: "POST", headers: { "X-CSRFToken": getCookie("csrftoken") } }) .then(response => response.json()) .then(data => { if (data.success) { // Supprimer l'élément du DOM const phaseContainer = document.getElementById(`phase-container-${phaseId}`); if (phaseContainer) { phaseContainer.remove(); } // Fermer la modale const modal = bootstrap.Modal.getInstance(document.getElementById('deletePhaseModal')); if (modal) modal.hide(); // Recharger la page pour mettre à jour l'affichage location.reload(); } else { alert(`${gettext("Erreur")} : ` + (data.error || gettext("Une erreur est survenue"))); } // Réinitialiser phaseToDeleteId = null; phaseToDeleteName = null; }) .catch(error => { alert(`${gettext("Erreur")} : ` + error); phaseToDeleteId = null; phaseToDeleteName = null; }); } // ─── Document management (add form with tags + history modal) ─────────────── const docFileInput = document.getElementById('docFileInput'); const docFileContainer = document.getElementById('docFileFieldsContainer'); // Runtime config loaded via API let docConfig = { use_managed_documents: false, can_add_unique_tag_documents: false, config_url: null }; let docAvailableTags = []; let docAvailableFolders = []; (function loadDocConfig() { try { const el = document.getElementById('projectDocumentConfigData'); if (el) { docConfig = JSON.parse(el.textContent); } } catch (e) {} if (!docConfig.config_url) return; fetch(docConfig.config_url) .then(r => r.json()) .then(data => { docConfig.use_managed_documents = data.use_managed_documents; docConfig.can_add_unique_tag_documents = data.can_add_unique_tag_documents; docAvailableTags = data.tags || []; docAvailableFolders = data.folders || []; }) .catch(() => {}); })(); function renderDocFileFields() { if (!docFileContainer || !docFileInput) return; docFileContainer.innerHTML = ''; const files = Array.from(docFileInput.files); files.forEach((file, index) => { const row = document.createElement('div'); row.classList.add('d-flex', 'align-items-center', 'gap-3', 'mb-2', 'p-2', 'border', 'rounded'); // File name (read-only label) const fileName = document.createElement('span'); fileName.classList.add('flex-grow-1', 'text-truncate'); fileName.style.maxWidth = '220px'; fileName.textContent = file.name; fileName.title = file.name; row.appendChild(fileName); if (docConfig.use_managed_documents && docAvailableTags.length > 0) { // ── Tag select ── const tagSel = document.createElement('select'); tagSel.name = `tag_${index}`; tagSel.classList.add('form-select', 'w-auto'); tagSel.style.minWidth = '160px'; const def = document.createElement('option'); def.value = ''; def.textContent = gettext('-- Sans tag --'); tagSel.appendChild(def); docAvailableTags.forEach(tag => { const opt = document.createElement('option'); opt.value = tag.slug; opt.textContent = tag.name; opt.dataset.isUnique = tag.is_unique; if (tag.is_unique && !docConfig.can_add_unique_tag_documents) { opt.disabled = true; opt.textContent += ' (non autorisé)'; } tagSel.appendChild(opt); }); row.appendChild(tagSel); // ── Folder select (non-unique tags only) ── const folderSel = document.createElement('select'); folderSel.name = `folder_${index}`; folderSel.classList.add('form-select', 'w-auto', 'd-none'); folderSel.style.minWidth = '180px'; docAvailableFolders.forEach(folder => { const opt = document.createElement('option'); opt.value = folder.id; opt.textContent = folder.path; if (folder.is_root) opt.selected = true; folderSel.appendChild(opt); }); row.appendChild(folderSel); tagSel.addEventListener('change', function () { const selOpt = tagSel.options[tagSel.selectedIndex]; const isUniq = selOpt.dataset.isUnique === 'true' || selOpt.dataset.isUnique === 'True'; if (!isUniq && tagSel.value) { folderSel.classList.remove('d-none'); } else { folderSel.classList.add('d-none'); } }); } else { // ── Legacy: hidden input keeps count consistent ── const hidden = document.createElement('input'); hidden.type = 'hidden'; hidden.name = `document_type_${index}`; hidden.value = 'other'; row.appendChild(hidden); } // ── Remove button ── const delBtn = document.createElement('button'); delBtn.type = 'button'; delBtn.classList.add('btn', 'btn-outline-danger', 'btn-sm'); delBtn.innerHTML = ''; delBtn.onclick = () => { const remaining = Array.from(docFileInput.files).filter((_, i) => i !== index); const dt = new DataTransfer(); remaining.forEach(f => dt.items.add(f)); docFileInput.files = dt.files; renderDocFileFields(); }; row.appendChild(delBtn); docFileContainer.appendChild(row); }); // hidden file_count const countInput = document.createElement('input'); countInput.type = 'hidden'; countInput.name = 'file_count'; countInput.value = files.length; docFileContainer.appendChild(countInput); } if (docFileInput) { docFileInput.addEventListener('change', renderDocFileFields); } // ── Document history modal ──────────────────────────────────────────────────── const docHistoryModalEl = document.getElementById('documentHistoryModal'); if (docHistoryModalEl) { docHistoryModalEl.addEventListener('show.bs.modal', function (event) { const btn = event.relatedTarget; const tagSlug = btn.getAttribute('data-tag-slug'); const locationId = btn.getAttribute('data-location-id'); const useManaged = btn.getAttribute('data-use-managed') === 'true'; const uniqueParams = btn.getAttribute('data-unique-params'); const contentEl = document.getElementById('documentHistoryContent'); if (contentEl) { contentEl.innerHTML = '
'; } if (!useManaged || !tagSlug) return; let url = `${document_history_url}?tag_slug=${encodeURIComponent(tagSlug)}`; if (uniqueParams && uniqueParams !== '{}' && uniqueParams !== 'null') { url += `&unique_params=${encodeURIComponent(uniqueParams)}`; } fetch(url) .then(r => r.json()) .then(data => { if (contentEl) contentEl.innerHTML = data.html || ''; }) .catch(() => { if (contentEl) contentEl.innerHTML = '

Impossible de charger l\'historique.

'; }); }); } // ─── End document management ───────────────────────────────────────────────── // Gestion de la suppression de documents document.addEventListener('DOMContentLoaded', function () { const documentsContainer = document.getElementById('documentsContainer'); const deleteDocumentModalEl = document.getElementById('deleteDocumentModal'); const confirmDeleteBtn = document.getElementById('confirmDeleteDocument'); const documentsAlertContainer = document.getElementById('documentsAlertContainer'); if (!documentsContainer || !deleteDocumentModalEl || !confirmDeleteBtn) { return; } const modal = bootstrap.Modal.getOrCreateInstance(deleteDocumentModalEl); const documentNameContainer = document.getElementById('documentNameToDelete'); const successDeleteDocumentMsg = typeof gettext !== 'undefined' ? gettext('Document supprimé !') : 'Document supprimé !'; const errorDeleteDocumentMsg = typeof gettext !== 'undefined' ? gettext('Une erreur est survenue lors de la suppression du document.') : 'Une erreur est survenue lors de la suppression du document.'; let documentIdToDelete = null; let documentCardToDelete = null; documentsContainer.addEventListener('click', (event) => { const deleteBtn = event.target.closest('.delete-document-btn'); if (!deleteBtn) { return; } documentIdToDelete = deleteBtn.dataset.documentId || null; documentCardToDelete = documentIdToDelete ? document.getElementById(`document-card-${documentIdToDelete}`) : null; if (documentNameContainer) { documentNameContainer.textContent = deleteBtn.dataset.documentName || ''; } modal.show(); }); deleteDocumentModalEl.addEventListener('hidden.bs.modal', () => { documentIdToDelete = null; documentCardToDelete = null; confirmDeleteBtn.disabled = false; if (documentNameContainer) { documentNameContainer.textContent = ''; } }); confirmDeleteBtn.addEventListener('click', () => { if (!documentIdToDelete) { return; } confirmDeleteBtn.disabled = true; fetch(`/projects/${project_id}/documents/${documentIdToDelete}/delete/`, { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken'), 'X-Requested-With': 'XMLHttpRequest', }, }) .then(response => response.json().then(data => ({ ok: response.ok, data }))) .then(({ ok, data }) => { if (ok && data.success) { if (documentCardToDelete) { documentCardToDelete.remove(); } if (documentsAlertContainer) { showAlert(`✅ ${successDeleteDocumentMsg}`, documentsAlertContainer, 'success'); } } else { if (documentsAlertContainer) { const errorMsg = data && data.error ? data.error : errorDeleteDocumentMsg; showAlert(`❌ ${errorMsg}`, documentsAlertContainer, 'danger'); } } }) .catch(() => { if (documentsAlertContainer) { showAlert(`❌ ${errorDeleteDocumentMsg}`, documentsAlertContainer, 'danger'); } }) .finally(() => { confirmDeleteBtn.disabled = false; modal.hide(); }); }); });