diff --git a/pachafamily_source.md b/pachafamily_source.md index ac74aab..56302f7 100644 --- a/pachafamily_source.md +++ b/pachafamily_source.md @@ -1,6 +1,6 @@ # 🩙 Source Code PachaFamily -> *GĂ©nĂ©rĂ© le 2026-05-04 17:27:25* +> *GĂ©nĂ©rĂ© le 2026-05-04 18:29:00* ### 📄 Fichier : `budget.php` ```php @@ -1208,6 +1208,35 @@ body.no-scroll { margin-top: auto; } +/* Notifications Toasts */ +.pf-toast { + position: fixed; + bottom: 20px; + right: 20px; + padding: 12px 24px; + background: white; + border-radius: 12px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + border-left: 5px solid var(--primary); + z-index: 10000; + transform: translateX(120%); + transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); + font-weight: 600; + color: var(--text-main); +} +.pf-toast.is-visible { + transform: translateX(0); +} +.pf-toast--success { + border-left-color: #10b981; +} +.pf-toast--error { + border-left-color: #ef4444; +} +.pf-toast--warning { + border-left-color: #f59e0b; +} + /* === MOBILE RESPONSIVE === */ @media (max-width: 768px) { .pf-header { @@ -1247,6 +1276,42 @@ body.no-scroll { .pf-mobile-logout { color: var(--danger); } + /* ✅ Transformation de toutes les modales en "Bottom Sheets" */ + .pf-modal { + align-items: flex-end; /* Aligne la modale en bas */ + padding: 0; + } + + .pf-modal-content { + width: 100% !important; + max-width: none !important; + border-radius: 24px 24px 0 0 !important; /* Arrondi seulement en haut */ + padding: 24px 20px !important; + padding-bottom: env( + safe-area-inset-bottom, + 30px + ) !important; /* GĂšre l'encoche iPhone */ + transform: translateY(100%); + animation: slideUpSheet 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; + max-height: 92vh; /* Laisse un petit espace en haut */ + } + + /* Barre de drag visuelle en haut de la modale */ + .pf-modal-content::before { + content: ""; + display: block; + width: 40px; + height: 5px; + background: #cbd5e1; + border-radius: 10px; + margin: -10px auto 20px auto; + } + + @keyframes slideUpSheet { + to { + transform: translateY(0); + } + } } ``` @@ -1349,33 +1414,107 @@ $currentLang = $_SESSION['app_lang'] ?? 'fr'; + + /** + * pachaFetch : Utilitaire de requĂȘte robuste + */ + async function pachaFetch(url, options = {}) { + const finalUrl = url.startsWith('/') ? url.substring(1) : url; + + options.credentials = 'same-origin'; + options.headers = { + ...options.headers, + 'Accept': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }; + + try { + const response = await fetch(finalUrl, options); + const rawText = await response.text(); + + try { + return JSON.parse(rawText); + } catch (jsonErr) { + console.error("RĂ©ponse corrompue (HTML reçu au lieu de JSON) :", rawText); + throw new Error("Erreur serveur : format JSON invalide."); + } + } catch (err) { + console.error(`Erreur pachaFetch [${finalUrl}] :`, err); + throw err; + } + } + + /** + * UI Utility : Toasts (Notifications) + */ +function showToast(message, type = 'success') { + const toast = document.createElement('div'); + toast.className = `pf-toast pf-toast--${type}`; + toast.innerHTML = message; + document.body.appendChild(toast); + + // Animation et suppression + setTimeout(() => toast.classList.add('is-visible'), 100); + setTimeout(() => { + toast.classList.remove('is-visible'); + setTimeout(() => toast.remove(), 300); + }, 3000); +} + +/** + * UI Utility : Confirmation stylisĂ©e (Remplace confirm()) + */ +async function pachaConfirm(title, message) { + return new Promise((resolve) => { + const modal = document.createElement('div'); + modal.className = 'pf-modal open'; + modal.innerHTML = ` +
+

${title}

+

${message}

+ +
+ `; + document.body.appendChild(modal); + + document.getElementById('confirm-cancel').onclick = () => { modal.remove(); resolve(false); }; + document.getElementById('confirm-ok').onclick = () => { modal.remove(); resolve(true); }; + }); +} + ``` --- @@ -1444,10 +1583,14 @@ function require_login(?string $loginPage = '/login.php'): void } if (empty($_SESSION['user'])) { - // URL de la page courante (ex: /gift-list.php?foo=bar) - $currentUrl = $_SERVER['REQUEST_URI'] ?? '/'; + if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) || (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) { + header('Content-Type: application/json'); + http_response_code(401); + echo json_encode(['success' => false, 'error' => 'Session expirĂ©e. Veuillez vous reconnecter.']); + exit; + } - // On redirige vers la page de login en ajoutant ?redirect=... + $currentUrl = $_SERVER['REQUEST_URI'] ?? '/'; $redirectParam = urlencode($currentUrl); $target = ($loginPage ?? '/login.php') . '?redirect=' . $redirectParam; @@ -1460,10 +1603,26 @@ function require_login(?string $loginPage = '/login.php'): void --- +### 📄 Fichier : `includes/config.php` +```php + - 'Alex', ID_LAIA => 'Laia']; + + foreach ($family_members as $id => $p): $d = $salaryConfig[$p]; $restant = $d['salary'] - ($d['mensualite'] + $d['frais_func'] + $d['eco_perso'] + $d['eco_family']); - $borderColor = ($p === 'Alex') ? '#0891b2' : '#f59e0b'; + $borderColor = ($id === ID_ALEX) ? '#0891b2' : '#f59e0b'; ?> @@ -5163,7 +5328,7 @@ function getTranslatedMonthName($dateString) { -