801 lines
No EOL
29 KiB
JavaScript
801 lines
No EOL
29 KiB
JavaScript
|
|
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}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
`;
|
|
|
|
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 = `
|
|
<span><i class="bi bi-person me-1"></i>${member.first_name} ${member.last_name}${roleLabel}</span>
|
|
<a href="#" class="btn btn-sm btn-outline-secondary border-0" title="Supprimer"
|
|
onclick="openDeleteModal(this)"
|
|
data-member-id="${member.id}"
|
|
data-member-type="user"
|
|
data-member-name="${member.first_name} ${member.last_name}${roleLabel}"
|
|
data-bs-toggle="modal" data-bs-target="#deleteMemberModal">
|
|
<i class="bi bi-trash"></i>
|
|
</a>
|
|
`;
|
|
container.appendChild(wrapper);
|
|
} else {
|
|
let errorMsg = data.errors ? Object.values(data.errors).flat().join("<br>") : 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 = `
|
|
<span><i class="bi bi-people me-1"></i>${displayName}${roleLabel}</span>
|
|
<a href="#" class="btn btn-sm btn-outline-secondary border-0" title="Supprimer"
|
|
onclick="openDeleteModal(this)"
|
|
data-member-id="${ta.id}"
|
|
data-member-type="team"
|
|
data-member-name="${displayName}${roleLabel}"
|
|
data-bs-toggle="modal" data-bs-target="#deleteMemberModal">
|
|
<i class="bi bi-trash"></i>
|
|
</a>
|
|
`;
|
|
container.appendChild(wrapper);
|
|
} else {
|
|
let errorMsg = data.errors ? Object.values(data.errors).flat().join("<br>") : 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 = `<strong>${memberToDeleteName}</strong>`;
|
|
|
|
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 = `<strong>${orderToDissociateCode}</strong>`;
|
|
|
|
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 = `<strong>${phaseToDeleteName}</strong>`;
|
|
|
|
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 = '<i class="bi bi-x-lg"></i>';
|
|
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 = '<div class="text-center py-4"><div class="spinner-border text-primary" role="status"></div></div>';
|
|
}
|
|
|
|
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 = '<p class="text-muted">Impossible de charger l\'historique.</p>';
|
|
});
|
|
});
|
|
}
|
|
|
|
// ─── 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();
|
|
});
|
|
});
|
|
}); |