vue mobile
This commit is contained in:
+22
-6
@@ -125,7 +125,9 @@ body {
|
|||||||
top: 64px;
|
top: 64px;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
height: calc(100vh - 64px); /* Remplace le bottom: 0 pour éviter le bug iOS */
|
||||||
|
overflow-y: auto; /* Permet le scroll si le menu contient beaucoup d'items */
|
||||||
|
-webkit-overflow-scrolling: touch; /* Fluidité sur iOS */
|
||||||
background: white;
|
background: white;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -306,16 +308,30 @@ p {
|
|||||||
}
|
}
|
||||||
.pf-nav {
|
.pf-nav {
|
||||||
display: none;
|
display: none;
|
||||||
} /* Caché sur mobile, activé via burger */
|
}
|
||||||
.pf-header-actions {
|
.pf-header-actions {
|
||||||
display: none;
|
display: none;
|
||||||
} /* Caché sur mobile, dans le menu */
|
}
|
||||||
|
|
||||||
.pf-burger-btn {
|
.pf-burger-btn {
|
||||||
display: block;
|
display: block;
|
||||||
} /* Affiché sur mobile */
|
}
|
||||||
|
|
||||||
|
/* Ajustements d'espacement pour petits écrans */
|
||||||
|
.pf-main {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-modules-grid {
|
||||||
|
/* Sécurité : force 1 colonne sur mobile au lieu du auto-fill */
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-login-container {
|
||||||
|
margin: 30px 16px; /* Évite de toucher les bords de l'écran */
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Menu Mobile Ouvert */
|
|
||||||
.pf-mobile-nav-link {
|
.pf-mobile-nav-link {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ body.no-scroll {
|
|||||||
/* --- 4. CARTES --- */
|
/* --- 4. CARTES --- */
|
||||||
.hol-ideas-grid {
|
.hol-ideas-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
@@ -459,48 +459,74 @@ body.no-scroll {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* On empile proprement les boutons d'action */
|
||||||
.hol-title-actions {
|
.hol-title-actions {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex-direction: column; /* Essentiel pour que les boutons width: 100% ne s'écrasent pas */
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.hol-add-btn,
|
.hol-add-btn,
|
||||||
.hol-map-toggle {
|
.hol-map-toggle {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
justify-content: center; /* Centre le texte et l'icône */
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
/* Sur mobile, les colonnes s'empilent */
|
|
||||||
.hol-columns-wrapper {
|
.hol-columns-wrapper {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modale 100% écran avec scroll natif fluide */
|
||||||
.pf-modal-content {
|
.pf-modal-content {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
|
||||||
.hol-date-weather-wrapper {
|
|
||||||
gap: 6px; /* On rapproche la météo de la date sur mobile */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* On aligne le haut de la carte au cas où le contenu passe sur 2 lignes */
|
/* Adaptation des lignes de budget sur mobile pour éviter qu'elles ne soient compressées */
|
||||||
.hol-cp-header {
|
.savings-line-item {
|
||||||
align-items: flex-start;
|
flex-wrap: wrap;
|
||||||
}
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.savings-line-item input[type="text"] {
|
||||||
|
min-width: 100%; /* Le nom de la dépense prend toute la ligne */
|
||||||
|
}
|
||||||
|
.savings-line-item input[type="number"] {
|
||||||
|
flex-grow: 1; /* Le prix s'ajuste sur la 2ème ligne */
|
||||||
|
}
|
||||||
|
|
||||||
/* On autorise le titre et les dates à prendre la place nécessaire */
|
.hol-date-weather-wrapper {
|
||||||
.hol-cp-info-group {
|
gap: 6px;
|
||||||
flex: 1;
|
}
|
||||||
min-width: 0; /* Empêche le flexbox de "déborder" de l'écran */
|
|
||||||
}
|
|
||||||
|
|
||||||
.hol-cp-title {
|
.hol-cp-header {
|
||||||
white-space: normal; /* Permet au titre de l'étape de passer à la ligne si besoin */
|
align-items: flex-start;
|
||||||
}
|
flex-direction: column; /* On passe le titre et le prix l'un sous l'autre si besoin */
|
||||||
|
}
|
||||||
|
|
||||||
|
.hol-cp-info-group {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hol-cp-title {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hol-cp-actions-group {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -580,16 +580,63 @@ function deleteCheckpoint() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 5. GLISSER-DÉPOSER POUR RÉORDONNER LES ÉTAPES
|
// 5. RÉORDONNANCEMENT DES ÉTAPES (DRAG & DROP PC + FLÈCHES MOBILE)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
// On sort cette fonction pour pouvoir l'appeler depuis les boutons fléchés sur mobile
|
||||||
|
function saveCheckpointOrder() {
|
||||||
|
const locations = [
|
||||||
|
...document.querySelectorAll(".hol-checkpoint-draggable"),
|
||||||
|
].map((el) => el.getAttribute("data-location"));
|
||||||
|
const holidayId = document.querySelector('input[name="holiday_id"]').value;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("holiday_id", holidayId);
|
||||||
|
formData.append("locations", JSON.stringify(locations));
|
||||||
|
|
||||||
|
fetch("/modules/holidays/includes/api/reorder_checkpoints.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}).then(() => window.location.reload());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction appelée par les flèches Haut/Bas sur mobile
|
||||||
|
function moveStepMobile(btn, direction) {
|
||||||
|
const item = btn.closest(".hol-checkpoint-draggable");
|
||||||
|
const container = item.parentElement;
|
||||||
|
|
||||||
|
if (
|
||||||
|
direction === -1 &&
|
||||||
|
item.previousElementSibling &&
|
||||||
|
item.previousElementSibling.classList.contains("hol-checkpoint-draggable")
|
||||||
|
) {
|
||||||
|
container.insertBefore(item, item.previousElementSibling);
|
||||||
|
saveCheckpointOrder();
|
||||||
|
} else if (
|
||||||
|
direction === 1 &&
|
||||||
|
item.nextElementSibling &&
|
||||||
|
item.nextElementSibling.classList.contains("hol-checkpoint-draggable")
|
||||||
|
) {
|
||||||
|
container.insertBefore(item, item.nextElementSibling.nextElementSibling);
|
||||||
|
saveCheckpointOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const checkpoints = document.querySelectorAll(".hol-checkpoint-draggable");
|
const checkpoints = document.querySelectorAll(".hol-checkpoint-draggable");
|
||||||
const container = checkpoints[0]?.parentElement;
|
const container = checkpoints[0]?.parentElement;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 768;
|
||||||
let draggedItem = null;
|
let draggedItem = null;
|
||||||
|
|
||||||
checkpoints.forEach((item) => {
|
checkpoints.forEach((item) => {
|
||||||
|
// Si on est sur mobile, on supprime l'attribut draggable pour éviter les conflits de scroll
|
||||||
|
if (isMobile) {
|
||||||
|
item.removeAttribute("draggable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
item.addEventListener("dragstart", function (e) {
|
item.addEventListener("dragstart", function (e) {
|
||||||
draggedItem = this;
|
draggedItem = this;
|
||||||
setTimeout(() => (this.style.opacity = "0.4"), 0);
|
setTimeout(() => (this.style.opacity = "0.4"), 0);
|
||||||
@@ -633,22 +680,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
{ offset: Number.NEGATIVE_INFINITY },
|
{ offset: Number.NEGATIVE_INFINITY },
|
||||||
).element;
|
).element;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCheckpointOrder() {
|
|
||||||
const locations = [
|
|
||||||
...document.querySelectorAll(".hol-checkpoint-draggable"),
|
|
||||||
].map((el) => el.getAttribute("data-location"));
|
|
||||||
const holidayId = document.querySelector('input[name="holiday_id"]').value;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("holiday_id", holidayId);
|
|
||||||
formData.append("locations", JSON.stringify(locations));
|
|
||||||
|
|
||||||
fetch("/modules/holidays/includes/api/reorder_checkpoints.php", {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
}).then(() => window.location.reload());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -702,7 +733,11 @@ function openPlanningModal(step) {
|
|||||||
|
|
||||||
html += `
|
html += `
|
||||||
<div class="hol-day-column">
|
<div class="hol-day-column">
|
||||||
<div class="hol-calendar-day-header"><div class="hol-cal-weekday">${dayName}</div><div class="hol-cal-date">${dayNum}</div></div>
|
<div class="hol-calendar-day-header">
|
||||||
|
<div class="hol-cal-weekday">${dayName}</div>
|
||||||
|
<div class="hol-cal-date">${dayNum}</div>
|
||||||
|
<div id="plan-weather-${dateStr}" style="margin-top: 5px; display: flex; justify-content: center; min-height: 20px;"></div>
|
||||||
|
</div>
|
||||||
<div class="hol-time-slots-container">
|
<div class="hol-time-slots-container">
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -722,6 +757,10 @@ function openPlanningModal(step) {
|
|||||||
html += `</div></div>`;
|
html += `</div></div>`;
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
|
|
||||||
|
// 1. On détecte si on est sur mobile juste avant la boucle
|
||||||
|
const isMobile = window.innerWidth <= 768;
|
||||||
|
const dragAttr = isMobile ? "" : 'draggable="true"';
|
||||||
|
|
||||||
validItems.forEach((it) => {
|
validItems.forEach((it) => {
|
||||||
let icon = "🏷️";
|
let icon = "🏷️";
|
||||||
let catClass = "cat-activity";
|
let catClass = "cat-activity";
|
||||||
@@ -739,8 +778,9 @@ function openPlanningModal(step) {
|
|||||||
? `<div class="hol-drag-note">${it.notes}</div>`
|
? `<div class="hol-drag-note">${it.notes}</div>`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
|
// 2. MODIFICATION ICI : On remplace le texte en dur draggable="true" par la variable ${dragAttr}
|
||||||
const elHtml = `
|
const elHtml = `
|
||||||
<div class="hol-drag-item ${catClass}" draggable="true"
|
<div class="hol-drag-item ${catClass}" ${dragAttr}
|
||||||
id="drag-item-${it.id}" data-id="${it.id}"
|
id="drag-item-${it.id}" data-id="${it.id}"
|
||||||
style="--duration: ${dur};"
|
style="--duration: ${dur};"
|
||||||
ondragstart="dragStart(event)" onclick="handleItemTap(event, ${it.id})">
|
ondragstart="dragStart(event)" onclick="handleItemTap(event, ${it.id})">
|
||||||
@@ -774,6 +814,10 @@ function openPlanningModal(step) {
|
|||||||
|
|
||||||
document.getElementById("planningModal").style.display = "flex";
|
document.getElementById("planningModal").style.display = "flex";
|
||||||
document.body.classList.add("no-scroll");
|
document.body.classList.add("no-scroll");
|
||||||
|
|
||||||
|
datesToDisplay.forEach((dateStr) => {
|
||||||
|
loadWeatherForPlanning(step.lat, step.lng, dateStr);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeDuration(e, itemId, delta) {
|
function changeDuration(e, itemId, delta) {
|
||||||
@@ -883,3 +927,32 @@ function saveItemDateTime(itemId, dateStr, timeStr) {
|
|||||||
body: formData,
|
body: formData,
|
||||||
}).catch((err) => console.error("Erreur:", err));
|
}).catch((err) => console.error("Erreur:", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MÉTÉO SPÉCIFIQUE AU HEADER DU PLANNING
|
||||||
|
// ============================================================================
|
||||||
|
async function loadWeatherForPlanning(lat, lng, dateStr) {
|
||||||
|
const container = document.getElementById(`plan-weather-${dateStr}`);
|
||||||
|
if (!container || !lat || !lng) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(
|
||||||
|
`/modules/holidays/includes/api/get_weather.php?lat=${lat}&lng=${lng}&date=${dateStr}`,
|
||||||
|
);
|
||||||
|
const res = await resp.json();
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
const info = getWeatherInfo(res.data.code);
|
||||||
|
const approxSymbol = res.data.is_historical ? "~" : "";
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="pf-weather-badge" style="font-size: 0.65rem; padding: 2px 6px; ${res.data.is_historical ? "opacity: 0.8;" : ""}" title="${info.label}">
|
||||||
|
<span class="pf-weather-icon">${info.icon}</span>
|
||||||
|
<span>${approxSymbol}${Math.round(res.data.temp_min)}° / ${Math.round(res.data.temp_max)}°C</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Erreur météo planning", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,6 +74,14 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
|||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.mobile-only { display: none !important; }
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.desktop-only { display: none !important; }
|
||||||
|
.mobile-only { display: flex !important; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="pf-holidays-detail">
|
<div class="pf-holidays-detail">
|
||||||
|
|
||||||
<div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 20px; flex-wrap: wrap; gap: 15px;">
|
<div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 20px; flex-wrap: wrap; gap: 15px;">
|
||||||
@@ -197,7 +205,11 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
|||||||
<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-header">
|
||||||
<div class="hol-cp-info-group">
|
<div class="hol-cp-info-group">
|
||||||
<span style="color:#94a3b8; font-size:1.1rem; cursor:grab; user-select:none;">☰</span>
|
<span class="desktop-only" style="color:#94a3b8; font-size:1.1rem; cursor:grab; user-select:none;">☰</span>
|
||||||
|
<div class="mobile-only" style="flex-direction:column; gap:4px; margin-right:8px;">
|
||||||
|
<button type="button" onclick="moveStepMobile(this, -1)" class="btn-icon-small" style="width:24px!important; height:24px!important; font-size:0.6rem;">▲</button>
|
||||||
|
<button type="button" onclick="moveStepMobile(this, 1)" class="btn-icon-small" style="width:24px!important; height:24px!important; font-size:0.6rem;">▼</button>
|
||||||
|
</div>
|
||||||
<div class="hol-cp-title" onclick="panMapTo(<?= $step['lat'] ?>, <?= $step['lng'] ?>)" title="<?= htmlspecialchars($step['location_name']) ?>">
|
<div class="hol-cp-title" onclick="panMapTo(<?= $step['lat'] ?>, <?= $step['lng'] ?>)" title="<?= htmlspecialchars($step['location_name']) ?>">
|
||||||
📍 <?= htmlspecialchars($step['location_name']) ?>
|
📍 <?= htmlspecialchars($step['location_name']) ?>
|
||||||
<?php if (!empty($step['is_return'])): ?>
|
<?php if (!empty($step['is_return'])): ?>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ body.pf-home .pf-modules-grid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pf-hero h1 {
|
.pf-hero h1 {
|
||||||
font-size: 2rem;
|
font-size: clamp(1.8rem, 6vw, 2.5rem); /* Typographie fluide */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ajustement header mobile */
|
/* Ajustement header mobile */
|
||||||
|
|||||||
Reference in New Issue
Block a user