diff --git a/modules/holidays/holidays.css b/modules/holidays/holidays.css index 705bc6f..6adac49 100644 --- a/modules/holidays/holidays.css +++ b/modules/holidays/holidays.css @@ -2,15 +2,17 @@ /* --- 1. VARIABLES & BASE --- */ :root { - --primary: #2563eb; /* Bleu vibrant */ + --primary: #2563eb; --primary-hover: #1d4ed8; - --bg-page: #f1f5f9; /* Gris-bleu très pâle */ + --bg-page: #f1f5f9; --bg-card: #ffffff; - --text-main: #1e293b; /* Gris très foncé (pas noir) */ + --text-main: #1e293b; --text-muted: #64748b; + --radius-l: 16px; --radius-m: 10px; --radius-s: 6px; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --shadow-lg: @@ -27,24 +29,10 @@ -apple-system, sans-serif; color: var(--text-main); + padding-bottom: 50px; } -/* Titres */ -.pf-holidays h1 { - font-size: 1.8rem; - font-weight: 800; - letter-spacing: -0.025em; - color: #0f172a; - margin-bottom: 0.5rem; -} - -.pf-holidays p { - font-size: 0.95rem; - color: var(--text-muted); - line-height: 1.5; -} - -/* --- 2. EN-TÊTE (TITLEBAR) --- */ +/* --- 2. EN-TÊTE --- */ .pf-holidays__titlebar { background: transparent; padding-bottom: 1rem; @@ -57,17 +45,24 @@ gap: 1rem; } +.pf-holidays__titlebar h1 { + font-size: 1.8rem; + font-weight: 800; + color: #0f172a; + margin: 0; +} + .hol-title-actions { display: flex; gap: 12px; } -/* --- 3. BOUTONS MODERNES --- */ -.btn, +/* --- 3. BOUTONS --- */ .hol-add-btn, -.hol-map-toggle { +.hol-map-toggle, +.pf-btn { border: none; - border-radius: 50px; /* Pill shape */ + border-radius: 50px; padding: 10px 20px; font-size: 0.9rem; font-weight: 600; @@ -76,21 +71,21 @@ display: inline-flex; align-items: center; justify-content: center; - text-decoration: none; box-shadow: var(--shadow-sm); + text-decoration: none; } -/* Bouton principal (Ajouter) */ -.hol-add-btn { +.hol-add-btn, +.pf-btn { background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); color: white; } -.hol-add-btn:hover { +.hol-add-btn:hover, +.pf-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3); } -/* Bouton Carte */ .hol-map-toggle { background: white; color: #0f766e; @@ -98,53 +93,44 @@ } .hol-map-toggle:hover { background: #f0fdfa; - border-color: #99f6e4; color: #0d9488; + border-color: #99f6e4; } -/* Boutons classiques */ -.btn { +.pf-btn.btn-secondary { background: white; - color: var(--text-main); + color: var(--text-muted); border: 1px solid #cbd5e1; -} -.btn:hover { - background: #f8fafc; - border-color: #94a3b8; -} - -/* Actions contextuelles (edit/delete) dans les cards */ -.btn-edit, -.btn-delete { - border-radius: var(--radius-s); - padding: 6px 12px; - font-size: 0.8rem; box-shadow: none; } -.btn-edit { +.pf-btn.btn-secondary:hover { + background: #f8fafc; + color: var(--text-main); +} + +/* Petit bouton + pour les colonnes */ +.btn-icon-small { + background: transparent; + border: 1px dashed #cbd5e1; + border-radius: 4px; + padding: 6px 12px; + font-size: 0.8rem; + color: var(--text-muted); + cursor: pointer; + width: 100%; + margin-top: 10px; + transition: 0.2s; +} +.btn-icon-small:hover { background: #f1f5f9; - color: #475569; - border: none; -} -.btn-edit:hover { - background: #e2e8f0; - color: #1e293b; + color: var(--text-main); + border-color: var(--text-muted); } -.btn-delete { - background: #fef2f2; - color: #dc2626; - border: none; -} -.btn-delete:hover { - background: #fee2e2; - color: #b91c1c; -} - -/* --- 4. GRILLE & CARTES (Le cœur du design) --- */ +/* --- 4. CARTES --- */ .hol-ideas-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 24px; margin-bottom: 40px; } @@ -159,52 +145,31 @@ transition: transform 0.25s ease, box-shadow 0.25s ease; - border: 1px solid transparent; /* Pour éviter le saut au hover */ + border: 1px solid transparent; + cursor: pointer; position: relative; overflow: hidden; } -/* Effet au survol : la carte "flotte" */ .hol-idea-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-hover); border-color: #e2e8f0; } -/* Bande de couleur décorative en haut de carte selon statut (Optionnel mais joli) */ -.hol-idea-card::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: #e2e8f0; /* Default */ -} -.hol-idea-card:has(.hol-status--planned)::before { - background: #22c55e; -} -.hol-idea-card:has(.hol-status--favorite)::before { - background: #f59e0b; -} -.hol-idea-card:has(.hol-status--shortlist)::before { - background: #3b82f6; -} - .hol-idea-card__head { display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 12px; + margin-bottom: 8px; } .hol-idea-card h3 { margin: 0; font-size: 1.15rem; font-weight: 700; - line-height: 1.3; color: var(--text-main); - margin-right: 8px; + line-height: 1.3; } .hol-idea-meta { @@ -216,355 +181,310 @@ gap: 4px; } -/* Notes stylisées */ .hol-notes { - background: #fffbeb; /* Jaune très pâle, style post-it */ - color: #78350f; - padding: 10px 12px; - border-radius: var(--radius-m); font-size: 0.85rem; - font-style: italic; - margin-bottom: 16px; - flex-grow: 1; - border-left: 3px solid #fcd34d; -} - -.hol-card-actions { - margin-top: auto; - display: flex; - gap: 8px; - padding-top: 16px; - border-top: 1px solid #f1f5f9; -} - -/* Statuts (Badges Modernes) */ -.hol-status { - padding: 4px 10px; - border-radius: 20px; - font-size: 0.7rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; -} -.hol-status--planned { - background: #dcfce7; - color: #15803d; -} /* Vert */ -.hol-status--favorite { - background: #fff7ed; - color: #c2410c; - border: 1px solid #ffedd5; -} /* Orange */ -.hol-status--shortlist { - background: #eff6ff; - color: #1d4ed8; -} /* Bleu */ -.hol-status--draft { - background: #f1f5f9; color: #64748b; -} /* Gris */ -.hol-status--archived { - background: #f8fafc; - color: #94a3b8; - text-decoration: line-through; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + margin: 0; + font-style: italic; } -/* Style spécifique pour les archivés */ -.hol-idea-card--archived { - background: #f8fafc; - box-shadow: none; - border: 1px solid #e2e8f0; - opacity: 0.8; +/* --- 5. UTILITAIRES STATUTS --- */ +.bg-green-100 { + background-color: #dcfce7; } - -/* --- 5. VUE DÉTAIL (Panels) --- */ -.pf-section--panel { - background: white; - border-radius: var(--radius-l); - box-shadow: var(--shadow-sm); - padding: 24px; - margin-bottom: 24px; - border: 1px solid #f1f5f9; +.text-green-800 { + color: #166534; } - -.pf-section--panel h2 { - font-size: 1.25rem; - color: var(--text-main); - margin-bottom: 16px; - display: flex; - align-items: center; - gap: 8px; +.bg-blue-100 { + background-color: #dbeafe; } -/* Petite ligne décorative sous le titre h2 */ -.pf-section--panel h2::after { - content: ""; - flex: 1; - height: 1px; - background: #e2e8f0; - margin-left: 12px; -} - -.hol-list li { - padding: 12px 0; - border-bottom: 1px solid #f1f5f9; - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 8px; -} -.hol-list li:last-child { - border-bottom: none; -} - -.hol-budget-summary { - background: linear-gradient(to right, #eff6ff, #ffffff); - border: 1px solid #dbeafe; +.text-blue-800 { color: #1e40af; - padding: 16px; - border-radius: var(--radius-m); - font-weight: 600; - display: flex; - justify-content: space-between; - margin-bottom: 16px; +} +.bg-yellow-50 { + background-color: #fefce8; +} +.text-yellow-800 { + color: #854d0e; +} +.bg-gray-100 { + background-color: #f3f4f6; +} +.text-gray-600 { + color: #4b5563; } -/* Formulaires Inline (Détail) */ -.hol-inline-form { - background: #f8fafc; - padding: 12px; - border-radius: var(--radius-m); - border: 1px dashed #cbd5e1; - display: flex; - flex-wrap: wrap; - gap: 10px; - align-items: center; - margin-bottom: 20px; -} -.hol-inline-form input, -.hol-inline-form select { - border: 1px solid #cbd5e1; - border-radius: var(--radius-s); - padding: 8px 12px; - font-size: 0.9rem; - transition: border-color 0.2s; -} -.hol-inline-form input:focus, -.hol-inline-form select:focus { - border-color: var(--primary); - outline: 2px solid rgba(37, 99, 235, 0.1); -} -.hol-inline-form button { - background: #334155; - color: white; - border: none; - padding: 8px 16px; - border-radius: var(--radius-s); - cursor: pointer; -} -.hol-inline-form button:hover { - background: #1e293b; -} - -/* --- 6. MODALES MODERNES (Glassmorphism léger) --- */ -.hol-modal { +/* --- 6. MODALE & FORMULAIRE (Refonte Centrage & Largeur) --- */ +.pf-modal { display: none; -} -.hol-modal.open { - display: flex; - align-items: center; - justify-content: center; position: fixed; inset: 0; z-index: 9999; + align-items: center; /* Centrage Vertical */ + justify-content: center; /* Centrage Horizontal */ + background: rgba(15, 23, 42, 0.6); + backdrop-filter: blur(4px); +} +.pf-modal.open { + display: flex; } -.hol-backdrop { - position: absolute; - inset: 0; - background: rgba(15, 23, 42, 0.6); /* Plus sombre */ - backdrop-filter: blur(4px); /* Flou d'arrière plan */ -} - -.hol-dialog { +.pf-modal-content { position: relative; background: white; - width: min(600px, 90vw); + width: 95%; /* Responsive */ + max-width: 1100px; /* Largeur augmentée pour 3 colonnes */ max-height: 90vh; overflow-y: auto; - border-radius: 20px; /* Très arrondi */ + border-radius: 20px; box-shadow: var(--shadow-lg); - z-index: 10; - animation: modalPop 0.3s cubic-bezier(0.16, 1, 0.3, 1); + padding: 32px; + display: flex; + flex-direction: column; + margin: auto; /* Sécurité centrage */ } -@keyframes modalPop { - from { - transform: scale(0.95) translateY(10px); - opacity: 0; - } - to { - transform: scale(1) translateY(0); - opacity: 1; - } -} - -/* Modale Carte */ +/* Cas spécifique modale carte */ .hol-dialog--map { width: 95vw; height: 90vh; max-width: 1400px; padding: 0; + background: white; + border-radius: 12px; display: flex; flex-direction: column; } -/* Formulaires Modale */ -.hol-form { - padding: 32px; - display: flex; - flex-direction: column; - gap: 16px; -} -.hol-form h3 { - font-size: 1.5rem; - margin-bottom: 12px; - color: var(--text-main); -} -.hol-form label { +/* Inputs généraux */ +.pf-label { + display: block; font-weight: 600; font-size: 0.85rem; - color: #475569; - margin-bottom: 4px; + color: var(--text-muted); + margin-bottom: 6px; } -.hol-form input, -.hol-form select, -.hol-form textarea { + +.pf-input { width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: var(--radius-m); font-size: 1rem; - background: #f8fafc; - box-sizing: border-box; /* Important */ + background: #ffffff; /* Fond blanc forcé */ + color: var(--text-main); + box-sizing: border-box; + transition: all 0.2s; } -.hol-form input:focus, -.hol-form select:focus, -.hol-form textarea:focus { + +.pf-input:focus { background: white; border-color: var(--primary); - outline: 4px solid rgba(37, 99, 235, 0.1); + outline: none; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); } -.hol-inline { +.form-row { display: flex; gap: 16px; -} -.hol-inline > label { - flex: 1; + flex-wrap: wrap; + margin-bottom: 16px; } -.hol-actions { - margin-top: 16px; +/* --- 7. SECTIONS DE DÉTAILS (Transport/Hotel/Activité) --- */ +.hol-columns-wrapper { + display: flex; /* Flexbox */ + flex-direction: column; /* Alignement vertical (les uns sous les autres) */ + gap: 24px; /* Espace entre les blocs */ + width: 100%; +} + +.hol-col { + background: #f8fafc; + padding: 20px; + border-radius: 12px; + border: 1px solid #e2e8f0; + width: 100%; /* Prend toute la largeur disponible */ + box-sizing: border-box; /* Empêche le padding de casser la largeur */ +} + +.hol-col-header { + display: flex; + justify-content: space-between; /* Titre à gauche, bouton à droite */ + align-items: center; + border-bottom: 1px solid #e2e8f0; + padding-bottom: 8px; + margin-bottom: 12px; +} + +.hol-col-header h4 { + margin: 0; + font-size: 1rem; + text-transform: uppercase; + font-weight: 700; + /* On retire les bordures/marges de l'ancien h4 car c'est le header qui gère */ + border: none; + padding: 0; +} + +/* Le bouton "+" style Gift List */ +.btn-add-item { + /* Style de base */ + background: rgba(255, 255, 255, 0.6); + color: var(--text-muted); + border: 1px solid rgba(0, 0, 0, 0.1); + + /* Forme et Taille */ + width: 28px; + height: 28px; + border-radius: 50%; + + /* Texte */ + font-size: 18px; + line-height: 1; + + /* Flex pour centrer le + */ + display: flex; + align-items: center; + justify-content: center; + + cursor: pointer; + + /* Animation fluide "Cubic Bezier" copiée de gift-list */ + transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); +} + +.btn-add-item:hover { + background: white; + color: var(--primary); /* On ajoute la couleur primaire au survol */ + + /* L'effet de rotation et d'agrandissement */ + transform: rotate(90deg) scale(1.1); + + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); + border-color: currentColor; +} + +/* --- 8. ITEMS DYNAMIQUES (Inputs optimisés) --- */ +.savings-line-item { + display: flex; + align-items: center; + gap: 8px; /* Espace réduit */ + margin-bottom: 10px; + background: white; + padding: 8px; + border-radius: 8px; + border: 1px solid #f1f5f9; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); +} + +/* Champ NOM : prend toute la place */ +.savings-line-item input[type="text"] { + flex-grow: 1; + min-width: 120px; + padding: 6px 10px; + font-size: 0.9rem; +} + +/* Champ PRIX : compact */ +.savings-line-item input[type="number"] { + width: 120px !important; + padding: 6px 8px !important; + text-align: right; + font-weight: 600; + color: #059669; +} + +/* Checkbox Payé */ +.savings-line-item label { + display: flex; + align-items: center; + font-size: 0.75rem; + color: #64748b; + gap: 4px; + cursor: pointer; + white-space: nowrap; +} + +/* Bouton Supprimer (Croix) */ +.btn-remove { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + color: #ef4444; + border: none; + border-radius: 4px; + font-size: 1.2rem; + cursor: pointer; + line-height: 1; + transition: background 0.2s; +} +.btn-remove:hover { + background: #fef2f2; + color: #b91c1c; +} + +/* --- 9. FOOTER MODALE --- */ +.modal-footer { display: flex; justify-content: flex-end; - gap: 12px; -} -.hol-cancel { - background: transparent; - color: var(--text-muted); - border: none; - font-weight: 600; -} -.hol-cancel:hover { - color: var(--text-main); - background: #f1f5f9; + gap: 10px; + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid #e2e8f0; } -.hol-ok { - background: var(--primary); - color: white; - border: none; - padding: 10px 24px; - border-radius: 50px; - font-weight: 600; - box-shadow: 0 4px 6px rgba(37, 99, 235, 0.2); -} -.hol-ok:hover { - background: var(--primary-hover); - transform: translateY(-1px); -} - -/* --- 7. PICKER GEOCODE --- */ -.hol-geocode-picker { - background: white; - border: 1px solid #e2e8f0; - border-radius: var(--radius-m); - box-shadow: var(--shadow-lg); - margin-top: 8px; - overflow: hidden; -} -.hol-geocode-picker__header { - background: #f8fafc; - padding: 8px 12px; - font-weight: 600; - font-size: 0.85rem; - display: flex; - justify-content: space-between; -} -.hol-geocode-picker__item { - padding: 10px 12px; - border-bottom: 1px solid #f1f5f9; - cursor: pointer; - display: flex; - justify-content: space-between; - align-items: center; -} -.hol-geocode-picker__item:hover { - background: #f0f9ff; -} -.hol-geocode-picker__item button { - background: white; - border: 1px solid #cbd5e1; - border-radius: 4px; - font-size: 0.75rem; - padding: 4px 8px; -} - -/* --- 8. MOBILE RESPONSIVE --- */ +/* --- 10. MOBILE --- */ @media (max-width: 768px) { - .hol-inline { + .pf-holidays__titlebar { flex-direction: column; - gap: 12px; - } - .hol-inline-form { - flex-direction: column; - align-items: stretch; - } - .hol-inline-form button { - margin-top: 8px; - width: 100%; - } - - .pf-holidays h1 { - font-size: 1.5rem; - } - - .hol-idea-card { - padding: 16px; - } - .hol-ideas-grid { - gap: 16px; - } - - .btn, - .hol-add-btn, - .hol-map-toggle { - width: 100%; /* Boutons pleine largeur sur mobile */ - margin-bottom: 8px; + align-items: flex-start; } .hol-title-actions { - flex-direction: column; width: 100%; - gap: 4px; + } + .hol-add-btn, + .hol-map-toggle { + width: 100%; + } + .form-row { + flex-direction: column; + gap: 10px; + } + /* Sur mobile, les colonnes s'empilent */ + .hol-columns-wrapper { + grid-template-columns: 1fr; + } + + .pf-modal-content { + padding: 16px; + width: 100%; + height: 100%; + max-height: 100vh; + border-radius: 0; } } + +/* --- 11. CALENDRIER & DATES (Force Light Theme) --- */ +input[type="date"] { + color-scheme: light; /* Force le popup blanc */ + background-color: #ffffff; + color: #1e293b; +} + +/* Cible spécifique pour l'icône calendrier (Chrome/Edge/Safari) */ +input[type="date"]::-webkit-calendar-picker-indicator { + cursor: pointer; + opacity: 0.6; + filter: invert(0); +} +input[type="date"]::-webkit-calendar-picker-indicator:hover { + opacity: 1; +} diff --git a/modules/holidays/holidays.js b/modules/holidays/holidays.js index 85651de..160729c 100644 --- a/modules/holidays/holidays.js +++ b/modules/holidays/holidays.js @@ -1,360 +1,174 @@ -// modules/holidays/holidays.js - document.addEventListener("DOMContentLoaded", () => { - // --- 1. FONCTIONS UTILITAIRES --- - - /** - * Configure les comportements de fermeture d'une modale (Backdrop, Cancel, Escape) - */ - function setupModal(modalId, openAction = null) { - const modal = document.getElementById(modalId); - if (!modal) return null; - - const backdrop = modal.querySelector(".hol-backdrop"); - const cancelBtn = modal.querySelector(".hol-cancel"); - - const close = () => modal.classList.remove("open"); - const open = () => { - modal.classList.add("open"); - if (openAction) openAction(); - }; - - if (backdrop) backdrop.addEventListener("click", close); - if (cancelBtn) cancelBtn.addEventListener("click", close); - - // Fermeture avec ECHAP (uniquement si cette modale est ouverte) - document.addEventListener("keydown", (e) => { - if (e.key === "Escape" && modal.classList.contains("open")) { - close(); - } - }); - - return { modal, open, close }; - } - - /** - * Échappe les caractères HTML pour éviter les failles XSS simples - */ - function esc(s) { - if (s === null || s === undefined) return ""; - return String(s) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - } - - // --- 2. GESTION MODALE "AJOUTER" --- - const addModalCtrl = setupModal("hol-add-modal"); - const addBtn = document.getElementById("hol-add-open"); - if (addBtn && addModalCtrl) { - addBtn.addEventListener("click", addModalCtrl.open); - } - - // --- 3. GESTION MODALE "ÉDITER" --- - const editModalCtrl = setupModal("hol-edit-modal"); - - // Fonction pour charger et ouvrir l'édition via AJAX - async function openEditForId(id) { - if (!editModalCtrl) return; - - try { - const res = await fetch( - `/modules/holidays/view.php?id=${encodeURIComponent(id)}`, - { headers: { Accept: "application/json" } }, - ); - if (!res.ok) throw new Error("Erreur HTTP " + res.status); - - const it = await res.json(); - const modal = editModalCtrl.modal; - - // Helper pour remplir les champs - const setVal = (sel, val) => { - const el = modal.querySelector(sel); - if (el) el.value = val ?? ""; - }; - - // Remplissage du formulaire - setVal("#edit-id", it.id); - setVal("#edit-title", it.title); - setVal("#edit-country", it.country); - setVal("#edit-region", it.region); - setVal("#edit-city", it.city); - setVal("#edit-lat", it.lat); - setVal("#edit-lng", it.lng); - setVal("#edit-start", it.desired_start_date); - setVal("#edit-end", it.desired_end_date); - setVal("#edit-season", it.season_hint); - setVal("#edit-days", it.ideal_days); - setVal("#edit-status", it.status || "draft"); - setVal("#edit-notes", it.notes); - - editModalCtrl.open(); - } catch (err) { - console.error(err); - alert("Impossible de charger les données de l'idée."); - } - } - - // Écouteurs sur les boutons "Éditer" (Liste + Détail) - document.body.addEventListener("click", (e) => { - // Utilisation de la délégation d'événement pour gérer tous les boutons (même dynamiques) - const btn = e.target.closest(".btn-edit"); - if (btn && btn.hasAttribute("data-edit-id")) { - const id = btn.getAttribute("data-edit-id"); - openEditForId(id); - } - // Cas spécifique du bouton dans le header de la vue détail - else if (e.target.id === "hol-edit-open") { - const id = e.target.getAttribute("data-edit-id"); - openEditForId(id); - } - }); - - // --- 4. GESTION SUPPRESSION --- - document.body.addEventListener("click", (e) => { - const btn = e.target.closest(".btn-delete"); - if (btn && btn.hasAttribute("data-del-id")) { - const id = btn.getAttribute("data-del-id"); - if ( - confirm( - "Voulez-vous vraiment supprimer cette idée ?\nCette action est irréversible.", - ) - ) { - // Création d'un formulaire temporaire pour le POST - const form = document.createElement("form"); - form.method = "post"; - form.action = "/modules/holidays/save.php"; - form.innerHTML = ` - - - `; - document.body.appendChild(form); - form.submit(); - } - } - }); - - // --- 5. GÉOCODAGE (Nominatim) --- - document.querySelectorAll(".hol-geocode-btn").forEach((btn) => { - btn.addEventListener("click", async () => { - const scope = btn.getAttribute("data-scope"); // 'add' ou 'edit' - let latInput, - lngInput, - qParts = []; - - // Récupération des inputs selon le scope - if (scope === "edit") { - const getVal = (id) => - (document.getElementById(id)?.value || "").trim(); - qParts = [ - getVal("edit-city"), - getVal("edit-region"), - getVal("edit-country"), - ]; - latInput = document.getElementById("edit-lat"); - lngInput = document.getElementById("edit-lng"); - } else { - // Scope 'add' : on cherche dans le formulaire parent - const form = btn.closest("form"); - const getVal = (name) => - (form?.querySelector(`input[name="${name}"]`)?.value || "").trim(); - qParts = [getVal("city"), getVal("region"), getVal("country")]; - latInput = form?.querySelector('input[name="lat"]'); - lngInput = form?.querySelector('input[name="lng"]'); - } - - const q = qParts.filter(Boolean).join(", "); - if (!q) { - alert( - "Veuillez renseigner au moins une Ville ou un Pays pour géocoder.", - ); - return; - } - - // UI : chargement - removeNearbyPicker(btn); - btn.disabled = true; - const originalText = btn.textContent; - btn.textContent = "⏳..."; - - try { - const res = await fetch( - `/modules/holidays/geocode.php?q=${encodeURIComponent(q)}&limit=5`, - { - headers: { Accept: "application/json" }, - }, - ); - const data = await res.json(); - - if (!res.ok) throw new Error(data?.error || "Erreur inconnue"); - - // Cas 1: Résultat direct (lat/lng uniques) - if (data.lat && data.lng) { - if (latInput) latInput.value = data.lat; - if (lngInput) lngInput.value = data.lng; - } - // Cas 2: Liste de choix - else if (Array.isArray(data.results) && data.results.length > 0) { - renderGeocodePicker(btn, data.results, (choice) => { - if (latInput) latInput.value = choice.lat; - if (lngInput) lngInput.value = choice.lng; - removeNearbyPicker(btn); - }); - } else { - alert("Aucun résultat trouvé pour : " + q); - } - } catch (err) { - console.error(err); - alert("Erreur lors du géocodage."); - } finally { - btn.disabled = false; - btn.textContent = originalText; - } - }); - }); - - function renderGeocodePicker(anchorBtn, results, onPick) { - removeNearbyPicker(anchorBtn); - - const wrapper = document.createElement("div"); - wrapper.className = "hol-geocode-picker"; - - // Header - const header = document.createElement("div"); - header.className = "hol-geocode-picker__header"; - header.innerHTML = `Choix multiples`; - header - .querySelector(".hol-close") - .addEventListener("click", () => removeNearbyPicker(anchorBtn)); - wrapper.appendChild(header); - - // Liste - const list = document.createElement("ul"); - list.className = "hol-geocode-picker__list"; - - results.forEach((r) => { - const li = document.createElement("li"); - li.className = "hol-geocode-picker__item"; - li.innerHTML = ` -
Cette idée de vacances n'existe pas ou a été supprimée.
- -