From ebdbee71db91d3d483c0dae34c9832fa8316e309 Mon Sep 17 00:00:00 2001 From: "fefe.clochette" Date: Tue, 28 Jul 2026 16:37:39 +0200 Subject: [PATCH] planning mobil --- holidays.php | 14 +- modules/holidays/holidays.js | 307 +++++++++++++++--- .../holidays/includes/api/save_checkpoint.php | 17 +- modules/holidays/views/detail.php | 151 +++++---- modules/holidays/views/list.php | 9 +- modules/holidays/views/modal.php | 28 +- modules/holidays/views/pdf_template.php | 2 +- 7 files changed, 388 insertions(+), 140 deletions(-) diff --git a/holidays.php b/holidays.php index 83139ea..131d874 100644 --- a/holidays.php +++ b/holidays.php @@ -34,7 +34,17 @@ if ($tab === 'holiday_detail' && isset($_GET['id'])) { require __DIR__ . '/modules/holidays/views/list.php'; } -// 4. Inclusion du JS global du module (Pont i18n déjà géré dans le header) -echo ''; +// 4. Inclusion des librairies globales du module (Flatpickr) +?> + + + + + + + +'; require __DIR__ . '/footer.php'; \ No newline at end of file diff --git a/modules/holidays/holidays.js b/modules/holidays/holidays.js index 5e92d44..1163cb3 100644 --- a/modules/holidays/holidays.js +++ b/modules/holidays/holidays.js @@ -1,5 +1,22 @@ window.PLANNING_MODIFIED = false; +// Variable globale pour Flatpickr +let cpDateRangePicker = null; + +// Écouteur pour activer le thème sombre de Flatpickr selon le thème global HouseHub +document.addEventListener("DOMContentLoaded", () => { + const checkDarkTheme = () => { + const isDark = + document.documentElement.getAttribute("data-theme") === "dark"; + const themeLink = document.getElementById("flatpickr-dark-theme"); + if (themeLink) themeLink.disabled = !isDark; + }; + checkDarkTheme(); + document + .getElementById("theme-toggle") + ?.addEventListener("click", () => setTimeout(checkDarkTheme, 50)); +}); + // ============================================================================ // FONCTION DE TRADUCTION JS & LANGUE COURANTE // ============================================================================ @@ -87,6 +104,11 @@ function openHolidayModal(mode) { if (mode === "add") { document.getElementById("modalTitle").innerText = tr("hdl_modal_title"); btnDelete.style.display = "none"; + + // Réinitialisation du calendrier global + document.getElementById("inp_start").value = ""; + document.getElementById("inp_end").value = ""; + initHolFlatpickr("", ""); } else { document.getElementById("modalTitle").innerText = tr( "hdl_quick_edit_title", @@ -114,18 +136,27 @@ function editHoliday(data) { document.getElementById("inp_title").value = h.title; document.getElementById("inp_status").value = h.status; document.getElementById("inp_period").value = h.period_hint || ""; - document.getElementById("inp_start").value = h.start_date || ""; - document.getElementById("inp_end").value = h.end_date || ""; document.getElementById("inp_food").value = h.budget_food > 0 ? h.budget_food : ""; document.getElementById("inp_extra").value = h.budget_extra > 0 ? h.budget_extra : ""; - document.getElementById("inp_notes").value = h.notes || ""; + + if (document.getElementById("inp_notes")) { + document.getElementById("inp_notes").value = h.notes || ""; + } const vehicleInput = document.getElementById("inp_vehicle_id"); if (vehicleInput) { vehicleInput.value = h.vehicle_id || ""; } + + // Initialisation des dates + const startD = h.start_date || ""; + const endD = h.end_date || ""; + document.getElementById("inp_start").value = startD; + document.getElementById("inp_end").value = endD; + + initHolFlatpickr(startD, endD); } function deleteHoliday() { @@ -458,64 +489,100 @@ function panMapTo(lat, lng) { // ============================================================================ function openCheckpointModal(mode, data = null) { const searchBlock = document.getElementById("cpSearchBlock"); - const formBlock = document.getElementById("formCheckpoint"); const container = document.getElementById("cpExpensesContainer"); const btnDel = document.getElementById("btnDeleteCp"); + const insertGroup = document.getElementById("cp_insert_group"); + const insertSelect = document.getElementById("cp_insert_after"); container.innerHTML = ""; - - if (document.getElementById("cp_start_date")) - document.getElementById("cp_start_date").value = ""; - if (document.getElementById("cp_end_date")) - document.getElementById("cp_end_date").value = ""; if (document.getElementById("searchPlaceInput")) document.getElementById("searchPlaceInput").value = ""; if (document.getElementById("searchResults")) document.getElementById("searchResults").innerHTML = ""; - searchBlock.style.display = "block"; + const holidayData = JSON.parse( + document.getElementById("holidayDataJson").textContent, + ).main; + let tripStartDate = + holidayData.start_date || new Date().toISOString().split("T")[0]; + + // ========================================== + // CONFIGURATION DES DATES FLATPICKR + // ========================================== + let defaultStart = ""; + let defaultEnd = ""; if (mode === "add") { document.getElementById("cpModalTitle").innerText = tr("hdl_btn_add_step"); - formBlock.style.display = "none"; btnDel.style.display = "none"; + searchBlock.style.display = "block"; + insertGroup.style.display = "block"; + document.getElementById("cp_old_sort_order").value = ""; document.getElementById("cp_name").value = ""; + switchCpTab("info"); + if (document.getElementById("cp_step_type")) { document.getElementById("cp_step_type").value = "stop"; toggleStepDates("stop"); } - if (document.getElementById("cp_set_as_return")) { + if (document.getElementById("cp_set_as_return")) document.getElementById("cp_set_as_return").checked = false; + + let lastDate = tripStartDate; + if (window.MAP_POINTS && window.MAP_POINTS.length > 0) { + const lastStep = window.MAP_POINTS[window.MAP_POINTS.length - 1]; + lastDate = + lastStep.step_end_date || lastStep.step_start_date || tripStartDate; } + insertSelect.innerHTML = ``; + if (window.MAP_POINTS && window.MAP_POINTS.length > 0) { + window.MAP_POINTS.forEach((step) => { + let dateStr = ""; + if ( + step.step_start_date && + step.step_end_date && + step.step_start_date !== step.step_end_date + ) { + dateStr = ` (${new Date(step.step_start_date).toLocaleDateString(window.appLang, { day: "2-digit", month: "2-digit" })} > ${new Date(step.step_end_date).toLocaleDateString(window.appLang, { day: "2-digit", month: "2-digit" })})`; + } else if (step.step_start_date) { + dateStr = ` (${new Date(step.step_start_date).toLocaleDateString(window.appLang, { day: "2-digit", month: "2-digit" })})`; + } + insertSelect.innerHTML += ``; + }); + } + + defaultStart = lastDate; + defaultEnd = lastDate; addCpExpenseLine(); } else if (mode === "edit" && data) { document.getElementById("cpModalTitle").innerText = tr("hdl_js_edit_step"); - formBlock.style.display = "block"; btnDel.style.display = "block"; + searchBlock.style.display = "none"; + insertGroup.style.display = "none"; + + switchCpTab("prog"); document.getElementById("cp_lat").value = data.lat; document.getElementById("cp_lng").value = data.lng; document.getElementById("cp_old_sort_order").value = data.sort_order; document.getElementById("cp_name").value = data.location_name; - document.getElementById("cp_start_date").value = data.step_start_date || ""; - document.getElementById("cp_end_date").value = data.step_end_date || ""; - // 🔥 PRE-REMPLISSAGE DU TYPE D'ÉTAPE ET UI DATES if (document.getElementById("cp_step_type")) { const type = data.step_type || "stop"; document.getElementById("cp_step_type").value = type; toggleStepDates(type); } - - // 🔥 PRE-REMPLISSAGE DE LA CASE RETOUR BASEE SUR LA GLOBALE if (document.getElementById("cp_set_as_return")) { document.getElementById("cp_set_as_return").checked = window.GLOBAL_RETURN_STEP_ID == data.sort_order; } + defaultStart = data.step_start_date || tripStartDate; + defaultEnd = data.step_end_date || tripStartDate; + if (data.items && data.items.length > 0) { let visibleCount = 0; data.items.forEach((it) => { @@ -540,10 +607,137 @@ function openCheckpointModal(mode, data = null) { addCpExpenseLine(); } } + + // Destruction et ré-instanciation de Flatpickr pour forcer le saut au bon mois + if (cpDateRangePicker) cpDateRangePicker.destroy(); + + document.getElementById("cp_start_date").value = defaultStart; + document.getElementById("cp_end_date").value = defaultEnd; + + cpDateRangePicker = flatpickr("#cp_date_range", { + mode: "range", + altInput: true, // 💡 NOUVEAU : Crée un champ de présentation séparé + altFormat: "d/m", // 💡 NOUVEAU : Format ultra compact (ex: 15/08) + dateFormat: "Y-m-d", // Format technique (MariaDB) conservé en arrière-plan + defaultDate: + defaultStart && defaultEnd && defaultStart !== defaultEnd + ? [defaultStart, defaultEnd] + : [defaultStart], + locale: window.appLang === "ca-ES" ? "cat" : "fr", + onChange: function (selectedDates, dateStr, instance) { + if (selectedDates.length === 2) { + document.getElementById("cp_start_date").value = instance.formatDate( + selectedDates[0], + "Y-m-d", + ); + document.getElementById("cp_end_date").value = instance.formatDate( + selectedDates[1], + "Y-m-d", + ); + } else if (selectedDates.length === 1) { + document.getElementById("cp_start_date").value = instance.formatDate( + selectedDates[0], + "Y-m-d", + ); + document.getElementById("cp_end_date").value = instance.formatDate( + selectedDates[0], + "Y-m-d", + ); + } else { + document.getElementById("cp_start_date").value = ""; + document.getElementById("cp_end_date").value = ""; + } + }, + }); + document.getElementById("checkpointModal").style.display = "flex"; document.body.classList.add("no-scroll"); } +// ========================================== +// 🎯 DATES GLOBALES DU VOYAGE (MODALE PRINCIPALE) +// ========================================== +let holDateRangePicker = null; + +function initHolFlatpickr(defaultStart, defaultEnd) { + if (holDateRangePicker) holDateRangePicker.destroy(); + + holDateRangePicker = flatpickr("#hol_date_range", { + mode: "range", + altInput: true, + altFormat: "d/m", // 💡 Format ultra compact (ex: 15/08) + dateFormat: "Y-m-d", // 💡 Le vrai format envoyé au serveur + defaultDate: + defaultStart && defaultEnd && defaultStart !== defaultEnd + ? [defaultStart, defaultEnd] + : defaultStart + ? [defaultStart] + : [], + locale: window.appLang === "ca-ES" ? "cat" : "fr", + onChange: function (selectedDates, dateStr, instance) { + if (selectedDates.length === 2) { + document.getElementById("inp_start").value = instance.formatDate( + selectedDates[0], + "Y-m-d", + ); + document.getElementById("inp_end").value = instance.formatDate( + selectedDates[1], + "Y-m-d", + ); + } else if (selectedDates.length === 1) { + document.getElementById("inp_start").value = instance.formatDate( + selectedDates[0], + "Y-m-d", + ); + document.getElementById("inp_end").value = instance.formatDate( + selectedDates[0], + "Y-m-d", + ); + } else { + document.getElementById("inp_start").value = ""; + document.getElementById("inp_end").value = ""; + } + }, + }); +} + +function switchCpTab(tabId) { + const btnInfo = document.getElementById("tabBtnInfo"); + const btnProg = document.getElementById("tabBtnProg"); + const tabInfo = document.getElementById("cpTabInfo"); + const tabProg = document.getElementById("cpTabProg"); + + if (tabId === "info") { + btnInfo.style.borderBottomColor = "var(--primary)"; + btnInfo.style.color = "var(--primary)"; + btnProg.style.borderBottomColor = "transparent"; + btnProg.style.color = "var(--text-muted)"; + tabInfo.style.display = "block"; + tabProg.style.display = "none"; + } else { + btnProg.style.borderBottomColor = "var(--primary)"; + btnProg.style.color = "var(--primary)"; + btnInfo.style.borderBottomColor = "transparent"; + btnInfo.style.color = "var(--text-muted)"; + tabProg.style.display = "block"; + tabInfo.style.display = "none"; + } +} + +function injectDynamicDates(selectEl) { + const selectedOpt = selectEl.options[selectEl.selectedIndex]; + if (selectedOpt && selectedOpt.dataset.enddate) { + const dateToSet = selectedOpt.dataset.enddate; + document.getElementById("cp_start_date").value = dateToSet; + document.getElementById("cp_end_date").value = dateToSet; + + // On met à jour l'UI du calendrier + if (cpDateRangePicker) { + cpDateRangePicker.setDate([dateToSet, dateToSet]); + } + } +} + function searchPlace() { const q = document.getElementById("searchPlaceInput").value.trim(); if (q.length < 3) return; @@ -597,34 +791,41 @@ function addCpExpenseLine( itemDate = "", itemTime = "", itemDur = 1, - expenseContext = "local", // 🔥 FIX : On accepte le contexte dynamique + expenseContext = "local", ) { const container = document.getElementById("cpExpensesContainer"); const div = document.createElement("div"); div.className = "hol-form-row"; const isChecked = isPaid == 1 ? "checked" : ""; + // 💡 Nouveaux types (Les valeurs à 0 par défaut pour les visites gratuites) + let defaultAmount = amount; + if (category === "visit_free" && amount === "") defaultAmount = "0.00"; + div.innerHTML = `
- + + + - + - - + + - +
-
- +
+
@@ -1065,15 +1266,24 @@ function recalcAllBadges() { } function buildDragItemHtml(it) { + // Mapping intelligent des nouvelles icônes let icon = "🏷️"; let catClass = "cat-activity"; + if (it.category === "accommodation") { icon = "🏨"; catClass = "cat-accommodation"; - } - if (it.category === "transport") { + } else if (it.category === "transport") { icon = "🚗"; catClass = "cat-transport"; + } else if (it.category === "food") { + icon = "🍽️"; + } else if (it.category === "visit_free") { + icon = "🏞️"; + } else if (it.category === "activity") { + icon = "🎫"; + } else if (it.category === "other") { + icon = "🛍️"; } const dur = it.duration || 1; @@ -1096,28 +1306,38 @@ function buildDragItemHtml(it) { const isMobile = window.innerWidth <= 768; const dragAttr = isMobile ? "" : 'draggable="true"'; const htmlId = isVirtual ? it.id : `drag-item-${it.id}`; - const locHint = - !it.item_date && it.step_location - ? `
📍 ${it.step_location}
` + + // 💡 NOUVEAU : Affichage compact des dates associées pour faciliter le placement + let locHintHtml = ""; + if (!it.item_date) { + const datesStr = + it.step_start_date && + it.step_end_date && + it.step_start_date !== it.step_end_date + ? ` (${new Date(it.step_start_date).toLocaleDateString(currentLang, { day: "2-digit", month: "2-digit" })} > ${new Date(it.step_end_date).toLocaleDateString(currentLang, { day: "2-digit", month: "2-digit" })})` + : it.step_start_date + ? ` (${new Date(it.step_start_date).toLocaleDateString(currentLang, { day: "2-digit", month: "2-digit" })})` + : ""; + + locHintHtml = it.step_location + ? `
📍 ${it.step_location}${datesStr}
` : ""; + } return `
- ${locHint} + ${locHintHtml}
${visualName}
-
${durControls}
-
-
${noteHtml}
@@ -1493,18 +1713,15 @@ async function loadWeatherForPlanning(lat, lng, dateStr) { // 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"); + const dateLabel = document.getElementById("lbl_date_range"); + if (!dateLabel) return; if (type === "origin") { - grpEnd.style.display = "none"; - lblStart.innerText = "📅 Date de départ"; + dateLabel.innerText = "🛫 Date de départ"; } else if (type === "destination") { - grpEnd.style.display = "none"; - lblStart.innerText = "📅 Date d'arrivée"; + dateLabel.innerText = "🛬 Date d'arrivée finale"; } else { - grpEnd.style.display = "block"; - lblStart.innerText = tr("hdl_label_arrival"); + dateLabel.innerText = "📅 Période de l'étape (Arrivée ➔ Départ)"; } } diff --git a/modules/holidays/includes/api/save_checkpoint.php b/modules/holidays/includes/api/save_checkpoint.php index e27e1e5..4627b15 100644 --- a/modules/holidays/includes/api/save_checkpoint.php +++ b/modules/holidays/includes/api/save_checkpoint.php @@ -129,10 +129,19 @@ if ($holiday_id > 0 && !empty($location_name)) { $pdo->prepare("DELETE FROM pf_holidays_items WHERE holiday_id = ? AND sort_order = ?")->execute([$holiday_id, $old_sort_order]); $target_order = $old_sort_order; } else { - $stmtMax = $pdo->prepare("SELECT MAX(sort_order) FROM pf_holidays_items WHERE holiday_id = ?"); - $stmtMax->execute([$holiday_id]); - $max = $stmtMax->fetchColumn(); - $target_order = ($max !== null) ? (int)$max + 1 : 0; + // NOUVEAU : Logique d'intercalage d'étape + $insert_after = $_POST['insert_after'] ?? 'end'; + + if ($insert_after === 'end') { + $stmtMax = $pdo->prepare("SELECT MAX(sort_order) FROM pf_holidays_items WHERE holiday_id = ?"); + $stmtMax->execute([$holiday_id]); + $max = $stmtMax->fetchColumn(); + $target_order = ($max !== null) ? (int)$max + 1 : 0; + } else { + $target_order = (int)$insert_after + 1; + // On décale toutes les étapes suivantes vers le bas + $pdo->prepare("UPDATE pf_holidays_items SET sort_order = sort_order + 1 WHERE holiday_id = ? AND sort_order >= ?")->execute([$holiday_id, $target_order]); + } } $step_start = !empty($_POST['step_start_date']) ? $_POST['step_start_date'] : null; diff --git a/modules/holidays/views/detail.php b/modules/holidays/views/detail.php index b623fe3..b954894 100644 --- a/modules/holidays/views/detail.php +++ b/modules/holidays/views/detail.php @@ -66,8 +66,9 @@ $mapPoints = array_values($steps); // Affichage de la date $dateDisplay = htmlspecialchars($holiday['period_hint'] ?? ''); if (empty($dateDisplay) && $holiday['start_date']) { - $dateDisplay = date('d/m/Y', strtotime($holiday['start_date'])); - if ($holiday['end_date']) $dateDisplay .= ' → ' . date('d/m/Y', strtotime($holiday['end_date'])); + // 💡 Format Jour/Mois uniquement + $dateDisplay = date('d/m', strtotime($holiday['start_date'])); + if ($holiday['end_date']) $dateDisplay .= ' → ' . date('d/m', strtotime($holiday['end_date'])); } $cost = (float)$holiday['total_cost']; @@ -331,89 +332,99 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
-
-
-

📍

- +
+ + +
+

📍 Ajouter une étape

+ +
+ + +
+ +
-
- -
- - - -
- - - -
- - -
-
-
- -