+262
-45
@@ -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 = `<option value="end" data-enddate="${lastDate}">-- À la fin du voyage --</option>`;
|
||||
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 += `<option value="${step.sort_order}" data-enddate="${step.step_end_date || step.step_start_date || tripStartDate}">Après : ${step.location_name}${dateStr}</option>`;
|
||||
});
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div class="hol-form-inner">
|
||||
<select name="items[cat][]" class="pf-input hol-form-select">
|
||||
<select name="items[cat][]" class="pf-input hol-form-select" style="width:55px; padding:6px; font-size:1.1rem;" title="Catégorie">
|
||||
<option value="accommodation" ${category === "accommodation" ? "selected" : ""}>🏨</option>
|
||||
<option value="transport" ${category === "transport" ? "selected" : ""}>🚗</option>
|
||||
<option value="food" ${category === "food" ? "selected" : ""}>🍽️</option>
|
||||
<option value="activity" ${category === "activity" ? "selected" : ""}>🎫</option>
|
||||
<option value="visit_free" ${category === "visit_free" ? "selected" : ""}>🏞️</option>
|
||||
<option value="other" ${category === "other" ? "selected" : ""}>🛍️</option>
|
||||
</select>
|
||||
<!-- 🔥 FIX : Le champ hidden prend la vraie valeur -->
|
||||
|
||||
<input type="hidden" name="items[context][]" value="${expenseContext}">
|
||||
<input type="text" name="items[name][]" class="pf-input hol-form-text" placeholder="${tr("hdl_js_ph_expense_name")}" value="${name}">
|
||||
<input type="number" step="0.01" name="items[amount][]" class="pf-input hol-form-number" placeholder="0.00" value="${amount}">
|
||||
<input type="text" name="items[name][]" class="pf-input hol-form-text" placeholder="Description (Ex: Musée, Restaurant...)" value="${name}">
|
||||
<input type="number" step="0.01" name="items[amount][]" class="pf-input hol-form-number" placeholder="0.00" value="${defaultAmount}">
|
||||
|
||||
<label class="hol-form-paid-label" title="${tr("hdl_paid")}">
|
||||
<input type="checkbox" ${isChecked} onchange="this.nextElementSibling.value = this.checked ? 1 : 0">
|
||||
<input type="checkbox" ${isChecked} onchange="this.nextElementSibling.value = this.checked ? 1 : 0" style="accent-color:var(--success); width:16px; height:16px;">
|
||||
<input type="hidden" name="items[paid][]" value="${isPaid}">
|
||||
<span class="hol-form-paid-text">${tr("hdl_paid")}</span>
|
||||
<span class="hol-form-paid-text" style="display:none;">${tr("hdl_paid")}</span>
|
||||
</label>
|
||||
<button type="button" class="btn-icon-action delete btn-remove-expense" onclick="this.parentElement.parentElement.remove()" title="${tr("btn_delete")}">🗑️</button>
|
||||
<button type="button" class="btn-icon-action delete btn-remove-expense" onclick="this.parentElement.parentElement.remove()" title="${tr("btn_delete")}">🗑️</button>
|
||||
</div>
|
||||
<div class="hol-form-subrow">
|
||||
<input type="text" name="items[notes][]" class="pf-input hol-form-notes-input hol-form-notes-full" placeholder="${tr("hdl_ph_notes")}" value="${notes}">
|
||||
<div class="hol-form-subrow" style="padding-left:0; margin-top:4px;">
|
||||
<input type="text" name="items[notes][]" class="pf-input hol-form-notes-input hol-form-notes-full" placeholder="🔗 Réservation ou notes..." value="${notes}" style="margin-left:0; width:100%; border-radius:6px;">
|
||||
</div>
|
||||
<input type="hidden" name="items[id][]" value="${itemId}">
|
||||
<input type="hidden" name="items[date][]" value="${itemDate}">
|
||||
@@ -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
|
||||
? `<div style="font-size:0.65rem; color:var(--primary); font-weight:800; margin-bottom:4px; text-transform:uppercase;">📍 ${it.step_location}</div>`
|
||||
|
||||
// 💡 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
|
||||
? ` <span style="color:var(--text-muted); text-transform:none;">(${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" })})</span>`
|
||||
: it.step_start_date
|
||||
? ` <span style="color:var(--text-muted); text-transform:none;">(${new Date(it.step_start_date).toLocaleDateString(currentLang, { day: "2-digit", month: "2-digit" })})</span>`
|
||||
: "";
|
||||
|
||||
locHintHtml = it.step_location
|
||||
? `<div style="font-size:0.65rem; color:var(--primary); font-weight:800; margin-bottom:4px; text-transform:uppercase;">📍 ${it.step_location}${datesStr}</div>`
|
||||
: "";
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="hol-drag-item ${catClass}" ${dragAttr}
|
||||
id="${htmlId}" data-id="${it.id}" data-virtual="${isVirtual}" data-sort="${it.sort_order}"
|
||||
style="--duration: ${dur}; flex-shrink: 0; ${bgStyle} padding: 8px 10px; border-radius: 6px; cursor: grab; box-shadow: 0 2px 4px rgba(0,0,0,0.05); transition: transform 0.2s; z-index: 10;"
|
||||
ondragstart="dragStart(event)" ondragend="dragEnd(event)" onclick="handleItemTap(event, '${htmlId}')">
|
||||
${locHint}
|
||||
${locHintHtml}
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; gap: 8px;">
|
||||
<div class="hol-drag-title" style="flex:1; font-size: 0.85rem; font-weight: 700; color: var(--text-main); line-height:1.2;">${visualName}</div>
|
||||
|
||||
<div style="display:flex; align-items:center;">
|
||||
<div class="hol-item-duration-controls" style="display:flex; align-items:center; background:var(--bg-subtle); border-radius:4px; border:1px solid var(--border-light);">
|
||||
${durControls}
|
||||
</div>
|
||||
<!-- 🔄 Le fameux bouton d'annulation (Géré par CSS) -->
|
||||
<button type="button" class="hol-unplace-btn" onclick="unplaceItem(event, '${htmlId}')" title="Retirer du planning">↩️</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
${noteHtml}
|
||||
</div>
|
||||
@@ -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)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
<div id="checkpointModal" class="pf-modal">
|
||||
<div class="pf-modal-content" style="max-width: 600px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 id="cpModalTitle" style="margin:0;">📍 <?= tr('hdl_btn_add_step') ?></h3>
|
||||
<button type="button" onclick="closeCheckpointModal()" class="pf-modal-close">×</button>
|
||||
<div class="pf-modal-content" style="max-width: 600px; padding: 0; overflow: hidden;">
|
||||
|
||||
<!-- HEADER MODALE -->
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; padding: 20px 20px 0 20px;">
|
||||
<h3 id="cpModalTitle" style="margin:0; font-size:1.3rem;">📍 Ajouter une étape</h3>
|
||||
<button type="button" onclick="closeCheckpointModal()" class="pf-modal-close" style="margin-top:-10px;">×</button>
|
||||
</div>
|
||||
|
||||
<!-- NOUVEAU : SYSTÈME D'ONGLETS -->
|
||||
<div style="display:flex; border-bottom: 1px solid var(--border-light); margin-top: 15px; padding: 0 20px;">
|
||||
<button type="button" id="tabBtnInfo" onclick="switchCpTab('info')" style="flex:1; padding:10px; background:none; border:none; border-bottom:3px solid var(--primary); font-weight:bold; color:var(--primary); cursor:pointer; font-size:0.95rem;">📍 Infos de l'étape</button>
|
||||
<button type="button" id="tabBtnProg" onclick="switchCpTab('prog')" style="flex:1; padding:10px; background:none; border:none; border-bottom:3px solid transparent; font-weight:bold; color:var(--text-muted); cursor:pointer; font-size:0.95rem;">🎟️ Programme & Frais</button>
|
||||
</div>
|
||||
|
||||
<div id="cpSearchBlock" style="margin-bottom:20px;">
|
||||
<?php if (!empty($favorites)): ?>
|
||||
<div style="margin-bottom:15px; display:flex; gap:8px; flex-wrap:wrap;">
|
||||
<?php foreach($favorites as $fav): ?>
|
||||
<button type="button" class="pf-btn btn-secondary" style="padding:4px 10px; font-size:0.8rem; border-radius:20px;" onclick="selectPlace(<?= $fav['lat'] ?>, <?= $fav['lng'] ?>, '<?= htmlspecialchars(addslashes($fav['name'])) ?>')">
|
||||
⭐ <?= htmlspecialchars($fav['name']) ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<label class="pf-label"><?= tr('hdl_search_location') ?></label>
|
||||
<div style="display:flex; gap:10px;">
|
||||
<input type="text" id="searchPlaceInput" class="pf-input" placeholder="<?= tr('hdl_ph_search') ?>" onkeypress="if(event.key === 'Enter') { searchPlace(); return false; }">
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="searchPlace()">🔍</button>
|
||||
</div>
|
||||
<div id="searchResults" style="margin-top:10px; max-height:200px; overflow-y:auto;"></div>
|
||||
</div>
|
||||
|
||||
<form action="/modules/holidays/includes/api/save_checkpoint.php" method="POST" id="formCheckpoint" style="display:none; border-top:1px solid #e2e8f0; padding-top:20px;">
|
||||
<!-- DÉBUT DU FORMULAIRE UNIQUE -->
|
||||
<form action="/modules/holidays/includes/api/save_checkpoint.php" method="POST" id="formCheckpoint" style="display:flex; flex-direction:column; max-height: calc(90vh - 100px);">
|
||||
<input type="hidden" name="holiday_id" value="<?= $id ?>">
|
||||
<input type="hidden" name="old_location_name" id="cp_old_name">
|
||||
<input type="hidden" name="lat" id="cp_lat">
|
||||
<input type="hidden" name="lng" id="cp_lng">
|
||||
<input type="hidden" name="old_sort_order" id="cp_old_sort_order">
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label"><?= tr('hdl_label_step_name') ?></label>
|
||||
<input type="text" name="location_name" id="cp_name" class="pf-input" style="font-weight:bold; color:var(--primary);" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px; background:#fff7ed; padding:10px; border-radius:8px; border:1px solid #ffedd5;">
|
||||
<label class="pf-label" style="color:#ea580c;">📍 Type d'étape</label>
|
||||
<select name="step_type" id="cp_step_type" class="pf-input" onchange="toggleStepDates(this.value)">
|
||||
<option value="origin">DÉPART (Point de départ du voyage)</option>
|
||||
<option value="stop">SÉJOUR (Étape classique avec arrivée et départ)</option>
|
||||
<option value="destination">ARRIVÉE FINALE (Fin du voyage)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:15px; margin-bottom:15px; background:#f8fafc; padding:12px; border-radius:8px;">
|
||||
<div class="form-group" id="grp_start_date" style="flex:1;">
|
||||
<label class="pf-label" id="lbl_start_date"><?= tr('hdl_label_arrival') ?></label>
|
||||
<input type="date" name="step_start_date" id="cp_start_date" class="pf-input">
|
||||
<!-- ONGLET 1 : INFOS GÉNÉRIQUES -->
|
||||
<div id="cpTabInfo" style="padding: 20px; overflow-y: auto;">
|
||||
|
||||
<div id="cpSearchBlock" style="margin-bottom:15px; background:var(--bg-subtle); padding:12px; border-radius:8px; border: 1px dashed var(--border-strong);">
|
||||
<label class="pf-label" style="margin-bottom:4px; font-size:0.8rem;">🔍 Chercher un lieu (Autocomplétion)</label>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<input type="text" id="searchPlaceInput" class="pf-input" placeholder="Ville, hôtel, adresse..." onkeypress="if(event.key === 'Enter') { searchPlace(); return false; }">
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="searchPlace()" style="padding: 0 15px;">Go</button>
|
||||
</div>
|
||||
<div id="searchResults" style="margin-top:8px; max-height:150px; overflow-y:auto; display:flex; flex-direction:column; gap:4px;"></div>
|
||||
</div>
|
||||
<div class="form-group" id="grp_end_date" style="flex:1;">
|
||||
<label class="pf-label" id="lbl_end_date"><?= tr('hdl_label_departure') ?></label>
|
||||
<input type="date" name="step_end_date" id="cp_end_date" class="pf-input">
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Nom de l'étape</label>
|
||||
<input type="text" name="location_name" id="cp_name" class="pf-input" style="font-weight:bold; color:var(--primary); font-size:1.1rem;" required>
|
||||
</div>
|
||||
|
||||
<!-- DATES COMPACTES AVEC FLATPICKR -->
|
||||
<div class="form-group" style="margin-bottom:15px; background:var(--bg-subtle); padding:12px; border-radius:8px; border: 1px dashed var(--border-strong);">
|
||||
<label class="pf-label" id="lbl_date_range" style="margin-bottom:8px;">📅 Période de l'étape (Arrivée ➔ Départ)</label>
|
||||
<input type="text" id="cp_date_range" class="pf-input" placeholder="Sélectionnez les dates..." readonly style="cursor: pointer; background-color: var(--bg-panel); color: var(--primary); font-weight: bold; text-align: center; letter-spacing: 0.5px;">
|
||||
<!-- Les vraies valeurs envoyées au serveur -->
|
||||
<input type="hidden" name="step_start_date" id="cp_start_date">
|
||||
<input type="hidden" name="step_end_date" id="cp_end_date">
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="cp_insert_group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">🔽 Où placer cette étape ?</label>
|
||||
<select name="insert_after" id="cp_insert_after" class="pf-input" onchange="injectDynamicDates(this)">
|
||||
<!-- Rempli dynamiquement en JS avec Noms + Dates -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:10px;">
|
||||
<div class="form-group" style="flex:1; margin:0;">
|
||||
<label class="pf-label">📍 Type d'étape</label>
|
||||
<select name="step_type" id="cp_step_type" class="pf-input" onchange="toggleStepDates(this.value)">
|
||||
<option value="origin">DÉPART</option>
|
||||
<option value="stop" selected>SÉJOUR</option>
|
||||
<option value="destination">ARRIVÉE FINALE</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 15px; display:flex; flex-direction:column; gap:8px;">
|
||||
<label style="display:flex; align-items:center; cursor:pointer; font-size:0.85rem; color:var(--text-main);">
|
||||
<input type="checkbox" name="set_as_return" id="cp_set_as_return" value="1" style="margin-right:8px; width:16px; height:16px; accent-color:#ea580c;">
|
||||
🏁 Définir comme étape de retour (Tracé Orange)
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; cursor:pointer; font-size:0.85rem; color:var(--text-main);">
|
||||
<input type="checkbox" name="save_favorite" value="1" style="margin-right:8px; width:16px; height:16px;">
|
||||
⭐ Sauvegarder dans mes favoris
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px; padding-top: 15px; border-top: 1px dashed #e2e8f0;">
|
||||
<label style="display:flex; align-items:center; cursor:pointer; color:#ea580c; font-weight:600; font-size:0.9rem;">
|
||||
<input type="checkbox" name="set_as_return" id="cp_set_as_return" value="1" style="margin-right:8px; width:16px; height:16px;">
|
||||
🏁 Définir comme retour
|
||||
</label>
|
||||
<p style="margin: 4px 0 0 24px; font-size: 0.75rem; color: #64748b;">La route sera tracée en orange à partir d'ici.</p>
|
||||
<!-- ONGLET 2 : PROGRAMME ET DEPENSES -->
|
||||
<div id="cpTabProg" style="display:none; padding: 20px; overflow-y: auto;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; position:sticky; top:0; background:var(--bg-panel); z-index:10; padding-bottom:10px; border-bottom:1px solid var(--border-light);">
|
||||
<label class="pf-label" style="margin:0;">🎟️ Liste des activités & frais</label>
|
||||
<button type="button" class="pf-btn btn-secondary pf-btn-small" onclick="addCpExpenseLine()">+ Ajouter</button>
|
||||
</div>
|
||||
|
||||
<div id="cpExpensesContainer" style="display:flex; flex-direction:column; gap:10px;"></div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
|
||||
<label class="pf-label" style="margin:0;"><?= tr('hdl_planned_expenses') ?></label>
|
||||
<button type="button" class="pf-btn btn-secondary pf-btn-small" onclick="addCpExpenseLine()"><?= tr('hdl_btn_add_expense') ?></button>
|
||||
</div>
|
||||
|
||||
<div id="cpExpensesContainer" style="margin-bottom:15px;"></div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<label style="display:flex; align-items:center; cursor:pointer; font-size:0.85rem; color:#475569;">
|
||||
<input type="checkbox" name="save_favorite" value="1" style="margin-right:8px;">
|
||||
⭐ <?= tr('hdl_save_fav') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" style="padding-top:15px; border-top:1px solid #e2e8f0;">
|
||||
<button type="button" onclick="deleteCheckpoint()" id="btnDeleteCp" class="pf-btn btn-secondary" style="color:#ef4444; border-color:#fca5a5; display:none;"><?= tr('btn_delete') ?></button>
|
||||
<!-- FOOTER FIXE -->
|
||||
<div class="modal-footer" style="padding: 15px 20px; border-top:1px solid var(--border-light); background:var(--bg-page); margin-top:auto;">
|
||||
<button type="button" onclick="deleteCheckpoint()" id="btnDeleteCp" class="pf-btn btn-secondary" style="color:var(--danger); border-color:var(--border-danger-soft); display:none; margin-right:auto;">Supprimer</button>
|
||||
<button type="button" onclick="closeCheckpointModal()" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
|
||||
<button type="submit" class="pf-btn">💾 <?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -122,10 +122,12 @@ window.closeHolidayModal = window.closeHolidayModal || function() {
|
||||
if(modal) modal.style.display = 'none';
|
||||
document.body.classList.remove('no-scroll');
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
</script>
|
||||
<script src="/modules/holidays/holidays.js"></script>
|
||||
|
||||
|
||||
<?php
|
||||
function renderHolidayCard($h, $pdo) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM pf_holidays_items WHERE holiday_id = ?");
|
||||
@@ -136,8 +138,9 @@ function renderHolidayCard($h, $pdo) {
|
||||
$dateDisplay = htmlspecialchars($h['period_hint'] ?? '');
|
||||
|
||||
if (empty($dateDisplay) && $h['start_date']) {
|
||||
$dateDisplay = date('d/m/Y', strtotime($h['start_date']));
|
||||
if ($h['end_date']) $dateDisplay .= ' → ' . date('d/m/Y', strtotime($h['end_date']));
|
||||
// 💡 Format Jour/Mois uniquement
|
||||
$dateDisplay = date('d/m', strtotime($h['start_date']));
|
||||
if ($h['end_date']) $dateDisplay .= ' → ' . date('d/m', strtotime($h['end_date']));
|
||||
}
|
||||
|
||||
$statusClass = match($h['status']) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div id="holidayModal" class="pf-modal">
|
||||
<div class="pf-modal-content hol-modal-content" style="max-width: 500px;"> <h3 id="modalTitle" class="pf-modal-title"><?= tr('hdl_modal_title') ?></h3>
|
||||
<div class="pf-modal-content hol-modal-content" style="max-width: 500px;">
|
||||
<h3 id="modalTitle" class="pf-modal-title"><?= tr('hdl_modal_title') ?></h3>
|
||||
|
||||
<form action="/modules/holidays/includes/api/save_holiday.php" method="POST" id="holidayForm">
|
||||
<input type="hidden" name="id" id="inp_id">
|
||||
@@ -23,23 +24,21 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div>
|
||||
<div class="hol-flex-1">
|
||||
<label class="pf-label"><?= tr('hdl_label_period') ?></label>
|
||||
<input type="text" name="period_hint" id="inp_period" class="pf-input" placeholder="<?= tr('hdl_ph_period') ?>">
|
||||
</div>
|
||||
<div class="hol-date-range-group">
|
||||
<div class="hol-flex-1">
|
||||
<label class="pf-label"><?= tr('hdl_label_from') ?></label>
|
||||
<input type="date" name="start_date" id="inp_start" class="pf-input">
|
||||
</div>
|
||||
<div class="hol-flex-1">
|
||||
<label class="pf-label"><?= tr('hdl_label_to') ?></label>
|
||||
<input type="date" name="end_date" id="inp_end" class="pf-input">
|
||||
</div>
|
||||
<!-- DATES AVEC FLATPICKR -->
|
||||
<div class="hol-flex-2">
|
||||
<label class="pf-label">📅 Dates du voyage</label>
|
||||
<input type="text" id="hol_date_range" class="pf-input" placeholder="Sélectionnez les dates..." readonly style="cursor: pointer; background-color: var(--bg-panel); color: var(--primary); font-weight: bold; text-align: center; letter-spacing: 0.5px;">
|
||||
<!-- Vraies données pour save_holiday.php -->
|
||||
<input type="hidden" name="start_date" id="inp_start">
|
||||
<input type="hidden" name="end_date" id="inp_end">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px; padding: 10px; background: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0;">
|
||||
<div class="form-group" style="margin-top: 15px; padding: 10px; background: var(--bg-subtle); border-radius: 8px; border: 1px solid var(--border-light);">
|
||||
<label class="pf-label" style="margin-bottom: 5px;">🚗 Véhicule utilisé (Optionnel)</label>
|
||||
<select name="vehicle_id" id="inp_vehicle_id" class="pf-input">
|
||||
<option value="">-- Aucun / Autre transport --</option>
|
||||
@@ -52,17 +51,16 @@
|
||||
<hr class="hol-divider">
|
||||
|
||||
<div class="form-row">
|
||||
<div>
|
||||
<div class="hol-flex-1">
|
||||
<label class="pf-label">🍔 <?= tr('hdl_label_budget_food') ?></label>
|
||||
<input type="number" step="0.01" name="budget_food" id="inp_food" class="pf-input" placeholder="0.00">
|
||||
</div>
|
||||
<div>
|
||||
<div class="hol-flex-1">
|
||||
<label class="pf-label">🎁 <?= tr('hdl_label_budget_extras') ?></label>
|
||||
<input type="number" step="0.01" name="budget_extra" id="inp_extra" class="pf-input" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="deleteHoliday()" id="btn_delete" class="pf-btn btn-secondary hol-btn-delete"><?= tr('btn_delete') ?></button>
|
||||
<button type="button" onclick="closeHolidayModal()" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
|
||||
@@ -19,7 +19,7 @@ $grandTotal = $totalGeneral + $totalSteps;
|
||||
<h1 style="font-size: 2.5rem; color: #0f172a; margin-bottom: 10px;"><?= htmlspecialchars($holiday['title'] ?? $holiday['name'] ?? 'Mon Voyage') ?></h1>
|
||||
<h2 style="font-size: 1.5rem; color: #64748b; font-weight: normal; margin-top: 0;">
|
||||
<?php if (!empty($holiday['start_date']) && !empty($holiday['end_date'])): ?>
|
||||
Du <?= date('d/m/Y', strtotime($holiday['start_date'])) ?> au <?= date('d/m/Y', strtotime($holiday['end_date'])) ?>
|
||||
Du <?= date('d/m', strtotime($holiday['start_date'])) ?> au <?= date('d/m', strtotime($holiday['end_date'])) ?>
|
||||
<?php else: ?>
|
||||
Dates à définir
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user