`;
container.appendChild(div);
}
function deleteCheckpoint() {
if (!confirm(tr("hdl_js_confirm_del_step"))) return;
const form = document.getElementById("formCheckpoint");
const input = document.createElement("input");
input.type = "hidden";
input.name = "action_delete";
input.value = "1";
form.appendChild(input);
form.submit();
}
// ============================================================================
// 6. REORDONNANCEMENT DES ÉTAPES (DRAG & DROP PC + MOBILE)
// ============================================================================
function saveCheckpointOrder() {
const locations = [
...document.querySelectorAll(".hol-checkpoint-draggable"),
].map((el) => el.getAttribute("data-location"));
const holidayId = document.querySelector('input[name="holiday_id"]').value;
const formData = new FormData();
formData.append("holiday_id", holidayId);
formData.append("locations", JSON.stringify(locations));
fetch("/modules/holidays/includes/api/reorder_checkpoints.php", {
method: "POST",
body: formData,
}).then(() => window.location.reload());
}
function moveStepMobile(btn, direction) {
const item = btn.closest(".hol-checkpoint-draggable");
const container = item.parentElement;
if (
direction === -1 &&
item.previousElementSibling &&
item.previousElementSibling.classList.contains("hol-checkpoint-draggable")
) {
container.insertBefore(item, item.previousElementSibling);
saveCheckpointOrder();
} else if (
direction === 1 &&
item.nextElementSibling &&
item.nextElementSibling.classList.contains("hol-checkpoint-draggable")
) {
container.insertBefore(item, item.nextElementSibling.nextElementSibling);
saveCheckpointOrder();
}
}
document.addEventListener("DOMContentLoaded", () => {
const checkpoints = document.querySelectorAll(".hol-checkpoint-draggable");
const container = checkpoints[0]?.parentElement;
if (!container) return;
const isMobile = window.innerWidth <= 768;
let draggedItem = null;
checkpoints.forEach((item) => {
if (isMobile) {
item.removeAttribute("draggable");
return;
}
item.addEventListener("dragstart", function (e) {
draggedItem = this;
setTimeout(() => (this.style.opacity = "0.4"), 0);
});
item.addEventListener("dragend", function () {
setTimeout(() => {
this.style.opacity = "1";
draggedItem = null;
saveCheckpointOrder();
}, 0);
});
item.addEventListener("dragover", function (e) {
e.preventDefault();
const afterElement = getDragAfterElement(container, e.clientY);
if (afterElement == null) {
container.appendChild(draggedItem);
} else {
container.insertBefore(draggedItem, afterElement);
}
});
});
function getDragAfterElement(container, y) {
const draggableElements = [
...container.querySelectorAll(
'.hol-checkpoint-draggable:not([style*="opacity: 0.4"])',
),
];
return draggableElements.reduce(
(closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
},
{ offset: Number.NEGATIVE_INFINITY },
).element;
}
});
// ============================================================================
// 7. MOTEUR DRAG & DROP DU PLANNING CARNET DE BORD
// ============================================================================
function closePlanningModal() {
document.getElementById("planningModal").style.display = "none";
document.body.classList.remove("no-scroll");
}
function openPlanningModal(step) {
document.getElementById("planningModalTitle").innerText =
tr("hdl_planning_title") + " : " + step.location_name;
const container = document.getElementById("planningContainer");
selectedItemIdForMove = null;
let validItems = step.items.filter((it) => it.name !== "PF_TECHNICAL_POINT");
window.CURRENT_PLANNING_STEP = step; // Mémorise l'étape en cours
if (window.TRANSIT_DATA && window.TRANSIT_DATA[step.sort_order]) {
// Est-ce qu'on a déjà planifié ou ajouté ce trajet ?
let hasTransit = validItems.some((it) => it.expense_context === "transit");
if (!hasTransit) {
const tData = window.TRANSIT_DATA[step.sort_order];
const h = Math.max(1, Math.round(tData.sec / 3600)); // Arrondi en heures
validItems.push({
id: "virtual-transit",
name: `Essence depuis ${tData.from}`, // Garde ce nom pour lier avec le budget
category: "transport",
expense_context: "transit",
duration: h,
notes: `Trajet GPS (${Math.round(tData.sec / 60)} min). Déplacez pour planifier la route.`,
is_virtual: true,
});
}
}
if (!step.step_start_date || !step.step_end_date) {
container.innerHTML = `
${tr("hdl_js_missing_dates_title")}
${tr("hdl_js_missing_dates_msg")}
`;
document.getElementById("planningModal").style.display = "flex";
return;
}
let datesToDisplay = [];
let curr = new Date(step.step_start_date);
let end = new Date(step.step_end_date);
while (curr <= end) {
datesToDisplay.push(curr.toISOString().split("T")[0]);
curr.setDate(curr.getDate() + 1);
}
let html = `
`;
}
} catch (e) {
console.error("Erreur météo planning", e);
}
}
// Gère l'affichage des dates dans la modale d'étape
function toggleStepDates(type) {
const grpEnd = document.getElementById("grp_end_date");
const lblStart = document.getElementById("lbl_start_date");
if (type === "origin") {
grpEnd.style.display = "none";
lblStart.innerText = "📅 Date de départ";
} else if (type === "destination") {
grpEnd.style.display = "none";
lblStart.innerText = "📅 Date d'arrivée";
} else {
grpEnd.style.display = "block";
lblStart.innerText = tr("hdl_label_arrival");
}
}
// Ajout magique d'une dépense d'essence SÉCURISÉE et INSTANTANÉE
function addQuickTransitExpense(
holidayId,
sortOrder,
amount,
description,
btnElement,
durationSec = 3600,
) {
if (
!confirm(
`Ajouter une dépense de carburant de ${amount}€ pour cette étape ?`,
)
)
return;
// 1. UI OPTIMISTE : On change visuellement le bouton tout de suite sans attendre le serveur
const parentContainer = btnElement.parentElement;
if (parentContainer) {
parentContainer.innerHTML = `✓ Ajouté`;
}
// 2. On met à jour discrètement le compteur global en haut de page (+ montant)
const totalTransitEl = document.querySelector(".hol-summary-value strong");
if (totalTransitEl) {
const currentTotal =
parseFloat(totalTransitEl.innerText.replace(" €", "").replace(" ", "")) ||
0;
totalTransitEl.innerText = Math.round(currentTotal + amount) + " €";
}
const fd = new FormData();
fd.append("action", "add_single_item");
fd.append("holiday_id", holidayId);
fd.append("sort_order", sortOrder);
fd.append("category", "transport");
fd.append("name", description);
fd.append("amount", amount);
fd.append("context", "transit");
// 🔥 On sécurise la durée en base de données
const h = Math.max(1, Math.round(durationSec / 3600));
fd.append("duration", h);
fetch("/modules/holidays/includes/api/save_checkpoint.php", {
method: "POST",
body: fd,
})
.then((res) => res.json())
.then((data) => {
if (!data.success) alert("Erreur : " + data.error);
});
}
// Permet de modifier le prix du carburant à la volée
function updateFuelPrice() {
const currentPrice = window.FUEL_PRICE || 1.85;
let newPrice = prompt(
"Définit le prix du carburant estimé (€/L) pour tes trajets :",
currentPrice,
);
if (newPrice !== null) {
newPrice = parseFloat(newPrice.replace(",", "."));
if (!isNaN(newPrice) && newPrice > 0) {
localStorage.setItem("holidays_fuel_price", newPrice);
window.location.reload();
} else {
alert("Prix invalide.");
}
}
}
// Formatte les secondes en "XXhYY" ou "YYmin"
function formatDuration(seconds) {
if (!seconds || isNaN(seconds)) return "0min";
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
if (h > 0) {
return `${h}h${m.toString().padStart(2, "0")}`;
}
return `${m}min`;
}
// Ouvre et ferme la modale des détails (L'œil)
function openTransitModal() {
document.getElementById("transitModal").style.display = "flex";
document.body.classList.add("no-scroll");
}
function closeTransitModal() {
document.getElementById("transitModal").style.display = "none";
document.body.classList.remove("no-scroll");
}
// ============================================================================
// CHOIX DE L'APPLICATION GPS (Google Maps, Waze, Apple Maps)
// ============================================================================
window.currentGpsTarget = { lat: 0, lng: 0 };
function openGpsModal(lat, lng) {
window.currentGpsTarget = { lat: lat, lng: lng };
document.getElementById("gpsModal").style.display = "flex";
document.body.classList.add("no-scroll");
}
function closeGpsModal() {
document.getElementById("gpsModal").style.display = "none";
document.body.classList.remove("no-scroll");
}
function launchGpsApp(app) {
const lat = window.currentGpsTarget.lat;
const lng = window.currentGpsTarget.lng;
let url = "";
if (app === "waze") {
// Force l'ouverture de Waze en mode navigation
url = `https://waze.com/ul?ll=${lat},${lng}&navigate=yes`;
} else if (app === "gmaps") {
// Force l'ouverture de Google Maps en mode itinéraire
url = `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}`;
} else if (app === "amaps") {
// Force l'ouverture d'Apple Maps
url = `http://maps.apple.com/?daddr=${lat},${lng}`;
}
if (url !== "") {
window.open(url, "_blank");
closeGpsModal();
}
}
// ============================================================================
// GESTION DU PORTE-DOCUMENTS (UPLOAD)
// ============================================================================
window.currentDocsStepId = null;
// 🔥 On attache le verrou à window pour éviter les erreurs de redéclaration
window.isUploadingDocs = window.isUploadingDocs || false;
function openDocsModal(sortOrder) {
window.currentDocsStepId = sortOrder;
document.getElementById("docsModal").style.display = "flex";
document.body.classList.add("no-scroll");
document.getElementById("uploadStatus").innerHTML = "";
const listContainer = document.getElementById("docsListContainer");
listContainer.innerHTML =
'
⏳ Chargement des documents...
';
const holidayId = document.querySelector('input[name="holiday_id"]').value;
// 🔥 On va chercher les documents existants !
fetch(
`/modules/holidays/includes/api/get_attachments.php?holiday_id=${holidayId}&item_id=${sortOrder}`,
)
.then((response) => response.json())
.then((data) => {
listContainer.innerHTML = ""; // On vide le message de chargement
if (data.success && data.files.length > 0) {
data.files.forEach((f) => {
// On rend le nom du fichier cliquable pour ouvrir le document dans un nouvel onglet
const docHtml = `