budget
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/includes/auth.php';
|
||||||
|
require __DIR__ . '/includes/db.php';
|
||||||
|
require_login('/login.php');
|
||||||
|
|
||||||
|
// Gestion de l'onglet actif (par défaut 'recap')
|
||||||
|
$tab = $_GET['tab'] ?? 'recap';
|
||||||
|
|
||||||
|
$pageTitle = "PachaFamily - Budget";
|
||||||
|
$activePage = "budget";
|
||||||
|
$pageCss = "/modules/budget/budget.css";
|
||||||
|
$pageJs = "/modules/budget/budget.js";
|
||||||
|
|
||||||
|
require __DIR__ . '/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="pf-container">
|
||||||
|
<div class="pf-hero" style="text-align: center; margin-bottom: 30px;">
|
||||||
|
<h1 style="margin-bottom: 20px;">Gestion du Budget</h1>
|
||||||
|
|
||||||
|
<nav class="budget-tabs-container">
|
||||||
|
<a href="?tab=recap" class="tab-item <?= $tab == 'recap' ? 'active' : '' ?>">
|
||||||
|
<span class="tab-icon">📊</span>
|
||||||
|
<span>Récapitulatif</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="?tab=suivi" class="tab-item <?= $tab == 'suivi' ? 'active' : '' ?>">
|
||||||
|
<span class="tab-icon">🗓️</span>
|
||||||
|
<span>Suivi Mensuel</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="?tab=epargne" class="tab-item <?= $tab == 'epargne' ? 'active' : '' ?>">
|
||||||
|
<span class="tab-icon">🐷</span>
|
||||||
|
<span>Épargne</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="pf-section">
|
||||||
|
<?php
|
||||||
|
// On définit le chemin relatif par rapport à la racine du projet
|
||||||
|
$viewPath = __DIR__ . "/modules/budget/views/" . $tab . ".php";
|
||||||
|
|
||||||
|
if (file_exists($viewPath)) {
|
||||||
|
include $viewPath;
|
||||||
|
} else {
|
||||||
|
// Petit debug pour voir où PHP cherche exactement le fichier
|
||||||
|
echo "<p>Erreur : Le fichier est introuvable dans " . htmlspecialchars($viewPath) . "</p>";
|
||||||
|
echo "<p>Onglet en cours de développement...</p>";
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<script src="/modules/budget/budget.js"></script>
|
||||||
|
<?php require __DIR__ . '/footer.php'; ?>
|
||||||
@@ -21,6 +21,7 @@ $activePage = $activePage ?? "";
|
|||||||
<nav class="pf-nav">
|
<nav class="pf-nav">
|
||||||
<a href="/index.php" class="pf-nav-link <?= $activePage === 'home' ? 'pf-nav-link--active' : '' ?>">Accueil</a>
|
<a href="/index.php" class="pf-nav-link <?= $activePage === 'home' ? 'pf-nav-link--active' : '' ?>">Accueil</a>
|
||||||
<a href="/family-calendar.php" class="pf-nav-link <?= $activePage === 'family-calendar' ? 'pf-nav-link--active' : '' ?>">Calendrier</a>
|
<a href="/family-calendar.php" class="pf-nav-link <?= $activePage === 'family-calendar' ? 'pf-nav-link--active' : '' ?>">Calendrier</a>
|
||||||
|
<a href="/budget.php" class="pf-nav-link <?= $activePage === 'budget' ? 'pf-nav-link--active' : '' ?>">Budget</a>
|
||||||
<a href="/holidays.php" class="pf-nav-link <?= $activePage === 'holidays' ? 'pf-nav-link--active' : '' ?>">Vacances</a>
|
<a href="/holidays.php" class="pf-nav-link <?= $activePage === 'holidays' ? 'pf-nav-link--active' : '' ?>">Vacances</a>
|
||||||
<a href="/gift-list.php" class="pf-nav-link <?= $activePage === 'gift-list' ? 'pf-nav-link--active' : '' ?>">Cadeaux</a>
|
<a href="/gift-list.php" class="pf-nav-link <?= $activePage === 'gift-list' ? 'pf-nav-link--active' : '' ?>">Cadeaux</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ require __DIR__ . '/header.php';
|
|||||||
<span class="pf-card-cta">Voir les listes</span>
|
<span class="pf-card-cta">Voir les listes</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="pf-module-card pf-card--disabled">
|
<a href="/budget.php" class="pf-module-card">
|
||||||
<div class="pf-card-icon">💰</div>
|
<div class="pf-card-icon">💰</div>
|
||||||
<h3 class="pf-card-title">Budget</h3>
|
<h3 class="pf-card-title">Budget</h3>
|
||||||
<div class="pf-card-desc">
|
<div class="pf-card-desc">
|
||||||
Bientôt disponible : suivi des dépenses, catégories et budget mensuel.
|
Suivi des dépenses fixes, revenus et équilibre du compte familial.
|
||||||
</div>
|
</div>
|
||||||
<span class="pf-card-cta">À venir</span>
|
<span class="pf-card-cta">Gérer</span>
|
||||||
</div>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -0,0 +1,663 @@
|
|||||||
|
/* modules/budget/budget.css */
|
||||||
|
|
||||||
|
/* --- 1. VARIABLES & BASE --- */
|
||||||
|
:root {
|
||||||
|
--primary: #2563eb;
|
||||||
|
--primary-hover: #1d4ed8;
|
||||||
|
--success: #10b981; /* Vert */
|
||||||
|
--danger: #ef4444; /* Rouge */
|
||||||
|
--warning: #f59e0b; /* Jaune */
|
||||||
|
|
||||||
|
--bg-page: #f1f5f9;
|
||||||
|
--bg-card: #ffffff;
|
||||||
|
|
||||||
|
--text-main: #1e293b;
|
||||||
|
--text-muted: #64748b;
|
||||||
|
|
||||||
|
--radius-l: 16px;
|
||||||
|
--radius-m: 10px;
|
||||||
|
--radius-s: 6px;
|
||||||
|
|
||||||
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||||
|
--shadow-lg:
|
||||||
|
0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 2. LAYOUT & CONTAINER --- */
|
||||||
|
.budget-view {
|
||||||
|
font-family:
|
||||||
|
"Segoe UI",
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
sans-serif;
|
||||||
|
color: var(--text-main);
|
||||||
|
animation: fadeIn 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* En-tête de la vue */
|
||||||
|
.view-header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-header h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #0f172a; /* Très sombre, quasi noir */
|
||||||
|
margin: 0;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 3. NAVIGATION TABS (Pills) --- */
|
||||||
|
.budget-tabs-container {
|
||||||
|
display: inline-flex;
|
||||||
|
background: var(--bg-card);
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 50px;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 24px;
|
||||||
|
border-radius: 40px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item:hover {
|
||||||
|
background-color: var(--bg-page);
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-icon {
|
||||||
|
font-size: 1.1em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sous-onglets propriétaire (Alex/Laia) */
|
||||||
|
.owner-tabs {
|
||||||
|
display: inline-flex;
|
||||||
|
background: #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.owner-tab {
|
||||||
|
padding: 6px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.owner-tab.active {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--primary);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 4. BOUTONS --- */
|
||||||
|
.pf-btn {
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50px;
|
||||||
|
padding: 10px 24px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-btn.btn-secondary {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-muted);
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-btn.btn-secondary:hover {
|
||||||
|
background: var(--bg-page);
|
||||||
|
color: var(--text-main);
|
||||||
|
border-color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon,
|
||||||
|
.btn-icon-small {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
.btn-icon-small {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon:hover,
|
||||||
|
.btn-icon-small:hover {
|
||||||
|
background: #e2e8f0;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions groupées */
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 5. TABLEAU MODERNE --- */
|
||||||
|
.pf-table {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-l);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-table th {
|
||||||
|
background: #f8fafc;
|
||||||
|
padding: 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-table td {
|
||||||
|
padding: 3px 16px; /* Ajusté pour être moins haut */
|
||||||
|
border-bottom: 1px solid var(--bg-page);
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-table tbody tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.pf-table tbody tr:hover {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles spécifiques aux lignes */
|
||||||
|
.row-income td:first-child {
|
||||||
|
border-left: 4px solid var(--success);
|
||||||
|
}
|
||||||
|
.row-expense td:first-child {
|
||||||
|
border-left: 4px solid var(--danger);
|
||||||
|
}
|
||||||
|
.row-estimate td:first-child {
|
||||||
|
border-left: 4px solid var(--warning);
|
||||||
|
}
|
||||||
|
.row-estimate td {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles tableau épargne */
|
||||||
|
.savings-table .row-total td {
|
||||||
|
background: var(--bg-page);
|
||||||
|
border-bottom: 2px solid #cbd5e1;
|
||||||
|
}
|
||||||
|
.savings-table .row-extres td {
|
||||||
|
background: #fffbeb;
|
||||||
|
border-top: 2px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.sticky-col {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
background: var(--bg-card);
|
||||||
|
z-index: 2;
|
||||||
|
border-right: 1px solid var(--bg-page);
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cellules spéciales */
|
||||||
|
.cell-amount {
|
||||||
|
font-family: "Consolas", "Monaco", monospace;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-content {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton supprimer au survol */
|
||||||
|
.btn-cell-delete {
|
||||||
|
display: none;
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
color: var(--danger);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
right: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cell-delete:hover {
|
||||||
|
background: var(--danger);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:hover .btn-cell-delete {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer du tableau */
|
||||||
|
.pf-table tfoot {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-top: 2px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.pf-table tfoot td {
|
||||||
|
padding: 18px 16px;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 6. FORMULAIRES & INPUTS --- */
|
||||||
|
.pf-label {
|
||||||
|
display: block;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: var(--radius-m);
|
||||||
|
font-size: 1rem;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: var(--text-main);
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-input:focus {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-color: var(--primary);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox Custom */
|
||||||
|
input[type="checkbox"] {
|
||||||
|
appearance: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #cbd5e1;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked {
|
||||||
|
background-color: var(--success);
|
||||||
|
border-color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked::after {
|
||||||
|
content: "✓";
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 7. MODALE --- */
|
||||||
|
.pf-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background: rgba(15, 23, 42, 0.6);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
-webkit-backdrop-filter: blur(4px); /* Safari support */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-modal-content {
|
||||||
|
background: var(--bg-card);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
padding: 32px;
|
||||||
|
position: relative;
|
||||||
|
animation: modalPop 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modalPop {
|
||||||
|
from {
|
||||||
|
transform: scale(0.95) translateY(10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-modal-title {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-main);
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 8. UTILITAIRES & BADGES --- */
|
||||||
|
.text-success {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
.text-danger {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
.text-muted {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.font-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-type {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.badge-type.mensuel {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
.badge-type.annuel {
|
||||||
|
background: #f3e8ff;
|
||||||
|
color: #9333ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-note {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
opacity: 0.8;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 9. MOBILE RESPONSIVE --- */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.budget-tabs-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 15px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
padding: 10px 15px;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-btn {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sur mobile, les formulaires passent en 1 colonne */
|
||||||
|
.form-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- EN-TÊTE DES COLONNES MOIS (Optimisé) --- */
|
||||||
|
|
||||||
|
/* Conteneur principal dans le <th> */
|
||||||
|
.month-header-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* Empile texte et boutons */
|
||||||
|
align-items: center; /* Centre horizontalement */
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px; /* Espace entre le texte et les boutons */
|
||||||
|
min-width: 100px; /* Largeur min pour éviter l'écrasement */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Le texte du mois */
|
||||||
|
.month-name {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Conteneur des boutons */
|
||||||
|
.month-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
opacity: 0.5; /* Un peu transparent par défaut pour ne pas surcharger */
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* On affiche les boutons pleinement au survol de la colonne */
|
||||||
|
th:hover .month-actions {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style spécifique pour réduire la taille des boutons d'en-tête */
|
||||||
|
.month-actions .btn-icon-small {
|
||||||
|
font-size: 0.75rem; /* Icône plus petite */
|
||||||
|
padding: 2px 6px; /* Padding réduit */
|
||||||
|
height: 24px; /* Hauteur fixe et petite */
|
||||||
|
width: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-actions .btn-icon-small:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- LIGNES DYNAMIQUES (Modale Épargne) --- */
|
||||||
|
|
||||||
|
.savings-line-item {
|
||||||
|
display: flex; /* Flexbox pour aligner horizontalement */
|
||||||
|
align-items: center; /* Centrage vertical */
|
||||||
|
gap: 10px; /* Espace entre les éléments */
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* La catégorie prend tout l'espace disponible */
|
||||||
|
.savings-line-item .input-category-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Le montant a une largeur fixe pour être aligné proprement */
|
||||||
|
.savings-line-item .input-amount {
|
||||||
|
width: 110px !important; /* Force la largeur */
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Le bouton de suppression */
|
||||||
|
.savings-line-item .btn-remove {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
color: #ef4444; /* Rouge */
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.savings-line-item .btn-remove:hover {
|
||||||
|
background: #fef2f2;
|
||||||
|
border-color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- THÈMES NENS (POL & PEP) --- */
|
||||||
|
|
||||||
|
/* Style du Titre pour Pol (Bleu) */
|
||||||
|
.nens-title.theme-pol {
|
||||||
|
color: #1e3a8a !important; /* Bleu foncé */
|
||||||
|
background: #eaf2ff !important; /* Bleu très pâle */
|
||||||
|
border-left: 4px solid #1e3a8a !important;
|
||||||
|
border-radius: 0 8px 8px 0; /* Petit effet onglet */
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style du Tableau pour Pol */
|
||||||
|
.nens-table.theme-pol thead th {
|
||||||
|
background: #f5f9ff !important; /* Bleu glace */
|
||||||
|
color: #1e3a8a !important;
|
||||||
|
}
|
||||||
|
.nens-table.theme-pol {
|
||||||
|
border-top: 2px solid #bcd3ff !important; /* Bordure bleue */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style du Titre pour Pep (Vert) */
|
||||||
|
.nens-title.theme-pep {
|
||||||
|
color: #14532d !important; /* Vert foncé */
|
||||||
|
background: #eaf7ea !important; /* Vert très pâle */
|
||||||
|
border-left: 4px solid #14532d !important;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style du Tableau pour Pep */
|
||||||
|
.nens-table.theme-pep thead th {
|
||||||
|
background: #f6fdf6 !important; /* Vert glace */
|
||||||
|
color: #14532d !important;
|
||||||
|
}
|
||||||
|
.nens-table.theme-pep {
|
||||||
|
border-top: 2px solid #b9e3b9 !important; /* Bordure verte */
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
* Change l'état "payé/attente" d'un frais sans recharger la page
|
||||||
|
*/
|
||||||
|
function toggleCheck(id, isChecked) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("action", "toggle-check");
|
||||||
|
formData.append("id", id);
|
||||||
|
formData.append("status", isChecked ? 1 : 0);
|
||||||
|
|
||||||
|
fetch("/modules/budget/includes/api/manage-item.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) throw new Error("Erreur réseau");
|
||||||
|
// Optionnel : on pourrait actualiser un petit label ici
|
||||||
|
console.log("Statut mis à jour pour l'item " + id);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
alert("Erreur lors de la mise à jour");
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime un item après confirmation
|
||||||
|
*/
|
||||||
|
function deleteItem(id) {
|
||||||
|
if (confirm("Voulez-vous vraiment supprimer cet élément ?")) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("action", "delete");
|
||||||
|
formData.append("id", id);
|
||||||
|
|
||||||
|
fetch("/modules/budget/includes/api/manage-item.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}).then(() => {
|
||||||
|
window.location.reload(); // On recharge pour mettre à jour les totaux
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ouvre la modal et pré-remplit les champs pour la modification
|
||||||
|
*/
|
||||||
|
function editItem(item) {
|
||||||
|
// Si 'item' arrive sous forme de string JSON depuis l'attribut HTML
|
||||||
|
const data = typeof item === "string" ? JSON.parse(item) : item;
|
||||||
|
|
||||||
|
document.getElementById("modalTitle").innerText = "Modifier : " + data.name;
|
||||||
|
document.getElementById("item_id").value = data.id;
|
||||||
|
document.getElementById("item_name").value = data.name;
|
||||||
|
document.getElementById("item_amount").value = data.amount;
|
||||||
|
document.getElementById("item_category").value = data.category;
|
||||||
|
document.getElementById("item_type").value = data.type;
|
||||||
|
document.getElementById("item_day").value = data.payment_day;
|
||||||
|
document.getElementById("item_reg_month").value = data.reg_month || "";
|
||||||
|
document.getElementById("item_is_estimate").value = data.is_estimate;
|
||||||
|
|
||||||
|
document.getElementById("budgetModal").style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal(mode) {
|
||||||
|
if (mode === "add") {
|
||||||
|
document.getElementById("modalTitle").innerText = "Ajouter un élément";
|
||||||
|
document.getElementById("item_id").value = "";
|
||||||
|
document.querySelector("#budgetModal form").reset();
|
||||||
|
}
|
||||||
|
document.getElementById("budgetModal").style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
document.getElementById("budgetModal").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable globale pour stocker les catégories existantes (passées par PHP)
|
||||||
|
let knownCategories = [];
|
||||||
|
|
||||||
|
function openSavingsModal(mode, owner) {
|
||||||
|
document.getElementById("savingsModal").style.display = "flex";
|
||||||
|
document.getElementById("sav_owner").value = owner; // On stocke le owner dans le form caché
|
||||||
|
|
||||||
|
const container = document.getElementById("linesContainer");
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
if (mode === "add") {
|
||||||
|
document.getElementById("savingsModalTitle").innerText =
|
||||||
|
"Nouveau pour " + owner;
|
||||||
|
document.getElementById("sav_date").valueAsDate = new Date();
|
||||||
|
document.getElementById("sav_total").value = "";
|
||||||
|
// Par défaut, une ligne vide
|
||||||
|
createLine("", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function editMonth(monthDate, owner, dataValues, allCats) {
|
||||||
|
document.getElementById("savingsModal").style.display = "flex";
|
||||||
|
document.getElementById("savingsModalTitle").innerText =
|
||||||
|
"Modifier " + owner + " (" + monthDate + ")";
|
||||||
|
document.getElementById("sav_owner").value = owner;
|
||||||
|
document.getElementById("sav_date").value = monthDate;
|
||||||
|
|
||||||
|
knownCategories = allCats || [];
|
||||||
|
const container = document.getElementById("linesContainer");
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
// Total
|
||||||
|
if (dataValues["TOTAL_BANQUE"]) {
|
||||||
|
document.getElementById("sav_total").value = dataValues["TOTAL_BANQUE"];
|
||||||
|
delete dataValues["TOTAL_BANQUE"];
|
||||||
|
} else {
|
||||||
|
document.getElementById("sav_total").value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lignes existantes
|
||||||
|
for (const [category, amount] of Object.entries(dataValues)) {
|
||||||
|
createLine(category, amount);
|
||||||
|
}
|
||||||
|
createLine("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNewLine() {
|
||||||
|
createLine("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une ligne HTML dans la modale : [ Nom de la catégorie ] [ Montant ] [ X ]
|
||||||
|
*/
|
||||||
|
function createLine(name, amount) {
|
||||||
|
const container = document.getElementById("linesContainer");
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.className = "savings-line-item";
|
||||||
|
const listId = "list_" + Math.random().toString(36).substr(2, 9);
|
||||||
|
|
||||||
|
div.innerHTML = `
|
||||||
|
<div class="input-category-wrapper">
|
||||||
|
<input type="text" name="cat_names[]" class="pf-input" placeholder="Catégorie" value="${name}" list="${listId}">
|
||||||
|
<datalist id="${listId}">${knownCategories.map((c) => `<option value="${c}">`).join("")}</datalist>
|
||||||
|
</div>
|
||||||
|
<input type="number" step="0.01" name="cat_amounts[]" class="pf-input input-amount" placeholder="0.00" value="${amount}">
|
||||||
|
<button type="button" class="btn-remove" onclick="this.parentElement.remove()" title="Supprimer">×</button>
|
||||||
|
`;
|
||||||
|
container.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime une entrée spécifique (Mois + Catégorie) sans recharger la page (si possible)
|
||||||
|
*/
|
||||||
|
function deleteSavingsEntry(monthDate, category, owner) {
|
||||||
|
if (!confirm(`Supprimer le montant pour "${category}" ?`)) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("action", "delete_entry");
|
||||||
|
formData.append("month_date", monthDate);
|
||||||
|
formData.append("category", category);
|
||||||
|
formData.append("owner", owner);
|
||||||
|
|
||||||
|
fetch("/modules/budget/includes/api/save-savings.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}).then(() => window.location.reload());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplique le dernier mois vers le mois suivant
|
||||||
|
*/
|
||||||
|
function duplicateLastMonth(lastMonthDate, owner) {
|
||||||
|
let dateObj = new Date(lastMonthDate);
|
||||||
|
dateObj.setMonth(dateObj.getMonth() + 1);
|
||||||
|
let nextMonthStr = dateObj.toISOString().split("T")[0];
|
||||||
|
|
||||||
|
let newTotal = prompt(
|
||||||
|
`Dupliquer pour ${owner} vers ${nextMonthStr} ?\n\nNouveau TOTAL :`,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newTotal !== null && newTotal.trim() !== "") {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("action", "duplicate_month");
|
||||||
|
formData.append("source_date", lastMonthDate);
|
||||||
|
formData.append("target_date", nextMonthStr);
|
||||||
|
formData.append("new_total", newTotal);
|
||||||
|
formData.append("owner", owner);
|
||||||
|
|
||||||
|
fetch("/modules/budget/includes/api/save-savings.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((d) => {
|
||||||
|
if (d.success) window.location.reload();
|
||||||
|
else alert(d.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime toutes les données d'un mois pour le propriétaire actuel
|
||||||
|
*/
|
||||||
|
function deleteEntireMonth(monthDate, owner) {
|
||||||
|
if (!confirm(`Supprimer TOUT le mois de ${monthDate} pour ${owner} ?`))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("action", "delete_month_global");
|
||||||
|
formData.append("month_date", monthDate);
|
||||||
|
formData.append("owner", owner);
|
||||||
|
|
||||||
|
fetch("/modules/budget/includes/api/save-savings.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}).then(() => window.location.reload());
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../../../../includes/auth.php';
|
||||||
|
require __DIR__ . '/../../../../includes/db.php'; // On suppose que $pdo est défini ici
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
// --- ACTION : SAUVEGARDER (AJOUT OU MODIF) ---
|
||||||
|
if ($action === 'save') {
|
||||||
|
$id = !empty($_POST['id']) ? $_POST['id'] : null;
|
||||||
|
$name = $_POST['name'];
|
||||||
|
$amount = str_replace(',', '.', $_POST['amount']);
|
||||||
|
$type = $_POST['type'];
|
||||||
|
$day = !empty($_POST['payment_day']) ? $_POST['payment_day'] : null;
|
||||||
|
$month = $_POST['reg_month'] ?: null;
|
||||||
|
$cat = $_POST['category'];
|
||||||
|
$is_estimate = isset($_POST['is_estimate']) ? (int)$_POST['is_estimate'] : 0;
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
// Update : on ajoute is_estimate
|
||||||
|
$sql = "UPDATE pf_budget_items SET name=?, amount=?, type=?, payment_day=?, reg_month=?, category=?, is_estimate=? WHERE id=?";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$name, $amount, $type, $day, $month, $cat, $is_estimate, $id]);
|
||||||
|
} else {
|
||||||
|
// Insert : on ajoute is_estimate
|
||||||
|
$sql = "INSERT INTO pf_budget_items (name, amount, type, payment_day, reg_month, category, is_estimate) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$name, $amount, $type, $day, $month, $cat, $is_estimate]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirection après sauvegarde classique
|
||||||
|
header('Location: /budget.php?tab=recap');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ACTION : COCHER/DÉCOCHER RAPIDE (VIA JS FETCH) ---
|
||||||
|
if ($action === 'toggle-check') {
|
||||||
|
$id = $_POST['id'];
|
||||||
|
$status = $_POST['status']; // 1 ou 0
|
||||||
|
|
||||||
|
$sql = "UPDATE pf_budget_items SET is_checked = ? WHERE id = ?";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$status, $id]);
|
||||||
|
|
||||||
|
// On renvoie du JSON car c'est un appel JS (pas de redirection)
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ACTION : SUPPRIMER ---
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$id = $_POST['id'];
|
||||||
|
$pdo->prepare("DELETE FROM pf_budget_items WHERE id = ?")->execute([$id]);
|
||||||
|
|
||||||
|
// On peut répondre en JSON pour le JS ou rediriger
|
||||||
|
if (isset($_POST['ajax'])) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
header('Location: /budget.php?tab=recap');
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../../../../includes/auth.php';
|
||||||
|
require __DIR__ . '/../../../../includes/db.php';
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
// --- ACTION : SUPPRESSION D'UNE ENTRÉE UNIQUE ---
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete_entry') {
|
||||||
|
$owner = $_POST['owner'];
|
||||||
|
$redirectTab = $_POST['redirect_tab'] ?? $owner;
|
||||||
|
$date = $_POST['month_date'];
|
||||||
|
$cat = $_POST['category'];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM pf_savings WHERE owner = ? AND month_date = ? AND category = ?");
|
||||||
|
$stmt->execute([$owner, $date, $cat]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ACTION : DUPLICATION DE MOIS ---
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'duplicate_month') {
|
||||||
|
$owner = $_POST['owner'];
|
||||||
|
$sourceDate = $_POST['source_date'];
|
||||||
|
$targetDate = $_POST['target_date'];
|
||||||
|
$newTotal = floatval($_POST['new_total']);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// 1. Vérifier si le mois cible existe déjà
|
||||||
|
$check = $pdo->prepare("SELECT id FROM pf_savings WHERE owner = ? AND month_date = ? LIMIT 1");
|
||||||
|
$check->execute([$owner, $targetDate]);
|
||||||
|
if ($check->fetch()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Ce mois existe déjà !']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Insérer le nouveau TOTAL BANQUE
|
||||||
|
$stmtIns = $pdo->prepare("INSERT INTO pf_savings (owner, month_date, category, amount) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmtIns->execute([$owner, $targetDate, 'TOTAL_BANQUE', $newTotal]);
|
||||||
|
|
||||||
|
// 3. Copier toutes les catégories (sauf TOTAL_BANQUE) du mois source
|
||||||
|
// On insère directement avec une requête INSERT SELECT
|
||||||
|
$sqlCopy = "INSERT INTO pf_savings (owner, month_date, category, amount)
|
||||||
|
SELECT owner, ?, category, amount
|
||||||
|
FROM pf_savings
|
||||||
|
WHERE owner = ? AND month_date = ? AND category != 'TOTAL_BANQUE'";
|
||||||
|
|
||||||
|
$stmtCopy = $pdo->prepare($sqlCopy);
|
||||||
|
$stmtCopy->execute([$targetDate, $owner, $sourceDate]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ACTION : SUPPRESSION GLOBALE D'UN MOIS ---
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete_month_global') {
|
||||||
|
$owner = $_POST['owner'];
|
||||||
|
$date = $_POST['month_date'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM pf_savings WHERE owner = ? AND month_date = ?");
|
||||||
|
$stmt->execute([$owner, $date]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ACTION : SAUVEGARDE CLASSIQUE (MODALE) ---
|
||||||
|
// (Le code précédent reste ici pour la sauvegarde via le formulaire classique)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$owner = $_POST['owner'];
|
||||||
|
$dateInput = $_POST['month_date'];
|
||||||
|
$dateObj = new DateTime($dateInput);
|
||||||
|
$monthDate = $dateObj->format('Y-m-01');
|
||||||
|
$values = $_POST['values'] ?? []; // Pour la modale standard
|
||||||
|
|
||||||
|
// ... (Garde ton code précédent de sauvegarde ici) ...
|
||||||
|
// Note : Ajoute ce bloc TRY CATCH si tu ne l'avais pas déjà
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
$stmtDel = $pdo->prepare("DELETE FROM pf_savings WHERE owner = ? AND month_date = ?");
|
||||||
|
$stmtDel->execute([$owner, $monthDate]);
|
||||||
|
|
||||||
|
$stmtIns = $pdo->prepare("INSERT INTO pf_savings (owner, month_date, category, amount) VALUES (?, ?, ?, ?)");
|
||||||
|
foreach ($values as $category => $amount) {
|
||||||
|
$amount = floatval($amount);
|
||||||
|
if ($amount > 0 || $category === 'TOTAL_BANQUE') {
|
||||||
|
$stmtIns->execute([$owner, $monthDate, $category, $amount]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$pdo->commit();
|
||||||
|
header("Location: /budget.php?tab=epargne&owner=$redirectTab");
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
die("Erreur : " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
// 1. Gestion des propriétaires à afficher
|
||||||
|
$requestedOwner = $_GET['owner'] ?? 'Nens';
|
||||||
|
|
||||||
|
// Si l'onglet est "Nens", on affiche Pol et Pep. Sinon, on affiche juste la personne demandée.
|
||||||
|
$ownersToDisplay = ($requestedOwner === 'Nens') ? ['Pol', 'Pep'] : [$requestedOwner];
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="budget-view">
|
||||||
|
|
||||||
|
<div class="view-header">
|
||||||
|
<div class="owner-tabs">
|
||||||
|
<a href="?tab=epargne&owner=Alex" class="owner-tab <?= $requestedOwner === 'Alex' ? 'active' : '' ?>">Alex</a>
|
||||||
|
<a href="?tab=epargne&owner=Laia" class="owner-tab <?= $requestedOwner === 'Laia' ? 'active' : '' ?>">Laia</a>
|
||||||
|
<a href="?tab=epargne&owner=Nens" class="owner-tab <?= $requestedOwner === 'Nens' ? 'active' : '' ?>">Nens 👶</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php foreach ($ownersToDisplay as $currentOwner):
|
||||||
|
// --- Récupération des données pour $currentOwner ---
|
||||||
|
$stmt = $pdo->prepare("SELECT month_date, category, amount FROM pf_savings WHERE owner = ? ORDER BY month_date DESC");
|
||||||
|
$stmt->execute([$currentOwner]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
$months = [];
|
||||||
|
$allCategories = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$m = $row['month_date'];
|
||||||
|
$cat = $row['category'];
|
||||||
|
$val = $row['amount'];
|
||||||
|
$data[$m][$cat] = $val;
|
||||||
|
if (!in_array($m, $months)) $months[] = $m;
|
||||||
|
if ($cat !== 'TOTAL_BANQUE' && !in_array($cat, $allCategories)) $allCategories[] = $cat;
|
||||||
|
}
|
||||||
|
$months = array_slice($months, 0, 7); // 7 derniers mois
|
||||||
|
sort($allCategories);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; margin-top: <?= ($requestedOwner === 'Nens' && $currentOwner !== 'Pol') ? '40px' : '0' ?>;">
|
||||||
|
<div style="flex-grow: 1;">
|
||||||
|
<?php if ($requestedOwner === 'Nens'):
|
||||||
|
// On détermine la classe CSS basée sur le nom (pol ou pep)
|
||||||
|
$themeClass = 'theme-' . strtolower($currentOwner);
|
||||||
|
?>
|
||||||
|
<h3 class="nens-title <?= $themeClass ?>" style="margin:0; font-size:1.2rem;">
|
||||||
|
<?= $currentOwner ?>
|
||||||
|
</h3>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 10px; align-items: center;">
|
||||||
|
<?php if (!empty($months)): ?>
|
||||||
|
<button onclick="duplicateLastMonth('<?= $months[0] ?>', '<?= $currentOwner ?>')" class="pf-btn btn-secondary">
|
||||||
|
🔁 +1 Mois
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<button onclick="openSavingsModal('add', '<?= $currentOwner ?>')" class="pf-btn">
|
||||||
|
+ Saisir un mois
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive" style="background:white; border-radius:16px; box-shadow:var(--shadow-sm); border:1px solid #e2e8f0;">
|
||||||
|
<?php if (empty($months)): ?>
|
||||||
|
<div style="padding: 30px; text-align: center; color: #64748b;">
|
||||||
|
<p>Aucune donnée pour <?= htmlspecialchars($currentOwner) ?>.</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="pf-table savings-table nens-table theme-<?= strtolower($currentOwner) ?>" style="margin-top:0; box-shadow:none; border-radius:16px;"> <thead>
|
||||||
|
<tr>
|
||||||
|
<th class="sticky-col" style="background:#f8fafc;">Poste / Mois</th>
|
||||||
|
<?php foreach ($months as $month): ?>
|
||||||
|
<th>
|
||||||
|
<div class="month-header-container">
|
||||||
|
<span class="month-name"><?= date('M Y', strtotime($month)) ?></span>
|
||||||
|
<div class="month-actions">
|
||||||
|
<button class="btn-icon-small" title="Modifier"
|
||||||
|
onclick='editMonth("<?= $month ?>", "<?= $currentOwner ?>", <?= json_encode($data[$month] ?? []) ?>, <?= json_encode($allCategories) ?>)'>
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
<button class="btn-icon-small" title="Supprimer"
|
||||||
|
onclick="deleteEntireMonth('<?= $month ?>', '<?= $currentOwner ?>')"
|
||||||
|
style="color: #ef4444; border-color: #fca5a5; background: #fef2f2;">
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="row-total">
|
||||||
|
<td class="sticky-col"><strong>Total</strong></td>
|
||||||
|
<?php foreach ($months as $month): ?>
|
||||||
|
<td class="text-center font-bold" style="color: #2563eb;">
|
||||||
|
<?= number_format($data[$month]['TOTAL_BANQUE'] ?? 0, 0, ',', ' ') ?> €
|
||||||
|
</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<?php foreach ($allCategories as $cat): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="sticky-col"><?= htmlspecialchars($cat) ?></td>
|
||||||
|
<?php foreach ($months as $month): $amount = $data[$month][$cat] ?? 0; ?>
|
||||||
|
<td class="text-center text-muted">
|
||||||
|
<?php if ($amount != 0): ?>
|
||||||
|
<div class="cell-content">
|
||||||
|
<span>- <?= number_format($amount, 0, ',', ' ') ?> €</span>
|
||||||
|
<button class="btn-cell-delete"
|
||||||
|
onclick="deleteSavingsEntry('<?= $month ?>', '<?= htmlspecialchars($cat, ENT_QUOTES) ?>', '<?= $currentOwner ?>')">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php else: ?> - <?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<tr class="row-extres">
|
||||||
|
<td class="sticky-col"><strong>Extra</strong></td>
|
||||||
|
<?php foreach ($months as $month):
|
||||||
|
$total = $data[$month]['TOTAL_BANQUE'] ?? 0;
|
||||||
|
$sum = 0;
|
||||||
|
foreach ($allCategories as $cat) $sum += ($data[$month][$cat] ?? 0);
|
||||||
|
$extra = $total - $sum;
|
||||||
|
?>
|
||||||
|
<td class="text-center font-bold" style="color: <?= $extra >= 0 ? '#10b981' : '#ef4444' ?>">
|
||||||
|
<?= number_format($extra, 0, ',', ' ') ?> €
|
||||||
|
</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?> </div>
|
||||||
|
|
||||||
|
<div id="savingsModal" class="pf-modal">
|
||||||
|
<div class="pf-modal-content" style="max-width: 500px;">
|
||||||
|
<h3 id="savingsModalTitle" class="pf-modal-title">Saisir le mois</h3>
|
||||||
|
|
||||||
|
<form action="/modules/budget/includes/api/save-savings.php" method="POST" id="savingsForm">
|
||||||
|
<input type="hidden" name="owner" id="sav_owner">
|
||||||
|
|
||||||
|
<input type="hidden" name="redirect_tab" value="<?= $requestedOwner ?>">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="pf-label">Mois concerné</label>
|
||||||
|
<input type="date" name="month_date" id="sav_date" required class="pf-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="pf-label">Total (€)</label>
|
||||||
|
<input type="number" step="0.01" name="values[TOTAL_BANQUE]" id="sav_total" required class="pf-input" style="font-weight:bold; color:#2563eb;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="separator" style="margin: 20px 0; border-bottom: 1px solid #eee;"></div>
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
|
||||||
|
<h4 style="margin:0; font-size:0.9rem; color:#64748b;">Ventilation</h4>
|
||||||
|
<button type="button" class="btn-icon" onclick="addNewLine()" title="Ajouter une ligne" style="background:#e2e8f0;">+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="linesContainer" style="max-height: 300px; overflow-y: auto; padding-right:5px; display:flex; flex-direction:column; gap:8px;"></div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" onclick="document.getElementById('savingsModal').style.display='none'" class="pf-btn btn-secondary">Annuler</button>
|
||||||
|
<button type="submit" class="pf-btn">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<?php
|
||||||
|
// 1. Récupération des données
|
||||||
|
$stmt = $pdo->query("SELECT * FROM pf_budget_items ORDER BY category DESC, sort_order ASC, name ASC");
|
||||||
|
$items = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$totalDepensesMensuelles = 0;
|
||||||
|
$totalRevenusMensuels = 0;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="budget-view">
|
||||||
|
<div class="view-header">
|
||||||
|
<h2>Récapitulatif Mensuel</h2>
|
||||||
|
<button onclick="openModal('add')" class="pf-btn">+ Ajouter un frais / revenu</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="pf-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nom</th>
|
||||||
|
<th>Montant</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Jour</th>
|
||||||
|
<th>État prélèvement</th>
|
||||||
|
<th>Régularisation</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($items as $item):
|
||||||
|
// Calcul des totaux (Mensuel uniquement)
|
||||||
|
if ($item['type'] === 'Mensuel') {
|
||||||
|
if ($item['category'] === 'expense') {
|
||||||
|
$totalDepensesMensuelles += $item['amount'];
|
||||||
|
} else {
|
||||||
|
$totalRevenusMensuels += $item['amount'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowClass = ($item['category'] === 'income') ? 'row-income' : 'row-expense';
|
||||||
|
if ($item['is_estimate']) $rowClass .= ' row-estimate';
|
||||||
|
?>
|
||||||
|
<tr class="<?= $rowClass ?>">
|
||||||
|
<td>
|
||||||
|
<strong><?= htmlspecialchars($item['name']) ?></strong>
|
||||||
|
<?= $item['is_estimate'] ? ' <small>(Est.)</small>' : '' ?>
|
||||||
|
</td>
|
||||||
|
<td class="cell-amount">
|
||||||
|
<?= number_format($item['amount'], 2, ',', ' ') ?> €
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge-type <?= strtolower($item['type']) ?>">
|
||||||
|
<?= $item['type'] ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td><?= $item['payment_day'] ? $item['payment_day'] : '-' ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<input type="checkbox"
|
||||||
|
<?= $item['is_checked'] ? 'checked' : '' ?>
|
||||||
|
onclick="toggleCheck(<?= $item['id'] ?>, this.checked)"
|
||||||
|
title="Marquer comme payé">
|
||||||
|
<?= $item['is_checked'] ? ' <span class="text-success">Payé</span>' : ' <span style="color:var(--warning)">Attente</span>' ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em><?= htmlspecialchars($item['reg_month'] ?: '-') ?></em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn-icon" onclick='editItem(<?= htmlspecialchars(json_encode($item), ENT_QUOTES, 'UTF-8') ?>)' title="Modifier">✏️</button>
|
||||||
|
<button class="btn-icon" onclick="deleteItem(<?= $item['id'] ?>)" title="Supprimer">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1"><strong>Total Revenus Mensuels</strong></td>
|
||||||
|
<td colspan="6" class="text-success"><strong>+ <?= number_format($totalRevenusMensuels, 2, ',', ' ') ?> €</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="1"><strong>Total Dépenses Mensuelles</strong></td>
|
||||||
|
<td colspan="6" class="text-danger"><strong>- <?= number_format($totalDepensesMensuelles, 2, ',', ' ') ?> €</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 2px solid #e2e8f0; background: white;">
|
||||||
|
<td colspan="1"><strong>Équilibre du compte</strong></td>
|
||||||
|
<?php $balance = $totalRevenusMensuels - $totalDepensesMensuelles; ?>
|
||||||
|
<td colspan="6" style="font-size: 1.3em;" class="<?= $balance >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||||
|
<strong><?= number_format($balance, 2, ',', ' ') ?> € / mois</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="budget-note">
|
||||||
|
<p>* Note : Les frais de type "Annuel" sont affichés pour information mais ne sont pas inclus dans le calcul de l'équilibre mensuel.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="budgetModal" class="pf-modal">
|
||||||
|
<div class="pf-modal-content">
|
||||||
|
<h3 id="modalTitle" class="pf-modal-title">Ajouter un élément</h3>
|
||||||
|
|
||||||
|
<form action="/modules/budget/includes/api/manage-item.php" method="POST">
|
||||||
|
<input type="hidden" name="action" value="save">
|
||||||
|
<input type="hidden" name="id" id="item_id">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="pf-label">Nom</label>
|
||||||
|
<input type="text" name="name" id="item_name" required class="pf-input" placeholder="Ex: Loyer, Salaire...">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div>
|
||||||
|
<label class="pf-label">Montant (€)</label>
|
||||||
|
<input type="number" step="0.01" name="amount" id="item_amount" required class="pf-input">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="pf-label">Jour (1-31)</label>
|
||||||
|
<input type="number" min="1" max="31" name="payment_day" id="item_day" class="pf-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div>
|
||||||
|
<label class="pf-label">Catégorie</label>
|
||||||
|
<select name="category" id="item_category" class="pf-input">
|
||||||
|
<option value="expense">Dépense (Frais)</option>
|
||||||
|
<option value="income">Revenu (Salaire)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="pf-label">Fréquence</label>
|
||||||
|
<select name="type" id="item_type" class="pf-input">
|
||||||
|
<option value="Mensuel">Mensuel</option>
|
||||||
|
<option value="Annuel">Annuel</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div>
|
||||||
|
<label class="pf-label">Type de montant</label>
|
||||||
|
<select name="is_estimate" id="item_is_estimate" class="pf-input">
|
||||||
|
<option value="0">Fixe (Facture)</option>
|
||||||
|
<option value="1">Variable (Estimation)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="pf-label">Régularisation</label>
|
||||||
|
<select name="reg_month" id="item_reg_month" class="pf-input">
|
||||||
|
<option value="">Aucune</option>
|
||||||
|
<?php
|
||||||
|
$mois = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
|
||||||
|
foreach($mois as $m) echo "<option value='$m'>$m</option>";
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" onclick="closeModal()" class="pf-btn btn-secondary">Annuler</button>
|
||||||
|
<button type="submit" class="pf-btn">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -466,6 +466,29 @@
|
|||||||
background: #fef2f2;
|
background: #fef2f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
/* Sur écran tactile, on désactive l'effet de masquage */
|
||||||
|
.cl-gift-amount {
|
||||||
|
/* Option A: On garde le prix visible */
|
||||||
|
opacity: 1 !important;
|
||||||
|
transform: scale(1) !important;
|
||||||
|
margin-right: 30px; /* Faire de la place pour le bouton supprimer */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-gift-actions {
|
||||||
|
/* On rend les actions toujours visibles et clicables */
|
||||||
|
opacity: 1 !important;
|
||||||
|
transform: scale(1) !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
background: rgba(
|
||||||
|
255,
|
||||||
|
255,
|
||||||
|
255,
|
||||||
|
0.8
|
||||||
|
); /* Fond pour lisibilité si superposition */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* --- 8. BUDGET & TRICOUNT --- */
|
/* --- 8. BUDGET & TRICOUNT --- */
|
||||||
.pf-section--panel {
|
.pf-section--panel {
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
|||||||
Reference in New Issue
Block a user