diff --git a/modules/holidays/holidays.css b/modules/holidays/holidays.css index a3f4cd2..215f034 100644 --- a/modules/holidays/holidays.css +++ b/modules/holidays/holidays.css @@ -716,3 +716,226 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover { text-align: left !important; /* On annule l'alignement à droite du total */ } } + +/* ========================================================================== + 13. BARRE DE PROGRESSION & FINANCES + ========================================================================== */ +.hol-progress-bar { + width: 100%; + height: 12px; + background: #e2e8f0; + border-radius: 6px; + margin-bottom: 10px; + display: flex; + overflow: hidden; +} +.hol-progress-paid { + background: #10b981; +} +.hol-progress-saved { + background: #3b82f6; +} + +.hol-progress-labels { + display: flex; + justify-content: space-between; + font-size: 0.85rem; +} +.hol-label-paid { + color: #10b981; + font-weight: 600; +} +.hol-label-saved { + color: #3b82f6; + font-weight: 600; +} +.hol-label-left { + color: #ef4444; + font-weight: 700; +} + +/* ========================================================================== + 14. LÉGENDE DE LA CARTE + ========================================================================== */ +.hol-panel-header-group { + display: flex; + align-items: center; + gap: 15px; + flex-wrap: wrap; +} +.hol-map-legend { + display: flex; + gap: 12px; + font-size: 0.75rem; + color: #64748b; + background: white; + padding: 4px 10px; + border-radius: 12px; + border: 1px solid #e2e8f0; +} +.hol-legend-item { + display: flex; + align-items: center; + gap: 4px; +} +.hol-legend-color { + display: inline-block; + width: 12px; + height: 4px; + border-radius: 2px; +} +.hol-legend-color.aller { + background: #3b82f6; +} +.hol-legend-color.interm { + background: #8b5cf6; +} +.hol-legend-color.retour { + background: transparent; + height: 0; + border-bottom: 3px dashed #f97316; + border-radius: 0; +} + +/* ========================================================================== + 15. LIGNES DE DÉPENSES (Timeline & Modale JS) + ========================================================================== */ +.hol-expense-wrapper { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + margin-bottom: 8px; + border-bottom: 1px dashed #f1f5f9; + padding-bottom: 8px; +} +.hol-expense-wrapper:last-child { + margin-bottom: 0; + border-bottom: none; + padding-bottom: 0; +} + +.hol-expense-main { + display: flex; + justify-content: space-between; + width: 100%; + font-size: 0.85rem; +} +.hol-expense-name { + color: #475569; + font-weight: 500; +} +.hol-expense-amount { + color: var(--text-main); + font-weight: bold; +} +.status-paid { + color: #10b981; + margin-left: 5px; +} +.status-pending { + color: #f59e0b; + margin-left: 5px; +} + +.hol-expense-note { + font-size: 0.75rem; + color: #94a3b8; + padding-left: 24px; + word-break: break-all; +} +.hol-expense-link { + color: #3b82f6; + text-decoration: none; +} +.hol-expense-link:hover { + text-decoration: underline; +} + +.hol-empty-step { + font-size: 0.8rem; + color: var(--text-muted); + font-style: italic; + padding: 5px 0; +} + +/* Boutons de petite taille */ +.pf-btn-small { + padding: 6px 14px !important; + font-size: 0.85rem !important; + width: auto !important; + height: auto !important; + margin: 0 !important; +} + +/* Modale JS d'ajout de dépense */ +.hol-form-row { + display: flex; + flex-direction: column; + gap: 6px; + padding: 10px; + background: #f8fafc; + border-radius: 8px; + border: 1px solid #e2e8f0; +} +.hol-form-inner { + display: flex; + gap: 8px; + align-items: center; + width: 100%; +} +.hol-form-select { + width: 50px; + padding: 8px 4px; + font-size: 1.2rem; + cursor: pointer; +} +.hol-form-text { + flex: 2; + padding: 8px; + font-size: 0.9rem; +} +.hol-form-number { + width: 80px; + text-align: right; + padding: 8px; + font-size: 0.9rem; +} +.hol-form-paid-label { + display: flex; + align-items: center; + cursor: pointer; + width: 55px; +} +.hol-form-paid-text { + font-size: 0.75rem; + margin-left: 4px; + font-weight: bold; + color: #64748b; +} +.hol-form-notes-input { + font-size: 0.8rem; + padding: 6px 8px; + border-style: dashed; + color: #475569; + width: calc(100% - 58px); + margin-left: 58px; +} +.btn-remove-expense { + width: 28px; + height: 28px; + border: none; + background: #fee2e2; + color: #ef4444; + border-radius: 6px; + cursor: pointer; + font-weight: bold; + display: flex; + justify-content: center; + align-items: center; + transition: 0.2s; +} +.btn-remove-expense:hover { + background: #fca5a5; + color: #b91c1c; +} diff --git a/modules/holidays/holidays.js b/modules/holidays/holidays.js index c75b933..6a62c18 100644 --- a/modules/holidays/holidays.js +++ b/modules/holidays/holidays.js @@ -200,13 +200,12 @@ function initDetailMap() { detailMap = L.map("tripMap"); - // Fond de carte propre (CartoDB Voyager) - L.tileLayer( - "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png", - { - attribution: "© OpenStreetMap", - }, - ).addTo(detailMap); + // Carte standard : Affiche toujours la langue locale exacte (Français en France, Catalan en Catalogne) + L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { + maxZoom: 19, + attribution: + '© OpenStreetMap contributors', + }).addTo(detailMap); if (MAP_POINTS.length === 0) { detailMap.setView([46.6, 2.4], 5); // Centré sur la France par défaut si vide @@ -263,12 +262,87 @@ function initDetailMap() { }); }); - // On dessine le trait en pointillés pour relier le roadtrip ! + // On dessine le vrai trajet par la route avec gestion des superpositions if (latlngs.length > 1) { - L.polyline(latlngs, { - color: "#3b82f6", - weight: 3, - dashArray: "8, 8", + const routePromises = []; + + // 1. On lance toutes les requêtes de calcul de route en même temps + for (let i = 0; i < latlngs.length - 1; i++) { + const startPt = MAP_POINTS[i]; + const endPt = MAP_POINTS[i + 1]; + const coordsString = `${startPt.lng},${startPt.lat};${endPt.lng},${endPt.lat}`; + + const promise = fetch( + `https://router.project-osrm.org/route/v1/driving/${coordsString}?overview=full&geometries=geojson`, + ) + .then((response) => response.json()) + .then((data) => ({ + index: i, + data: data, + coords: [latlngs[i], latlngs[i + 1]], + })) + .catch((err) => ({ + index: i, + error: true, + coords: [latlngs[i], latlngs[i + 1]], + })); + + routePromises.push(promise); + } + + // 2. On attend que TOUTES les routes soient calculées pour les dessiner dans le bon ordre + Promise.all(routePromises).then((results) => { + // On s'assure de dessiner l'Étape 1 d'abord, puis la 2, etc. + results.sort((a, b) => a.index - b.index); + + results.forEach((res) => { + const i = res.index; + + // Styles par défaut (Aller / Étape 1) + let routeColor = "#3b82f6"; // Bleu + let routeWeight = 6; // Épais + let routeDash = null; // Ligne continue + + // Modification des styles selon le segment + if (i === latlngs.length - 2) { + // DERNIER SEGMENT (Retour) + routeColor = "#f97316"; // Orange vif (Tranche parfaitement avec le bleu) + routeWeight = 3; // Plus fin pour se superposer sans cacher + routeDash = "10, 10"; // En pointillés pour laisser voir le bleu en dessous + } else if (i > 0) { + // SEGMENTS INTERMÉDIAIRES + routeColor = "#8b5cf6"; // Violet + routeWeight = 5; + } + + // Dessin du trait + if (res.data && res.data.code === "Ok" && res.data.routes.length > 0) { + const routeCoords = res.data.routes[0].geometry.coordinates.map( + (c) => [c[1], c[0]], + ); + + L.polyline(routeCoords, { + color: routeColor, + weight: routeWeight, + dashArray: routeDash, + opacity: 0.9, + lineCap: "round", + lineJoin: "round", + }).addTo(detailMap); + } else { + // Plan B si pas de route carrossable (ex: avion/bateau) + drawFallbackLine(res.coords, routeColor, routeWeight); + } + }); + }); + } + + // Plan B : La ligne droite + function drawFallbackLine(coords, color, weight) { + L.polyline(coords, { + color: color, + weight: weight || 3, + dashArray: "8, 8", // Toujours en pointillés pour le plan B opacity: 0.7, }).addTo(detailMap); } @@ -395,35 +469,29 @@ function addCpExpenseLine( ) { const container = document.getElementById("cpExpensesContainer"); const div = document.createElement("div"); - div.style.display = "flex"; - div.style.flexDirection = "column"; - div.style.gap = "6px"; - div.style.padding = "10px"; - div.style.background = "#f8fafc"; - div.style.borderRadius = "8px"; - div.style.border = "1px solid #e2e8f0"; + div.className = "hol-form-row"; // Classe globale de la ligne const isChecked = isPaid == 1 ? "checked" : ""; div.innerHTML = ` -