maj MAP
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> 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 = `
|
||||
<div style="display:flex; gap:8px; align-items:center; width:100%;">
|
||||
<select name="items[cat][]" class="pf-input" style="width:50px; padding:8px 4px; font-size:1.2rem; cursor:pointer;" title="Catégorie">
|
||||
<div class="hol-form-inner">
|
||||
<select name="items[cat][]" class="pf-input hol-form-select" title="Catégorie">
|
||||
<option value="accommodation" ${category === "accommodation" ? "selected" : ""}>🏨</option>
|
||||
<option value="transport" ${category === "transport" ? "selected" : ""}>🚗</option>
|
||||
<option value="activity" ${category === "activity" ? "selected" : ""}>🎫</option>
|
||||
</select>
|
||||
<input type="text" name="items[name][]" class="pf-input" placeholder="Libellé (Optionnel)" value="${name}" style="flex:2; padding:8px; font-size:0.9rem;">
|
||||
<input type="number" step="0.01" name="items[amount][]" class="pf-input" placeholder="0.00" value="${amount}" style="width:80px; text-align:right; padding:8px; font-size:0.9rem;">
|
||||
<input type="text" name="items[name][]" class="pf-input hol-form-text" placeholder="Libellé (Optionnel)" value="${name}">
|
||||
<input type="number" step="0.01" name="items[amount][]" class="pf-input hol-form-number" placeholder="0.00" value="${amount}">
|
||||
|
||||
<label title="Payé ?" style="display:flex; align-items:center; cursor:pointer; width:55px;">
|
||||
<input type="checkbox" ${isChecked} onchange="this.nextElementSibling.value = this.checked ? 1 : 0" style="margin:0;">
|
||||
<label class="hol-form-paid-label" title="Payé ?">
|
||||
<input type="checkbox" ${isChecked} onchange="this.nextElementSibling.value = this.checked ? 1 : 0">
|
||||
<input type="hidden" name="items[paid][]" value="${isPaid}">
|
||||
<span style="font-size:0.75rem; margin-left:4px; font-weight:bold; color:#64748b;">Payé</span>
|
||||
<span class="hol-form-paid-text">Payé</span>
|
||||
</label>
|
||||
|
||||
<button type="button" onclick="this.parentElement.parentElement.remove()" style="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;">×</button>
|
||||
<button type="button" class="btn-remove-expense" onclick="this.parentElement.parentElement.remove()" title="Supprimer">×</button>
|
||||
</div>
|
||||
<input type="text" name="items[notes][]" class="pf-input" placeholder="🔗 Lien de réservation ou adresse exacte..." value="${notes}" style="font-size:0.8rem; padding:6px 8px; border-style:dashed; color:#475569; width:calc(100% - 58px); margin-left:58px;">
|
||||
<input type="text" name="items[notes][]" class="pf-input hol-form-notes-input" placeholder="🔗 Lien de réservation ou notes..." value="${notes}">
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
@@ -18,22 +18,19 @@ $stmt = $pdo->prepare("
|
||||
$stmt->execute([$id]);
|
||||
$holiday = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// IMPORTANT : Le tri se fait maintenant sur "sort_order" pour que le glisser-déposer fonctionne
|
||||
$stmtItems = $pdo->prepare("SELECT * FROM pf_holidays_items WHERE holiday_id = ? ORDER BY sort_order ASC, id ASC");
|
||||
$stmtItems->execute([$id]);
|
||||
$items = $stmtItems->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Récupération des favoris géographiques
|
||||
$stmtFav = $pdo->query("SELECT content FROM pf_notes WHERE note_type = 'holiday_favorites'");
|
||||
$favorites = json_decode($stmtFav->fetchColumn() ?: '[]', true);
|
||||
|
||||
// GROUPEMENT PAR INDEX D'ORDRE (Pour autoriser les doublons de lieux comme "Maison")
|
||||
$steps = [];
|
||||
$generalItems = [];
|
||||
|
||||
foreach ($items as $it) {
|
||||
if ($it['location_name'] !== null) {
|
||||
$orderKey = $it['sort_order']; // On utilise l'ordre comme clé unique de l'étape
|
||||
$orderKey = $it['sort_order'];
|
||||
if (!isset($steps[$orderKey])) {
|
||||
$steps[$orderKey] = [
|
||||
'location_name' => $it['location_name'],
|
||||
@@ -53,7 +50,6 @@ foreach ($items as $it) {
|
||||
ksort($steps);
|
||||
$mapPoints = array_values($steps);
|
||||
|
||||
// Calculs d'affichage (Dates & Finances)
|
||||
$dateDisplay = htmlspecialchars($holiday['period_hint'] ?? '');
|
||||
if (empty($dateDisplay) && $holiday['start_date']) {
|
||||
$dateDisplay = date('d/m/Y', strtotime($holiday['start_date']));
|
||||
@@ -75,11 +71,11 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
|
||||
<div class="hol-detail-header">
|
||||
<div class="hol-detail-title-group">
|
||||
<a href="?tab=list" class="pf-btn btn-secondary" style="padding:6px 12px; height:auto; width:auto; text-decoration:none;">◀ Retour</a>
|
||||
<a href="?tab=list" class="pf-btn btn-secondary">◀ Retour</a>
|
||||
<h1><?= htmlspecialchars($holiday['title']) ?></h1>
|
||||
<span class="hol-badge-status"><?= strtoupper($holiday['status']) ?></span>
|
||||
</div>
|
||||
<button onclick='editHoliday(<?= htmlspecialchars(json_encode(['main' => $holiday, 'items' => $generalItems]), ENT_QUOTES, 'UTF-8') ?>)' class="pf-btn btn-secondary" style="width:auto; margin:0;">⚙️ Modifier les bases</button>
|
||||
<button onclick='editHoliday(<?= htmlspecialchars(json_encode(['main' => $holiday, 'items' => $generalItems]), ENT_QUOTES, 'UTF-8') ?>)' class="pf-btn btn-secondary">⚙️ Modifier les bases</button>
|
||||
</div>
|
||||
|
||||
<div class="hol-summary-card">
|
||||
@@ -92,36 +88,43 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<div class="hol-summary-label">Budget Food & Extras</div>
|
||||
<div class="hol-summary-value">🍔 <?= number_format($holiday['budget_food'], 0) ?> € | 🎁 <?= number_format($holiday['budget_extra'], 0) ?> €</div>
|
||||
</div>
|
||||
<div class="hol-summary-item" style="text-align:right;">
|
||||
<div class="hol-summary-item">
|
||||
<div class="hol-summary-label">Coût Total Estimé</div>
|
||||
<div class="hol-summary-value total"><?= number_format($cost, 0, ',', ' ') ?> €</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="width:100%; height:12px; background:#e2e8f0; border-radius:6px; margin-bottom:10px; display:flex; overflow:hidden;">
|
||||
<div style="width:<?= $pctPaid ?>%; background:#10b981;" title="Payé"></div>
|
||||
<div style="width:<?= $pctSaved ?>%; background:#3b82f6;" title="Financé (Provision)"></div>
|
||||
<div class="hol-progress-bar">
|
||||
<div class="hol-progress-paid" style="width:<?= $pctPaid ?>%;" title="Payé"></div>
|
||||
<div class="hol-progress-saved" style="width:<?= $pctSaved ?>%;" title="Financé (Provision)"></div>
|
||||
</div>
|
||||
<div style="display:flex; justify-content:space-between; font-size:0.85rem;">
|
||||
<span style="color:#10b981; font-weight:600;">✓ Payé : <?= number_format($paid, 0, ',', ' ') ?> €</span>
|
||||
<span style="color:#3b82f6; font-weight:600;">💼 Provisionné : <?= number_format($saved, 0, ',', ' ') ?> €</span>
|
||||
<span style="color:#ef4444; font-weight:700;">⏳ Reste à payer : <?= number_format($leftToPay, 0, ',', ' ') ?> €</span>
|
||||
<div class="hol-progress-labels">
|
||||
<span class="hol-label-paid">✓ Payé : <?= number_format($paid, 0, ',', ' ') ?> €</span>
|
||||
<span class="hol-label-saved">💼 Provisionné : <?= number_format($saved, 0, ',', ' ') ?> €</span>
|
||||
<span class="hol-label-left">⏳ Reste à payer : <?= number_format($leftToPay, 0, ',', ' ') ?> €</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hol-layout-grid">
|
||||
|
||||
<div class="hol-panel">
|
||||
<div class="hol-panel-header">
|
||||
<h3>🗺️ Itinéraire & Checkpoints</h3>
|
||||
<button class="pf-btn" onclick="openCheckpointModal('add')" style="padding:6px 12px; height:auto; width:auto; font-size:0.85rem; margin:0;">📍 Placer une étape</button>
|
||||
<div class="hol-panel-header" style="flex-wrap: wrap; gap: 10px;">
|
||||
<div class="hol-panel-header-group">
|
||||
<h3 style="margin:0;">🗺️ Itinéraire</h3>
|
||||
<div class="hol-map-legend">
|
||||
<span class="hol-legend-item" title="Trajet aller"><span class="hol-legend-color aller"></span> Aller</span>
|
||||
<span class="hol-legend-item" title="Étapes intermédiaires"><span class="hol-legend-color interm"></span> Interm.</span>
|
||||
<span class="hol-legend-item" title="Trajet retour"><span class="hol-legend-color retour"></span> Retour</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="pf-btn pf-btn-small" onclick="openCheckpointModal('add')">📍 Placer une étape</button>
|
||||
</div>
|
||||
<div id="tripMap" style="flex:1; width:100%; background:#f1f5f9;"></div>
|
||||
</div>
|
||||
|
||||
<div class="hol-panel">
|
||||
<div class="hol-panel-header">
|
||||
<h3>📝 Détail des étapes</h3>
|
||||
<h3 style="margin:0;">📝 Détail des étapes</h3>
|
||||
</div>
|
||||
|
||||
<div class="hol-panel-body">
|
||||
@@ -129,7 +132,7 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<p style="color:var(--text-muted); font-style:italic; text-align:center; margin-top:40px;">Aucune étape planifiée.</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($steps as $step): ?>
|
||||
<div id="step-card-<?= $step['sort_order'] ?>" class="hol-checkpoint hol-checkpoint-draggable" draggable="true" data-location="<?= htmlspecialchars($step['location_name']) ?>">
|
||||
<div id="step-card-<?= $step['sort_order'] ?>" class="hol-checkpoint hol-checkpoint-draggable" draggable="true" data-location="<?= htmlspecialchars($step['location_name']) ?>">
|
||||
<div class="hol-cp-header">
|
||||
<div class="hol-cp-info-group">
|
||||
<span style="color:#94a3b8; font-size:1.1rem; cursor:grab; user-select:none;">☰</span>
|
||||
@@ -137,12 +140,10 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
📍 <?= htmlspecialchars($step['location_name']) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hol-cp-actions-group">
|
||||
<div class="hol-cp-total"><?= number_format($step['total_amount'], 2, ',', ' ') ?> €</div>
|
||||
<button onclick='openCheckpointModal("edit", <?= htmlspecialchars(json_encode($step), ENT_QUOTES, "UTF-8") ?>)' class="btn-icon-small" title="Modifier">✏️</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hol-cp-body">
|
||||
@@ -153,18 +154,18 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
$visibleItemsCount++;
|
||||
$icon = match($it['category']) { 'transport' => '🚗', 'accommodation' => '🏨', 'activity' => '🎫', default => '🏷️' };
|
||||
?>
|
||||
<div class="hol-expense-line" style="display:flex; flex-direction:column; align-items:flex-start; gap:4px;">
|
||||
<div style="display:flex; justify-content:space-between; width:100%;">
|
||||
<span style="color:#475569; font-weight:500;"><?= $icon ?> <?= htmlspecialchars($it['name']) ?></span>
|
||||
<div class="hol-expense-wrapper">
|
||||
<div class="hol-expense-main">
|
||||
<span class="hol-expense-name"><?= $icon ?> <?= htmlspecialchars($it['name']) ?></span>
|
||||
<span>
|
||||
<strong style="color:var(--text-main);"><?= number_format($it['amount'], 2, ',', ' ') ?> €</strong>
|
||||
<span style="margin-left:5px; color:<?= $it['is_paid'] ? '#10b981' : '#f59e0b' ?>;"><?= $it['is_paid'] ? '✓' : '⏳' ?></span>
|
||||
<strong class="hol-expense-amount"><?= number_format($it['amount'], 2, ',', ' ') ?> €</strong>
|
||||
<span class="<?= $it['is_paid'] ? 'status-paid' : 'status-pending' ?>" title="<?= $it['is_paid'] ? 'Payé' : 'À payer' ?>"><?= $it['is_paid'] ? '✓' : '⏳' ?></span>
|
||||
</span>
|
||||
</div>
|
||||
<?php if (!empty($it['notes'])): ?>
|
||||
<div style="font-size:0.75rem; color:#94a3b8; padding-left: 24px; word-break: break-all;">
|
||||
<div class="hol-expense-note">
|
||||
<?php if (filter_var($it['notes'], FILTER_VALIDATE_URL)): ?>
|
||||
🔗 <a href="<?= htmlspecialchars($it['notes']) ?>" target="_blank" style="color:#3b82f6; text-decoration:none;">Voir le lien</a>
|
||||
🔗 <a href="<?= htmlspecialchars($it['notes']) ?>" target="_blank" class="hol-expense-link">Voir le lien</a>
|
||||
<?php else: ?>
|
||||
📝 <?= htmlspecialchars($it['notes']) ?>
|
||||
<?php endif; ?>
|
||||
@@ -174,7 +175,7 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ($visibleItemsCount === 0): ?>
|
||||
<div style="font-size:0.8rem; color:var(--text-muted); font-style:italic; padding: 5px 0;">
|
||||
<div class="hol-empty-step">
|
||||
📍 Point de passage (Aucune dépense)
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -205,7 +206,7 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<?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; height:auto; width:auto; border-radius:20px; background:#f0f9ff; color:#0369a1; border-color:#bae6fd;" onclick="selectPlace(<?= $fav['lat'] ?>, <?= $fav['lng'] ?>, '<?= htmlspecialchars(addslashes($fav['name'])) ?>')">
|
||||
<button type="button" class="pf-btn btn-secondary" style="padding:4px 10px; font-size:0.8rem; height:auto; width:auto; border-radius:20px;" onclick="selectPlace(<?= $fav['lat'] ?>, <?= $fav['lng'] ?>, '<?= htmlspecialchars(addslashes($fav['name'])) ?>')">
|
||||
⭐ <?= htmlspecialchars($fav['name']) ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
@@ -228,12 +229,12 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<input type="hidden" name="old_sort_order" id="cp_old_sort_order">
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Nom de l'étape (Ce qui s'affichera sur la carte)</label>
|
||||
<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);" required>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
|
||||
<label class="pf-label" style="margin:0;">Dépenses prévues à cette étape</label>
|
||||
<label class="pf-label" style="margin:0;">Dépenses prévues</label>
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="addCpExpenseLine()" style="padding:4px 8px; font-size:0.8rem; height:auto; width:auto; margin:0;">+ Ajouter une dépense</button>
|
||||
</div>
|
||||
|
||||
@@ -242,18 +243,18 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
|
||||
<div style="margin-bottom: 20px; padding-left: 5px;">
|
||||
<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; cursor:pointer; width:16px; height:16px;">
|
||||
⭐ Sauvegarder cette adresse dans mes favoris rapides
|
||||
<input type="checkbox" name="save_favorite" value="1" style="margin-right:8px;">
|
||||
⭐ Sauvegarder dans mes favoris
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; border-top:1px solid #e2e8f0; padding-top:15px;">
|
||||
<div>
|
||||
<button type="button" onclick="deleteCheckpoint()" id="btnDeleteCp" class="pf-btn btn-secondary" style="color:#ef4444; border-color:#fca5a5; display:none; width:auto; margin:0;">🗑️ Supprimer l'étape</button>
|
||||
<button type="button" onclick="deleteCheckpoint()" id="btnDeleteCp" class="pf-btn btn-secondary" style="color:#ef4444; border-color:#fca5a5; display:none; width:auto; margin:0;">🗑️ Supprimer</button>
|
||||
</div>
|
||||
<div style="display:flex; gap:10px;">
|
||||
<button type="button" onclick="document.getElementById('checkpointModal').style.display='none'" class="pf-btn btn-secondary" style="width:auto; margin:0;">Annuler</button>
|
||||
<button type="submit" class="pf-btn" style="width:auto; margin:0;">Enregistrer l'étape</button>
|
||||
<button type="submit" class="pf-btn" style="width:auto; margin:0;">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user