diff --git a/family-calendar.php b/family-calendar.php
index ea6554d..c791353 100644
--- a/family-calendar.php
+++ b/family-calendar.php
@@ -196,5 +196,28 @@ require __DIR__ . '/header.php';
+
\ No newline at end of file
diff --git a/includes/lang/ca.php b/includes/lang/ca.php
index 0932224..04210d6 100644
--- a/includes/lang/ca.php
+++ b/includes/lang/ca.php
@@ -206,6 +206,20 @@ return [
'hdl_js_missing_dates_title'=> 'Dates mancants',
'hdl_js_missing_dates_msg' => 'Cal definir una data d’arribada i sortida per organitzar la planificació.',
'hdl_to_place' => 'Per situar',
+ 'hdl_from' => 'Des del',
+ 'hdl_to' => 'Fins al',
+ 'hdl_btn_add_step' => 'Afegir una etapa',
+ 'hdl_quick_edit_title' => 'Modificació ràpida',
+ 'hdl_paid' => 'Pagat',
+ 'weather_forecast' => 'Previsions meteorològiques',
+ 'weather_not_available' => 'Temps no disponible',
+ 'temp_max' => 'Temp. Màxima',
+ 'weather_loading' => 'Carregant el temps...',
+ 'weather_error' => 'Temps no disponible',
+ 'weather_sunny' => 'Assolellat',
+ 'weather_cloudy' => 'Ennuvolat',
+ 'weather_rainy' => 'Plujós',
+ 'weather_snowy' => 'Nevat',
// Anciennes clés Holidays non préfixées (conservades)
'outbound_trip' => 'Anada',
@@ -373,6 +387,9 @@ return [
'bud_reg_none' => 'Cap',
'bud_recap_confirm_delete' => 'Segur que vols suprimir aquest element?',
'bud_err_delete' => 'Error en suprimir.',
+ 'bud_sav_modal_title_add' => 'Introduir un mes',
+ 'bud_sav_ph_name' => 'Nom (ex: Vacances)',
+ 'bud_prev_label_name' => 'Nom',
// --- BUDGET : EPARGNE ---
'bud_sav_add_month' => 'Introduir un mes',
diff --git a/includes/lang/fr.php b/includes/lang/fr.php
index 1937c87..416ed2b 100644
--- a/includes/lang/fr.php
+++ b/includes/lang/fr.php
@@ -206,6 +206,20 @@ return [
'hdl_js_missing_dates_title'=> 'Dates manquantes',
'hdl_js_missing_dates_msg' => 'Veuillez définir une date d’arrivée et de départ pour organiser le planning.',
'hdl_to_place' => 'À placer',
+ 'hdl_from' => 'Du',
+ 'hdl_to' => 'Au',
+ 'hdl_btn_add_step' => 'Ajouter une étape',
+ 'hdl_quick_edit_title' => 'Modification rapide',
+ 'hdl_paid' => 'Payé',
+ 'weather_forecast' => 'Prévisions météo',
+ 'weather_not_available' => 'Météo indisponible',
+ 'temp_max' => 'Temp. Max',
+ 'weather_loading' => 'Chargement météo...',
+ 'weather_error' => 'Météo indisponible',
+ 'weather_sunny' => 'Ensoleillé',
+ 'weather_cloudy' => 'Nuageux',
+ 'weather_rainy' => 'Pluvieux',
+ 'weather_snowy' => 'Neigeux',
// Anciennes clés Holidays non préfixées (conservées pour compatibilité si utilisées)
'outbound_trip' => 'Aller',
@@ -438,6 +452,9 @@ return [
'bud_prev_err_no_history' => 'Impossible : pas d\'historique disponible pour copier.',
'bud_prev_confirm_copy' => "Voulez-vous copier les données de %s vers %t ?\n\n⚠️ Cela écrasera toutes les valeurs déjà présentes pour %t.",
'bud_prev_confirm_transfers' => "Confirmer que %p a bien effectué tous ses virements pour %m ?\n\nCela mettra à jour l'Épargne automatiquement.",
+ 'bud_sav_modal_title_add' => 'Saisir un mois',
+ 'bud_sav_ph_name' => 'Nom (ex: Vacances)',
+ 'bud_prev_label_name' => 'Nom',
// ==========================================
// CADEAUX (GIFTS)
diff --git a/modules/budget/views/budget_prev.php b/modules/budget/views/budget_prev.php
index 4f5b3a6..6ab128d 100644
--- a/modules/budget/views/budget_prev.php
+++ b/modules/budget/views/budget_prev.php
@@ -571,19 +571,22 @@ body.sum-mode-active .sum-target {
-
+// --- 1. SÉCURISATION TRADUCTIONS ET LANGUE ---
+window.appLang = document.documentElement.lang === "ca" ? "ca-ES" : "fr-FR";
+window.I18N = {
+ ...(window.I18N || {}),
+ 'bud_sav_modal_title_add': "= tr('bud_sav_modal_title_add') ?>",
+ 'bud_sav_modal_title_edit': "= tr('bud_sav_modal_title_edit') ?>",
+ 'bud_sav_ph_name': "= tr('bud_sav_ph_name') ?>",
+ 'bud_sav_confirm_delete_month': "= tr('bud_sav_confirm_delete_month') ?>",
+ 'bud_sav_prompt_duplicate': "= tr('bud_sav_prompt_duplicate') ?>",
+ 'bud_err_tech': "= tr('bud_err_tech') ?>",
+ 'bud_err_server': "= tr('bud_err_server') ?>",
+ 'bud_err_network_dup': "= tr('bud_err_network_dup') ?>",
+ 'bud_sav_saving': "= tr('bud_sav_saving') ?>",
+ 'bud_err_delete': "= tr('bud_err_delete') ?>"
+};
-
\ No newline at end of file
diff --git a/modules/family-calendar/family-calendar.css b/modules/family-calendar/family-calendar.css
index 047e153..81067ef 100644
--- a/modules/family-calendar/family-calendar.css
+++ b/modules/family-calendar/family-calendar.css
@@ -168,7 +168,6 @@
min-width: 1000px;
border-collapse: separate;
border-spacing: 0;
- table-layout: fixed;
}
/* Sticky Header */
@@ -303,12 +302,25 @@
width: auto !important;
border-right: 2px solid var(--border-color) !important;
}
-.col-total,
-.col-alex-sub,
-.col-laia-sub {
+th.col-total,
+td.col-total,
+th.col-alex-sub,
+td.col-alex-sub,
+th.col-laia-sub,
+td.col-laia-sub {
width: 32px !important;
min-width: 32px !important;
max-width: 32px !important;
+ box-sizing: border-box;
+ padding: 0 2px !important; /* On réduit le padding pour que les chiffres respirent */
+ overflow: hidden;
+}
+
+th.col-day,
+td.col-day {
+ width: auto !important;
+ min-width: 35px !important;
+ border-right: 2px solid var(--border-color) !important;
}
.rotated-text span {
diff --git a/modules/family-calendar/family-calendar.js b/modules/family-calendar/family-calendar.js
index 79acda8..0514536 100644
--- a/modules/family-calendar/family-calendar.js
+++ b/modules/family-calendar/family-calendar.js
@@ -944,10 +944,10 @@ document.addEventListener("DOMContentLoaded", () => {
});
div.innerHTML = `
-
Off Carole${parseFloat(stats.off.toFixed(1))} jours
- Extra Off Carole${parseFloat(stats.extra.toFixed(1))} jours
- Pep Malade${parseFloat(stats.sick.toFixed(1))} jours
- Présence Pep${parseFloat(stats.pep.toFixed(1))} jours
+ ${tr("leg_off_carole")}${parseFloat(stats.off.toFixed(1))} ${tr("fc_unit_days")}
+ ${tr("leg_extra_off")}${parseFloat(stats.extra.toFixed(1))} ${tr("fc_unit_days")}
+ ${tr("leg_pep_sick")}${parseFloat(stats.sick.toFixed(1))} ${tr("fc_unit_days")}
+ ${tr("leg_presence")} Pep${parseFloat(stats.pep.toFixed(1))} ${tr("fc_unit_days")}
`;
}
diff --git a/modules/holidays/holidays.css b/modules/holidays/holidays.css
index 94c4751..10125af 100644
--- a/modules/holidays/holidays.css
+++ b/modules/holidays/holidays.css
@@ -1196,3 +1196,26 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover {
box-shadow: 0 0 0 3px var(--primary);
transform: scale(1.02);
}
+
+/* Style du badge météo injecté par le JS */
+.hol-weather-info {
+ display: inline-flex;
+}
+
+.pf-weather-badge {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ background: #f8fafc; /* Slate 50 */
+ border: 1px solid #e2e8f0; /* Slate 200 */
+ padding: 1px 6px;
+ border-radius: 6px;
+ color: #475569; /* Slate 600 */
+ font-weight: 600;
+ font-size: 0.7rem;
+ line-height: 1;
+}
+
+.pf-weather-icon {
+ font-size: 0.9rem;
+}
diff --git a/modules/holidays/holidays.js b/modules/holidays/holidays.js
index f3aed12..99a6abf 100644
--- a/modules/holidays/holidays.js
+++ b/modules/holidays/holidays.js
@@ -5,8 +5,60 @@ function tr(key) {
return window.I18N && window.I18N[key] ? window.I18N[key] : key;
}
-// On détecte la langue de la page (définie dans la balise du header)
-const currentLang = document.documentElement.lang === "ca" ? "ca-ES" : "fr-FR";
+// On utilise 'var' au lieu de 'const/let' pour éviter les crashs si le fichier est lu 2 fois
+var currentLang = document.documentElement.lang === "ca" ? "ca-ES" : "fr-FR";
+var selectedItemIdForMove = null; // Déplacé ici pour plus de clarté
+
+// ============================================================================
+// UTILITAIRES MÉTÉO
+// ============================================================================
+function getWeatherInfo(code) {
+ // Transformation en conditions pour regrouper les codes WMO
+ if (code === 0) return { icon: "☀️", label: tr("weather_sunny") };
+ if ([1, 2].includes(code)) return { icon: "🌤️", label: tr("weather_sunny") };
+ if ([3, 45, 48].includes(code))
+ return { icon: "☁️", label: tr("weather_cloudy") };
+ // Les codes 51 à 67 et 80 à 82 couvrent toutes les formes de pluie et bruine
+ if ([51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82].includes(code))
+ return { icon: "🌧️", label: tr("weather_rainy") };
+ // Les codes neigeux
+ if ([71, 73, 75, 77, 85, 86].includes(code))
+ return { icon: "❄️", label: tr("weather_snowy") };
+ // Orages
+ if ([95, 96, 99].includes(code))
+ return { icon: "⛈️", label: tr("weather_rainy") };
+
+ return { icon: "🌡️", label: tr("weather_forecast") };
+}
+
+async function loadWeatherForStep(pt) {
+ if (!pt.step_start_date || !pt.lat || !pt.lng) return;
+
+ const container = document.querySelector(
+ `#step-card-${pt.sort_order} .hol-weather-info`,
+ );
+ if (!container) return;
+
+ try {
+ const resp = await fetch(
+ `/modules/holidays/includes/api/get_weather.php?lat=${pt.lat}&lng=${pt.lng}&date=${pt.step_start_date}`,
+ );
+ const res = await resp.json();
+
+ console.log(`Météo pour ${pt.location_name} :`, res);
+
+ if (res.success) {
+ const info = getWeatherInfo(res.data.code);
+ container.innerHTML = `
+
+ ${info.icon}
+ ${Math.round(res.data.temp_min)}° / ${Math.round(res.data.temp_max)}°C
+
`;
+ }
+ } catch (e) {
+ console.error("Weather error", e);
+ }
+}
// ============================================================================
// FERMETURE UNIVERSELLE DES MODALES
@@ -150,7 +202,7 @@ function deleteHoliday() {
// --- 3. GESTION DE LA CARTE ---
-let map = null;
+var map = null;
function toggleMap() {
const modal = document.getElementById("hol-map-modal");
@@ -197,7 +249,7 @@ function initMap() {
// 4. GESTION DE LA CARTE DÉTAILLÉE (ROADTRIP) ET GÉOCODAGE
// ============================================================================
-let detailMap = null;
+var detailMap = null;
document.addEventListener("DOMContentLoaded", () => {
if (document.getElementById("tripMap")) {
@@ -208,6 +260,11 @@ document.addEventListener("DOMContentLoaded", () => {
function initDetailMap() {
if (typeof L === "undefined" || typeof MAP_POINTS === "undefined") return;
+ const mapContainer = L.DomUtil.get("tripMap");
+ if (mapContainer !== null && mapContainer._leaflet_id) {
+ mapContainer._leaflet_id = null;
+ }
+
detailMap = L.map("tripMap");
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
@@ -323,6 +380,11 @@ function initDetailMap() {
}
});
});
+ detailMap.fitBounds(bounds, { padding: [50, 50] });
+ // Lancement de la météo pour chaque étape
+ if (typeof MAP_POINTS !== "undefined") {
+ MAP_POINTS.forEach((pt) => loadWeatherForStep(pt));
+ }
}
function drawFallbackLine(coords, color, weight) {
@@ -582,7 +644,6 @@ document.addEventListener("DOMContentLoaded", () => {
// ============================================================================
// MOTEUR DRAG & DROP DU PLANNING
// ============================================================================
-let selectedItemIdForMove = null;
function closePlanningModal() {
document.getElementById("planningModal").style.display = "none";
diff --git a/modules/holidays/includes/api/get_weather.php b/modules/holidays/includes/api/get_weather.php
new file mode 100644
index 0000000..c6914b8
--- /dev/null
+++ b/modules/holidays/includes/api/get_weather.php
@@ -0,0 +1,63 @@
+ false, 'message' => 'Paramètres manquants']);
+ exit;
+}
+
+$date = substr($date, 0, 10);
+$today = new DateTime();
+$targetDate = new DateTime($date);
+$interval = $today->diff($targetDate);
+$daysDiff = (int)$interval->format('%R%a');
+
+// 4. Choix du bon modèle météo (Prévisions vs Historique)
+if ($daysDiff < -2) {
+ $baseUrl = "https://archive-api.open-meteo.com/v1/archive";
+} else {
+ $baseUrl = "https://api.open-meteo.com/v1/forecast";
+}
+
+$url = "$baseUrl?latitude=$lat&longitude=$lng&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=auto&start_date=$date&end_date=$date";
+
+// 5. Interrogation d'Open-Meteo
+$res = @file_get_contents($url);
+
+if (!$res) {
+ echo json_encode(['success' => false, 'message' => 'Erreur API Open-Meteo']);
+ exit;
+}
+
+$data = json_decode($res, true);
+
+// 6. Formatage et envoi au Javascript
+if (isset($data['daily']['weather_code'][0])) {
+ echo json_encode([
+ 'success' => true,
+ 'data' => [
+ 'code' => $data['daily']['weather_code'][0],
+ 'temp_max' => $data['daily']['temperature_2m_max'][0],
+ 'temp_min' => $data['daily']['temperature_2m_min'][0]
+ ]
+ ]);
+} else {
+ echo json_encode(['success' => false, 'message' => 'Aucune donnée pour cette date']);
+}
\ No newline at end of file
diff --git a/modules/holidays/views/detail.php b/modules/holidays/views/detail.php
index cd401f8..510527b 100644
--- a/modules/holidays/views/detail.php
+++ b/modules/holidays/views/detail.php
@@ -204,8 +204,11 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
🏁 = tr('hdl_return') ?>
-
- = tr('hdl_from') ?> = date('d/m', strtotime($step['step_start_date'])) ?> = tr('hdl_to') ?> = date('d/m', strtotime($step['step_end_date'])) ?>
+
+
+ = tr('hdl_from') ?> = date('d/m', strtotime($step['step_start_date'])) ?> = tr('hdl_to') ?> = date('d/m', strtotime($step['step_end_date'])) ?>
+
+
@@ -352,19 +355,52 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
+
+
\ No newline at end of file
+ // --- NOUVELLES CLÉS MÉTÉO ICI ---
+ 'weather_sunny': "= tr('weather_sunny') ?>",
+ 'weather_cloudy': "= tr('weather_cloudy') ?>",
+ 'weather_rainy': "= tr('weather_rainy') ?>",
+ 'weather_snowy': "= tr('weather_snowy') ?>",
+ 'weather_forecast': "= tr('weather_forecast') ?>"
+};
+
+// Fallback de sécurité pour s'assurer que les modales peuvent toujours se fermer
+window.closeCheckpointModal = window.closeCheckpointModal || function() {
+ const modal = document.getElementById('checkpointModal');
+ if(modal) modal.style.display = 'none';
+ document.body.classList.remove('no-scroll');
+};
+
+window.closePlanningModal = window.closePlanningModal || function() {
+ const modal = document.getElementById('planningModal');
+ if(modal) modal.style.display = 'none';
+ document.body.classList.remove('no-scroll');
+};
+
+
+
\ No newline at end of file
diff --git a/modules/holidays/views/list.php b/modules/holidays/views/list.php
index ef28719..9f317b7 100644
--- a/modules/holidays/views/list.php
+++ b/modules/holidays/views/list.php
@@ -90,6 +90,37 @@ foreach ($active as $h) {
+
+
+
+
prepare("SELECT * FROM pf_holidays_items WHERE holiday_id = ?");
@@ -119,7 +150,6 @@ function renderHolidayCard($h, $pdo) {
$pctPaid = $cost > 0 ? min(100, ($paid / $cost) * 100) : 0;
$pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
- // Traduction dynamique du statut
$statusLabel = tr('hdl_status_' . $h['status']);
echo "