budget 2026
This commit is contained in:
+22
-7
@@ -8,8 +8,10 @@ $tab = $_GET['tab'] ?? 'recap';
|
|||||||
|
|
||||||
$pageTitle = "PachaFamily - Budget";
|
$pageTitle = "PachaFamily - Budget";
|
||||||
$activePage = "budget";
|
$activePage = "budget";
|
||||||
|
// Note: budget.css est conservé pour le style global,
|
||||||
|
// mais budget.js n'est plus requis si tu l'as supprimé comme prévu.
|
||||||
$pageCss = "/modules/budget/budget.css";
|
$pageCss = "/modules/budget/budget.css";
|
||||||
$pageJs = "/modules/budget/budget.js";
|
// $pageJs = "/modules/budget/budget.js"; // Ligne commentée ou supprimée
|
||||||
|
|
||||||
require __DIR__ . '/header.php';
|
require __DIR__ . '/header.php';
|
||||||
?>
|
?>
|
||||||
@@ -24,6 +26,11 @@ require __DIR__ . '/header.php';
|
|||||||
<span>Récapitulatif</span>
|
<span>Récapitulatif</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="?tab=budget_prev" class="tab-item <?= $tab == 'budget_prev' ? 'active' : '' ?>">
|
||||||
|
<span class="tab-icon">🎯</span>
|
||||||
|
<span>Budget 2026</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href="?tab=suivi" class="tab-item <?= $tab == 'suivi' ? 'active' : '' ?>">
|
<a href="?tab=suivi" class="tab-item <?= $tab == 'suivi' ? 'active' : '' ?>">
|
||||||
<span class="tab-icon">🗓️</span>
|
<span class="tab-icon">🗓️</span>
|
||||||
<span>Suivi Mensuel</span>
|
<span>Suivi Mensuel</span>
|
||||||
@@ -34,21 +41,29 @@ require __DIR__ . '/header.php';
|
|||||||
<span>Épargne</span>
|
<span>Épargne</span>
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="pf-section">
|
<section class="pf-section">
|
||||||
<?php
|
<?php
|
||||||
// On définit le chemin relatif par rapport à la racine du projet
|
// ROUTAGE DYNAMIQUE
|
||||||
|
// Sécurisation basique pour éviter d'inclure n'importe quoi
|
||||||
|
$allowedTabs = ['recap', 'suivi', 'epargne', 'budget_prev'];
|
||||||
|
|
||||||
|
if (in_array($tab, $allowedTabs)) {
|
||||||
$viewPath = __DIR__ . "/modules/budget/views/" . $tab . ".php";
|
$viewPath = __DIR__ . "/modules/budget/views/" . $tab . ".php";
|
||||||
|
|
||||||
if (file_exists($viewPath)) {
|
if (file_exists($viewPath)) {
|
||||||
include $viewPath;
|
include $viewPath;
|
||||||
} else {
|
} else {
|
||||||
// Petit debug pour voir où PHP cherche exactement le fichier
|
echo "<div style='text-align:center; padding:50px; color:#ef4444;'>";
|
||||||
echo "<p>Erreur : Le fichier est introuvable dans " . htmlspecialchars($viewPath) . "</p>";
|
echo "<h3>Oups ! Fichier introuvable.</h3>";
|
||||||
echo "<p>Onglet en cours de développement...</p>";
|
echo "<p>Vérifiez que le fichier <code>views/" . htmlspecialchars($tab) . ".php</code> existe bien.</p>";
|
||||||
|
echo "</div>";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "<p>Page non autorisée.</p>";
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<?php require __DIR__ . '/footer.php'; ?>
|
<?php require __DIR__ . '/footer.php'; ?>
|
||||||
+303
-234
@@ -4,9 +4,9 @@
|
|||||||
:root {
|
:root {
|
||||||
--primary: #2563eb;
|
--primary: #2563eb;
|
||||||
--primary-hover: #1d4ed8;
|
--primary-hover: #1d4ed8;
|
||||||
--success: #10b981; /* Vert */
|
--success: #10b981;
|
||||||
--danger: #ef4444; /* Rouge */
|
--danger: #ef4444;
|
||||||
--warning: #f59e0b; /* Jaune */
|
--warning: #f59e0b;
|
||||||
|
|
||||||
--bg-page: #f1f5f9;
|
--bg-page: #f1f5f9;
|
||||||
--bg-card: #ffffff;
|
--bg-card: #ffffff;
|
||||||
@@ -46,7 +46,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* En-tête de la vue */
|
|
||||||
.view-header {
|
.view-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -55,15 +54,48 @@
|
|||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-header h2 {
|
.view-header h2 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #0f172a; /* Très sombre, quasi noir */
|
color: #0f172a;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nens-title.theme-pol {
|
||||||
|
color: #1e3a8a !important;
|
||||||
|
background: #eaf2ff !important;
|
||||||
|
border-left: 4px solid #1e3a8a !important;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nens-table.theme-pol thead th {
|
||||||
|
background: #f5f9ff !important;
|
||||||
|
color: #1e3a8a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nens-table.theme-pol {
|
||||||
|
border-top: 2px solid #bcd3ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nens-title.theme-pep {
|
||||||
|
color: #14532d !important;
|
||||||
|
background: #eaf7ea !important;
|
||||||
|
border-left: 4px solid #14532d !important;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nens-table.theme-pep thead th {
|
||||||
|
background: #f6fdf6 !important;
|
||||||
|
color: #14532d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nens-table.theme-pep {
|
||||||
|
border-top: 2px solid #b9e3b9 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- 3. NAVIGATION TABS (Pills) --- */
|
/* --- 3. NAVIGATION TABS (Pills) --- */
|
||||||
.budget-tabs-container {
|
.budget-tabs-container {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -75,7 +107,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -89,32 +120,27 @@
|
|||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item:hover {
|
.tab-item:hover {
|
||||||
background-color: var(--bg-page);
|
background-color: var(--bg-page);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item.active {
|
.tab-item.active {
|
||||||
background: var(--primary);
|
background: var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-icon {
|
.tab-icon {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sous-onglets propriétaire (Alex/Laia) */
|
|
||||||
.owner-tabs {
|
.owner-tabs {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
background: #e2e8f0;
|
background: #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owner-tab {
|
.owner-tab {
|
||||||
padding: 6px 20px;
|
padding: 6px 20px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -123,28 +149,35 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owner-tab.active {
|
.owner-tab.active {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- 4. BOUTONS --- */
|
/* --- 4. BOUTONS UNIFIÉS (Global) --- */
|
||||||
|
|
||||||
|
/* Style de Base (Taille Standard : 40px) */
|
||||||
.pf-btn {
|
.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;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
height: 40px; /* Hauteur Fixe */
|
||||||
|
padding: 0 24px;
|
||||||
|
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 50px;
|
||||||
|
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-btn:hover {
|
.pf-btn:hover {
|
||||||
@@ -152,22 +185,23 @@
|
|||||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Variante Secondaire (Fond Blanc) */
|
||||||
.pf-btn.btn-secondary {
|
.pf-btn.btn-secondary {
|
||||||
background: var(--bg-card);
|
background: white;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
border: 1px solid #cbd5e1;
|
border: 1px solid #cbd5e1;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-btn.btn-secondary:hover {
|
.pf-btn.btn-secondary:hover {
|
||||||
background: var(--bg-page);
|
background: #f8fafc;
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
border-color: #94a3b8;
|
border-color: #94a3b8;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon,
|
/* Boutons Icônes seuls */
|
||||||
.btn-icon-small {
|
.btn-icon {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -175,27 +209,41 @@
|
|||||||
border-radius: var(--radius-s);
|
border-radius: var(--radius-s);
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
.btn-icon-small {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-icon:hover,
|
.btn-icon:hover:hover {
|
||||||
.btn-icon-small:hover {
|
|
||||||
background: #e2e8f0;
|
background: #e2e8f0;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions groupées */
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.month-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-actions .btn-icon-small {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 2px 6px;
|
||||||
|
height: 24px;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/* --- 5. TABLEAU MODERNE --- */
|
/* --- 5. TABLEAU MODERNE --- */
|
||||||
.pf-table {
|
.pf-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -206,7 +254,6 @@
|
|||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-table th {
|
.pf-table th {
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
@@ -219,14 +266,12 @@
|
|||||||
border-bottom: 1px solid #e2e8f0;
|
border-bottom: 1px solid #e2e8f0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-table td {
|
.pf-table td {
|
||||||
padding: 3px 16px; /* Ajusté pour être moins haut */
|
padding: 3px 16px;
|
||||||
border-bottom: 1px solid var(--bg-page);
|
border-bottom: 1px solid var(--bg-page);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-table tbody tr:last-child td {
|
.pf-table tbody tr:last-child td {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
@@ -234,7 +279,6 @@
|
|||||||
background-color: #f8fafc;
|
background-color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styles spécifiques aux lignes */
|
|
||||||
.row-income td:first-child {
|
.row-income td:first-child {
|
||||||
border-left: 4px solid var(--success);
|
border-left: 4px solid var(--success);
|
||||||
}
|
}
|
||||||
@@ -249,16 +293,6 @@
|
|||||||
font-style: italic;
|
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 {
|
.table-responsive {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
@@ -271,12 +305,10 @@
|
|||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cellules spéciales */
|
|
||||||
.cell-amount {
|
.cell-amount {
|
||||||
font-family: "Consolas", "Monaco", monospace;
|
font-family: "Consolas", "Monaco", monospace;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-content {
|
.cell-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -286,35 +318,41 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bouton supprimer au survol */
|
.savings-table .row-extres td {
|
||||||
|
background: #fffbeb;
|
||||||
|
border-top: 2px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton supprimer au survol (Style Epargne) */
|
||||||
.btn-cell-delete {
|
.btn-cell-delete {
|
||||||
display: none;
|
display: none;
|
||||||
background: rgba(239, 68, 68, 0.1);
|
background: rgba(239, 68, 68, 0.1);
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: 20px;
|
width: 24px;
|
||||||
height: 20px;
|
height: 24px;
|
||||||
font-size: 14px;
|
font-size: 1.1rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -5px;
|
right: 5px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-cell-delete:hover {
|
.btn-cell-delete:hover {
|
||||||
background: var(--danger);
|
background: var(--danger);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:hover .btn-cell-delete {
|
td:hover .btn-cell-delete {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer du tableau */
|
|
||||||
.pf-table tfoot {
|
.pf-table tfoot {
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
border-top: 2px solid #e2e8f0;
|
border-top: 2px solid #e2e8f0;
|
||||||
@@ -332,7 +370,6 @@ td:hover .btn-cell-delete {
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-input {
|
.pf-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
@@ -344,26 +381,22 @@ td:hover .btn-cell-delete {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-input:focus {
|
.pf-input:focus {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
border-color: var(--primary);
|
border-color: var(--primary);
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox Custom */
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
@@ -378,12 +411,10 @@ input[type="checkbox"] {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"]:checked {
|
input[type="checkbox"]:checked {
|
||||||
background-color: var(--success);
|
background-color: var(--success);
|
||||||
border-color: var(--success);
|
border-color: var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"]:checked::after {
|
input[type="checkbox"]:checked::after {
|
||||||
content: "✓";
|
content: "✓";
|
||||||
color: white;
|
color: white;
|
||||||
@@ -391,6 +422,17 @@ input[type="checkbox"]:checked::after {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Suppression flèches input number (Cross-browser) */
|
||||||
|
input[type="number"]::-webkit-outer-spin-button,
|
||||||
|
input[type="number"]::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- 7. MODALE --- */
|
/* --- 7. MODALE --- */
|
||||||
.pf-modal {
|
.pf-modal {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -399,12 +441,11 @@ input[type="checkbox"]:checked::after {
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
background: rgba(15, 23, 42, 0.6);
|
background: rgba(15, 23, 42, 0.6);
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
-webkit-backdrop-filter: blur(4px); /* Safari support */
|
-webkit-backdrop-filter: blur(4px);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-modal-content {
|
.pf-modal-content {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -418,7 +459,6 @@ input[type="checkbox"]:checked::after {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes modalPop {
|
@keyframes modalPop {
|
||||||
from {
|
from {
|
||||||
transform: scale(0.95) translateY(10px);
|
transform: scale(0.95) translateY(10px);
|
||||||
@@ -429,7 +469,6 @@ input[type="checkbox"]:checked::after {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-modal-title {
|
.pf-modal-title {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
@@ -439,7 +478,6 @@ input[type="checkbox"]:checked::after {
|
|||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -462,7 +500,6 @@ input[type="checkbox"]:checked::after {
|
|||||||
.font-bold {
|
.font-bold {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-type {
|
.badge-type {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
@@ -479,7 +516,6 @@ input[type="checkbox"]:checked::after {
|
|||||||
background: #f3e8ff;
|
background: #f3e8ff;
|
||||||
color: #9333ea;
|
color: #9333ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
.budget-note {
|
.budget-note {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
@@ -496,7 +532,6 @@ input[type="checkbox"]:checked::after {
|
|||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -504,196 +539,230 @@ input[type="checkbox"]:checked::after {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-header {
|
.view-header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-btn {
|
.pf-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-table {
|
.pf-table {
|
||||||
display: block;
|
display: block;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sur mobile, les formulaires passent en 1 colonne */
|
|
||||||
.form-row {
|
.form-row {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- EN-TÊTE DES COLONNES MOIS (Optimisé) --- */
|
/* --- 10. BUDGET PREVISIONNEL (TIMELINE 2025) --- */
|
||||||
|
|
||||||
/* Conteneur principal dans le <th> */
|
.prev-container {
|
||||||
.month-header-container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column; /* Empile texte et boutons */
|
flex-direction: column;
|
||||||
align-items: center; /* Centre horizontalement */
|
gap: 30px;
|
||||||
justify-content: center;
|
|
||||||
gap: 6px; /* Espace entre le texte et les boutons */
|
|
||||||
min-width: 100px; /* Largeur min pour éviter l'écrasement */
|
|
||||||
}
|
}
|
||||||
|
.prev-section-header {
|
||||||
/* Le texte du mois */
|
display: flex;
|
||||||
.month-name {
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.prev-section-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--text-main);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--text-main);
|
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
line-height: 1.2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Conteneur des boutons */
|
/* Inputs "Invisible" */
|
||||||
.month-actions {
|
.prev-input {
|
||||||
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%;
|
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;
|
border: 1px solid transparent;
|
||||||
color: #ef4444; /* Rouge */
|
background: transparent;
|
||||||
cursor: pointer;
|
text-align: center;
|
||||||
font-size: 1.2rem;
|
font-family: inherit;
|
||||||
line-height: 1;
|
font-size: 0.9rem;
|
||||||
padding: 4px 8px;
|
color: var(--text-main);
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 4px 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.prev-input:hover,
|
||||||
|
.prev-input:focus {
|
||||||
|
background: #eef2ff;
|
||||||
|
border-color: #6366f1;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.prev-input.bold-blue {
|
||||||
|
color: #0284c7;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tableaux Config & Alloc */
|
||||||
|
.prev-salary-table {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 900px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: var(--radius-l);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.prev-salary-table th {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.prev-salary-table th:first-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.prev-salary-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-timeline-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: var(--radius-l);
|
||||||
|
background: var(--bg-card);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.prev-alloc-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
.prev-alloc-table th {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-right: 1px solid #f1f5f9;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-alloc-table td {
|
||||||
|
padding: 3px 14px;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
border-right: 1px solid #f1f5f9;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Colonne Sticky */
|
||||||
|
.col-sticky {
|
||||||
|
position: sticky !important;
|
||||||
|
left: 0 !important;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: var(--bg-card);
|
||||||
|
border-right: 2px solid #e2e8f0;
|
||||||
|
text-align: left !important;
|
||||||
|
min-width: 220px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
.col-sticky.header-cell {
|
||||||
|
top: 0;
|
||||||
|
z-index: 30 !important;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.col-sticky small {
|
||||||
|
display: block;
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* En-têtes Mois */
|
||||||
|
.th-month {
|
||||||
|
background: #1e293b !important;
|
||||||
|
color: white !important;
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
border-left: 2px solid white;
|
||||||
|
padding: 8px !important;
|
||||||
|
}
|
||||||
|
.th-month.current {
|
||||||
|
background: var(--primary) !important;
|
||||||
|
}
|
||||||
|
.th-sub {
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lignes */
|
||||||
|
.row-total td {
|
||||||
|
background: #f8fafc;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #334155;
|
||||||
|
border-top: 2px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.row-restant td {
|
||||||
|
background: #eff6ff;
|
||||||
|
font-weight: 700;
|
||||||
|
border-bottom: 2px solid #bfdbfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Couleurs */
|
||||||
|
.txt-alex {
|
||||||
|
color: #0891b2;
|
||||||
|
}
|
||||||
|
.txt-laia {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
.txt-global {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 700;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
.val-ok {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
.val-ko {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation */
|
||||||
|
.nav-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
gap: 5px;
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
.btn-nav {
|
||||||
.savings-line-item .btn-remove:hover {
|
background: white;
|
||||||
background: #fef2f2;
|
border: 1px solid #cbd5e1;
|
||||||
border-color: #fca5a5;
|
padding: 4px 10px;
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: 0.2s;
|
||||||
}
|
}
|
||||||
|
.btn-nav:hover {
|
||||||
/* --- 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 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Styles spécifiques Suivi Mensuel */
|
|
||||||
|
|
||||||
/* Animation d'apparition */
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollbar fine pour les listes */
|
|
||||||
.cat-card div::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
.cat-card div::-webkit-scrollbar-track {
|
|
||||||
background: #f1f5f9;
|
background: #f1f5f9;
|
||||||
}
|
color: var(--text-main);
|
||||||
.cat-card div::-webkit-scrollbar-thumb {
|
border-color: #94a3b8;
|
||||||
background: #cbd5e1;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.cat-card div::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #94a3b8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Input datalist chrome fix */
|
|
||||||
input::-webkit-calendar-picker-indicator {
|
|
||||||
opacity: 100;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../../../../includes/auth.php';
|
||||||
|
require __DIR__ . '/../../../../includes/db.php';
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
// 1. MISE A JOUR TABLEAU SALAIRES
|
||||||
|
if ($action === 'update_salary_config') {
|
||||||
|
$year = $_POST['year'];
|
||||||
|
$person = $_POST['person'];
|
||||||
|
$field = $_POST['field']; // salary, mensualite, etc.
|
||||||
|
$value = floatval($_POST['value']);
|
||||||
|
|
||||||
|
// Liste des champs autorisés pour éviter les injections
|
||||||
|
$allowed = ['salary', 'mensualite', 'frais_func', 'eco_perso', 'eco_family'];
|
||||||
|
if (!in_array($field, $allowed)) { echo json_encode(['error'=>'Champ invalide']); exit; }
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO pf_salary_config (year, person, $field) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE $field = VALUES($field)");
|
||||||
|
$stmt->execute([$year, $person, $value]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. MISE A JOUR TABLEAU REPARTITION
|
||||||
|
if ($action === 'update_allocation') {
|
||||||
|
$date = $_POST['month_date'];
|
||||||
|
$catId = $_POST['cat_id'];
|
||||||
|
$person = $_POST['person']; // 'amount_alex' ou 'amount_laia'
|
||||||
|
$value = floatval($_POST['value']);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO pf_alloc_values (month_date, cat_id, $person) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE $person = VALUES($person)");
|
||||||
|
$stmt->execute([$date, $catId, $value]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. GESTION DES CATEGORIES (Ajout / Suppression)
|
||||||
|
if ($action === 'add_category') {
|
||||||
|
$name = trim($_POST['name']);
|
||||||
|
$target = trim($_POST['target']);
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO pf_alloc_categories (name, target) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$name, $target]);
|
||||||
|
header("Location: /budget.php?tab=budget_prev"); exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete_category') {
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
$pdo->prepare("DELETE FROM pf_alloc_categories WHERE id = ?")->execute([$id]);
|
||||||
|
$pdo->prepare("DELETE FROM pf_alloc_values WHERE cat_id = ?")->execute([$id]); // Nettoyage valeurs
|
||||||
|
header("Location: /budget.php?tab=budget_prev"); exit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,359 @@
|
|||||||
|
<?php
|
||||||
|
// modules/budget/views/budget_prev.php
|
||||||
|
|
||||||
|
$currentYear = date('Y');
|
||||||
|
|
||||||
|
// 1. Récupération Config Salaires
|
||||||
|
$salaryConfig = [];
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM pf_salary_config WHERE year = ?");
|
||||||
|
$stmt->execute([$currentYear]);
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$salaryConfig[$row['person']] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init si vide
|
||||||
|
foreach (['Alex', 'Laia'] as $p) {
|
||||||
|
if (!isset($salaryConfig[$p])) {
|
||||||
|
$salaryConfig[$p] = ['salary'=>0, 'mensualite'=>0, 'frais_func'=>0, 'eco_perso'=>0, 'eco_family'=>0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Récupération Catégories
|
||||||
|
$cats = $pdo->query("SELECT * FROM pf_alloc_categories ORDER BY sort_order ASC, id ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 3. Gestion du mois FOCUS et Navigation
|
||||||
|
$focusDate = isset($_GET['focus_date']) ? $_GET['focus_date'] : date('Y-m-01');
|
||||||
|
$focusTs = strtotime($focusDate);
|
||||||
|
|
||||||
|
// On affiche 6 mois (Timeline inversée)
|
||||||
|
$months = [];
|
||||||
|
for ($i = 0; $i < 6; $i++) {
|
||||||
|
$months[] = date('Y-m-01', strtotime("-$i months", $focusTs));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liens navigation
|
||||||
|
$prevMonthLink = date('Y-m-01', strtotime("-1 month", $focusTs));
|
||||||
|
$nextMonthLink = date('Y-m-01', strtotime("+1 month", $focusTs));
|
||||||
|
|
||||||
|
// 4. Récupération Valeurs Répartition
|
||||||
|
$inQuery = implode(',', array_fill(0, count($months), '?'));
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM pf_alloc_values WHERE month_date IN ($inQuery)");
|
||||||
|
$stmt->execute($months);
|
||||||
|
|
||||||
|
$allocs = [];
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$allocs[$row['month_date']][$row['cat_id']] = $row;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="prev-container">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="prev-section-header">
|
||||||
|
<h2>Revenus <?= $currentYear ?></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="prev-salary-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Personne</th>
|
||||||
|
<th>Salaire</th>
|
||||||
|
<th>Mensualité<br><small style="font-weight:normal; text-transform:none;">(Cpt Commun)</small></th>
|
||||||
|
<th>Frais Func.</th>
|
||||||
|
<th>Eco Perso</th>
|
||||||
|
<th style="background:#f0f9ff; color:#0284c7;">Eco Family<br><small style="font-weight:normal; text-transform:none;">(Dispo)</small></th>
|
||||||
|
<th>Restant Perso</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach (['Alex', 'Laia'] as $p):
|
||||||
|
$d = $salaryConfig[$p];
|
||||||
|
$restant = $d['salary'] - ($d['mensualite'] + $d['frais_func'] + $d['eco_perso'] + $d['eco_family']);
|
||||||
|
$borderColor = ($p === 'Alex') ? '#0891b2' : '#f59e0b';
|
||||||
|
?>
|
||||||
|
<tr data-person="<?= $p ?>">
|
||||||
|
<td style="text-align:left; font-weight:bold; color:var(--text-main); border-left:4px solid <?= $borderColor ?>;">
|
||||||
|
<?= $p ?>
|
||||||
|
</td>
|
||||||
|
<td><input type="number" class="prev-input" data-field="salary" value="<?= round($d['salary']) ?>" onchange="updateSalary('<?= $p ?>', this)"></td>
|
||||||
|
<td><input type="number" class="prev-input" data-field="mensualite" value="<?= round($d['mensualite']) ?>" onchange="updateSalary('<?= $p ?>', this)"></td>
|
||||||
|
<td><input type="number" class="prev-input" data-field="frais_func" value="<?= round($d['frais_func']) ?>" onchange="updateSalary('<?= $p ?>', this)"></td>
|
||||||
|
<td><input type="number" class="prev-input" data-field="eco_perso" value="<?= round($d['eco_perso']) ?>" onchange="updateSalary('<?= $p ?>', this)"></td>
|
||||||
|
<td style="background:#f0f9ff;">
|
||||||
|
<input type="number" class="prev-input bold-blue" id="eco_family_<?= $p ?>" data-field="eco_family" value="<?= round($d['eco_family']) ?>" onchange="updateSalary('<?= $p ?>', this)">
|
||||||
|
</td>
|
||||||
|
<td id="restant_<?= $p ?>" style="font-weight:bold; color:var(--text-muted); text-align:center;">
|
||||||
|
<?= number_format($restant, 0, ',', ' ') ?> €
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="prev-section-header">
|
||||||
|
<div style="display:flex; gap:10px; align-items:center;">
|
||||||
|
<h2>Répartition Budgétaire</h2>
|
||||||
|
<div class="nav-group">
|
||||||
|
<a href="?tab=budget_prev&focus_date=<?= $prevMonthLink ?>" class="btn-nav">◀</a>
|
||||||
|
<a href="?tab=budget_prev&focus_date=<?= date('Y-m-01') ?>" class="btn-nav">Auj.</a>
|
||||||
|
<a href="?tab=budget_prev&focus_date=<?= $nextMonthLink ?>" class="btn-nav">▶</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; gap:10px;">
|
||||||
|
<button class="pf-btn btn-secondary" onclick="duplicateMonth()">
|
||||||
|
🔁 +1 Mois
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="pf-btn" onclick="document.getElementById('addCatModal').style.display='flex'">
|
||||||
|
+ Nouvelle Ligne
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="prev-timeline-wrapper">
|
||||||
|
<table class="prev-alloc-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-sticky header-cell"></th>
|
||||||
|
<?php foreach ($months as $month):
|
||||||
|
$isCurrent = ($month == date('Y-m-01'));
|
||||||
|
$cls = $isCurrent ? 'current' : '';
|
||||||
|
?>
|
||||||
|
<th colspan="3" class="th-month <?= $cls ?>">
|
||||||
|
<?= date('F Y', strtotime($month)) ?>
|
||||||
|
</th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="col-sticky header-cell">
|
||||||
|
|
||||||
|
</th>
|
||||||
|
<?php foreach ($months as $month): ?>
|
||||||
|
<th class="th-sub">Global</th>
|
||||||
|
<th class="th-sub txt-alex">Alex</th>
|
||||||
|
<th class="th-sub txt-laia">Laia</th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="row-total">
|
||||||
|
<td class="col-sticky" style="text-align:right !important;">Total</td>
|
||||||
|
<?php foreach ($months as $m): ?>
|
||||||
|
<td class="txt-global" style="border-left:2px solid #e2e8f0;" id="total_global_<?= $m ?>">0</td>
|
||||||
|
<td class="txt-alex" id="total_alex_<?= $m ?>">0</td>
|
||||||
|
<td class="txt-laia" id="total_laia_<?= $m ?>">0</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr class="row-restant">
|
||||||
|
<td class="col-sticky" style="text-align:right !important;">Restant (Eco Family)</td>
|
||||||
|
<?php foreach ($months as $m): ?>
|
||||||
|
<td style="border-left:2px solid #cbd5e1; background:#e2e8f0;">-</td>
|
||||||
|
<td class="val-ok" id="restant_alloc_alex_<?= $m ?>">0</td>
|
||||||
|
<td class="val-ok" id="restant_alloc_laia_<?= $m ?>">0</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<?php foreach ($cats as $cat): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="col-sticky" style="position:relative;">
|
||||||
|
<div style="font-weight:600; color:var(--text-main); "><?= htmlspecialchars($cat['name']) ?></div>
|
||||||
|
<div style="font-size:0.75rem; color:var(--text-muted); font-style:italic; text-align:right;"><?= htmlspecialchars($cat['target']) ?></div>
|
||||||
|
|
||||||
|
<a href="?tab=budget_prev&id=<?= $cat['id'] ?>&action=delete_category"
|
||||||
|
onclick="return confirm('Supprimer cette ligne et tout son historique ?')"
|
||||||
|
class="btn-cell-delete"
|
||||||
|
title="Supprimer la ligne">
|
||||||
|
×
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<?php foreach ($months as $m):
|
||||||
|
$val = $allocs[$m][$cat['id']] ?? ['amount_alex'=>0, 'amount_laia'=>0];
|
||||||
|
?>
|
||||||
|
<td class="txt-global" style="border-left:2px solid #e2e8f0;" id="g_<?= $m ?>_<?= $cat['id'] ?>">0</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<input type="number" step="1" class="prev-input txt-alex inp-alex-<?= $m ?>"
|
||||||
|
value="<?= $val['amount_alex'] == 0 ? '' : round($val['amount_alex']) ?>"
|
||||||
|
placeholder="-"
|
||||||
|
onchange="updateAlloc('<?= $m ?>', <?= $cat['id'] ?>, 'amount_alex', this)">
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<input type="number" step="1" class="prev-input txt-laia inp-laia-<?= $m ?>"
|
||||||
|
value="<?= $val['amount_laia'] == 0 ? '' : round($val['amount_laia']) ?>"
|
||||||
|
placeholder="-"
|
||||||
|
onchange="updateAlloc('<?= $m ?>', <?= $cat['id'] ?>, 'amount_laia', this)">
|
||||||
|
</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="addCatModal" class="pf-modal" style="display:none; position:fixed; inset:0; z-index:9999; background:rgba(15, 23, 42, 0.6); backdrop-filter:blur(4px); align-items:center; justify-content:center;">
|
||||||
|
<div class="pf-modal-content" style="background:white; width:95%; max-width:400px; border-radius:20px; box-shadow:0 20px 25px -5px rgba(0,0,0,0.1); padding:30px;">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||||
|
<h3 class="pf-modal-title" style="margin:0; border:none; font-size:1.2rem;">Nouvelle Ligne de Budget</h3>
|
||||||
|
<button onclick="document.getElementById('addCatModal').style.display='none'" style="border:none; background:none; font-size:1.5rem; cursor:pointer; color:var(--text-muted);">×</button>
|
||||||
|
</div>
|
||||||
|
<form action="/modules/budget/includes/api/save-budget.php" method="POST">
|
||||||
|
<input type="hidden" name="action" value="add_category">
|
||||||
|
<div class="form-group" style="margin-bottom:15px;">
|
||||||
|
<label class="pf-label">Nom (ex: Vacances)</label>
|
||||||
|
<input type="text" name="name" class="pf-input" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom:20px;">
|
||||||
|
<label class="pf-label">Cible (ex: vers L.Pol)</label>
|
||||||
|
<input type="text" name="target" class="pf-input" list="targets">
|
||||||
|
<datalist id="targets">
|
||||||
|
<option value="vers L.Pol">
|
||||||
|
<option value="vers L.Perso">
|
||||||
|
<option value="vers commune">
|
||||||
|
</datalist>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" onclick="document.getElementById('addCatModal').style.display='none'" class="pf-btn btn-secondary">Annuler</button>
|
||||||
|
<button type="submit" class="pf-btn">Ajouter</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const currentYear = <?= $currentYear ?>;
|
||||||
|
const months = <?= json_encode($months) ?>;
|
||||||
|
|
||||||
|
// --- SALAIRES ---
|
||||||
|
function updateSalary(person, input) {
|
||||||
|
const row = input.closest('tr');
|
||||||
|
const salary = parseFloat(row.querySelector('[data-field="salary"]').value) || 0;
|
||||||
|
const mens = parseFloat(row.querySelector('[data-field="mensualite"]').value) || 0;
|
||||||
|
const frais = parseFloat(row.querySelector('[data-field="frais_func"]').value) || 0;
|
||||||
|
const ecoP = parseFloat(row.querySelector('[data-field="eco_perso"]').value) || 0;
|
||||||
|
const ecoF = parseFloat(row.querySelector('[data-field="eco_family"]').value) || 0;
|
||||||
|
|
||||||
|
const restant = salary - (mens + frais + ecoP + ecoF);
|
||||||
|
// Affichage sans décimale
|
||||||
|
document.getElementById('restant_' + person).innerText = Math.round(restant).toLocaleString('fr-FR') + ' €';
|
||||||
|
|
||||||
|
saveData('update_salary_config', { year: currentYear, person: person, field: input.dataset.field, value: input.value });
|
||||||
|
recalcAllAllocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ALLOCATIONS ---
|
||||||
|
function updateAlloc(month, catId, personField, input) {
|
||||||
|
saveData('update_allocation', { month_date: month, cat_id: catId, person: personField, value: input.value || 0 });
|
||||||
|
recalcAllAllocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction utilitaire : Retourne le mois en MAJUSCULES (ex: "FÉVRIER")
|
||||||
|
function getMonthName(dateStr) {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return date.toLocaleDateString('fr-FR', { month: 'long' }).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function duplicateMonth() {
|
||||||
|
// months[0] = Le mois affiché le plus à gauche (Cible)
|
||||||
|
// months[1] = Le mois juste à droite (Source)
|
||||||
|
const targetDateStr = months[0];
|
||||||
|
const sourceDateStr = months[1];
|
||||||
|
|
||||||
|
if (!sourceDateStr) {
|
||||||
|
alert("Impossible : pas d'historique disponible pour copier.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetName = getMonthName(targetDateStr); // ex: MARS
|
||||||
|
const sourceName = getMonthName(sourceDateStr); // ex: FÉVRIER
|
||||||
|
|
||||||
|
// On utilise des tirets ou étoiles pour attirer l'attention à défaut de gras
|
||||||
|
const message = `Voulez-vous copier les valeurs de ${sourceName} vers ${targetName} ?\n\n` +
|
||||||
|
`Cela écrasera toutes les valeurs présentes sur ${targetName}.`;
|
||||||
|
|
||||||
|
if(!confirm(message)) return;
|
||||||
|
|
||||||
|
// Le reste du code de copie reste identique...
|
||||||
|
document.querySelectorAll('.inp-alex-' + sourceDateStr).forEach(sourceInput => {
|
||||||
|
const catIdMatch = sourceInput.getAttribute('onchange').match(/, (\d+),/);
|
||||||
|
if(!catIdMatch) return;
|
||||||
|
const catId = catIdMatch[1];
|
||||||
|
const row = sourceInput.closest('tr');
|
||||||
|
|
||||||
|
// Copie Alex
|
||||||
|
const valAlex = sourceInput.value;
|
||||||
|
const targetAlex = row.querySelector('.inp-alex-' + targetDateStr);
|
||||||
|
if(targetAlex) {
|
||||||
|
targetAlex.value = valAlex;
|
||||||
|
updateAlloc(targetDateStr, catId, 'amount_alex', targetAlex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copie Laia
|
||||||
|
const valLaia = row.querySelector('.inp-laia-' + sourceDateStr).value;
|
||||||
|
const targetLaia = row.querySelector('.inp-laia-' + targetDateStr);
|
||||||
|
if(targetLaia) {
|
||||||
|
targetLaia.value = valLaia;
|
||||||
|
updateAlloc(targetDateStr, catId, 'amount_laia', targetLaia);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalcAllAllocations() {
|
||||||
|
const budgetAlex = parseFloat(document.getElementById('eco_family_Alex').value) || 0;
|
||||||
|
const budgetLaia = parseFloat(document.getElementById('eco_family_Laia').value) || 0;
|
||||||
|
|
||||||
|
months.forEach(m => {
|
||||||
|
let sumAlex = 0;
|
||||||
|
let sumLaia = 0;
|
||||||
|
|
||||||
|
document.querySelectorAll('.inp-alex-' + m).forEach(inp => {
|
||||||
|
const val = parseFloat(inp.value) || 0;
|
||||||
|
sumAlex += val;
|
||||||
|
|
||||||
|
const row = inp.closest('tr');
|
||||||
|
const laiaVal = parseFloat(row.querySelector('.inp-laia-' + m).value) || 0;
|
||||||
|
const globalSum = val + laiaVal;
|
||||||
|
|
||||||
|
const onchangeStr = inp.getAttribute('onchange');
|
||||||
|
const matches = onchangeStr.match(/, (\d+),/);
|
||||||
|
if(matches && matches[1]) {
|
||||||
|
const gCell = document.getElementById('g_' + m + '_' + matches[1]);
|
||||||
|
if(gCell) gCell.innerText = globalSum > 0 ? Math.round(globalSum) : '-';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.inp-laia-' + m).forEach(inp => sumLaia += (parseFloat(inp.value) || 0));
|
||||||
|
|
||||||
|
// Totaux arrondis
|
||||||
|
document.getElementById('total_alex_' + m).innerText = Math.round(sumAlex) + ' €';
|
||||||
|
document.getElementById('total_laia_' + m).innerText = Math.round(sumLaia) + ' €';
|
||||||
|
document.getElementById('total_global_' + m).innerText = Math.round(sumAlex + sumLaia) + ' €';
|
||||||
|
|
||||||
|
const restAlex = budgetAlex - sumAlex;
|
||||||
|
const restLaia = budgetLaia - sumLaia;
|
||||||
|
|
||||||
|
const elRestAlex = document.getElementById('restant_alloc_alex_' + m);
|
||||||
|
const elRestLaia = document.getElementById('restant_alloc_laia_' + m);
|
||||||
|
|
||||||
|
elRestAlex.innerText = Math.round(restAlex) + ' €';
|
||||||
|
elRestAlex.className = 'val-' + (restAlex >= 0 ? 'ok' : 'ko');
|
||||||
|
|
||||||
|
elRestLaia.innerText = Math.round(restLaia) + ' €';
|
||||||
|
elRestLaia.className = 'val-' + (restLaia >= 0 ? 'ok' : 'ko');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveData(action, data) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('action', action);
|
||||||
|
for (const key in data) formData.append(key, data[key]);
|
||||||
|
fetch('/modules/budget/includes/api/save-budget.php', { method: 'POST', body: formData });
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', recalcAllAllocations);
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user