fix holidfays
Deploy HouseHub / deploy (push) Successful in 2s

This commit is contained in:
2026-07-31 13:14:50 +02:00
parent 685c649e7c
commit 536f479423
3 changed files with 828 additions and 513 deletions
+65 -23
View File
@@ -3,7 +3,6 @@
/* ========================================================================== /* ==========================================================================
1. VARIABLES & BASE 1. VARIABLES & BASE
========================================================================== */ ========================================================================== */
body.no-scroll { body.no-scroll {
overflow: hidden !important; overflow: hidden !important;
} }
@@ -224,7 +223,6 @@ body.no-scroll {
.hol-hero-overlay { .hol-hero-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 1; z-index: 1;
} }
@@ -279,7 +277,6 @@ body.no-scroll {
gap: 6px; gap: 6px;
} }
/* --- PILLS (Metrics Route & Logistique) --- */
.hol-hero-pills { .hol-hero-pills {
display: flex; display: flex;
gap: 10px; gap: 10px;
@@ -320,7 +317,6 @@ body.no-scroll {
opacity: 1; opacity: 1;
} }
/* --- FINANCES GLOBALES --- */
.hol-hero-finances { .hol-hero-finances {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -366,7 +362,6 @@ body.no-scroll {
.hol-label-paid-text { .hol-label-paid-text {
color: #a7f3d0; color: #a7f3d0;
} }
.hol-label-saved-text { .hol-label-saved-text {
color: #bfdbfe; color: #bfdbfe;
} }
@@ -436,22 +431,18 @@ body.no-scroll {
align-items: center; align-items: center;
gap: 4px; gap: 4px;
} }
.hol-legend-color { .hol-legend-color {
display: inline-block; display: inline-block;
width: 12px; width: 12px;
height: 4px; height: 4px;
border-radius: 2px; border-radius: 2px;
} }
.hol-legend-color.aller { .hol-legend-color.aller {
background: #3b82f6; background: #3b82f6;
} }
.hol-legend-color.interm { .hol-legend-color.interm {
background: #8b5cf6; background: #8b5cf6;
} }
.hol-legend-color.retour { .hol-legend-color.retour {
background: transparent; background: transparent;
height: 0; height: 0;
@@ -544,12 +535,10 @@ body.no-scroll {
background: #ecfdf5; background: #ecfdf5;
color: #059669; color: #059669;
} }
.hol-step-icon-destination { .hol-step-icon-destination {
background: #fef2f2; background: #fef2f2;
color: #e11d48; color: #e11d48;
} }
.hol-step-icon-stop { .hol-step-icon-stop {
background: transparent; background: transparent;
color: #64748b; color: #64748b;
@@ -698,7 +687,6 @@ body.no-scroll {
color: #10b981; color: #10b981;
margin-left: 5px; margin-left: 5px;
} }
.status-pending { .status-pending {
color: #f59e0b; color: #f59e0b;
margin-left: 5px; margin-left: 5px;
@@ -820,7 +808,7 @@ body.no-scroll {
} }
/* ========================================================================== /* ==========================================================================
9. MODALES & DIALOGUES 9. MODALES & DIALOGUES (Sécurisation complète)
========================================================================== */ ========================================================================== */
.pf-modal { .pf-modal {
display: none; display: none;
@@ -852,7 +840,6 @@ body.no-scroll {
margin: auto; margin: auto;
} }
/* --- Modale Étape (Checkpoint) --- */
.hol-modal-cp { .hol-modal-cp {
max-width: 600px; max-width: 600px;
padding: 0; padding: 0;
@@ -1014,6 +1001,15 @@ body.no-scroll {
display: none; display: none;
} }
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--border-light);
}
/* --- Modale Planning Global --- */ /* --- Modale Planning Global --- */
.hol-modal-planning { .hol-modal-planning {
max-width: 95vw; max-width: 95vw;
@@ -1107,15 +1103,6 @@ body.no-scroll {
gap: 8px; gap: 8px;
} }
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--border-light);
}
/* ========================================================================== /* ==========================================================================
10. PLANNING DRAG & DROP 10. PLANNING DRAG & DROP
========================================================================== */ ========================================================================== */
@@ -1511,3 +1498,58 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover {
display: none !important; display: none !important;
} }
} }
/* Modale JS d'ajout de dépense */
.hol-form-row {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
background: var(--bg-page);
border-radius: 8px;
border: 1px solid var(--border-light);
margin-bottom: 10px;
}
.hol-form-inner {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
width: 100%;
}
.hol-form-select {
flex: 0 0 50px;
padding: 8px 4px;
font-size: 1.1rem;
cursor: pointer;
margin: 0 !important;
}
.hol-form-text {
flex: 1;
min-width: 140px;
padding: 8px;
font-size: 0.9rem;
margin: 0 !important;
}
.hol-form-number {
flex: 0 0 90px;
text-align: right;
padding: 8px;
font-size: 0.9rem;
font-weight: bold;
color: var(--primary);
margin: 0 !important;
}
.hol-form-paid-label {
display: flex;
align-items: center;
cursor: pointer;
flex: 0 0 60px;
margin: 0 !important;
}
.hol-form-paid-text {
font-size: 0.8rem;
margin-left: 6px;
font-weight: 600;
color: var(--text-muted);
}
File diff suppressed because it is too large Load Diff
+339 -131
View File
@@ -331,99 +331,102 @@ $bgImage = !empty($holiday['image_url']) ? htmlspecialchars($holiday['image_url'
<div id="checkpointModal" class="pf-modal"> <div id="checkpointModal" class="pf-modal">
<div class="pf-modal-content hol-modal-cp"> <div class="pf-modal-content" style="max-width: 600px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
<!-- HEADER MODALE --> <h3 id="cpModalTitle" style="margin:0;">📍 <?= tr('hdl_btn_add_step') ?></h3>
<div class="hol-modal-header">
<h3 id="cpModalTitle" class="hol-modal-title">📍 Ajouter une étape</h3>
<button type="button" onclick="closeCheckpointModal()" class="pf-modal-close">&times;</button> <button type="button" onclick="closeCheckpointModal()" class="pf-modal-close">&times;</button>
</div> </div>
<!-- SYSTÈME D'ONGLETS -->
<div class="hol-modal-tabs">
<button type="button" id="tabBtnInfo" class="hol-modal-tab-btn active" onclick="switchCpTab('info')">📍 Infos de l'étape</button>
<button type="button" id="tabBtnProg" class="hol-modal-tab-btn" onclick="switchCpTab('prog')">🎟️ Programme & Frais</button>
</div>
<!-- DÉBUT DU FORMULAIRE UNIQUE --> <!-- LES ONGLETS SONT ICI -->
<form action="/modules/holidays/includes/api/save_checkpoint.php" method="POST" id="formCheckpoint" class="hol-modal-form"> <div style="display: flex; gap: 15px; border-bottom: 1px solid var(--border-light); margin-bottom: 20px;">
<button type="button" id="tabBtnInfo" onclick="switchCpTab('info')" style="background: none; border: none; font-weight: bold; font-size: 1rem; color: var(--primary); border-bottom: 2px solid var(--primary); padding-bottom: 8px; cursor: pointer; flex: 1;">📍 Infos de l'étape</button>
<button type="button" id="tabBtnProg" onclick="switchCpTab('prog')" style="background: none; border: none; font-weight: bold; font-size: 1rem; color: var(--text-muted); border-bottom: 2px solid transparent; padding-bottom: 8px; cursor: pointer; flex: 1;">🎟️ 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>
<!-- FORMULAIRE (Le contenu apparaîtra ici) -->
<form action="/modules/holidays/includes/api/save_checkpoint.php" method="POST" id="formCheckpoint" style="display:none; border-top:1px solid #e2e8f0; padding-top:20px;">
<input type="hidden" name="holiday_id" value="<?= $id ?>"> <input type="hidden" name="holiday_id" value="<?= $id ?>">
<input type="hidden" name="old_location_name" id="cp_old_name"> <input type="hidden" name="old_location_name" id="cp_old_name">
<input type="hidden" name="lat" id="cp_lat"> <input type="hidden" name="lat" id="cp_lat">
<input type="hidden" name="lng" id="cp_lng"> <input type="hidden" name="lng" id="cp_lng">
<input type="hidden" name="old_sort_order" id="cp_old_sort_order"> <input type="hidden" name="old_sort_order" id="cp_old_sort_order">
<!-- ONGLET 1 : INFOS GÉNÉRIQUES --> <!-- CONTENU DE L'ONGLET INFO -->
<div id="cpTabInfo" class="hol-tab-content"> <div id="cpTabInfo">
<div class="form-group" style="margin-bottom:15px;">
<div id="cpSearchBlock" class="hol-search-block"> <label class="pf-label"><?= tr('hdl_label_step_name') ?></label>
<label class="pf-label hol-search-label">🔍 Chercher un lieu (Autocomplétion)</label> <input type="text" name="location_name" id="cp_name" class="pf-input" style="font-weight:bold; color:var(--primary);" required>
<div class="hol-search-input-group">
<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 hol-search-btn" onclick="searchPlace()">Go</button>
</div>
<div id="searchResults" class="hol-search-results"></div>
</div> </div>
<div class="form-group"> <div class="form-group" style="margin-bottom:15px; background:#fff7ed; padding:10px; border-radius:8px; border:1px solid #ffedd5;">
<label class="pf-label">Nom de l'étape</label> <label class="pf-label" style="color:#ea580c;">📍 Type d'étape</label>
<input type="text" name="location_name" id="cp_name" class="pf-input hol-input-highlight" required> <select name="step_type" id="cp_step_type" class="pf-input" onchange="toggleStepDates(this.value)">
</div> <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>
<!-- DATES COMPACTES AVEC FLATPICKR --> <option value="destination">ARRIVÉE FINALE (Fin du voyage)</option>
<div class="form-group hol-date-range-block">
<label class="pf-label" id="lbl_date_range">📅 Période de l'étape (Arrivée ➔ Départ)</label>
<input type="text" id="cp_date_range" class="pf-input hol-date-picker-input" placeholder="Sélectionnez les dates..." readonly>
<!-- 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">
<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> </select>
</div> </div>
<div class="hol-form-row"> <div style="display:flex; gap:15px; margin-bottom:15px; background:#f8fafc; padding:12px; border-radius:8px;">
<div class="form-group hol-flex-1"> <div class="form-group" id="grp_start_date" style="flex:1;">
<label class="pf-label">📍 Type d'étape</label> <label class="pf-label" id="lbl_start_date"><?= tr('hdl_label_arrival') ?></label>
<select name="step_type" id="cp_step_type" class="pf-input" onchange="toggleStepDates(this.value)"> <input type="date" name="step_start_date" id="cp_start_date" class="pf-input">
<option value="origin">DÉPART</option> </div>
<option value="stop" selected>SÉJOUR</option> <div class="form-group" id="grp_end_date" style="flex:1;">
<option value="destination">ARRIVÉE FINALE</option> <label class="pf-label" id="lbl_end_date"><?= tr('hdl_label_departure') ?></label>
</select> <input type="date" name="step_end_date" id="cp_end_date" class="pf-input">
</div> </div>
</div> </div>
<div class="hol-checkbox-group"> <div style="margin-bottom: 20px; padding-top: 15px; border-top: 1px dashed #e2e8f0;">
<label class="hol-checkbox-label"> <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" class="hol-checkbox-return"> <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 étape de retour (Tracé Orange) 🏁 Définir comme retour
</label>
<label class="hol-checkbox-label">
<input type="checkbox" name="save_favorite" value="1" class="hol-checkbox">
⭐ Sauvegarder dans mes favoris
</label> </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>
</div> </div>
</div> </div>
<!-- ONGLET 2 : PROGRAMME ET DEPENSES --> <!-- CONTENU DE L'ONGLET PROGRAMME & FRAIS -->
<div id="cpTabProg" class="hol-tab-content hol-hidden"> <div id="cpTabProg" class="hol-hidden" style="display: none;">
<div class="hol-prog-header"> <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
<label class="pf-label">🎟️ Liste des activités & frais</label> <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()">+ Ajouter</button> <button type="button" class="pf-btn btn-secondary pf-btn-small" onclick="addCpExpenseLine()"><?= tr('hdl_btn_add_expense') ?></button>
</div> </div>
<div id="cpExpensesContainer" class="hol-expenses-container"></div> <div id="cpExpensesContainer" style="margin-bottom:15px;"></div>
</div> </div>
<!-- FOOTER FIXE --> <div style="margin-bottom: 20px;">
<div class="modal-footer"> <label style="display:flex; align-items:center; cursor:pointer; font-size:0.85rem; color:#475569;">
<button type="button" onclick="deleteCheckpoint()" id="btnDeleteCp" class="pf-btn btn-secondary hol-btn-delete">Supprimer</button> <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>
<button type="button" onclick="closeCheckpointModal()" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></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> </div>
</form> </form>
</div> </div>
@@ -512,6 +515,10 @@ $bgImage = !empty($holiday['image_url']) ? htmlspecialchars($holiday['image_url'
<?php include __DIR__ . '/modal.php'; ?> <?php include __DIR__ . '/modal.php'; ?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<?php include __DIR__ . '/pdf_template.php'; ?>
<script> <script>
// --- 1. SÉCURISATION TRADUCTIONS ET VARIABLES --- // --- 1. SÉCURISATION TRADUCTIONS ET VARIABLES ---
window.MAP_POINTS = <?= json_encode($mapPoints ?? []) ?>; window.MAP_POINTS = <?= json_encode($mapPoints ?? []) ?>;
@@ -519,80 +526,281 @@ $bgImage = !empty($holiday['image_url']) ? htmlspecialchars($holiday['image_url'
window.I18N = { window.I18N = {
...(window.I18N || {}), ...(window.I18N || {}),
'hdl_js_search_loading': "<?= tr('hdl_js_search_loading') ?>", 'hdl_js_search_loading': <?= json_encode(tr('hdl_js_search_loading')) ?>,
'hdl_js_no_result': "<?= tr('hdl_js_no_result') ?>", 'hdl_js_no_result': <?= json_encode(tr('hdl_js_no_result')) ?>,
'hdl_js_confirm_del_trip': "<?= tr('hdl_js_confirm_del_trip') ?>", 'hdl_js_confirm_del_trip': <?= json_encode(tr('hdl_js_confirm_del_trip')) ?>,
'hdl_js_confirm_del_step': "<?= tr('hdl_js_confirm_del_step') ?>", 'hdl_js_confirm_del_step': <?= json_encode(tr('hdl_js_confirm_del_step')) ?>,
'hdl_js_step_label': "<?= tr('hdl_js_step_label') ?>", 'hdl_js_step_label': <?= json_encode(tr('hdl_js_step_label')) ?>,
'hdl_js_ph_expense_name': "<?= tr('hdl_js_ph_expense_name') ?>", 'hdl_js_ph_expense_name': <?= json_encode(tr('hdl_js_ph_expense_name')) ?>,
'hdl_js_delete_line': "<?= tr('btn_delete') ?>", 'hdl_js_delete_line': <?= json_encode(tr('btn_delete')) ?>,
'hdl_planning_title': "<?= tr('hdl_planning_title') ?>", 'hdl_planning_title': <?= json_encode(tr('hdl_planning_title')) ?>,
'hdl_to_place': "<?= tr('hdl_to_place') ?>", 'hdl_to_place': <?= json_encode(tr('hdl_to_place')) ?>,
'hdl_js_missing_dates_title': "<?= tr('hdl_js_missing_dates_title') ?>", 'hdl_js_missing_dates_title': <?= json_encode(tr('hdl_js_missing_dates_title')) ?>,
'hdl_js_missing_dates_msg': "<?= tr('hdl_js_missing_dates_msg') ?>", 'hdl_js_missing_dates_msg': <?= json_encode(tr('hdl_js_missing_dates_msg')) ?>,
'hdl_modal_title': "<?= tr('hdl_modal_title') ?>", 'hdl_modal_title': <?= json_encode(tr('hdl_modal_title')) ?>,
'hdl_js_edit_step': "<?= tr('hdl_js_edit_step') ?>", 'hdl_js_edit_step': <?= json_encode(tr('hdl_js_edit_step')) ?>,
'hdl_ph_notes': "<?= tr('hdl_ph_notes') ?>", 'hdl_ph_notes': <?= json_encode(tr('hdl_ph_notes')) ?>,
'hdl_btn_add_step': "<?= tr('hdl_btn_add_step') ?>", 'hdl_btn_add_step': <?= json_encode(tr('hdl_btn_add_step')) ?>,
'hdl_quick_edit_title': "<?= tr('hdl_quick_edit_title') ?>", 'hdl_quick_edit_title': <?= json_encode(tr('hdl_quick_edit_title')) ?>,
'hdl_paid': "<?= tr('hdl_paid') ?>", 'hdl_paid': <?= json_encode(tr('hdl_paid')) ?>,
'weather_sunny': <?= json_encode(tr('weather_sunny')) ?>,
// --- NOUVELLES CLÉS MÉTÉO ICI --- 'weather_cloudy': <?= json_encode(tr('weather_cloudy')) ?>,
'weather_sunny': "<?= tr('weather_sunny') ?>", 'weather_rainy': <?= json_encode(tr('weather_rainy')) ?>,
'weather_cloudy': "<?= tr('weather_cloudy') ?>", 'weather_snowy': <?= json_encode(tr('weather_snowy')) ?>,
'weather_rainy': "<?= tr('weather_rainy') ?>", 'weather_forecast': <?= json_encode(tr('weather_forecast')) ?>,
'weather_snowy': "<?= tr('weather_snowy') ?>", 'weather_historical': <?= json_encode(tr('weather_historical')) ?>
'weather_forecast': "<?= tr('weather_forecast') ?>",
'weather_historical': "<?= tr('weather_historical') ?>"
}; };
// 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');
};
// 1. On charge le prix depuis le navigateur (ou 1.85 par défaut)
const savedFuelPrice = localStorage.getItem('holidays_fuel_price') || 1.85; const savedFuelPrice = localStorage.getItem('holidays_fuel_price') || 1.85;
window.FUEL_PRICE = parseFloat(savedFuelPrice); window.FUEL_PRICE = parseFloat(savedFuelPrice);
// 2. On met à jour le texte du petit bouton "✏️" en haut de la page
const displayFuelEl = document.getElementById('display_fuel_price'); const displayFuelEl = document.getElementById('display_fuel_price');
if (displayFuelEl) displayFuelEl.innerText = window.FUEL_PRICE.toFixed(2); if (displayFuelEl) displayFuelEl.innerText = window.FUEL_PRICE.toFixed(2);
// 3. Variables voiture et retour
window.VEHICLE_CONSUMPTION = <?= !empty($holiday['vehicle_consumption']) ? (float)$holiday['vehicle_consumption'] : 7 ?>; window.VEHICLE_CONSUMPTION = <?= !empty($holiday['vehicle_consumption']) ? (float)$holiday['vehicle_consumption'] : 7 ?>;
window.GLOBAL_RETURN_STEP_ID = <?= $holiday['return_step_id'] !== null ? $holiday['return_step_id'] : 'null' ?>; window.GLOBAL_RETURN_STEP_ID = <?= $holiday['return_step_id'] !== null ? $holiday['return_step_id'] : 'null' ?>;
// --- GESTION DES ONGLETS LOGISTIQUES --- // --- 2. SURCHARGE SÉCURISÉE DES FONCTIONS JS (APRÈS CHARGEMENT DE HOLIDAYS.JS) ---
window.switchHolTab = function(tabId, btn) { document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.hol-tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.hol-tab-content').forEach(c => c.style.display = 'none'); // 1. Correction du conflit d'onglet
btn.classList.add('active'); window.switchCpTab = function(tabId) {
document.getElementById(tabId).style.display = 'block'; const btnInfo = document.getElementById('tabBtnInfo');
}; const btnProg = document.getElementById('tabBtnProg');
const tabInfo = document.getElementById('cpTabInfo');
const tabProg = document.getElementById('cpTabProg');
// --- BASCULE DE LA CARTE --- if (tabId === 'info') {
window.toggleTripMap = function() { if (btnInfo) { btnInfo.style.borderBottomColor = "var(--primary)"; btnInfo.style.color = "var(--primary)"; }
const mapWrapper = document.getElementById('tripMapWrapper'); if (btnProg) { btnProg.style.borderBottomColor = "transparent"; btnProg.style.color = "var(--text-muted)"; }
if (mapWrapper.classList.contains('hol-hidden')) { if (tabInfo) { tabInfo.style.display = "block"; tabInfo.classList.remove("hol-hidden"); }
mapWrapper.classList.remove('hol-hidden'); if (tabProg) { tabProg.style.display = "none"; tabProg.classList.add("hol-hidden"); }
if (typeof detailMap !== 'undefined' && detailMap !== null) { } else {
setTimeout(() => detailMap.invalidateSize(), 100); if (btnProg) { btnProg.style.borderBottomColor = "var(--primary)"; btnProg.style.color = "var(--primary)"; }
if (btnInfo) { btnInfo.style.borderBottomColor = "transparent"; btnInfo.style.color = "var(--text-muted)"; }
if (tabProg) { tabProg.style.display = "block"; tabProg.classList.remove("hol-hidden"); }
if (tabInfo) { tabInfo.style.display = "none"; tabInfo.classList.add("hol-hidden"); }
} }
} else { };
mapWrapper.classList.add('hol-hidden');
} // 2. Correction modale : ajout de l'injection lat/lng aux items
}; window.openCheckpointModal = function(mode, data = null) {
const searchBlock = document.getElementById("cpSearchBlock");
const formBlock = document.getElementById("formCheckpoint");
const container = document.getElementById("cpExpensesContainer");
const btnDel = document.getElementById("btnDeleteCp");
const modalTitle = document.getElementById("cpModalTitle");
if(container) 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 = "";
window.switchCpTab('info');
if (mode === "add") {
if (modalTitle) modalTitle.innerText = "📍 " + (window.I18N['hdl_btn_add_step'] || "Ajouter une étape");
if (searchBlock) searchBlock.style.display = "block";
if (formBlock) formBlock.style.display = "block";
if (btnDel) btnDel.style.display = "none";
if (document.getElementById("cp_old_sort_order")) document.getElementById("cp_old_sort_order").value = "";
if (document.getElementById("cp_name")) document.getElementById("cp_name").value = "";
if (document.getElementById("cp_step_type")) {
document.getElementById("cp_step_type").value = "stop";
if (typeof toggleStepDates === 'function') toggleStepDates("stop");
}
if (document.getElementById("cp_set_as_return")) document.getElementById("cp_set_as_return").checked = false;
if (typeof window.addCpExpenseLine === 'function') window.addCpExpenseLine();
} else if (mode === "edit" && data) {
if (modalTitle) modalTitle.innerText = "✏️ " + (window.I18N['hdl_js_edit_step'] || "Modifier l'étape");
if (searchBlock) searchBlock.style.display = "none";
if (formBlock) formBlock.style.display = "block";
if (btnDel) btnDel.style.display = "block";
if (document.getElementById("cp_lat")) document.getElementById("cp_lat").value = data.lat;
if (document.getElementById("cp_lng")) document.getElementById("cp_lng").value = data.lng;
if (document.getElementById("cp_old_sort_order")) document.getElementById("cp_old_sort_order").value = data.sort_order;
if (document.getElementById("cp_name")) document.getElementById("cp_name").value = data.location_name;
if (document.getElementById("cp_start_date")) document.getElementById("cp_start_date").value = data.step_start_date || "";
if (document.getElementById("cp_end_date")) document.getElementById("cp_end_date").value = data.step_end_date || "";
if (document.getElementById("cp_step_type")) {
const type = data.step_type || "stop";
document.getElementById("cp_step_type").value = type;
if (typeof toggleStepDates === 'function') toggleStepDates(type);
}
if (document.getElementById("cp_set_as_return")) {
document.getElementById("cp_set_as_return").checked = (window.GLOBAL_RETURN_STEP_ID == data.sort_order);
}
// Génération avec transmission de la latitude et longitude de chaque dépense
if (data.items && data.items.length > 0) {
let visibleCount = 0;
data.items.forEach((it) => {
if (it.name !== "PF_TECHNICAL_POINT") {
if (typeof window.addCpExpenseLine === 'function') {
window.addCpExpenseLine(
it.category, it.name, it.amount, it.is_paid, it.notes || "",
it.id || "", it.item_date || "", it.item_time || "", it.duration || 1,
it.expense_context || "local", it.lat || "", it.lng || ""
);
}
visibleCount++;
}
});
if (visibleCount === 0 && typeof window.addCpExpenseLine === 'function') window.addCpExpenseLine();
} else {
if (typeof window.addCpExpenseLine === 'function') window.addCpExpenseLine();
}
}
try {
if (typeof cpDateRangePicker !== 'undefined' && cpDateRangePicker && typeof cpDateRangePicker.destroy === 'function') {
cpDateRangePicker.destroy();
}
} catch (e) {}
const modal = document.getElementById("checkpointModal");
if (modal) {
modal.style.display = "flex";
document.body.classList.add("no-scroll");
}
};
// 3. LA FONCTION AVEC LA CLASSE CSS PARFAITE ET LE BOUTON 📍
window.addCpExpenseLine = function(category = "accommodation", name = "", amount = "", isPaid = 0, notes = "", itemId = "", itemDate = "", itemTime = "", itemDur = 1, expenseContext = "local", itemLat = "", itemLng = "") {
const container = document.getElementById("cpExpensesContainer");
if (!container) return;
const div = document.createElement("div");
div.className = "hol-form-row"; // Force l'utilisation du CSS
const isChecked = isPaid == 1 ? "checked" : "";
// Indicateur visuel (bouton vert) si on a déjà des coordonnées pour cette activité
const pinBtnStyle = (itemLat && itemLng) ? "background:var(--success);color:white;border-color:var(--success);" : "";
div.innerHTML = `
<div class="hol-form-inner">
<select name="items[cat][]" class="pf-input hol-form-select">
<option value="accommodation" ${category === "accommodation" ? "selected" : ""}>🏨</option>
<option value="transport" ${category === "transport" ? "selected" : ""}>🚗</option>
<option value="activity" ${category === "activity" ? "selected" : ""}>🎫</option>
</select>
<input type="hidden" name="items[context][]" value="${expenseContext}">
<input type="text" name="items[name][]" class="pf-input hol-form-text" placeholder="${window.I18N['hdl_js_ph_expense_name'] || 'Nom de la dépense'}" value="${name}" required>
<button type="button" class="btn-icon-action" style="width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border-light);border-radius:6px; margin:0; flex-shrink:0; ${pinBtnStyle}" onclick="window.toggleItemSearch(this)" title="Adresse précise">📍</button>
<input type="number" step="0.01" name="items[amount][]" class="pf-input hol-form-number" placeholder="0.00" value="${amount}" required>
<label class="hol-form-paid-label" title="${window.I18N['hdl_paid'] || 'Payé'}">
<input type="checkbox" ${isChecked} onchange="this.nextElementSibling.value = this.checked ? 1 : 0" style="width:16px;height:16px;margin:0;accent-color:var(--primary);">
<input type="hidden" name="items[paid][]" value="${isPaid}">
<span class="hol-form-paid-text">Payé</span>
</label>
<button type="button" onclick="this.parentElement.parentElement.remove()" title="${window.I18N['hdl_js_delete_line'] || 'Supprimer'}" class="btn-icon-action delete btn-remove-expense" style="margin: 0;">🗑️</button>
</div>
<div class="hol-item-search-box" style="display:none; padding: 10px; background: var(--bg-subtle); border-radius: 8px; margin-top: 6px; width: 100%;">
<div style="display:flex; gap:10px;">
<input type="text" class="pf-input item-search-input" placeholder="Chercher une adresse..." onkeypress="if(event.key === 'Enter') { window.searchItemPlace(this); return false; }">
<button type="button" class="pf-btn btn-secondary" onclick="window.searchItemPlace(this)">🔍</button>
</div>
<div class="item-search-results" style="margin-top:10px; max-height:150px; overflow-y:auto;"></div>
</div>
<div class="hol-form-subrow" style="display: flex; padding-left: 58px; margin-top: 6px;">
<input type="text" name="items[notes][]" class="pf-input hol-form-notes-input" placeholder="${window.I18N['hdl_ph_notes'] || 'Notes...'}" value="${notes}" style="font-size: 0.8rem; padding: 6px 8px; border-style: dashed; color: var(--text-muted); width: 100%; background: transparent; margin: 0;">
</div>
<input type="hidden" name="items[id][]" value="${itemId}">
<input type="hidden" name="items[date][]" value="${itemDate}">
<input type="hidden" name="items[time][]" value="${itemTime}">
<input type="hidden" name="items[duration][]" value="${itemDur}">
<input type="hidden" name="items[lat][]" value="${itemLat}">
<input type="hidden" name="items[lng][]" value="${itemLng}">
`;
container.appendChild(div);
};
// 4. RÉINTÉGRATION DES FONCTIONS DE GÉOCODAGE DANS LE MÊME SCOPE
window.toggleItemSearch = function(btn) {
const searchBox = btn.closest(".hol-form-row").querySelector(".hol-item-search-box");
searchBox.style.display = searchBox.style.display === "none" ? "block" : "none";
};
window.searchItemPlace = async function(btn) {
const container = btn.closest(".hol-item-search-box");
const input = container.querySelector(".item-search-input");
const resultsDiv = container.querySelector(".item-search-results");
const q = input.value.trim();
if (q.length < 3) return;
resultsDiv.innerHTML = `<span style="color:#64748b; font-size:0.85rem;">Recherche en cours...</span>`;
try {
const data = await pachaFetch("/modules/holidays/includes/api/geocode.php?limit=5&q=" + encodeURIComponent(q), { method: 'GET' });
resultsDiv.innerHTML = "";
if (data.error || !data.results || data.results.length === 0) {
resultsDiv.innerHTML = `<span style="color:#ef4444; font-size:0.85rem;">Aucun résultat</span>`;
return;
}
data.results.forEach((place) => {
const b = document.createElement("button");
b.type = "button";
b.className = "pf-btn btn-secondary";
b.style.textAlign = "left";
b.style.padding = "8px";
b.style.marginBottom = "4px";
b.style.width = "100%";
b.innerText = "📍 " + place.display_name;
b.onclick = () => window.selectItemPlace(container, place.lat, place.lng, place.display_name);
resultsDiv.appendChild(b);
});
} catch (err) {
resultsDiv.innerHTML = `<span style="color:#ef4444; font-size:0.85rem;">Erreur de connexion</span>`;
}
};
window.selectItemPlace = function(container, lat, lng, displayName) {
const row = container.closest(".hol-form-row");
row.querySelector('input[name="items[lat][]"]').value = lat;
row.querySelector('input[name="items[lng][]"]').value = lng;
// Si le champ note est vide, on l'utilise pour y placer le nom complet de l'adresse
const notesInput = row.querySelector('input[name="items[notes][]"]');
if (notesInput.value === "") {
notesInput.value = displayName;
}
container.style.display = "none";
// Le bouton devient vert pour confirmer la présence de coordonnées
const pinBtn = row.querySelector('.btn-icon-action[title="Adresse précise"]');
if (pinBtn) {
pinBtn.style.background = "var(--success)";
pinBtn.style.color = "white";
pinBtn.style.borderColor = "var(--success)";
}
};
window.closeCheckpointModal = function() {
const modal = document.getElementById('checkpointModal');
if(modal) modal.style.display = 'none';
document.body.classList.remove('no-scroll');
};
window.closePlanningModal = function() {
const modal = document.getElementById('planningModal');
if(modal) modal.style.display = 'none';
document.body.classList.remove('no-scroll');
};
});
</script> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<?php include __DIR__ . '/pdf_template.php'; ?>