loko/streetup/interventions/static/interventions/timeline.js
2026-07-22 14:48:40 +02:00

837 lines
32 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const statusTimeline = document.getElementById("statusTimeline");
let currentIndex = -1;
/* -------- Status color map (réfère à la constante de status_colors.js) -------- */
const STATUS_COLORS = INTERVENTION_STATUS_COLORS;
const STATUS_TEXT_DARK = new Set(['to_be_approved', 'to_be_planned', 'assigned', 'on_pause', 'processed', 'to_be_corrected', 'corrected', 'canceled']);
STATUS_CHOICES.forEach((status, index) => {
if (status.key === "to_be_approved") {
STATUS_CHOICES[index].transition_label = gettext("Envoyer pour approbation");
}
if (status.key === "to_be_planned") {
STATUS_CHOICES[index].transition_label = gettext("Envoyer pour planification");
}
if (status.key === "to_be_processed") {
STATUS_CHOICES[index].transition_label = gettext("Envoyer pour traitement");
}
if (status.key === "assigned") {
STATUS_CHOICES[index].transition_label = gettext("Prendre en charge");
}
if (status.key === "in_progress") {
STATUS_CHOICES[index].transition_label = gettext("Démarrer l'intervention");
}
else if (status.key === "on_pause") {
STATUS_CHOICES[index].transition_label = gettext("Mettre en pause");
}
else if (status.key === "finished") {
STATUS_CHOICES[index].transition_label = gettext("Terminer l'intervention");
}
else if (status.key === "processed") {
STATUS_CHOICES[index].transition_label = gettext("Envoyer pour validation");
}
else if (status.key === "to_be_corrected") {
STATUS_CHOICES[index].transition_label = gettext("Demander correction");
}
else if (status.key === "corrected") {
STATUS_CHOICES[index].transition_label = gettext("Corriger");
}
else if (status.key === "validated") {
STATUS_CHOICES[index].transition_label = gettext("Valider");
}
else if (status.key === "invoiced") {
STATUS_CHOICES[index].transition_label = gettext("Facturer");
}
else if (status.key === "closed") {
STATUS_CHOICES[index].transition_label = gettext("Finaliser");
}
else if (status.key === "canceled") {
STATUS_CHOICES[index].transition_label = gettext("Annuler");
}
});
/* -------- Transition helpers -------- */
const TRANSITIONS = Array.isArray(allowed_transitions) ? allowed_transitions : [];
// Ensemble des "to" autorisés depuis le statut courant (en tenant compte de t.allowed)
const ALLOWED_TO_SET = new Set(
TRANSITIONS.filter(t => t.allowed !== false).map(t => t.to)
);
// Vrai si on peut aller vers `statusKey`
function canGoTo(statusKey) {
// Filtrage spécial pour 'assigned' (prise en charge) et 'in_progress' (démarrer) :
// Nécessite un contrat ET un accès au contrat (canTakeOver)
if (statusKey === 'assigned' || statusKey === 'in_progress') {
if (typeof canTakeOver === 'undefined' || !canTakeOver) {
return false;
}
}
return ALLOWED_TO_SET.has(statusKey);
}
// Métadonnées d'une transition vers `statusKey` (label, reason…)
function transitionMeta(statusKey) {
return TRANSITIONS.find(t => t.to === statusKey);
}
// Libellé d'action à afficher pour le bouton
function transitionLabel(statusKey) {
const t = transitionMeta(statusKey);
return (t && t.label) || (STATUS_CHOICES?.[statusKey] ?? statusKey);
}
// Raison éventuelle de blocage (quand t.allowed === false ; prêt pour les guards)
function transitionReason(statusKey) {
const t = transitionMeta(statusKey);
return (t && t.allowed === false && t.reason) ? t.reason : "";
}
/* -------- Pause reason modal -------- */
const pauseReasonModalEl = document.getElementById('pauseReasonModal');
let pauseReasonModal = null;
let pauseReasonSelect;
let pauseReasonOtherGroup;
let pauseReasonOtherInput;
let pauseReasonError;
if (pauseReasonModalEl) {
pauseReasonModal = bootstrap.Modal.getOrCreateInstance(pauseReasonModalEl);
pauseReasonSelect = document.getElementById('pauseReasonSelect');
pauseReasonOtherGroup = document.getElementById('pauseReasonOtherGroup');
pauseReasonOtherInput = document.getElementById('pauseReasonOtherInput');
pauseReasonError = document.getElementById('pauseReasonError');
const confirmPauseReasonBtn = document.getElementById('confirmPauseReason');
function hidePauseReasonError() {
if (pauseReasonError) {
pauseReasonError.classList.add('d-none');
pauseReasonError.textContent = '';
}
}
function showPauseReasonError(message) {
if (!pauseReasonError) {
return;
}
pauseReasonError.textContent = message;
pauseReasonError.classList.remove('d-none');
}
function resetPauseReasonModal() {
if (pauseReasonSelect) {
pauseReasonSelect.value = '';
}
if (pauseReasonOtherGroup) {
pauseReasonOtherGroup.classList.add('d-none');
}
if (pauseReasonOtherInput) {
pauseReasonOtherInput.value = '';
}
hidePauseReasonError();
}
if (pauseReasonSelect) {
pauseReasonSelect.addEventListener('change', () => {
hidePauseReasonError();
if (!pauseReasonOtherGroup || !pauseReasonOtherInput) {
return;
}
if (pauseReasonSelect.value === 'other') {
pauseReasonOtherGroup.classList.remove('d-none');
} else {
pauseReasonOtherGroup.classList.add('d-none');
pauseReasonOtherInput.value = '';
}
});
}
if (pauseReasonModalEl) {
pauseReasonModalEl.addEventListener('hidden.bs.modal', resetPauseReasonModal);
}
if (confirmPauseReasonBtn) {
confirmPauseReasonBtn.addEventListener('click', () => {
if (!pauseReasonSelect) {
return;
}
const reason = pauseReasonSelect.value;
const otherText = pauseReasonOtherInput ? pauseReasonOtherInput.value.trim() : '';
if (!reason) {
showPauseReasonError(gettext('Veuillez sélectionner une raison.'));
return;
}
if (reason === 'other' && !otherText) {
showPauseReasonError(gettext('Veuillez préciser la raison de la mise en pause.'));
return;
}
pauseReasonModal.hide();
hidePauseReasonError();
updateStatus('on_pause', {
pause_reason: reason,
pause_reason_other: otherText,
});
});
}
}
// Trouver l'index du dernier statut visible et le prochain statut possible
STATUS_CHOICES.forEach((status, index) => {
if (status.key === currentStatus) {
currentIndex = index;
}
});
// Les transitions autorisées sont déterminées entièrement via canGoTo() / ALLOWED_TO_SET,
// qui provient du backend et tient compte de tous les rôles de l'utilisateur.
// Générer la timeline
// On affiche TOUS les statuts visibles (passés, courant, actions possibles, annulé).
// Les cercles qui correspondent à une transition autorisée deviennent des <button>
// colorés avec la couleur du statut cible.
STATUS_CHOICES.forEach((status, index) => {
let statusClass = "hide";
let dashedClass = "";
let label = status.label;
const isAllowed = canGoTo(status.key);
const isActionTarget = isAllowed;
if (index === currentIndex) {
statusClass = "current";
dashedClass = "next-step-dashed";
} else if (index < currentIndex) {
statusClass = "past";
if (!isActionTarget) {
label = "";
}
} else {
if (isActionTarget) {
statusClass = "next";
dashedClass = "next-step-dashed";
}
}
if (index === (STATUS_CHOICES.length - 1)) {
dashedClass = "";
}
// Ne pas rendre 'canceled' dans le flux principal (il a son propre cercle à la fin)
if (status.key === "canceled" && statusClass === "hide") return;
const step = document.createElement("div");
step.className = `step ${statusClass} ${dashedClass}`;
// Cercle futur passif (pas d'action autorisée, pas le statut courant)
if (statusClass === "hide") {
step.innerHTML = `
<div id="status_${status.key}" class="circle" title="${label}">
${index + 1}
</div>
<div class="spacer"></div>
`;
statusTimeline.appendChild(step);
return;
}
// Si l'utilisateur peut envoyer l'intervention vers ce statut → bouton coloré cliquable
if (isActionTarget && isAllowed) {
const hexColor = STATUS_COLORS[status.key] || '#757575';
const textColor = STATUS_TEXT_DARK.has(status.key) ? '#000000' : '#ffffff';
const isOperator = Array.isArray(userRoles) && userRoles.includes('operator');
const isRecommended = isOperator && status.key === recommendedNextStatus;
const recommendedClass = isRecommended ? ' circle-recommended' : '';
const transLabel = status.transition_label || status.label;
step.innerHTML = `
<button type="button"
id="status_${status.key}"
class="circle circle-action${recommendedClass}"
style="background-color: ${hexColor} !important; color: ${textColor} !important;"
data-status="${status.key}"
title="${transLabel}"
data-bs-toggle="tooltip" data-bs-placement="bottom">
${index + 1}
</button>
<div class="steplabel">${label}</div>
`;
} else if (index === currentIndex) {
// Cercle du statut actuel : coloré + bordure distincte
const hexColor = STATUS_COLORS[status.key] || '#757575';
const textColor = STATUS_TEXT_DARK.has(status.key) ? '#000000' : '#ffffff';
step.innerHTML = `
<div id="status_${status.key}" class="circle circle-current" style="background-color: ${hexColor} !important; box-shadow: 0 0 0 3px ${hexColor} !important; color: ${textColor} !important;">
${index + 1}
</div>
<div class="steplabel">${label}</div>
`;
} else {
// Cercle passif (passé ou transition non autorisée)
step.innerHTML = `
<div id="status_${status.key}" class="circle ${status.key}">
${index + 1}
</div>
<div class="steplabel">${label}</div>
`;
}
statusTimeline.appendChild(step);
});
// Initialize Bootstrap tooltips on clickable status circles
if (statusTimeline) {
statusTimeline.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
new bootstrap.Tooltip(el);
});
// Auto-scroll horizontally to center the current status circle
const currentCircle = statusTimeline.querySelector('.circle-current');
if (currentCircle) {
const step = currentCircle.closest('.step');
if (step) {
const scrollLeft = step.offsetLeft - (statusTimeline.clientWidth / 2) + (step.offsetWidth / 2);
statusTimeline.scrollLeft = Math.max(0, scrollLeft);
}
}
}
// $('.circle.action').on('click', function () {
// const newStatus = $(this).attr('status');
// $.ajax({
// url: `/interventions/${interventionId}/update-status/`,
// method: 'POST',
// headers: {'X-CSRFToken': csrftoken},
// data: {
// intervention_id: interventionId,
// new_status: newStatus
// },
// success: function (response) {
// // alert("Statut mis à jour avec succès !");
// location.reload(); // Recharge la page actuelle
// },
// error: function (xhr) {
// alert("Erreur lors de la mise à jour : " + xhr.responseText);
// }
// });
// });
let pendingStatus = null;
let pendingExtraData = null;
// Period validation modal
const periodValidationModalEl = document.getElementById('periodValidationModal');
let periodValidationModal = null;
let periodValidationMessageEl = null;
let confirmPeriodValidationBtn = null;
if (periodValidationModalEl) {
periodValidationModal = bootstrap.Modal.getOrCreateInstance(periodValidationModalEl);
periodValidationMessageEl = document.getElementById('periodValidationMessage');
confirmPeriodValidationBtn = document.getElementById('confirmPeriodValidationBtn');
if (confirmPeriodValidationBtn) {
confirmPeriodValidationBtn.addEventListener('click', function () {
if (!pendingStatus) return;
const extra = pendingExtraData || {};
periodValidationModal.hide();
updateStatus(pendingStatus, extra);
pendingStatus = null;
pendingExtraData = null;
});
}
}
// Correction reason modal
const correctionReasonModalEl = document.getElementById('correctionReasonModal');
let correctionReasonModal = null;
let reasonForCorrectionInput = null;
let confirmCorrectionReasonBtn = null;
if (correctionReasonModalEl) {
correctionReasonModal = bootstrap.Modal.getOrCreateInstance(correctionReasonModalEl);
reasonForCorrectionInput = document.getElementById('reasonForCorrectionInput');
confirmCorrectionReasonBtn = document.getElementById('confirmCorrectionReason');
if (confirmCorrectionReasonBtn) {
confirmCorrectionReasonBtn.addEventListener('click', function () {
const reason = reasonForCorrectionInput ? reasonForCorrectionInput.value.trim() : '';
if (!reason) {
const errorEl = document.getElementById('correctionReasonError');
if (errorEl) {
errorEl.textContent = gettext('Veuillez indiquer une raison de correction.');
errorEl.classList.remove('d-none');
}
return;
}
correctionReasonModal.hide();
// Sauvegarder la raison et changer le statut
saveCorrectionReasonThenUpdateStatus(reason);
});
}
// Nettoyer la modale quand elle se ferme
if (correctionReasonModalEl) {
correctionReasonModalEl.addEventListener('hidden.bs.modal', function () {
if (reasonForCorrectionInput) {
reasonForCorrectionInput.value = '';
}
const errorEl = document.getElementById('correctionReasonError');
if (errorEl) {
errorEl.classList.add('d-none');
errorEl.textContent = '';
}
pendingStatus = null;
});
}
}
// Correction details modal
const correctionDetailsModalEl = document.getElementById('correctionDetailsModal');
let correctionDetailsModal = null;
let correctionDetailsInput = null;
let confirmCorrectionDetailsBtn = null;
if (correctionDetailsModalEl) {
correctionDetailsModal = bootstrap.Modal.getOrCreateInstance(correctionDetailsModalEl);
correctionDetailsInput = document.getElementById('correctionDetailsInput');
confirmCorrectionDetailsBtn = document.getElementById('confirmCorrectionDetails');
if (confirmCorrectionDetailsBtn) {
confirmCorrectionDetailsBtn.addEventListener('click', function () {
const details = correctionDetailsInput ? correctionDetailsInput.value.trim() : '';
if (!details) {
const errorEl = document.getElementById('correctionDetailsError');
if (errorEl) {
errorEl.textContent = gettext('Veuillez indiquer les détails de la correction.');
errorEl.classList.remove('d-none');
}
return;
}
correctionDetailsModal.hide();
// Sauvegarder les détails et changer le statut
saveCorrectionDetailsThenUpdateStatus(details);
});
}
// Nettoyer la modale quand elle se ferme
if (correctionDetailsModalEl) {
correctionDetailsModalEl.addEventListener('hidden.bs.modal', function () {
if (correctionDetailsInput) {
correctionDetailsInput.value = '';
}
const errorEl = document.getElementById('correctionDetailsError');
if (errorEl) {
errorEl.classList.add('d-none');
errorEl.textContent = '';
}
pendingStatus = null;
});
}
}
// Modal d'avertissement : aucune prestation encodée au passage à 'Traité'
const warnNoPrestationModalEl = document.getElementById('warnNoPrestationModal');
if (warnNoPrestationModalEl) {
const confirmWarnNoPrestationBtn = document.getElementById('confirmWarnNoPrestationBtn');
if (confirmWarnNoPrestationBtn) {
confirmWarnNoPrestationBtn.addEventListener('click', function () {
const statusToSet = pendingStatus;
pendingStatus = null;
pendingExtraData = null;
bootstrap.Modal.getInstance(warnNoPrestationModalEl).hide();
if (statusToSet) {
updateStatus(statusToSet);
}
});
}
warnNoPrestationModalEl.addEventListener('hidden.bs.modal', function () {
pendingStatus = null;
pendingExtraData = null;
});
}
$('#statusTimeline').on('click', 'button', function () {
const newStatus = $(this).data('status');
if (newStatus === 'canceled') {
pendingStatus = newStatus;
const modal = new bootstrap.Modal(document.getElementById('cancelConfirmModal'));
modal.show();
} else if (newStatus === 'on_pause' && pauseReasonModal) {
pauseReasonModal.show();
} else if (newStatus === 'to_be_corrected') {
// Afficher la modale pour demander la raison de correction
pendingStatus = newStatus;
const correctionReasonModal = document.getElementById('correctionReasonModal');
if (correctionReasonModal) {
const modal = new bootstrap.Modal(correctionReasonModal);
modal.show();
} else {
// Fallback si la modale n'existe pas
updateStatus(newStatus);
}
} else if (newStatus === 'corrected') {
// Afficher la modale pour les détails de correction
pendingStatus = newStatus;
const correctionDetailsModal = document.getElementById('correctionDetailsModal');
if (correctionDetailsModal) {
const modal = new bootstrap.Modal(correctionDetailsModal);
modal.show();
} else {
// Fallback si la modale n'existe pas
updateStatus(newStatus);
}
} else if (newStatus === 'validated' && typeof periodValidationEnabled !== 'undefined' && periodValidationEnabled) {
// Intercepter la validation lorsque la validation par période est active
pendingStatus = newStatus;
const isFinal = (typeof periodIsFinal !== 'undefined' && periodIsFinal);
if (periodValidationModal && periodValidationMessageEl) {
if (isFinal) {
periodValidationMessageEl.textContent = gettext("Vous êtes sur le point de valider définitivement l'intervention (dernière période).");
pendingExtraData = {}; // validation définitive
} else {
periodValidationMessageEl.textContent = gettext("Cette action repassera l'intervention au statut 'À planifier' et passera à la période suivante.");
pendingExtraData = { period_validation_rollover: 1 };
}
periodValidationModal.show();
} else {
// Fallback simple si la modale n'est pas disponible
if (isFinal) {
updateStatus(newStatus);
} else if (confirm(gettext("Cette action repassera l'intervention à 'À planifier' et passera à la période suivante. Confirmer ?"))) {
updateStatus(newStatus, { period_validation_rollover: 1 });
}
pendingStatus = null;
pendingExtraData = null;
}
} else if (newStatus === 'processed' && typeof contractWarnNoPrestationOnProcessed !== 'undefined' && contractWarnNoPrestationOnProcessed) {
// Avertissement si aucune prestation n'a été encodée
let hasPrestations = (typeof hasPrestationsEncoded !== 'undefined') && hasPrestationsEncoded;
const contractTableBody = document.querySelector('#contractTable tbody');
if (contractTableBody) {
hasPrestations = contractTableBody.querySelectorAll('tr[data-contract-post-id]').length > 0;
}
if (!hasPrestations) {
pendingStatus = newStatus;
pendingExtraData = null;
const warnModalEl = document.getElementById('warnNoPrestationModal');
if (warnModalEl) {
bootstrap.Modal.getOrCreateInstance(warnModalEl).show();
} else {
if (confirm(gettext("Aucune prestation n'a été encodée. Voulez-vous quand même passer au statut « Traité » ?"))) {
updateStatus(newStatus);
}
pendingStatus = null;
}
} else {
updateStatus(newStatus);
}
} else if (typeof isMobileTimeline !== 'undefined' && isMobileTimeline) {
// Mobile : modale de confirmation avant changement de statut
const confirmModalEl = document.getElementById('mobileStatusConfirmModal');
if (confirmModalEl) {
const statusObj = STATUS_CHOICES.find(s => s.key === newStatus);
const transLabel = statusObj && statusObj.transition_label ? statusObj.transition_label : (statusObj ? statusObj.label : newStatus);
const msgEl = document.getElementById('mobileStatusConfirmMessage');
if (msgEl) {
msgEl.textContent = transLabel + ' ?';
}
pendingStatus = newStatus;
pendingExtraData = null;
const confirmModal = bootstrap.Modal.getOrCreateInstance(confirmModalEl);
confirmModal.show();
} else {
updateStatus(newStatus);
}
} else if (newStatus === 'finished' && typeof photosRequired !== 'undefined' && photosRequired) {
// Vérifier la présence d'au moins une photo (post 'to_be_processed' si date connue)
const docContainer = document.getElementById('documentsContainer');
let hasDocuments = false;
if (docContainer) {
const cards = Array.from(docContainer.querySelectorAll('[id^="document-card-"], [id^="doc-thumb-"]'));
const since = (typeof photosRequiredSince !== 'undefined' && photosRequiredSince)
? new Date(photosRequiredSince).getTime()
: null;
if (since) {
hasDocuments = cards.some(card => {
const uploadDate = card.dataset.uploadDate;
return uploadDate && new Date(uploadDate).getTime() >= since;
});
} else {
hasDocuments = cards.length > 0;
}
}
if (!hasDocuments) {
const msg = (typeof photosRequiredSince !== 'undefined' && photosRequiredSince)
? gettext('Au moins une photo ajoutée après le passage au statut « À traiter » est obligatoire pour terminer cette intervention.')
: gettext('Au moins une photo est obligatoire pour terminer cette intervention.');
let alertContainer = document.getElementById('documentsAlertContainer');
if (!alertContainer) {
alertContainer = document.getElementById('statusAlertContainer');
}
if (alertContainer) {
alertContainer.innerHTML = '<div class="alert alert-warning alert-dismissible fade show" role="alert"><i class="bi bi-camera-fill me-2"></i>' +
msg + '<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
const docSection = document.getElementById('section-documents') || alertContainer;
docSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
alert(msg);
}
return;
}
updateStatus(newStatus);
} else {
updateStatus(newStatus);
}
});
// Mobile : confirmer le changement de statut
$(document).on('click', '#confirmMobileStatusBtn', function () {
const confirmModalEl = document.getElementById('mobileStatusConfirmModal');
if (confirmModalEl) {
const modalInstance = bootstrap.Modal.getInstance(confirmModalEl);
if (modalInstance) modalInstance.hide();
}
if (pendingStatus) {
const extra = pendingExtraData || {};
updateStatus(pendingStatus, extra);
pendingStatus = null;
pendingExtraData = null;
}
});
// Afficher l'astérisque obligatoire sur le commentaire quand le type 'Autre' est sélectionné
$(document).on('change', '#cancellationTypeSelect', function () {
const isOther = $(this).val() === 'other';
const reqMark = document.getElementById('cancellationReasonRequired');
if (reqMark) reqMark.classList.toggle('d-none', !isOther);
});
$('#confirmCancelBtn').on('click', function () {
if (pendingStatus) {
// Récupérer le type et la raison d'annulation
const cancellationType = $('#cancellationTypeSelect').val();
const cancellationReason = $('#cancellationReasonInput').val().trim();
const errorEl = document.getElementById('cancellationReasonError');
// Valider que le type est sélectionné
if (!cancellationType) {
if (errorEl) {
errorEl.classList.remove('d-none');
errorEl.textContent = gettext("Le type d'annulation est obligatoire.");
}
return;
}
// Le commentaire est obligatoire uniquement pour le motif 'Autre'
if (cancellationType === 'other' && !cancellationReason) {
if (errorEl) {
errorEl.classList.remove('d-none');
errorEl.textContent = gettext("Un commentaire est obligatoire pour le motif \"Autre\".");
}
return;
}
// Cacher le message d'erreur si présent
if (errorEl) {
errorEl.classList.add('d-none');
errorEl.textContent = '';
}
// Envoyer le statut avec le type et la raison d'annulation
updateStatus(pendingStatus, { cancellation_type: cancellationType, cancellation_reason: cancellationReason });
pendingStatus = null;
// Fermer la modale
const modalElement = document.getElementById('cancelConfirmModal');
const modalInstance = bootstrap.Modal.getInstance(modalElement);
if (modalInstance) {
modalInstance.hide();
}
// Réinitialiser les champs après fermeture
$('#cancellationTypeSelect').val('');
$('#cancellationReasonInput').val('');
const reqMark = document.getElementById('cancellationReasonRequired');
if (reqMark) reqMark.classList.add('d-none');
}
});
/**
* Sauvegarde la raison de correction, puis change le statut
*/
function saveCorrectionReasonThenUpdateStatus(reason) {
$.ajax({
url: `/interventions/${interventionId}/update-correction-reason/`,
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json'
},
data: JSON.stringify({
reason_for_correction: reason
}),
success: function (response) {
// Raison sauvegardée, maintenant changer le statut
updateStatus('to_be_corrected');
},
error: function (xhr) {
const errorEl = document.getElementById('correctionReasonError');
if (errorEl) {
try {
const error = JSON.parse(xhr.responseText);
errorEl.textContent = error.error || gettext("Erreur lors de la sauvegarde");
} catch (e) {
errorEl.textContent = gettext("Erreur lors de la sauvegarde");
}
errorEl.classList.remove('d-none');
}
}
});
}
/**
* Sauvegarde les détails de correction, puis change le statut
*/
function saveCorrectionDetailsThenUpdateStatus(details) {
$.ajax({
url: `/interventions/${interventionId}/update-correction-details/`,
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/json'
},
data: JSON.stringify({
correction_details: details
}),
success: function (response) {
// Détails sauvegardés, maintenant changer le statut
updateStatus('corrected');
},
error: function (xhr) {
const errorEl = document.getElementById('correctionDetailsError');
if (errorEl) {
try {
const error = JSON.parse(xhr.responseText);
errorEl.textContent = error.error || gettext("Erreur lors de la sauvegarde");
} catch (e) {
errorEl.textContent = gettext("Erreur lors de la sauvegarde");
}
errorEl.classList.remove('d-none');
}
}
});
}
function updateStatus(newStatus, extraData = {}) {
const payload = Object.assign({
intervention_id: interventionId,
new_status: newStatus,
}, extraData || {});
$.ajax({
url: `/interventions/${interventionId}/update-status/`,
method: 'POST',
headers: {'X-CSRFToken': csrftoken},
data: payload,
success: function (response) {
// Check if email failed but status was updated successfully
if (response.email_failed && response.email_warning) {
showStatusAlert(response.email_warning, 'warning');
}
const _isOperator = Array.isArray(userRoles) && userRoles.includes('operator');
// Opérateurs : redirection serveur ou page d'accueil pour les brouillons
if (_isOperator) {
if (response.redirect_url && response.redirect_url !== '') {
window.location.href = response.redirect_url;
return;
}
if (['in_preparation', 'to_be_approved', 'to_be_planned'].includes(currentStatus)) {
window.location.href = `/`;
return;
}
}
// Autres rôles (et opérateurs hors brouillon) : rester sur la page
location.reload();
},
error: function (xhr) {
let message = gettext("Erreur lors de la mise à jour");
try {
const errorData = JSON.parse(xhr.responseText);
if (errorData.message) {
message = errorData.message;
}
} catch (e) {
// If parsing fails, use the response text directly if available and not HTML
if (xhr.responseText && !xhr.responseText.trim().startsWith('<')) {
message = xhr.responseText;
}
}
showStatusAlert(message, 'danger');
}
});
}
function showStatusAlert(message, type = 'danger') {
// Créer un conteneur d'alerte s'il n'existe pas
let alertContainer = document.getElementById('statusUpdateAlertContainer');
if (!alertContainer) {
alertContainer = document.createElement('div');
alertContainer.id = 'statusUpdateAlertContainer';
alertContainer.style.position = 'fixed';
alertContainer.style.top = '20px';
alertContainer.style.left = '50%';
alertContainer.style.transform = 'translateX(-50%)';
alertContainer.style.zIndex = '9999';
alertContainer.style.minWidth = '300px';
alertContainer.style.textAlign = 'center';
document.body.appendChild(alertContainer);
}
// Créer l'élément d'alerte
const alertEl = document.createElement('div');
alertEl.className = `alert alert-${type} alert-dismissible fade show`;
alertEl.role = 'alert';
alertEl.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
alertContainer.appendChild(alertEl);
// Retirer l'alerte après 5 secondes (si pas fermée manuellement)
setTimeout(() => {
if (alertEl.parentNode) {
alertEl.remove();
}
}, 5000);
}