1 parent
c05802fc79
commit
21c9b37ae0
11 files changed
+2838
-621
No files matched your search
+196
-113
@@ -1,127 +1,210 @@
|
||||
/* ════════════════════════════════════════════════════════════════════
|
||||
dark-mode.css — Dark mode overrides (all modules)
|
||||
dark-mode.css — Dark mode overrides (Cleaned & Optimized)
|
||||
Chargé en dernier dans le head pour garantir la cascade.
|
||||
════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ─── HOME PAGE ─────────────────────────────────────────────────────────────── */
|
||||
/* ─── FONDS & CONTENEURS PRINCIPAUX ────────────────────────────────── */
|
||||
[data-theme="dark"] .pf-card,
|
||||
[data-theme="dark"] .pf-modal-content,
|
||||
[data-theme="dark"] .budget-tabs-container,
|
||||
[data-theme="dark"] .fc-header-row,
|
||||
[data-theme="dark"] .fc-month-nav-row,
|
||||
[data-theme="dark"] .fc-month-calendar-wrapper,
|
||||
[data-theme="dark"] .fc-month-balances,
|
||||
[data-theme="dark"] .hol-idea-card,
|
||||
[data-theme="dark"] .pf-holidays__titlebar,
|
||||
[data-theme="dark"] .hol-filters-row,
|
||||
[data-theme="dark"] .cl-titlebar,
|
||||
[data-theme="dark"] .pf-child-section,
|
||||
[data-theme="dark"] .pf-child-totals-bar,
|
||||
[data-theme="dark"] .pf-gift-card-compact,
|
||||
[data-theme="dark"] .epargne-grid-table,
|
||||
[data-theme="dark"] .sticky-col {
|
||||
background: var(--bg-panel) !important;
|
||||
border-color: var(--border-light) !important;
|
||||
color: var(--text-main) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .owner-tabs,
|
||||
[data-theme="dark"] .fc-view-controls,
|
||||
[data-theme="dark"] .fc-day--other-month,
|
||||
[data-theme="dark"] .fc-minimal-balance-card,
|
||||
[data-theme="dark"] .pf-summary-pill,
|
||||
[data-theme="dark"] .pf-filter-bar {
|
||||
background: var(--bg-page) !important;
|
||||
border-color: var(--border-light) !important;
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
/* ─── TYPOGRAPHIE GLOBALE ──────────────────────────────────────────── */
|
||||
[data-theme="dark"] h1,
|
||||
[data-theme="dark"] h2,
|
||||
[data-theme="dark"] h3,
|
||||
[data-theme="dark"] .pf-modal-title,
|
||||
[data-theme="dark"] .pf-card-title,
|
||||
[data-theme="dark"] .hol-main-title,
|
||||
[data-theme="dark"] .pf-gift-title,
|
||||
[data-theme="dark"] .budget-view h2,
|
||||
[data-theme="dark"] .view-header h2,
|
||||
[data-theme="dark"] .month-header-container {
|
||||
color: var(--text-main) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pf-card-desc,
|
||||
[data-theme="dark"] .tab-item {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ─── INPUTS & SÉLECTEURS ──────────────────────────────────────────── */
|
||||
[data-theme="dark"] .pf-input,
|
||||
[data-theme="dark"] .pf-filter-select,
|
||||
[data-theme="dark"] .hol-year-select,
|
||||
[data-theme="dark"] .budget-view input,
|
||||
[data-theme="dark"] .budget-view select,
|
||||
[data-theme="dark"] .budget-view textarea,
|
||||
[data-theme="dark"] .hol-idea-card input,
|
||||
[data-theme="dark"] .hol-idea-card textarea,
|
||||
[data-theme="dark"] .hol-idea-card select {
|
||||
background: #1c2128 !important;
|
||||
border-color: var(--border-light) !important;
|
||||
color: var(--text-main) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pf-input::placeholder {
|
||||
color: #484f58 !important;
|
||||
}
|
||||
|
||||
/* ─── BOUTONS & ÉTATS ACTIFS ───────────────────────────────────────── */
|
||||
[data-theme="dark"] .pf-btn.btn-secondary,
|
||||
[data-theme="dark"] .fc-nav-button,
|
||||
[data-theme="dark"] .fc-view-button,
|
||||
[data-theme="dark"] .hol-map-toggle,
|
||||
[data-theme="dark"] .cl-view-btn,
|
||||
[data-theme="dark"] .pf-btn-icon-text,
|
||||
[data-theme="dark"] .owner-tab {
|
||||
background: var(--bg-panel) !important;
|
||||
color: var(--text-muted) !important;
|
||||
border-color: var(--border-light) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pf-btn.btn-secondary:hover,
|
||||
[data-theme="dark"] .fc-nav-button:hover,
|
||||
[data-theme="dark"] .hol-map-toggle:hover,
|
||||
[data-theme="dark"] .pf-btn-icon-text:hover {
|
||||
background: rgba(255, 255, 255, 0.06) !important;
|
||||
color: var(--text-main) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tab-item.active,
|
||||
[data-theme="dark"] .fc-view-button--active,
|
||||
[data-theme="dark"] .cl-view-btn.is-active {
|
||||
background: rgba(59, 130, 246, 0.15) !important;
|
||||
color: var(--primary) !important;
|
||||
border-color: rgba(59, 130, 246, 0.3) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .owner-tab.active {
|
||||
background: var(--primary) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* ─── TABLES ───────────────────────────────────────────────────────── */
|
||||
[data-theme="dark"] .pf-table,
|
||||
[data-theme="dark"] .fc-month-table,
|
||||
[data-theme="dark"] #planningTable-wrapper {
|
||||
background: var(--bg-panel) !important;
|
||||
color: var(--text-main);
|
||||
border-color: var(--border-light) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pf-table thead th,
|
||||
[data-theme="dark"] .fc-month-table th,
|
||||
[data-theme="dark"] #planningTable thead th,
|
||||
[data-theme="dark"] th.sticky-col,
|
||||
[data-theme="dark"] .pf-child-header {
|
||||
background: #1c2128 !important;
|
||||
color: var(--text-muted) !important;
|
||||
border-color: var(--border-light) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pf-table tbody td,
|
||||
[data-theme="dark"] .fc-month-table td,
|
||||
[data-theme="dark"] #planningTable tbody td {
|
||||
border-color: rgba(48, 54, 61, 0.5) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pf-table tbody tr:hover td,
|
||||
[data-theme="dark"] .fc-month-table td:hover:not(.fc-day--other-month) {
|
||||
background: rgba(255, 255, 255, 0.04) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pf-table tfoot td {
|
||||
background: #1c2128 !important;
|
||||
border-color: rgba(48, 54, 61, 0.5) !important;
|
||||
}
|
||||
|
||||
/* ─── MODULES SPÉCIFIQUES ──────────────────────────────────────────── */
|
||||
|
||||
/* Accueil */
|
||||
[data-theme="dark"] body[data-page="home"] .pf-module-card {
|
||||
background: rgba(22,27,34,.92) !important;
|
||||
border: 1px solid var(--border-light) !important;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,.4) !important;
|
||||
background: rgba(22, 27, 34, 0.92) !important;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
[data-theme="dark"] body[data-page="home"] .pf-module-card:hover {
|
||||
border-color: var(--primary) !important;
|
||||
}
|
||||
[data-theme="dark"] body[data-page="home"] .pf-card-title { color: var(--text-main); }
|
||||
[data-theme="dark"] body[data-page="home"] .pf-card-desc { color: var(--text-muted); }
|
||||
[data-theme="dark"] body[data-page="home"] .pf-card-cta { color: var(--primary); }
|
||||
|
||||
/* ─── SHARED / GLOBAL PATTERNS ──────────────────────────────────────────────── */
|
||||
[data-theme="dark"] .pf-card { background: var(--bg-panel) !important; border-color: var(--border-light) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .pf-table { background: var(--bg-panel); color: var(--text-main); }
|
||||
[data-theme="dark"] .pf-table thead th { background: #1c2128 !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-table tbody td { background: var(--bg-panel); color: var(--text-main); border-color: rgba(48,54,61,.5); }
|
||||
[data-theme="dark"] .pf-table tbody tr:hover td { background: rgba(255,255,255,.03); }
|
||||
[data-theme="dark"] .pf-table tfoot,
|
||||
[data-theme="dark"] .pf-table tfoot td { background: #1c2128; color: var(--text-main); border-color: rgba(48,54,61,.5); }
|
||||
[data-theme="dark"] .pf-modal-content { background: var(--bg-panel) !important; border: 1px solid var(--border-light) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .pf-modal-title { color: var(--text-main) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-input,
|
||||
[data-theme="dark"] .pf-filter-select,
|
||||
[data-theme="dark"] .hol-year-select { background: #1c2128 !important; border-color: var(--border-light) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .pf-input::placeholder { color: #484f58 !important; }
|
||||
[data-theme="dark"] .pf-btn.btn-secondary { background: var(--bg-panel) !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-btn.btn-secondary:hover { background: rgba(255,255,255,.06) !important; color: var(--text-main) !important; }
|
||||
/* Épargne & Grilles */
|
||||
[data-theme="dark"] .eg-header-row {
|
||||
background: #111827 !important;
|
||||
border-color: #30363d !important;
|
||||
color: #e6edf3 !important;
|
||||
}
|
||||
[data-theme="dark"] .eg-row:nth-child(even) {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
[data-theme="dark"] .eg-row:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* ─── BUDGET MODULE ─────────────────────────────────────────────────────────── */
|
||||
[data-theme="dark"] .budget-view { color: var(--text-main); }
|
||||
[data-theme="dark"] .budget-view h2,
|
||||
[data-theme="dark"] .view-header h2 { color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .budget-tabs-container { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .tab-item { background: var(--bg-panel); color: var(--text-muted); border-color: var(--border-light); }
|
||||
[data-theme="dark"] .tab-item:hover { background: rgba(255,255,255,.06); color: var(--text-main); }
|
||||
[data-theme="dark"] .tab-item.active { background: rgba(59,130,246,.15) !important; color: var(--primary) !important; border-color: rgba(59,130,246,.3) !important; }
|
||||
[data-theme="dark"] .owner-tabs { background: var(--bg-page); border-color: var(--border-light); }
|
||||
[data-theme="dark"] .owner-tab { background: var(--bg-panel) !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .owner-tab.active { background: var(--primary) !important; color: #fff !important; }
|
||||
[data-theme="dark"] .month-header-container { color: var(--text-main); }
|
||||
/* sticky col: uses var(--bg-panel) already but th.sticky-col uses hardcoded #f8fafc */
|
||||
[data-theme="dark"] th.sticky-col { background: #1c2128 !important; color: var(--text-muted) !important; }
|
||||
[data-theme="dark"] .sticky-col { background: var(--bg-panel) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .month-actions .btn-icon-small { background: #1c2128 !important; border-color: var(--border-light) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .month-actions .btn-icon-small:hover { background: rgba(255,255,255,.06) !important; }
|
||||
[data-theme="dark"] .savings-table .row-extres td { background: rgba(245,158,11,.08) !important; }
|
||||
[data-theme="dark"] .nens-title.theme-pol { background: rgba(30,58,138,.2) !important; color: #93c5fd !important; border-color: #3b82f6 !important; }
|
||||
[data-theme="dark"] .nens-title.theme-pep { background: rgba(20,83,45,.2) !important; color: #86efac !important; border-color: #22c55e !important; }
|
||||
[data-theme="dark"] .nens-table.theme-pol thead th { background: rgba(30,58,138,.15) !important; color: #93c5fd !important; }
|
||||
[data-theme="dark"] .nens-table.theme-pep thead th { background: rgba(20,83,45,.15) !important; color: #86efac !important; }
|
||||
/* Specific input/selects in budget */
|
||||
[data-theme="dark"] .budget-view input,
|
||||
[data-theme="dark"] .budget-view select,
|
||||
[data-theme="dark"] .budget-view textarea { background: #1c2128 !important; border-color: var(--border-light) !important; color: var(--text-main) !important; }
|
||||
/* sum selected highlight */
|
||||
[data-theme="dark"] .sum-selected { background: rgba(59,130,246,.1) !important; }
|
||||
/* Éléments colorés spécifiques */
|
||||
[data-theme="dark"] .savings-table .row-extres td,
|
||||
[data-theme="dark"] .eg-extres-row {
|
||||
background: rgba(245, 158, 11, 0.08) !important;
|
||||
}
|
||||
|
||||
/* ─── FAMILY CALENDAR MODULE ────────────────────────────────────────────────── */
|
||||
[data-theme="dark"] .pf-family-calendar { color: var(--text-main); }
|
||||
[data-theme="dark"] .fc-header-row { background: var(--bg-panel); border-color: var(--border-light); }
|
||||
[data-theme="dark"] .fc-view-controls { background: var(--bg-page) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .fc-view-button { background: var(--bg-panel) !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .fc-view-button--active { background: rgba(59,130,246,.15) !important; color: var(--primary) !important; border-color: rgba(59,130,246,.3) !important; }
|
||||
[data-theme="dark"] .fc-month-nav-row { background: var(--bg-panel); border-color: var(--border-light); }
|
||||
[data-theme="dark"] .fc-month-nav-row h3 { color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .fc-nav-button { background: var(--bg-panel) !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .fc-nav-button:hover { background: rgba(255,255,255,.06) !important; }
|
||||
/* Calendar table — lots of !important in source */
|
||||
[data-theme="dark"] .fc-month-table { border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .fc-month-table th { background: #1c2128 !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .fc-month-table td { background: var(--bg-panel) !important; border-color: rgba(48,54,61,.5) !important; }
|
||||
[data-theme="dark"] .fc-month-table td:hover:not(.fc-day--other-month) { background: rgba(255,255,255,.04) !important; }
|
||||
[data-theme="dark"] .fc-day--other-month { background: var(--bg-page) !important; color: var(--text-muted) !important; }
|
||||
[data-theme="dark"] .fc-month-calendar-wrapper,
|
||||
[data-theme="dark"] .fc-month-balances { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .fc-minimal-balance-card { background: var(--bg-page) !important; border-color: var(--border-light) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .fc-min-chip { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .fc-burn-alert { background: rgba(239,68,68,.1) !important; color: #f87171 !important; }
|
||||
/* Planning table (vue planning) */
|
||||
[data-theme="dark"] #planningTable-wrapper { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] #planningTable thead th { background: #1c2128 !important; color: var(--text-muted) !important; }
|
||||
[data-theme="dark"] #planningTable tbody td { border-color: rgba(48,54,61,.5) !important; }
|
||||
[data-theme="dark"] #planningTable tbody td.col-sticky-mois,
|
||||
[data-theme="dark"] #planningTable tbody td.col-sticky-sem { background: var(--bg-panel) !important; }
|
||||
[data-theme="dark"] .col-month { background: var(--bg-panel) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .fc-week-nav-controls { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-btn-icon-text { background: var(--bg-panel) !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-btn-icon-text:hover { background: rgba(255,255,255,.06) !important; }
|
||||
[data-theme="dark"] .nens-title.theme-pol {
|
||||
background: rgba(30, 58, 138, 0.2) !important;
|
||||
color: #93c5fd !important;
|
||||
border-color: #3b82f6 !important;
|
||||
}
|
||||
[data-theme="dark"] .nens-table.theme-pol thead th {
|
||||
background: rgba(30, 58, 138, 0.15) !important;
|
||||
color: #93c5fd !important;
|
||||
}
|
||||
|
||||
/* ─── HOLIDAYS MODULE ───────────────────────────────────────────────────────── */
|
||||
[data-theme="dark"] .pf-holidays { color: var(--text-main); }
|
||||
[data-theme="dark"] .pf-holidays__titlebar { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .hol-main-title { color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .hol-idea-card { background: var(--bg-panel) !important; border-color: var(--border-light) !important; color: var(--text-main) !important; box-shadow: var(--shadow) !important; }
|
||||
[data-theme="dark"] .hol-idea-card:hover { border-color: var(--primary) !important; }
|
||||
[data-theme="dark"] .hol-idea-card input,
|
||||
[data-theme="dark"] .hol-idea-card textarea,
|
||||
[data-theme="dark"] .hol-idea-card select { background: #1c2128 !important; border-color: var(--border-light) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .hol-filters-row { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .hol-map-toggle { background: var(--bg-panel) !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .hol-map-toggle:hover { background: rgba(255,255,255,.06) !important; }
|
||||
[data-theme="dark"] .hol-add-btn { background: var(--primary) !important; color: #fff !important; }
|
||||
[data-theme="dark"] .nens-title.theme-pep {
|
||||
background: rgba(20, 83, 45, 0.2) !important;
|
||||
color: #86efac !important;
|
||||
border-color: #22c55e !important;
|
||||
}
|
||||
[data-theme="dark"] .nens-table.theme-pep thead th {
|
||||
background: rgba(20, 83, 45, 0.15) !important;
|
||||
color: #86efac !important;
|
||||
}
|
||||
|
||||
/* ─── GIFT-LIST MODULE ──────────────────────────────────────────────────────── */
|
||||
[data-theme="dark"] .pf-gift-list { color: var(--text-main); }
|
||||
[data-theme="dark"] .pf-gift-list h1 { color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .cl-titlebar { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .cl-view-btn { background: var(--bg-panel) !important; color: var(--text-muted) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .cl-view-btn.is-active { background: rgba(59,130,246,.15) !important; color: var(--primary) !important; border-color: rgba(59,130,246,.3) !important; }
|
||||
[data-theme="dark"] .pf-filter-bar { background: var(--bg-page) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-filter-select { background: #1c2128 !important; color: var(--text-main) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-child-section { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-child-header { background: #1c2128 !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-child-header h3 { color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .pf-child-totals-bar { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-summary-pill { background: var(--bg-page) !important; border-color: var(--border-light) !important; color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .pf-gift-card-compact { background: var(--bg-panel) !important; border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .pf-gift-card-compact:hover { border-color: var(--primary) !important; }
|
||||
[data-theme="dark"] .pf-gift-title { color: var(--text-main) !important; }
|
||||
[data-theme="dark"] .pf-gift-footer { border-color: var(--border-light) !important; }
|
||||
[data-theme="dark"] .sum-selected {
|
||||
background: rgba(59, 130, 246, 0.1) !important;
|
||||
}
|
||||
[data-theme="dark"] .fc-burn-alert {
|
||||
background: rgba(239, 68, 68, 0.1) !important;
|
||||
color: #f87171 !important;
|
||||
}
|
||||
[data-theme="dark"] .month-actions .btn-icon-small {
|
||||
background: #1c2128 !important;
|
||||
border-color: var(--border-light) !important;
|
||||
color: var(--text-main) !important;
|
||||
}
|
||||
[data-theme="dark"] .month-actions .btn-icon-small:hover {
|
||||
background: rgba(255, 255, 255, 0.06) !important;
|
||||
}
|
||||
+482
@@ -445,6 +445,81 @@ body.pf-home .pf-mobile-close-btn {
|
||||
font-size: 0.95rem;
|
||||
color: var(--text-main);
|
||||
}
|
||||
/* --- PALETTES DE COULEURS CUSTOM --- */
|
||||
.pf-color-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.pf-color-trigger {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border-light);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.pf-color-trigger:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.pf-color-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
margin-top: 8px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 100;
|
||||
width: 180px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.pf-color-menu.is-open {
|
||||
display: flex;
|
||||
animation: pfModalPop 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.pf-color-swatch {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.pf-color-swatch:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
/* Palette Radio (Pour le formulaire d'ajout) */
|
||||
.pf-radio-swatch {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
.pf-radio-swatch:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.pf-radio-swatch:has(input[type="radio"]:checked) {
|
||||
transform: scale(1.1);
|
||||
box-shadow:
|
||||
0 0 0 2px var(--bg-panel),
|
||||
0 0 0 4px var(--text-main);
|
||||
}
|
||||
[data-theme="dark"] .pf-radio-swatch:has(input[type="radio"]:checked) {
|
||||
box-shadow:
|
||||
0 0 0 2px var(--bg-panel),
|
||||
0 0 0 4px var(--text-main);
|
||||
}
|
||||
|
||||
/* Encadré pointillé (code d’invitation, etc.) */
|
||||
.pf-dashed-panel {
|
||||
@@ -1326,3 +1401,410 @@ details[open].pf-accordion > .pf-accordion-summary::after {
|
||||
90deg
|
||||
); /* 90deg rend mieux sur une roue crantée qu'un 45deg ! */
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
UTILITAIRES GLOBAUX (Remplacement des styles en ligne)
|
||||
========================================================================== */
|
||||
.pf-mt-xs {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
.pf-mt-md {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.pf-mt-2 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.pf-mb-md {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.pf-m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
.pf-py-8-16 {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
.pf-input-sm {
|
||||
max-width: 120px;
|
||||
}
|
||||
.pf-flex-0 {
|
||||
flex: 0;
|
||||
}
|
||||
.pf-flex-1-5 {
|
||||
flex: 1.5;
|
||||
}
|
||||
.pf-flex-2 {
|
||||
flex: 2;
|
||||
}
|
||||
.pf-min-w-120 {
|
||||
min-width: 120px;
|
||||
}
|
||||
.pf-label-sm {
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.pf-flex-gap-12-center {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Composants Spécifiques Paramètres */
|
||||
.pf-empty-dashed {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border: 1px dashed var(--border-light);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.pf-border-left-dynamic {
|
||||
border-left: 4px solid var(--owner-color, var(--primary));
|
||||
}
|
||||
.pf-attached-form {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
background: var(--bg-soft);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed var(--border-light);
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.pf-input-color-sm {
|
||||
width: 28px !important;
|
||||
height: 28px !important;
|
||||
}
|
||||
/* --- SÉLECTEUR DE COULEUR (PALETTE) --- */
|
||||
.pf-color-palette {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.pf-color-swatch {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.2s;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pf-color-swatch:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
.pf-color-swatch.selected {
|
||||
transform: scale(1.15);
|
||||
border-color: var(--bg-panel);
|
||||
box-shadow: 0 0 0 3px var(--text-main); /* Un anneau extérieur pour marquer la sélection */
|
||||
}
|
||||
|
||||
/* Variante Dark Mode */
|
||||
[data-theme="dark"] .pf-color-swatch.selected {
|
||||
border-color: var(--bg-panel);
|
||||
box-shadow: 0 0 0 3px var(--text-main);
|
||||
}
|
||||
|
||||
/* --- PALETTE DE COULEURS CUSTOM (HouseHub) --- */
|
||||
.pf-palette-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.pf-palette-sm {
|
||||
gap: 6px;
|
||||
}
|
||||
.pf-color-swatch {
|
||||
position: relative;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
.pf-color-swatch:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.pf-color-swatch-sm {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.pf-color-swatch input[type="radio"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.pf-color-swatch:has(input[type="radio"]:checked) {
|
||||
box-shadow:
|
||||
0 0 0 2px var(--bg-panel),
|
||||
0 0 0 4px var(--primary);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
[data-theme="dark"] .pf-color-swatch:has(input[type="radio"]:checked) {
|
||||
box-shadow:
|
||||
0 0 0 2px var(--bg-panel),
|
||||
0 0 0 4px var(--primary);
|
||||
}
|
||||
|
||||
/* --- PROFILS RATTACHÉS (Settings) --- */
|
||||
.pf-attached-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
padding: 14px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.pf-attached-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.pf-attached-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.pf-attached-color-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--bg-page);
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.pf-attached-add-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
background: var(--bg-soft);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed var(--border-light);
|
||||
}
|
||||
.pf-attached-add-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.pf-attached-add-col {
|
||||
margin-bottom: 0;
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
.pf-attached-btn-add {
|
||||
align-self: flex-end;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* --- UTILS --- */
|
||||
.pf-mt-lg {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.pf-mb-lg {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.pf-mb-md {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.pf-m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
.pf-label-sm {
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.pf-empty-box {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border: 1px dashed var(--border-light);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Couleurs Prédéfinies */
|
||||
.pf-color-red {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
.pf-color-orange {
|
||||
background-color: #f97316;
|
||||
}
|
||||
.pf-color-amber {
|
||||
background-color: #f59e0b;
|
||||
}
|
||||
.pf-color-lime {
|
||||
background-color: #84cc16;
|
||||
}
|
||||
.pf-color-green {
|
||||
background-color: #22c55e;
|
||||
}
|
||||
.pf-color-teal {
|
||||
background-color: #14b8a6;
|
||||
}
|
||||
.pf-color-cyan {
|
||||
background-color: #06b6d4;
|
||||
}
|
||||
.pf-color-sky {
|
||||
background-color: #0ea5e9;
|
||||
}
|
||||
.pf-color-blue {
|
||||
background-color: #3b82f6;
|
||||
}
|
||||
.pf-color-indigo {
|
||||
background-color: #6366f1;
|
||||
}
|
||||
.pf-color-violet {
|
||||
background-color: #8b5cf6;
|
||||
}
|
||||
.pf-color-fuchsia {
|
||||
background-color: #d946ef;
|
||||
}
|
||||
.pf-color-rose {
|
||||
background-color: #f43f5e;
|
||||
}
|
||||
.pf-color-slate {
|
||||
background-color: #64748b;
|
||||
}
|
||||
|
||||
/* Popover Palette (Profils rattachés) */
|
||||
.pf-palette-dropdown {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.pf-palette-popover {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 120%;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
width: 200px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
z-index: 100;
|
||||
}
|
||||
.pf-palette-dropdown.is-open .pf-palette-popover {
|
||||
display: flex;
|
||||
animation: pfModalPop 0.2s ease-out;
|
||||
}
|
||||
.pf-profile-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.pf-profile-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.pf-m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
.pf-empty-dashed {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border: 1px dashed var(--border-light);
|
||||
border-radius: 8px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* --- UTILITAIRES & PROFILS RATTACHÉS (SETTINGS) --- */
|
||||
.pf-mt-lg {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.pf-mb-md {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.pf-mb-lg {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.pf-mb-0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.pf-m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pf-profile-color-note {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.pf-attached-card {
|
||||
border-left: 4px solid var(--owner-color, #3b82f6);
|
||||
}
|
||||
|
||||
.pf-attached-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pf-color-picker-sm {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
min-width: 24px !important;
|
||||
}
|
||||
|
||||
.pf-btn-delete-sm {
|
||||
font-size: 1.2rem;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.pf-empty-dashed {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border: 1px dashed var(--border-light);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.pf-attached-form {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
background: var(--bg-soft);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed var(--border-light);
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pf-flex-2 {
|
||||
flex: 2;
|
||||
min-width: 120px;
|
||||
}
|
||||
.pf-flex-1-5 {
|
||||
flex: 1.5;
|
||||
min-width: 120px;
|
||||
}
|
||||
.pf-col-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pf-label-sm {
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pf-btn-pad-sm {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
@@ -170,6 +170,109 @@ return [
|
||||
'set_care_modes' => 'Modalitats de cura habituals',
|
||||
'set_add_care_mode' => 'Afegir una modalitat',
|
||||
'btn_save' => 'Desar',
|
||||
'settings_profile_added' => 'Perfil de %s afegit amb èxit.',
|
||||
'settings_err_name_required' => 'El nom és obligatori.',
|
||||
'settings_attached_profiles_title' => 'Perfils adjunts',
|
||||
'settings_attached_profiles_desc' => 'Membres de la llar (Nens, mainaderes, familiars) sense accés directe.',
|
||||
'settings_no_attached_profiles' => 'Cap perfil adjunt.',
|
||||
'label_firstname' => 'Nom',
|
||||
'placeholder_firstname_ex' => 'Ex: Pol, Carole...',
|
||||
'label_role' => 'Rol',
|
||||
'role_child_icon' => '👶 Nen/a',
|
||||
'role_helper_icon' => '💼 Ajudant',
|
||||
'role_relative_icon' => '👵 Familiar',
|
||||
'role_unknown' => 'Desconegut',
|
||||
'label_color' => 'Color',
|
||||
'confirm_delete_profile' => 'Voleu eliminar definitivament aquest perfil?',
|
||||
'change_color' => 'Canviar el color',
|
||||
'color_updated' => 'Color actualitzat!',
|
||||
'set_success_color_profile' => 'Color del perfil actualitzat.',
|
||||
'set_success_color_update' => 'Color actualitzat!',
|
||||
'set_desc_profile_color' => 'Aquest color s\'utilitzarà per a les teves graelles de pressupost, calendaris i indicadors personals.',
|
||||
'placeholder_apple_id' => 'nom@icloud.com',
|
||||
'placeholder_apple_pwd' => 'xxxx-xxxx-xxxx-xxxx',
|
||||
|
||||
// --- SETTINGS ---
|
||||
'error_csrf' => 'Sessió invàlida (CSRF). Si us plau, recarrega la pàgina.',
|
||||
'set_err_min_module' => 'Has de mantenir almenys un mòdul actiu.',
|
||||
'set_success_modules' => 'Mòduls actualitzats amb èxit.',
|
||||
'set_success_lang' => 'Idioma actualitzat.',
|
||||
'set_err_empty_name' => 'El nom no pot estar buit.',
|
||||
'set_success_profile' => 'Perfil actualitzat amb èxit.',
|
||||
'set_err_wrong_pwd' => 'Contrasenya actual incorrecta.',
|
||||
'set_err_pwd_length' => 'La nova contrasenya ha de tenir almenys 6 caràcters.',
|
||||
'set_err_pwd_match' => 'Les contrasenyes no coincideixen.',
|
||||
'set_success_pwd' => 'Contrasenya canviada amb èxit.',
|
||||
'set_success_family' => 'Nom de l\'espai actualitzat.',
|
||||
'set_success_invite' => 'Nou codi d\'invitació generat.',
|
||||
'set_err_bg_format' => 'Format no compatible (jpg, png, webp).',
|
||||
'set_success_bg' => 'Imatge d\'inici actualitzada.',
|
||||
'set_err_upload' => 'Error durant la càrrega.',
|
||||
'set_success_bg_reset' => 'Imatge d\'inici restablerta.',
|
||||
'set_err_db_unavailable' => 'Base de dades familiar no disponible.',
|
||||
'set_err_ios_missing' => 'Si us plau, proporciona un ID d\'iCloud, contrasenya d\'aplicació i URL CalDAV.',
|
||||
'set_success_ios' => 'Connexió de calendari iOS desada.',
|
||||
'set_success_ios_auto' => ' URL del calendari detectada automàticament.',
|
||||
'set_err_ios_save' => 'No es pot desar la connexió iOS: ',
|
||||
'set_err_ios_none' => 'Cap connexió iOS configurada.',
|
||||
'set_success_ios_test' => 'Connexió iCloud CalDAV vàlida (HTTP %s).',
|
||||
'set_err_ios_test' => 'La prova de connexió ha fallat (HTTP %s). Revisa les teves credencials.',
|
||||
'set_err_ios_test_fail' => 'Prova de connexió no disponible: ',
|
||||
'set_success_ios_del' => 'Connexió iOS eliminada.',
|
||||
'set_success_profile_add' => 'Perfil de %s afegit amb èxit.',
|
||||
'set_err_name_required' => 'El nom és obligatori.',
|
||||
'set_success_profile_del' => 'Perfil suprimit.',
|
||||
'set_title_page' => '⚙️ Configuració',
|
||||
'set_title_lang' => '🌐 Idioma',
|
||||
'set_title_modules' => '🧩 Mòduls Actius',
|
||||
'set_desc_modules' => 'Tria els mòduls visibles a la navegació (compartits amb tots els membres de l\'espai).',
|
||||
'set_title_ios' => '📱 Integració de Calendari iOS (CalDAV)',
|
||||
'set_desc_ios' => 'Configura aquí el teu calendari iCloud per sincronitzar els esdeveniments creats a HouseHub.',
|
||||
'set_label_apple_id' => 'ID d\'Apple (correu iCloud)',
|
||||
'set_label_apple_pwd' => 'Contrasenya d\'aplicació d\'Apple',
|
||||
'set_label_apple_url' => 'URL del calendari CalDAV',
|
||||
'set_btn_test_ios' => 'Prova la connexió',
|
||||
'set_btn_disconnect_ios' => 'Desconnecta',
|
||||
'set_title_bg' => '🖼️ Imatge d\'Inici',
|
||||
'set_desc_bg' => 'Foto de fons mostrada a la pàgina d\'inici (compartida amb tots els membres).',
|
||||
'set_badge_custom_bg' => 'Imatge personalitzada',
|
||||
'set_label_new_bg' => 'Nova imatge (jpg, png, webp — màx 10 MB)',
|
||||
'set_btn_reset_bg' => '🔄 Restableix la imatge per defecte',
|
||||
'set_confirm_reset_bg' => 'Tornar a la imatge per defecte?',
|
||||
'set_title_profile' => '👤 El meu Perfil',
|
||||
'set_label_username' => 'Nom d\'usuari',
|
||||
'set_label_display_name' => 'Nom / Àlies mostrat',
|
||||
'set_label_profile_color' => 'Color temàtic del meu perfil',
|
||||
'set_desc_profile_color' => 'Aquest color s\'utilitzarà per a les teves graelles de pressupost, calendaris i indicadors personals.',
|
||||
'set_title_pwd' => '🔒 Canviar Contrasenya',
|
||||
'set_label_cur_pwd' => 'Contrasenya actual',
|
||||
'set_label_new_pwd' => 'Nova contrasenya',
|
||||
'set_label_confirm_pwd' => 'Confirma',
|
||||
'set_btn_edit' => 'Modifica',
|
||||
'set_title_family' => '🏠 El meu Espai Familiar',
|
||||
'set_label_family_name' => 'Nom de l\'espai',
|
||||
'set_desc_invite' => 'Codi d\'invitació — comparteix-lo per convidar algú',
|
||||
'set_title_copy' => 'Fes clic per copiar',
|
||||
'set_msg_copied' => '✓ Copiat!',
|
||||
'set_btn_new_code' => '🔄 Nou codi',
|
||||
'set_confirm_new_code' => 'Regenerar codi? L\'antic serà invalidat.',
|
||||
'set_desc_register_link' => 'Enllaç de registre:',
|
||||
'set_title_members' => 'Membres',
|
||||
'set_badge_admin' => 'Admin',
|
||||
'set_title_attached' => '🧸 Perfils Adjunts',
|
||||
'set_desc_attached' => 'Membres gestionats per la llar (Nens, Mainaderes, Familiars) sense accés directe.',
|
||||
'role_unknown' => 'Desconegut',
|
||||
'role_child' => '👶 Nen/a',
|
||||
'role_helper' => '💼 Ajudant',
|
||||
'role_relative' => '👵 Familiar',
|
||||
'set_confirm_del_profile' => 'Eliminar definitivament aquest perfil?',
|
||||
'set_empty_attached' => 'Cap perfil adjunt.',
|
||||
'set_label_firstname' => 'Nom',
|
||||
'set_label_role' => 'Rol',
|
||||
'set_label_color' => 'Color',
|
||||
'set_link_admin' => '→ Tauler d\'Administració',
|
||||
'set_success_color' => 'Color actualitzat!',
|
||||
'error_network' => 'Error de xarxa',
|
||||
|
||||
// ==========================================
|
||||
// FAMILY CALENDAR
|
||||
@@ -736,6 +839,8 @@ return [
|
||||
'bud_prev_monthly_target' => 'Import objectiu / Mensualitat (€)',
|
||||
'bud_prev_transfer_dest' => 'Destinació de la transferència',
|
||||
'bud_prev_none' => 'Cap destinació (Despesa estàndard)',
|
||||
'bud_sav_new_line' => 'New line',
|
||||
'bud_sav_prompt_new_line' => 'Name of the new savings line:',
|
||||
|
||||
// --- BUDGET : EPARGNE ---
|
||||
'bud_sav_add_month' => 'Introduir un mes',
|
||||
|
||||
@@ -75,6 +75,88 @@ return [
|
||||
'login_subtitle' => 'Please sign in to continue',
|
||||
'login_remember_me' => 'Remember me',
|
||||
|
||||
// --- SETTINGS ---
|
||||
'error_csrf' => 'Invalid session (CSRF). Please reload the page.',
|
||||
'set_err_min_module' => 'You must keep at least one active module.',
|
||||
'set_success_modules' => 'Modules successfully updated.',
|
||||
'set_success_lang' => 'Language updated.',
|
||||
'set_err_empty_name' => 'First name cannot be empty.',
|
||||
'set_success_profile' => 'Profile updated successfully.',
|
||||
'set_err_wrong_pwd' => 'Incorrect current password.',
|
||||
'set_err_pwd_length' => 'New password must be at least 6 characters long.',
|
||||
'set_err_pwd_match' => 'Passwords do not match.',
|
||||
'set_success_pwd' => 'Password successfully changed.',
|
||||
'set_success_family' => 'Space name updated.',
|
||||
'set_success_invite' => 'New invitation code generated.',
|
||||
'set_err_bg_format' => 'Unsupported format (jpg, png, webp).',
|
||||
'set_success_bg' => 'Home image updated.',
|
||||
'set_err_upload' => 'Error during upload.',
|
||||
'set_success_bg_reset' => 'Home image reset.',
|
||||
'set_err_db_unavailable' => 'Family database unavailable.',
|
||||
'set_err_ios_missing' => 'Please provide an iCloud ID, app password, and CalDAV URL.',
|
||||
'set_success_ios' => 'iOS calendar connection saved.',
|
||||
'set_success_ios_auto' => ' Calendar URL automatically detected.',
|
||||
'set_err_ios_save' => 'Cannot save iOS connection: ',
|
||||
'set_err_ios_none' => 'No iOS connection configured.',
|
||||
'set_success_ios_test' => 'Valid iCloud CalDAV connection (HTTP %s).',
|
||||
'set_err_ios_test' => 'Connection test failed (HTTP %s). Check your credentials.',
|
||||
'set_err_ios_test_fail' => 'Connection test unavailable: ',
|
||||
'set_success_ios_del' => 'iOS connection removed.',
|
||||
'set_success_profile_add' => 'Profile for %s successfully added.',
|
||||
'set_err_name_required' => 'First name is required.',
|
||||
'set_success_profile_del' => 'Profile deleted.',
|
||||
'set_title_page' => '⚙️ Settings',
|
||||
'set_title_lang' => '🌐 Language',
|
||||
'set_title_modules' => '🧩 Active Modules',
|
||||
'set_desc_modules' => 'Choose the modules visible in the navigation (shared with all space members).',
|
||||
'set_title_ios' => '📱 iOS Calendar Integration (CalDAV)',
|
||||
'set_desc_ios' => 'Configure your iCloud calendar here to sync events created in HouseHub.',
|
||||
'set_label_apple_id' => 'Apple ID (iCloud email)',
|
||||
'set_label_apple_pwd' => 'Apple app password',
|
||||
'set_label_apple_url' => 'CalDAV calendar URL',
|
||||
'set_btn_test_ios' => 'Test connection',
|
||||
'set_btn_disconnect_ios' => 'Disconnect',
|
||||
'set_title_bg' => '🖼️ Home Image',
|
||||
'set_desc_bg' => 'Background photo displayed on the homepage (shared with all members).',
|
||||
'set_badge_custom_bg' => 'Custom image',
|
||||
'set_label_new_bg' => 'New image (jpg, png, webp — max 10 MB)',
|
||||
'set_btn_reset_bg' => '🔄 Reset to default image',
|
||||
'set_confirm_reset_bg' => 'Revert to default image?',
|
||||
'set_title_profile' => '👤 My Profile',
|
||||
'set_label_username' => 'Username',
|
||||
'set_label_display_name' => 'First name / Display name',
|
||||
'set_label_profile_color' => 'My profile theme color',
|
||||
'set_desc_profile_color' => 'This color will be used for your budget grids, calendars, and personal indicators.',
|
||||
'set_title_pwd' => '🔒 Change Password',
|
||||
'set_label_cur_pwd' => 'Current password',
|
||||
'set_label_new_pwd' => 'New password',
|
||||
'set_label_confirm_pwd' => 'Confirm',
|
||||
'set_btn_edit' => 'Edit',
|
||||
'set_title_family' => '🏠 My Family Space',
|
||||
'set_label_family_name' => 'Space name',
|
||||
'set_desc_invite' => 'Invitation code — share it to invite someone',
|
||||
'set_title_copy' => 'Click to copy',
|
||||
'set_msg_copied' => '✓ Copied!',
|
||||
'set_btn_new_code' => '🔄 New code',
|
||||
'set_confirm_new_code' => 'Regenerate code? The old one will be invalidated.',
|
||||
'set_desc_register_link' => 'Registration link:',
|
||||
'set_title_members' => 'Members',
|
||||
'set_badge_admin' => 'Admin',
|
||||
'set_title_attached' => '🧸 Attached Profiles',
|
||||
'set_desc_attached' => 'Members managed by the household (Children, Nannies, Relatives) without direct access.',
|
||||
'role_unknown' => 'Unknown',
|
||||
'role_child' => '👶 Child',
|
||||
'role_helper' => '💼 Helper',
|
||||
'role_relative' => '👵 Relative',
|
||||
'set_confirm_del_profile' => 'Permanently delete this profile?',
|
||||
'set_empty_attached' => 'No attached profiles.',
|
||||
'set_label_firstname' => 'First name',
|
||||
'set_label_role' => 'Role',
|
||||
'set_label_color' => 'Color',
|
||||
'set_link_admin' => '→ Administration Panel',
|
||||
'set_success_color' => 'Color updated!',
|
||||
'error_network' => 'Network error',
|
||||
|
||||
// ==========================================
|
||||
// HOME / INDEX
|
||||
// ==========================================
|
||||
@@ -171,6 +253,27 @@ return [
|
||||
'set_care_modes' => 'Common childcare arrangements',
|
||||
'set_add_care_mode' => 'Ajouter un modeAdd a mode',
|
||||
'btn_save' => 'Save',
|
||||
'settings_profile_added' => 'Profile of %s added successfully.',
|
||||
'settings_err_name_required' => 'First name is required.',
|
||||
'settings_attached_profiles_title' => 'Attached Profiles',
|
||||
'settings_attached_profiles_desc' => 'Household members (Children, Nannies, Relatives) without direct access.',
|
||||
'settings_no_attached_profiles' => 'No attached profiles.',
|
||||
'label_firstname' => 'First Name',
|
||||
'placeholder_firstname_ex' => 'e.g., Pol, Carole...',
|
||||
'label_role' => 'Role',
|
||||
'role_child_icon' => '👶 Child',
|
||||
'role_helper_icon' => '💼 Helper',
|
||||
'role_relative_icon' => '👵 Relative',
|
||||
'role_unknown' => 'Unknown',
|
||||
'label_color' => 'Color',
|
||||
'confirm_delete_profile' => 'Permanently delete this profile?',
|
||||
'change_color' => 'Change color',
|
||||
'color_updated' => 'Color updated!',
|
||||
'set_success_color_profile' => 'Profile color updated.',
|
||||
'set_success_color_update' => 'Color updated!',
|
||||
'set_desc_profile_color' => 'This color will be used for your budget grids, calendars, and personal indicators.',
|
||||
'placeholder_apple_id' => 'name@icloud.com',
|
||||
'placeholder_apple_pwd' => 'xxxx-xxxx-xxxx-xxxx',
|
||||
|
||||
// ==========================================
|
||||
// FAMILY CALENDAR
|
||||
@@ -768,6 +871,8 @@ return [
|
||||
'bud_sav_sum_mode_title' => 'Toggle calculator',
|
||||
'bud_sav_selection' => 'Selection:',
|
||||
'bud_sav_ph_name' => 'Account name',
|
||||
'bud_sav_new_line' => 'New line',
|
||||
'bud_sav_prompt_new_line' => 'Name of the new savings line:',
|
||||
|
||||
// JS & Errors
|
||||
'bud_sav_confirm_delete_month' => 'Are you sure you want to delete all data of %m for %o?',
|
||||
|
||||
@@ -172,6 +172,109 @@ return [
|
||||
'set_care_modes' => 'Modes de garde usuels',
|
||||
'set_add_care_mode' => 'Ajouter un mode',
|
||||
'btn_save_config' => 'Enregistrer',
|
||||
'settings_profile_added' => 'Profil de %s ajouté avec succès.',
|
||||
'settings_err_name_required' => 'Le prénom est obligatoire.',
|
||||
'settings_attached_profiles_title' => 'Profils rattachés',
|
||||
'settings_attached_profiles_desc' => 'Membres gérés par le foyer (Enfants, Nounous, Proches) n\'ayant pas d\'accès direct.',
|
||||
'settings_no_attached_profiles' => 'Aucun profil rattaché.',
|
||||
'label_firstname' => 'Prénom',
|
||||
'placeholder_firstname_ex' => 'Ex: Pol, Carole...',
|
||||
'label_role' => 'Rôle',
|
||||
'role_child_icon' => '👶 Enfant',
|
||||
'role_helper_icon' => '💼 Intervenant',
|
||||
'role_relative_icon' => '👵 Proche',
|
||||
'role_unknown' => 'Inconnu',
|
||||
'label_color' => 'Couleur',
|
||||
'confirm_delete_profile' => 'Supprimer définitivement ce profil ?',
|
||||
'change_color' => 'Changer la couleur',
|
||||
'color_updated' => 'Couleur mise à jour !',
|
||||
'set_success_color_profile' => 'Couleur du profil mise à jour.',
|
||||
'set_success_color_update' => 'Couleur mise à jour !',
|
||||
'set_desc_profile_color' => 'Cette couleur sera utilisée pour vos grilles de budget, calendriers et indicateurs personnels.',
|
||||
'placeholder_apple_id' => 'nom@icloud.com',
|
||||
'placeholder_apple_pwd' => 'xxxx-xxxx-xxxx-xxxx',
|
||||
|
||||
// Fichier : includes/lang/fr.php
|
||||
'error_csrf' => 'Session invalide (CSRF). Rechargez la page.',
|
||||
'set_err_min_module' => 'Vous devez garder au moins un module actif.',
|
||||
'set_success_modules' => 'Modules mis à jour avec succès.',
|
||||
'set_success_lang' => 'Langue mise à jour.',
|
||||
'set_err_empty_name' => 'Le prénom ne peut pas être vide.',
|
||||
'set_success_profile' => 'Profil mis à jour avec succès.',
|
||||
'set_err_wrong_pwd' => 'Mot de passe actuel incorrect.',
|
||||
'set_err_pwd_length' => 'Le nouveau mot de passe doit faire au moins 6 caractères.',
|
||||
'set_err_pwd_match' => 'Les mots de passe ne correspondent pas.',
|
||||
'set_success_pwd' => 'Mot de passe modifié avec succès.',
|
||||
'set_success_family' => 'Nom de l\'espace mis à jour.',
|
||||
'set_success_invite' => 'Nouveau code d\'invitation généré.',
|
||||
'set_err_bg_format' => 'Format non supporté (jpg, png, webp).',
|
||||
'set_success_bg' => 'Image d\'accueil mise à jour.',
|
||||
'set_err_upload' => 'Erreur lors de l\'upload.',
|
||||
'set_success_bg_reset' => 'Image d\'accueil réinitialisée.',
|
||||
'set_err_db_unavailable' => 'Base famille indisponible.',
|
||||
'set_err_ios_missing' => 'Merci de renseigner identifiant iCloud, mot de passe d\'app et URL CalDAV.',
|
||||
'set_success_ios' => 'Connexion calendrier iOS enregistrée.',
|
||||
'set_success_ios_auto' => ' URL du calendrier détectée automatiquement.',
|
||||
'set_err_ios_save' => 'Impossible d\'enregistrer la connexion iOS : ',
|
||||
'set_err_ios_none' => 'Aucune connexion iOS configurée.',
|
||||
'set_success_ios_test' => 'Connexion iCloud CalDAV valide (HTTP %s).',
|
||||
'set_err_ios_test' => 'Test connexion échoué (HTTP %s). Vérifiez vos identifiants.',
|
||||
'set_err_ios_test_fail' => 'Test connexion impossible : ',
|
||||
'set_success_ios_del' => 'Connexion iOS supprimée.',
|
||||
'set_success_profile_add' => 'Profil de %s ajouté avec succès.',
|
||||
'set_err_name_required' => 'Le prénom est obligatoire.',
|
||||
'set_success_profile_del' => 'Profil supprimé.',
|
||||
'set_title_page' => '⚙️ Paramètres',
|
||||
'set_title_lang' => '🌐 Langue / Language',
|
||||
'set_title_modules' => '🧩 Modules actifs',
|
||||
'set_desc_modules' => 'Choisissez les modules visibles dans la navigation (partagé avec tous les membres de l\'espace).',
|
||||
'set_title_ios' => '📱 Intégration Calendrier iOS (CalDAV)',
|
||||
'set_desc_ios' => 'Configurez ici votre calendrier iCloud pour synchroniser les événements créés dans HouseHub.',
|
||||
'set_label_apple_id' => 'Identifiant Apple (email iCloud)',
|
||||
'set_label_apple_pwd' => 'Mot de passe d\'app Apple',
|
||||
'set_label_apple_url' => 'URL du calendrier CalDAV',
|
||||
'set_btn_test_ios' => 'Tester la connexion',
|
||||
'set_btn_disconnect_ios' => 'Déconnecter',
|
||||
'set_title_bg' => '🖼️ Image d\'accueil',
|
||||
'set_desc_bg' => 'Photo de fond affichée sur la page d\'accueil (partagée avec tous les membres).',
|
||||
'set_badge_custom_bg' => 'Image personnalisée',
|
||||
'set_label_new_bg' => 'Nouvelle image (jpg, png, webp — max 10 Mo)',
|
||||
'set_btn_reset_bg' => '🔄 Remettre l\'image par défaut',
|
||||
'set_confirm_reset_bg' => 'Revenir à l\'image par défaut ?',
|
||||
'set_title_profile' => '👤 Mon profil',
|
||||
'set_label_username' => 'Nom d\'utilisateur',
|
||||
'set_label_display_name' => 'Prénom / Pseudo affiché',
|
||||
'set_label_profile_color' => 'Couleur thématique de mon profil',
|
||||
'set_desc_profile_color' => 'Cette couleur sera utilisée pour vos grilles de budget, calendriers et indicateurs personnels.',
|
||||
'set_title_pwd' => '🔒 Changer le mot de passe',
|
||||
'set_label_cur_pwd' => 'Mot de passe actuel',
|
||||
'set_label_new_pwd' => 'Nouveau mot de passe',
|
||||
'set_label_confirm_pwd' => 'Confirmer',
|
||||
'set_btn_edit' => 'Modifier',
|
||||
'set_title_family' => '🏠 Mon espace familial',
|
||||
'set_label_family_name' => 'Nom de l\'espace',
|
||||
'set_desc_invite' => 'Code d\'invitation — partagez-le pour inviter quelqu\'un',
|
||||
'set_title_copy' => 'Cliquer pour copier',
|
||||
'set_msg_copied' => '✓ Copié !',
|
||||
'set_btn_new_code' => '🔄 Nouveau code',
|
||||
'set_confirm_new_code' => 'Regénérer le code ? L\'ancien sera invalidé.',
|
||||
'set_desc_register_link' => 'Lien d\'inscription :',
|
||||
'set_title_members' => 'Membres',
|
||||
'set_badge_admin' => 'Admin',
|
||||
'set_title_attached' => '🧸 Profils rattachés',
|
||||
'set_desc_attached' => 'Membres gérés par le foyer (Enfants, Nounous, Proches) n\'ayant pas d\'accès direct.',
|
||||
'role_unknown' => 'Inconnu',
|
||||
'role_child' => '👶 Enfant',
|
||||
'role_helper' => '💼 Intervenant',
|
||||
'role_relative' => '👵 Proche',
|
||||
'set_confirm_del_profile' => 'Supprimer définitivement ce profil ?',
|
||||
'set_empty_attached' => 'Aucun profil rattaché.',
|
||||
'set_label_firstname' => 'Prénom',
|
||||
'set_label_role' => 'Rôle',
|
||||
'set_label_color' => 'Couleur',
|
||||
'set_link_admin' => '→ Panneau d\'administration',
|
||||
'set_success_color' => 'Couleur mise à jour !',
|
||||
'error_network' => 'Erreur réseau',
|
||||
|
||||
// ==========================================
|
||||
// FAMILY CALENDAR
|
||||
@@ -789,6 +892,8 @@ return [
|
||||
'bud_sav_sum_mode_title' => 'Activer/Désactiver la calculatrice',
|
||||
'bud_sav_selection' => 'Sélection :',
|
||||
'bud_sav_ph_name' => 'Nom du compte',
|
||||
'bud_sav_new_line' => 'Nouvelle ligne',
|
||||
'bud_sav_prompt_new_line' => 'Nom de la nouvelle ligne d\'épargne :',
|
||||
|
||||
|
||||
// --- BUDGET : PREVISION ---
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Mockup Épargne HouseHub</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #3b82f6;
|
||||
--bg-page: #f8fafc;
|
||||
--bg-panel: #ffffff;
|
||||
--text-main: #0f172a;
|
||||
--text-muted: #64748b;
|
||||
--border-light: #e2e8f0;
|
||||
--radius: 12px;
|
||||
--shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
--focus-ring: rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
background: var(--bg-page);
|
||||
color: var(--text-main);
|
||||
padding: 40px;
|
||||
}
|
||||
.controls {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--primary);
|
||||
background: white;
|
||||
color: var(--primary);
|
||||
font-weight: bold;
|
||||
}
|
||||
button.active {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* BASE GRID (Ta structure actuelle simplifiée) */
|
||||
.epargne-grid-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-panel);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-light);
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
}
|
||||
.eg-row,
|
||||
.eg-header-row {
|
||||
display: grid;
|
||||
grid-template-columns: 180px repeat(3, minmax(115px, 1fr));
|
||||
transition: 0.2s;
|
||||
}
|
||||
.eg-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.eg-sticky {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
justify-content: flex-start;
|
||||
z-index: 10;
|
||||
font-weight: 600;
|
||||
}
|
||||
.eg-input {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
padding: 4px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.eg-input:focus {
|
||||
background: var(--bg-panel);
|
||||
border-color: var(--primary);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--focus-ring);
|
||||
}
|
||||
|
||||
/* ========================================= */
|
||||
/* STYLE A : COMPACT & AÉRIEN */
|
||||
/* ========================================= */
|
||||
.style-a .eg-row,
|
||||
.style-a .eg-header-row {
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
.style-a .eg-cell {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
.style-a .eg-header-row {
|
||||
background: #f8fafc;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.style-a .eg-header-row .eg-sticky {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.style-a .eg-row .eg-sticky {
|
||||
background: var(--bg-panel);
|
||||
border-right: 1px solid var(--border-light);
|
||||
}
|
||||
.style-a .eg-row:hover {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
.style-a .eg-row:hover .eg-sticky {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
.style-a .eg-input:hover {
|
||||
background: #e2e8f0;
|
||||
}
|
||||
|
||||
/* ========================================= */
|
||||
/* STYLE B : ZÉBRÉ DOUX */
|
||||
/* ========================================= */
|
||||
.style-b .eg-cell {
|
||||
padding: 8px 10px;
|
||||
border-right: 1px dashed #e2e8f0;
|
||||
}
|
||||
.style-b .eg-header-row {
|
||||
background: #1e293b;
|
||||
color: white;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.style-b .eg-header-row .eg-sticky {
|
||||
background: #1e293b;
|
||||
border-right: 2px solid #334155;
|
||||
}
|
||||
.style-b .eg-row:nth-child(even) {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.style-b .eg-row:nth-child(even) .eg-sticky {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.style-b .eg-row .eg-sticky {
|
||||
background: var(--bg-panel);
|
||||
border-right: 2px solid var(--border-light);
|
||||
}
|
||||
.style-b .eg-input {
|
||||
background: #f1f5f9;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
.style-b .eg-input:hover {
|
||||
border-color: #cbd5e1;
|
||||
}
|
||||
|
||||
/* ========================================= */
|
||||
/* STYLE C : SAAS DASHBOARD */
|
||||
/* ========================================= */
|
||||
.style-c .eg-row,
|
||||
.style-c .eg-header-row {
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
.style-c .eg-cell {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.style-c .eg-header-row {
|
||||
background: #ffffff;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
border-bottom: 2px solid var(--border-light);
|
||||
}
|
||||
.style-c .eg-header-row .eg-sticky {
|
||||
background: #ffffff;
|
||||
}
|
||||
.style-c .eg-row .eg-sticky {
|
||||
background: var(--bg-panel);
|
||||
position: relative;
|
||||
}
|
||||
/* Barre de surbrillance au hover */
|
||||
.style-c .eg-row:hover {
|
||||
background: #eff6ff;
|
||||
}
|
||||
.style-c .eg-row:hover .eg-sticky {
|
||||
background: #eff6ff;
|
||||
}
|
||||
.style-c .eg-row:hover .eg-sticky::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
background: var(--primary);
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
.style-c .eg-input {
|
||||
background: transparent;
|
||||
border-bottom: 1px dashed #cbd5e1;
|
||||
border-radius: 0;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.style-c .eg-input:focus {
|
||||
border-bottom: 1px solid var(--primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="controls">
|
||||
<button onclick="setStyle('style-a')" id="btn-a" class="active">
|
||||
Style A : Compact
|
||||
</button>
|
||||
<button onclick="setStyle('style-b')" id="btn-b">Style B : Zébré</button>
|
||||
<button onclick="setStyle('style-c')" id="btn-c">Style C : SaaS</button>
|
||||
</div>
|
||||
|
||||
<!-- Grille Mockup -->
|
||||
<div class="epargne-grid-table style-a" id="grid-container">
|
||||
<div class="eg-header-row">
|
||||
<div class="eg-cell eg-sticky">Poste / Mois</div>
|
||||
<div class="eg-cell">Août 2026</div>
|
||||
<div class="eg-cell">Juillet 2026</div>
|
||||
<div class="eg-cell">Juin 2026</div>
|
||||
</div>
|
||||
<div class="eg-row" style="font-weight: bold; background: #f0f9ff">
|
||||
<div
|
||||
class="eg-cell eg-sticky"
|
||||
style="background: #f0f9ff; color: #0369a1"
|
||||
>
|
||||
TOTAL BANQUE
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input
|
||||
type="number"
|
||||
class="eg-input"
|
||||
style="color: #0369a1; font-weight: bold"
|
||||
value="7947"
|
||||
/>
|
||||
€
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input
|
||||
type="number"
|
||||
class="eg-input"
|
||||
style="color: #0369a1; font-weight: bold"
|
||||
value="6807"
|
||||
/>
|
||||
€
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input
|
||||
type="number"
|
||||
class="eg-input"
|
||||
style="color: #0369a1; font-weight: bold"
|
||||
value="7846"
|
||||
/>
|
||||
€
|
||||
</div>
|
||||
</div>
|
||||
<div class="eg-row">
|
||||
<div class="eg-cell eg-sticky">Ecole P&P</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="480" />
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="480" />
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="480" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="eg-row">
|
||||
<div class="eg-cell eg-sticky">Extra casa</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="635" />
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="635" />
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="635" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="eg-row">
|
||||
<div class="eg-cell eg-sticky">Vacances</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="1002" />
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="1002" />
|
||||
</div>
|
||||
<div class="eg-cell">
|
||||
<input type="number" class="eg-input" value="2002" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="eg-row" style="background: #fffbeb">
|
||||
<div
|
||||
class="eg-cell eg-sticky"
|
||||
style="background: #fffbeb; color: #b45309"
|
||||
>
|
||||
NON ALLOUÉ
|
||||
</div>
|
||||
<div class="eg-cell" style="color: #10b981; font-weight: bold">
|
||||
1230 €
|
||||
</div>
|
||||
<div class="eg-cell" style="color: #10b981; font-weight: bold">
|
||||
660 €
|
||||
</div>
|
||||
<div class="eg-cell" style="color: #10b981; font-weight: bold">
|
||||
699 €
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function setStyle(styleClass) {
|
||||
const grid = document.getElementById("grid-container");
|
||||
grid.className = "epargne-grid-table " + styleClass;
|
||||
|
||||
document
|
||||
.querySelectorAll(".controls button")
|
||||
.forEach((btn) => btn.classList.remove("active"));
|
||||
document
|
||||
.getElementById("btn-" + styleClass.split("-")[1])
|
||||
.classList.add("active");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+920
-69
File diff suppressed because it is too large.
Load diff
@@ -20,14 +20,14 @@ if ($action === 'update_single_entry') {
|
||||
$cat = $_POST['category'] ?? '';
|
||||
$owner = $_POST['owner'] ?? '';
|
||||
$amount = (float)($_POST['amount'] ?? 0);
|
||||
$forceInsert = !empty($_POST['force_insert']);
|
||||
|
||||
try {
|
||||
if ($amount == 0 && $cat !== 'TOTAL_BANQUE') {
|
||||
// Si on met à 0 une ligne (autre que le total), on supprime l'entrée pour garder la base propre
|
||||
// On supprime la ligne si elle est à 0, SAUF si on demande explicitement son initialisation (force_insert)
|
||||
if ($amount == 0 && $cat !== 'TOTAL_BANQUE' && !$forceInsert) {
|
||||
$stmt = $pdo->prepare("DELETE FROM pf_savings WHERE month_date=? AND owner=? AND category=?");
|
||||
$stmt->execute([$month, $owner, $cat]);
|
||||
} else {
|
||||
// Sinon on insère ou on met à jour
|
||||
$stmt = $pdo->prepare("INSERT INTO pf_savings (month_date, owner, category, amount) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE amount = VALUES(amount)");
|
||||
$stmt->execute([$month, $owner, $cat, $amount]);
|
||||
}
|
||||
|
||||
+185
-183
@@ -2,24 +2,24 @@
|
||||
// modules/budget/views/epargne.php
|
||||
|
||||
// --- 🧠 CONFIGURATION AGNOSTIQUE DES MEMBRES (MULTI-TENANT) ---
|
||||
$stmtPeople = $pdo->query("SELECT name, role FROM pf_people ORDER BY id ASC");
|
||||
$stmtPeople = $pdo->query("SELECT name, role, color FROM pf_people ORDER BY id ASC");
|
||||
$familyParents = [];
|
||||
$familyKids = [];
|
||||
$personColors = [];
|
||||
|
||||
while ($row = $stmtPeople->fetch(PDO::FETCH_ASSOC)) {
|
||||
$role = strtolower(trim($row['role'] ?? ''));
|
||||
$personColors[$row['name']] = $row['color'] ?? '#3b82f6';
|
||||
|
||||
if ($role === 'parent') {
|
||||
$familyParents[] = $row['name'];
|
||||
} elseif ($role === 'nounou' || $role === 'helper') {
|
||||
// On ignore la nounou dans l'épargne
|
||||
continue;
|
||||
} else {
|
||||
$familyKids[] = $row['name'];
|
||||
}
|
||||
}
|
||||
|
||||
// Sécurité anti-page blanche si la BDD est mal configurée
|
||||
if (empty($familyParents)) $familyParents = ['Parent 1', 'Parent 2'];
|
||||
|
||||
$requestedOwner = $_GET['owner'] ?? (!empty($familyKids) ? 'KIDS' : ($familyParents[0] ?? 'KIDS'));
|
||||
@@ -46,10 +46,9 @@ function getMonthName($dateString) {
|
||||
<div class="budget-view">
|
||||
<div class="view-header">
|
||||
<div class="owner-tabs">
|
||||
|
||||
<?php if (!empty($familyKids)): ?>
|
||||
<a href="?tab=epargne&owner=KIDS" class="owner-tab <?= $requestedOwner === 'KIDS' ? 'active' : '' ?>">
|
||||
<?= tr('budget_tab_kids') ?? 'Enfants 👶' ?>
|
||||
<?= tr('budget_tab_kids') ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -81,181 +80,174 @@ function getMonthName($dateString) {
|
||||
$months = array_slice($months, 0, 7);
|
||||
sort($allCategories);
|
||||
|
||||
$ownerTextClass = 'txt-global';
|
||||
// 🔄 CORRECTION ICI : Création d'un nom "safe" sans espace pour les classes CSS
|
||||
$safeOwnerCls = htmlspecialchars(str_replace(' ', '_', $currentOwner), ENT_QUOTES);
|
||||
$ownerColor = $personColors[$currentOwner] ?? '#1e293b';
|
||||
?>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; margin-top: <?= ($requestedOwner === 'KIDS' && $index > 0) ? '40px' : '0' ?>;">
|
||||
<div style="flex-grow: 1;">
|
||||
<?php if ($requestedOwner === 'KIDS'):
|
||||
$themeClass = 'theme-' . strtolower($safeOwnerCls);
|
||||
?>
|
||||
<h3 class="nens-title <?= $themeClass ?>" style="margin:0; font-size:1.2rem;">
|
||||
<div class="epargne-header-bar <?= ($requestedOwner === 'KIDS' && $index > 0) ? 'pf-mt-40' : '' ?>">
|
||||
<div class="pf-flex-1">
|
||||
<?php if ($requestedOwner === 'KIDS'): ?>
|
||||
<h3 class="nens-title" style="--owner-color: <?= htmlspecialchars($ownerColor) ?>;">
|
||||
<?= htmlspecialchars($currentOwner) ?>
|
||||
</h3>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<div class="pf-flex-gap-12-center">
|
||||
<?php if (!empty($months)): ?>
|
||||
<button onclick="duplicateLastMonth('<?= $months[0] ?>', '<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>')" class="pf-btn btn-secondary">
|
||||
🔁 <?= tr('bud_sav_add_one_month') ?>
|
||||
</button>
|
||||
<button onclick="promptNewSavingsLine('<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>', '<?= $months[0] ?>')" class="pf-btn">
|
||||
+ <?= tr('bud_sav_new_line') ?>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button onclick="openCustomSavingsModal('<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>')" class="pf-btn">
|
||||
+ <?= tr('bud_sav_add_month') ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<button onclick="openCustomSavingsModal('<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>')" class="pf-btn">
|
||||
+ <?= tr('bud_sav_add_month') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive" style="background:white; border-radius:16px; box-shadow:var(--shadow-sm); border:1px solid #e2e8f0;">
|
||||
<div class="table-responsive pf-card-epargne">
|
||||
<?php if (empty($months)): ?>
|
||||
<div style="padding: 30px; text-align: center; color: #64748b;">
|
||||
<div class="pf-empty-dashed">
|
||||
<p><?= sprintf(tr('bud_sav_no_data'), htmlspecialchars($currentOwner)) ?></p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<table class="pf-table savings-table nens-table theme-<?= strtolower($safeOwnerCls) ?>" style="margin-top:0; box-shadow:none; border-radius:16px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-col" style="background:#f8fafc;"><?= tr('bud_sav_post_month') ?></th>
|
||||
<?php foreach ($months as $month): ?>
|
||||
<th>
|
||||
<div class="month-header-container" style="display: flex; flex-direction: column; align-items: center; gap: 6px;">
|
||||
<div style="display:flex; flex-direction:column; text-align:center;">
|
||||
<span class="month-name" style="text-transform:capitalize;"><?= getMonthName($month) ?></span>
|
||||
<?php
|
||||
if (isset($cycleConfigs[$month]) && !empty($cycleConfigs[$month]['start_date'])) {
|
||||
$cStart = date('d/m', strtotime($cycleConfigs[$month]['start_date']));
|
||||
echo "<span style='font-size:0.75rem; font-weight:normal; color:#64748b;'>" . sprintf(tr('bud_sav_from_date'), $cStart) . "</span>";
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="month-actions" style="justify-content: center; width: 100%;">
|
||||
<button class="btn-icon-small btn-safe-click" title="<?= tr('bud_sav_edit_modal') ?>"
|
||||
data-json="<?= htmlspecialchars(json_encode($data[$month] ?? []), ENT_QUOTES, 'UTF-8') ?>"
|
||||
onclick='editCustomSavingsMonth("<?= $month ?>", "<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>", JSON.parse(this.getAttribute("data-json")))'>
|
||||
✏️
|
||||
</button>
|
||||
<button class="btn-icon-small btn-safe-click" title="<?= tr('bud_sav_delete_month') ?>"
|
||||
onclick="deleteEntireMonth('<?= $month ?>', '<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>')"
|
||||
style="color: #ef4444; border-color: #fca5a5; background: #fef2f2;">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
<div class="epargne-grid-table" style="--cols: <?= count($months) ?>; --owner-color: <?= htmlspecialchars($ownerColor) ?>;">
|
||||
|
||||
<div class="eg-header-row">
|
||||
<div class="eg-cell eg-sticky"><?= tr('bud_sav_post_month') ?></div>
|
||||
<?php foreach ($months as $month): ?>
|
||||
<div class="eg-cell">
|
||||
<div class="month-header-container">
|
||||
<div class="month-header-title">
|
||||
<span class="month-name text-inherit"><?= getMonthName($month) ?></span>
|
||||
<?php
|
||||
if (isset($cycleConfigs[$month]) && !empty($cycleConfigs[$month]['start_date'])) {
|
||||
$cStart = date('d/m', strtotime($cycleConfigs[$month]['start_date']));
|
||||
echo "<span class='month-cycle-start'>" . sprintf(tr('bud_sav_from_date'), $cStart) . "</span>";
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="row-total">
|
||||
<td class="sticky-col"><strong><?= tr('bud_sav_total_bank') ?></strong></td>
|
||||
<?php foreach ($months as $month):
|
||||
$val = $data[$month]['TOTAL_BANQUE'] ?? 0;
|
||||
?>
|
||||
<td class="text-center" style="padding:4px;">
|
||||
<div style="display:flex; align-items:center; justify-content:center; gap:2px;">
|
||||
<input type="number" step="0.01"
|
||||
class="prev-input total-input-<?= $safeOwnerCls ?>-<?= $month ?>"
|
||||
style="width: 70px; font-weight:bold; color:#2563eb;"
|
||||
value="<?= $val != 0 ? round($val) : '' ?>"
|
||||
placeholder="0"
|
||||
onchange="updateEpargneCell('<?= $month ?>', 'TOTAL_BANQUE', '<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>', this)">
|
||||
<span style="color:#2563eb; font-weight:bold; font-size:0.9rem;">€</span>
|
||||
<div class="month-actions">
|
||||
<button class="btn-icon-small btn-safe-click" title="<?= tr('bud_sav_edit_modal') ?>"
|
||||
data-json="<?= htmlspecialchars(json_encode($data[$month] ?? []), ENT_QUOTES, 'UTF-8') ?>"
|
||||
onclick='editCustomSavingsMonth("<?= $month ?>", "<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>", JSON.parse(this.getAttribute("data-json")))'>
|
||||
✏️
|
||||
</button>
|
||||
<button class="btn-icon-small btn-safe-click btn-danger-soft" title="<?= tr('bud_sav_delete_month') ?>"
|
||||
onclick="deleteEntireMonth('<?= $month ?>', '<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>')">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</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" style="padding:4px;">
|
||||
<div style="display:flex; align-items:center; justify-content:center; gap:2px;">
|
||||
<input type="number" step="0.01"
|
||||
class="prev-input <?= $ownerTextClass ?> cat-input-<?= $safeOwnerCls ?>-<?= $month ?>"
|
||||
style="width: 70px;"
|
||||
value="<?= $amount != 0 ? round($amount) : '' ?>"
|
||||
placeholder="-"
|
||||
onchange="updateEpargneCell('<?= $month ?>', '<?= htmlspecialchars($cat, ENT_QUOTES) ?>', '<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>', this)">
|
||||
</div>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<tr class="row-extres">
|
||||
<td class="sticky-col"><strong><?= tr('bud_sav_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 sum-target" id="extra_<?= $safeOwnerCls ?>_<?= $month ?>" style="color: <?= $extra >= 0 ? '#10b981' : '#ef4444' ?>; padding:12px;">
|
||||
<?= number_format($extra, 0, ',', ' ') ?> €
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="eg-row eg-total-row">
|
||||
<div class="eg-cell eg-sticky"><strong><?= tr('bud_sav_total_bank') ?></strong></div>
|
||||
<?php foreach ($months as $month):
|
||||
$val = $data[$month]['TOTAL_BANQUE'] ?? 0;
|
||||
?>
|
||||
<div class="eg-cell">
|
||||
<div class="eg-cell-content">
|
||||
<input type="number" step="0.01"
|
||||
class="prev-input eg-input eg-input-total total-input-<?= $safeOwnerCls ?>-<?= $month ?>"
|
||||
value="<?= $val != 0 ? round($val) : '' ?>"
|
||||
placeholder="0"
|
||||
onchange="updateEpargneCell('<?= $month ?>', 'TOTAL_BANQUE', '<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>', this)">
|
||||
<span class="eg-currency-symbol">€</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php foreach ($allCategories as $cat): ?>
|
||||
<div class="eg-row">
|
||||
<div class="eg-cell eg-sticky"><?= htmlspecialchars($cat) ?></div>
|
||||
<?php foreach ($months as $month):
|
||||
$amount = $data[$month][$cat] ?? 0;
|
||||
?>
|
||||
<div class="eg-cell">
|
||||
<input type="number" step="0.01"
|
||||
class="prev-input eg-input cat-input-<?= $safeOwnerCls ?>-<?= $month ?>"
|
||||
value="<?= $amount != 0 ? round($amount) : '' ?>"
|
||||
placeholder="-"
|
||||
onchange="updateEpargneCell('<?= $month ?>', '<?= htmlspecialchars($cat, ENT_QUOTES) ?>', '<?= htmlspecialchars($currentOwner, ENT_QUOTES) ?>', this)">
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="eg-row eg-extres-row">
|
||||
<div class="eg-cell eg-sticky"><strong><?= tr('bud_sav_extra') ?></strong></div>
|
||||
<?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;
|
||||
?>
|
||||
<div class="eg-cell font-bold sum-target <?= $extra >= 0 ? 'text-success' : 'text-danger' ?>" id="extra_<?= $safeOwnerCls ?>_<?= $month ?>">
|
||||
<?= number_format($extra, 0, ',', ' ') ?> €
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div id="savingsModal" class="pf-modal">
|
||||
<div class="pf-modal-content" style="max-width: 600px; width: 95%;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 id="savingsModalTitle" class="pf-modal-title" style="margin:0;"><?= tr('bud_sav_modal_title_add') ?></h3>
|
||||
<button type="button" onclick="document.getElementById('savingsModal').style.display='none'; document.body.classList.remove('no-scroll');" style="border:none; background:none; font-size:1.8rem; cursor:pointer; color:#64748b; line-height:1;">×</button>
|
||||
<div class="pf-modal-content modal-epargne">
|
||||
<div class="pf-modal-header">
|
||||
<h3 id="savingsModalTitle" class="pf-modal-title"><?= tr('bud_sav_modal_title_add') ?></h3>
|
||||
<button type="button" onclick="document.getElementById('savingsModal').classList.remove('open'); document.body.classList.remove('no-scroll');" class="pf-modal-close">×</button>
|
||||
</div>
|
||||
|
||||
<form action="modules/budget/includes/api/save-savings.php" method="POST" id="savingsForm">
|
||||
<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" id="redirect_tab" value="<?= htmlspecialchars($requestedOwner) ?>">
|
||||
<input type="hidden" name="month_date" id="sav_date_hidden">
|
||||
|
||||
<div style="display:flex; gap:15px; margin-bottom:20px;">
|
||||
<div class="form-group" style="flex:1; margin:0;">
|
||||
<div class="pf-form-row">
|
||||
<div class="pf-form-group pf-flex-1 pf-m-0">
|
||||
<label class="pf-label"><?= tr('bud_sav_month_concerned') ?></label>
|
||||
<input type="month" id="sav_month" required class="pf-input">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="flex:1; margin:0;">
|
||||
<div class="pf-form-group pf-flex-1 pf-m-0">
|
||||
<label class="pf-label"><?= tr('bud_sav_total_bank_eur') ?></label>
|
||||
<input type="number" step="0.01" name="values[TOTAL_BANQUE]" id="sav_total" required class="pf-input no-spinners" style="font-weight:bold; color:#2563eb;">
|
||||
<input type="number" step="0.01" name="values[TOTAL_BANQUE]" id="sav_total" required class="pf-input no-spinners pf-input-total">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator" style="margin: 20px 0; border-bottom: 1px solid #e2e8f0;"></div>
|
||||
<hr class="pf-divider">
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px;">
|
||||
<div class="ventilation-header">
|
||||
<div>
|
||||
<h4 style="margin:0; font-size:1rem; color:#1e293b;"><?= tr('bud_sav_ventilation') ?></h4>
|
||||
<span style="font-size:0.8rem; color:#64748b;"><?= tr('bud_sav_adj_help') ?></span>
|
||||
<h4 class="pf-m-0 text-main"><?= tr('bud_sav_ventilation') ?></h4>
|
||||
<span class="pf-muted-note"><?= tr('bud_sav_adj_help') ?></span>
|
||||
</div>
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="addCustomEpargneLine()" style="padding:4px 10px; height:auto; width:auto; font-size:0.9rem;">+ <?= tr('bud_sav_add_line') ?></button>
|
||||
<button type="button" class="pf-btn btn-secondary pf-btn-sm" onclick="addCustomEpargneLine()">+ <?= tr('bud_sav_add_line') ?></button>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:10px; padding:0 5px 5px 5px; font-size:0.8rem; color:#64748b; font-weight:600;">
|
||||
<div style="flex:2;"><?= tr('bud_category') ?></div>
|
||||
<div style="width:100px;"><?= tr('bud_sav_current') ?></div>
|
||||
<div style="width:90px;"><?= tr('bud_sav_adjust') ?></div>
|
||||
<div style="width:100px;"><?= tr('bud_sav_new') ?></div>
|
||||
<div style="width:28px;"></div>
|
||||
<div class="ventilation-cols">
|
||||
<div class="vl-col-name"><?= tr('bud_category') ?></div>
|
||||
<div class="vl-col-base"><?= tr('bud_sav_current') ?></div>
|
||||
<div class="vl-col-adj"><?= tr('bud_sav_adjust') ?></div>
|
||||
<div class="vl-col-final"><?= tr('bud_sav_new') ?></div>
|
||||
<div class="vl-col-btn"></div>
|
||||
</div>
|
||||
|
||||
<div id="linesContainer" style="max-height: 350px; overflow-y: auto; padding-right:5px; display:flex; flex-direction:column; gap:10px;">
|
||||
</div>
|
||||
<div id="linesContainer" class="ventilation-list"></div>
|
||||
|
||||
<div style="margin-top:25px; display:flex; justify-content:flex-end; gap:10px;">
|
||||
<button type="button" onclick="document.getElementById('savingsModal').style.display='none'; document.body.classList.remove('no-scroll');" class="pf-btn btn-secondary" style="width:auto; margin:0;"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn" style="width:auto; margin:0;"><?= tr('btn_save') ?></button>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="document.getElementById('savingsModal').classList.remove('open'); document.body.classList.remove('no-scroll');" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -272,7 +264,6 @@ function getMonthName($dateString) {
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// --- 1. SÉCURISATION TRADUCTIONS ET LANGUE ---
|
||||
window.appLang = document.documentElement.lang === "ca" ? "ca-ES" : "fr-FR";
|
||||
window.I18N = {
|
||||
...(window.I18N || {}),
|
||||
@@ -281,19 +272,47 @@ window.I18N = {
|
||||
'bud_sav_ph_name': <?= json_encode(tr('bud_sav_ph_name')) ?>,
|
||||
'bud_sav_confirm_delete_month': <?= json_encode(tr('bud_sav_confirm_delete_month')) ?>,
|
||||
'bud_sav_prompt_duplicate': <?= json_encode(tr('bud_sav_prompt_duplicate')) ?>,
|
||||
'bud_sav_prompt_new_line': <?= json_encode(tr('bud_sav_prompt_new_line')) ?>,
|
||||
'bud_err_tech': <?= json_encode(tr('bud_err_tech')) ?>,
|
||||
'bud_err_server': <?= json_encode(tr('bud_err_server')) ?>,
|
||||
'bud_err_network_dup': <?= json_encode(tr('bud_err_network_dup')) ?>,
|
||||
'bud_sav_saving': <?= json_encode(tr('bud_sav_saving')) ?>,
|
||||
'bud_err_delete': <?= json_encode(tr('bud_err_delete')) ?>
|
||||
'bud_err_delete': <?= json_encode(tr('bud_err_delete')) ?>,
|
||||
'btn_delete': <?= json_encode(tr('btn_delete')) ?>
|
||||
};
|
||||
|
||||
// Sécurisation de la devise pour la calculatrice
|
||||
const systemCurrency = (typeof window.CONFIG !== 'undefined' && window.CONFIG.CURRENCY) ? window.CONFIG.CURRENCY : '€';
|
||||
|
||||
// --- 2. GESTION DE L'ÉDITION INVISIBLE EN DIRECT (CORRIGÉE 🚀) ---
|
||||
const cycleConfigs = <?= json_encode($cycleConfigs ?? []) ?>;
|
||||
|
||||
async function promptNewSavingsLine(owner, latestMonth) {
|
||||
const catName = prompt(window.I18N['bud_sav_prompt_new_line']);
|
||||
if (!catName || catName.trim() === "") return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'update_single_entry');
|
||||
formData.append('month_date', latestMonth);
|
||||
formData.append('category', catName.trim());
|
||||
formData.append('owner', owner);
|
||||
formData.append('amount', 0);
|
||||
formData.append('force_insert', '1'); // 🛡️ Empêche la suppression auto par l'API
|
||||
formData.append('ajax', '1');
|
||||
|
||||
try {
|
||||
const result = await pachaFetch('/modules/budget/includes/api/save-savings.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(window.I18N['bud_err_tech'] + " : " + (result.error || "Erreur"));
|
||||
}
|
||||
} catch (err) {
|
||||
alert(window.I18N['bud_err_tech']);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateEpargneCell(month, category, owner, inputEl) {
|
||||
const val = parseFloat(inputEl.value) || 0;
|
||||
const formData = new FormData();
|
||||
@@ -302,27 +321,19 @@ async function updateEpargneCell(month, category, owner, inputEl) {
|
||||
formData.append('category', category);
|
||||
formData.append('owner', owner);
|
||||
formData.append('amount', val);
|
||||
formData.append('ajax', '1'); // 🚀 CRITIQUE : Informe l'API
|
||||
formData.append('ajax', '1');
|
||||
|
||||
try {
|
||||
// 🚀 CRITIQUE : pachaFetch + Chemin absolu
|
||||
const result = await pachaFetch('/modules/budget/includes/api/save-savings.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await pachaFetch('/modules/budget/includes/api/save-savings.php', { method: 'POST', body: formData });
|
||||
if (!result.success) {
|
||||
console.error("Erreur de sauvegarde :", result.error);
|
||||
alert(window.I18N['bud_err_tech'] + " : " + (result.error || "Erreur serveur"));
|
||||
return; // On stoppe la mise à jour visuelle si la BDD a refusé
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Erreur réseau :", err);
|
||||
alert(window.I18N['bud_err_tech'] || 'Erreur technique');
|
||||
alert(window.I18N['bud_err_tech']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Si on arrive ici, la BDD est bien à jour ! On met à jour le visuel.
|
||||
const safeOwnerClass = owner.replace(/\s+/g, '_');
|
||||
const totalInput = document.querySelector(`.total-input-${CSS.escape(safeOwnerClass)}-${month}`);
|
||||
const totalVal = parseFloat(totalInput ? totalInput.value : 0) || 0;
|
||||
@@ -337,7 +348,13 @@ async function updateEpargneCell(month, category, owner, inputEl) {
|
||||
|
||||
if (extraCell) {
|
||||
extraCell.innerText = Math.round(extra).toLocaleString(window.appLang) + ' €';
|
||||
extraCell.style.color = extra >= 0 ? '#10b981' : '#ef4444';
|
||||
if (extra >= 0) {
|
||||
extraCell.classList.remove('text-danger');
|
||||
extraCell.classList.add('text-success');
|
||||
} else {
|
||||
extraCell.classList.remove('text-success');
|
||||
extraCell.classList.add('text-danger');
|
||||
}
|
||||
}
|
||||
|
||||
if(isSumModeActive) updateSumResult();
|
||||
@@ -349,20 +366,20 @@ function addCustomEpargneLine(catName = '', amount = '') {
|
||||
const inputName = catName ? `values[${catName}]` : '';
|
||||
|
||||
const html = `
|
||||
<div class="ventilation-line" style="display:flex; gap:10px; align-items:center; background:#f8fafc; padding:8px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
<div style="flex:2;">
|
||||
<input type="text" class="pf-input cat-name-input" value="${catName}" placeholder="${window.I18N['bud_sav_ph_name'] || 'Catégorie'}" oninput="updateCustomFieldName(this)" style="padding:6px; font-size:0.9rem;" required>
|
||||
<div class="ventilation-line">
|
||||
<div class="vl-col-name">
|
||||
<input type="text" class="pf-input cat-name-input pf-input-sm" value="${catName}" placeholder="${window.I18N['bud_sav_ph_name'] || 'Catégorie'}" oninput="updateCustomFieldName(this)" required>
|
||||
</div>
|
||||
<div style="width:100px;">
|
||||
<input type="number" step="0.01" class="pf-input base-amount no-spinners" value="${baseAmount}" oninput="recalculateCustomLine(this)" style="padding:6px; font-size:0.9rem; background:#fff;">
|
||||
<div class="vl-col-base">
|
||||
<input type="number" step="0.01" class="pf-input base-amount no-spinners pf-input-sm bg-white" value="${baseAmount}" oninput="recalculateCustomLine(this)">
|
||||
</div>
|
||||
<div style="width:90px;">
|
||||
<input type="number" step="0.01" class="pf-input adjustment-amount no-spinners" placeholder="+ / -" oninput="recalculateCustomLine(this)" style="padding:6px; font-size:0.9rem; color:#f59e0b; font-weight:bold;">
|
||||
<div class="vl-col-adj">
|
||||
<input type="number" step="0.01" class="pf-input adjustment-amount no-spinners pf-input-sm vl-input-adj" placeholder="+ / -" oninput="recalculateCustomLine(this)">
|
||||
</div>
|
||||
<div style="width:100px;">
|
||||
<input type="number" step="0.01" name="${inputName}" class="pf-input final-amount no-spinners" value="${baseAmount}" style="padding:6px; font-size:0.9rem; font-weight:bold; background:#e0f2fe; border-color:#bae6fd; color:#0369a1;" readonly>
|
||||
<div class="vl-col-final">
|
||||
<input type="number" step="0.01" name="${inputName}" class="pf-input final-amount no-spinners pf-input-sm vl-input-final" value="${baseAmount}" readonly>
|
||||
</div>
|
||||
<button type="button" onclick="this.parentElement.remove()" style="width:28px; height:28px; border:none; background:#fee2e2; color:#ef4444; border-radius:4px; cursor:pointer; display:flex; align-items:center; justify-content:center; font-weight:bold;">×</button>
|
||||
<button type="button" class="btn-remove-line" onclick="this.parentElement.remove()" title="${window.I18N['btn_delete']}">×</button>
|
||||
</div>
|
||||
`;
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
@@ -408,7 +425,7 @@ function editCustomSavingsMonth(monthDate, owner, rowData) {
|
||||
|
||||
if (container.children.length === 0) addCustomEpargneLine();
|
||||
|
||||
document.getElementById('savingsModal').style.display = 'flex';
|
||||
document.getElementById('savingsModal').classList.add('open');
|
||||
document.body.classList.add('no-scroll');
|
||||
}
|
||||
|
||||
@@ -423,18 +440,10 @@ function openCustomSavingsModal(owner) {
|
||||
container.innerHTML = '';
|
||||
addCustomEpargneLine();
|
||||
|
||||
document.getElementById('savingsModal').style.display = 'flex';
|
||||
document.getElementById('savingsModal').classList.add('open');
|
||||
document.body.classList.add('no-scroll');
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('savingsModal');
|
||||
if (event.target == modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
}
|
||||
|
||||
const savingsForm = document.getElementById('savingsForm');
|
||||
if (savingsForm) {
|
||||
savingsForm.addEventListener('submit', async function(e) {
|
||||
@@ -451,25 +460,19 @@ if (savingsForm) {
|
||||
submitBtn.disabled = true;
|
||||
|
||||
const formData = new FormData(this);
|
||||
formData.append('ajax', '1'); // 🚀 CRITIQUE
|
||||
formData.append('ajax', '1');
|
||||
|
||||
try {
|
||||
// 🚀 CRITIQUE : pachaFetch + Chemin absolu forcé
|
||||
const result = await pachaFetch('/modules/budget/includes/api/save-savings.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await pachaFetch('/modules/budget/includes/api/save-savings.php', { method: 'POST', body: formData });
|
||||
if (result.success) {
|
||||
window.location.reload(); // Ne recharge QUE si le serveur a dit OK
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert((window.I18N['bud_err_server'] || 'Erreur serveur : ') + (result.error || "Inconnue"));
|
||||
submitBtn.innerText = originalText;
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur catch:", error);
|
||||
alert(window.I18N['bud_err_tech'] || 'Erreur Technique');
|
||||
alert(window.I18N['bud_err_tech']);
|
||||
submitBtn.innerText = originalText;
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
@@ -477,7 +480,7 @@ if (savingsForm) {
|
||||
}
|
||||
|
||||
async function deleteEntireMonth(monthDate, owner) {
|
||||
const rawMsg = window.I18N['bud_sav_confirm_delete_month'] || "Supprimer %m pour %o ?";
|
||||
const rawMsg = window.I18N['bud_sav_confirm_delete_month'];
|
||||
const msg = rawMsg.replace('%m', monthDate).replace('%o', owner);
|
||||
if (!confirm(msg)) return;
|
||||
|
||||
@@ -485,14 +488,14 @@ async function deleteEntireMonth(monthDate, owner) {
|
||||
formData.append("action", "delete_month_global");
|
||||
formData.append("month_date", monthDate);
|
||||
formData.append("owner", owner);
|
||||
formData.append("ajax", "1"); // 🚀 CRITIQUE
|
||||
formData.append("ajax", "1");
|
||||
|
||||
try {
|
||||
const result = await pachaFetch('/modules/budget/includes/api/save-savings.php', { method: "POST", body: formData });
|
||||
if (result.success) window.location.reload();
|
||||
else alert(result.error || window.I18N['bud_err_delete']);
|
||||
} catch(err) {
|
||||
alert(window.I18N['bud_err_delete'] || 'Erreur lors de la suppression');
|
||||
alert(window.I18N['bud_err_delete']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,7 +516,7 @@ async function duplicateLastMonth(lastMonthDate, owner) {
|
||||
defaultTotal = cycleConfigs[nextMonthStr].start_balance;
|
||||
}
|
||||
|
||||
const rawMsg = window.I18N['bud_sav_prompt_duplicate'] || "Dupliquer vers le mois suivant ?";
|
||||
const rawMsg = window.I18N['bud_sav_prompt_duplicate'];
|
||||
const message = rawMsg.replace('%s', formatMonth(lastMonthDate)).replace(/%t[12]/g, formatMonth(nextMonthStr));
|
||||
|
||||
let newTotal = prompt(message, defaultTotal);
|
||||
@@ -525,7 +528,7 @@ async function duplicateLastMonth(lastMonthDate, owner) {
|
||||
formData.append("target_date", nextMonthStr);
|
||||
formData.append("new_total", newTotal);
|
||||
formData.append("owner", owner);
|
||||
formData.append("ajax", "1"); // 🚀 CRITIQUE
|
||||
formData.append("ajax", "1");
|
||||
|
||||
try {
|
||||
const result = await pachaFetch('/modules/budget/includes/api/save-savings.php', { method: "POST", body: formData });
|
||||
@@ -535,8 +538,7 @@ async function duplicateLastMonth(lastMonthDate, owner) {
|
||||
alert((window.I18N['bud_err_server'] || 'Erreur serveur : ') + (result.error || "Inconnue"));
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
alert(window.I18N['bud_err_network_dup'] || 'Erreur réseau.');
|
||||
alert(window.I18N['bud_err_network_dup']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+147
-148
@@ -425,90 +425,90 @@ $monthName = $monthNames[(int)$viewM] . ' ' . $viewY;
|
||||
|
||||
<div class="budget-view">
|
||||
|
||||
<div style="background:white; padding:20px; border-radius:16px; box-shadow:var(--shadow-sm); margin-bottom:24px; border:1px solid #e2e8f0; position:relative;">
|
||||
<div class="bud-summary-card">
|
||||
|
||||
<?php if ($isClosed): ?>
|
||||
<div style="position:absolute; top:0; left:0; right:0; background:#f1f5f9; padding:5px; text-align:center; font-size:0.8rem; font-weight:bold; color:#64748b; border-radius:16px 16px 0 0; border-bottom:1px solid #e2e8f0;">
|
||||
<div class="bud-closed-banner">
|
||||
🔒 <?= tr('bud_closed_archived') ?>
|
||||
</div>
|
||||
<div style="height:20px;"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px; margin-bottom: 20px;">
|
||||
<div style="display:flex; align-items:center; gap:15px;">
|
||||
<h2 style="margin:0; font-size:1.3rem; color:#0f172a; text-transform:capitalize;"><?= tr('bud_budget_of') ?> <?= $monthName ?></h2>
|
||||
<div class="bud-header-group">
|
||||
<div class="bud-header-title-wrapper">
|
||||
<h2 class="bud-header-title"><?= tr('bud_budget_of') ?> <?= $monthName ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="action-toolbar" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
|
||||
<?php if (!$isClosed): ?>
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="openSuiviModal('importCsvModal')" style="padding:6px 12px; height:auto; width:auto; font-size:0.85rem;">
|
||||
📂 <?= tr('bud_import_csv') ?? 'Importer CSV' ?>
|
||||
</button>
|
||||
|
||||
<form method="POST" onsubmit="return confirm('<?= tr('bud_confirm_close') ?>');">
|
||||
<input type="hidden" name="action" value="close_month">
|
||||
<input type="hidden" name="close_month_date" value="<?= $viewMonthDate ?>">
|
||||
<input type="hidden" name="freeze_solde_actuel" value="<?= $solde_actuel ?>">
|
||||
<input type="hidden" name="freeze_capacite_max" value="<?= $capacite_max ?>">
|
||||
<input type="hidden" name="freeze_solde_theorique" value="<?= $solde_theorique ?>">
|
||||
<input type="hidden" name="freeze_reste_a_venir" value="<?= $reste_a_venir ?>">
|
||||
<button type="submit" class="pf-btn" style="background:linear-gradient(135deg, #10b981, #059669); padding:6px 12px; height:auto; width:auto; font-size:0.85rem;">🔒 <?= tr('bud_close_btn') ?></button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<form method="POST" onsubmit="return confirm('<?= tr('bud_confirm_reopen') ?>');">
|
||||
<input type="hidden" name="action" value="reopen_month">
|
||||
<button type="submit" class="pf-btn btn-secondary" style="padding:6px 12px; height:auto; width:auto; font-size:0.85rem;">🔓 <?= tr('bud_reopen_btn') ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="suivi-nav-group">
|
||||
<a href="<?= $prevLink ?>" class="suivi-btn-nav">◀</a>
|
||||
<a href="<?= $defaultLink ?>" class="suivi-btn-nav"><?= tr('bud_active_month_btn') ?></a>
|
||||
<a href="<?= $nextLink ?>" class="suivi-btn-nav">▶</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap:15px; margin-bottom:25px;">
|
||||
<div style="padding:15px; background:#f8fafc; border-radius:12px; position:relative; border:1px solid #e2e8f0;">
|
||||
<div style="font-size:0.85rem; color:#64748b; margin-bottom:4px;"><?= tr('bud_bank_balance') ?> <?= $isClosed ? "(".tr('bud_frozen').")" : "(".tr('bud_current').")" ?></div>
|
||||
<div style="font-size:1.4rem; font-weight:700; color:#0f172a;">
|
||||
<?= number_format($solde_actuel, 2, ',', ' ') ?> €
|
||||
</div>
|
||||
<div class="action-toolbar bud-toolbar">
|
||||
<?php if (!$isClosed): ?>
|
||||
<button onclick="openSuiviModal('snapshotModal')" style="position:absolute; top:12px; right:12px; background:white; border:1px solid #cbd5e1; border-radius:6px; padding:4px 8px; cursor:pointer; font-size:0.8rem; color:#475569; transition:0.2s;" onmouseover="this.style.background='#f1f5f9'" onmouseout="this.style.background='white'">✏️ <?= tr('bud_update_btn') ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<button type="button" class="pf-btn btn-secondary pf-btn-small" onclick="openSuiviModal('importCsvModal')">
|
||||
📂 <?= tr('bud_import_csv') ?? 'Importer CSV' ?>
|
||||
</button>
|
||||
|
||||
<div style="padding:15px; background:#f8fafc; border-radius:12px; border:1px solid #e2e8f0;">
|
||||
<div style="font-size:0.85rem; color:#64748b; margin-bottom:4px;"><?= tr('bud_theoretical_balance') ?></div>
|
||||
<div style="font-size:1.4rem; font-weight:700; color:<?= $solde_theorique < 0 ? '#ef4444' : '#334155' ?>;">
|
||||
<?= number_format($solde_theorique, 2, ',', ' ') ?> €
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding:15px; background:#fef2f2; border-radius:12px; border:1px solid #fecaca; position: relative;">
|
||||
<div style="font-size:0.85rem; color:#991b1b; margin-bottom:4px; display:flex; justify-content:space-between; align-items:center;">
|
||||
<span><?= tr('bud_upcoming_charges') ?></span>
|
||||
<button onclick="toggleDiv('pendingDetailsList')" style="background:none; border:none; cursor:pointer; font-size:1rem; padding:0; filter: grayscale(1); opacity: 0.7; transition: 0.2s;" onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.7">👁️</button>
|
||||
</div>
|
||||
<div style="font-size:1.4rem; font-weight:700; color:#b91c1c;">
|
||||
- <?= number_format($reste_a_venir, 2, ',', ' ') ?> €
|
||||
</div>
|
||||
<div id="pendingDetailsList" style="display:none; margin-top: 12px; padding-top: 12px; border-top: 1px dashed #fca5a5; font-size: 0.8rem; color: #7f1d1d;">
|
||||
<?php if (empty($pending_charges)): ?>
|
||||
<div style="text-align:center; font-style:italic; opacity:0.8;"><?= tr('bud_all_paid') ?></div>
|
||||
<?php else: ?>
|
||||
<?php foreach($pending_charges as $pc): ?>
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:6px;" <?= !empty($pc['tooltip']) ? 'title="'.htmlspecialchars($pc['tooltip']).'" style="cursor:help;"' : '' ?>>
|
||||
<span><?= htmlspecialchars($pc['name']) ?></span>
|
||||
<strong><?= number_format($pc['amount'], 0, ',', ' ') ?> €</strong>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<form method="POST" onsubmit="return confirm('<?= tr('bud_confirm_close') ?>');">
|
||||
<input type="hidden" name="action" value="close_month">
|
||||
<input type="hidden" name="close_month_date" value="<?= $viewMonthDate ?>">
|
||||
<input type="hidden" name="freeze_solde_actuel" value="<?= $solde_actuel ?>">
|
||||
<input type="hidden" name="freeze_capacite_max" value="<?= $capacite_max ?>">
|
||||
<input type="hidden" name="freeze_solde_theorique" value="<?= $solde_theorique ?>">
|
||||
<input type="hidden" name="freeze_reste_a_venir" value="<?= $reste_a_venir ?>">
|
||||
<button type="submit" class="pf-btn bud-btn-lock">🔒 <?= tr('bud_close_btn') ?></button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<form method="POST" onsubmit="return confirm('<?= tr('bud_confirm_reopen') ?>');">
|
||||
<input type="hidden" name="action" value="reopen_month">
|
||||
<button type="submit" class="pf-btn btn-secondary pf-btn-small">🔓 <?= tr('bud_reopen_btn') ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="suivi-nav-group">
|
||||
<a href="<?= $prevLink ?>" class="suivi-btn-nav">◀</a>
|
||||
<a href="<?= $defaultLink ?>" class="suivi-btn-nav"><?= tr('bud_active_month_btn') ?></a>
|
||||
<a href="<?= $nextLink ?>" class="suivi-btn-nav">▶</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bud-kpi-grid">
|
||||
<div class="bud-kpi-card">
|
||||
<div class="bud-kpi-label"><?= tr('bud_bank_balance') ?> <?= $isClosed ? "(".tr('bud_frozen').")" : "(".tr('bud_current').")" ?></div>
|
||||
<div class="bud-kpi-value">
|
||||
<?= number_format($solde_actuel, 2, ',', ' ') ?> €
|
||||
</div>
|
||||
<?php if (!$isClosed): ?>
|
||||
<button onclick="openSuiviModal('snapshotModal')" class="bud-kpi-edit-btn">✏️ <?= tr('bud_update_btn') ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="bud-kpi-card">
|
||||
<div class="bud-kpi-label"><?= tr('bud_theoretical_balance') ?></div>
|
||||
<div class="bud-kpi-value <?= $solde_theorique < 0 ? 'bud-kpi-value--danger' : '' ?>">
|
||||
<?= number_format($solde_theorique, 2, ',', ' ') ?> €
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bud-kpi-card bud-kpi-card--danger">
|
||||
<div class="bud-kpi-label bud-kpi-label--danger">
|
||||
<span><?= tr('bud_upcoming_charges') ?></span>
|
||||
<button onclick="toggleDiv('pendingDetailsList')" class="bud-pending-toggle">👁️</button>
|
||||
</div>
|
||||
<div class="bud-kpi-value bud-kpi-value--danger">
|
||||
- <?= number_format($reste_a_venir, 2, ',', ' ') ?> €
|
||||
</div>
|
||||
<div id="pendingDetailsList" class="bud-pending-list" style="display:none;">
|
||||
<?php if (empty($pending_charges)): ?>
|
||||
<div style="text-align:center; font-style:italic; opacity:0.8;"><?= tr('bud_all_paid') ?></div>
|
||||
<?php else: ?>
|
||||
<?php foreach($pending_charges as $pc): ?>
|
||||
<div class="bud-pending-item" <?= !empty($pc['tooltip']) ? 'title="'.htmlspecialchars($pc['tooltip']).'" style="cursor:help;"' : '' ?>>
|
||||
<span><?= htmlspecialchars($pc['name']) ?></span>
|
||||
<strong><?= number_format($pc['amount'], 0, ',', ' ') ?> €</strong>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// --- 1. CALCULS DE L'ÉCHELLE DYNAMIQUE ---
|
||||
@@ -534,92 +534,91 @@ $monthName = $monthNames[(int)$viewM] . ' ' . $viewY;
|
||||
}
|
||||
?>
|
||||
|
||||
<div style="margin-top: 65px; margin-bottom: 75px; position: relative; font-family: inherit;">
|
||||
<div class="bud-scale-wrapper">
|
||||
<div class="bud-scale-track">
|
||||
<?php if ($posZero > 0): ?>
|
||||
<div class="bud-scale-zero" style="width: <?= $posZero ?>%;"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="position: relative; height: 24px; background: #e2e8f0; border-radius: 12px; width: 100%; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);">
|
||||
<?php if ($posZero > 0): ?>
|
||||
<div style="position: absolute; left: 0; width: <?= $posZero ?>%; height: 100%; background: #fef2f2; border-radius: 12px 0 0 12px;"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="position: absolute; left: <?= $leftActuel ?>%; width: <?= $widthActuel ?>%; height: 100%; background: repeating-linear-gradient(45deg, #fbbf24, #fbbf24 10px, #f59e0b 10px, #f59e0b 20px); border-radius: <?= $solde_actuel >= 0 ? '0 12px 12px 0' : '12px' ?>; transition: width 0.5s ease-out;"></div>
|
||||
|
||||
<?php if ($solde_theorique < 0): ?>
|
||||
<div style="position: absolute; left: <?= $leftDanger ?>%; width: <?= $widthDanger ?>%; height: 100%; background: repeating-linear-gradient(45deg, #ef4444, #ef4444 10px, #dc2626 10px, #dc2626 20px); border-radius: 12px 0 0 12px; opacity: 0.95;"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="position: absolute; left: <?= $posZero ?>%; top: -5px; height: 35px; width: 2px; background: #94a3b8; z-index: 10;"></div>
|
||||
<div style="position: absolute; left: <?= $posZero ?>%; top: 32px; transform: translateX(-50%); font-size: 0.85rem; font-weight: 800; color: #64748b;">0 €</div>
|
||||
|
||||
<div style="position: absolute; left: <?= $posActuel ?>%; top: -15px; height: 40px; width: 3px; background: #1e293b; z-index: 15;"></div>
|
||||
<div style="position: absolute; left: <?= $posActuel ?>%; top: -35px; transform: translateX(-50%); font-size: 0.8rem; font-weight: bold; color: #1e293b;"><?= tr('bud_bar_actual') ?? 'Actuel' ?></div>
|
||||
|
||||
<div style="position: absolute; left: <?= $posTheorique ?>%; top: 24px; height: 30px; width: 3px; background: <?= $solde_theorique >= 0 ? '#8b5cf6' : '#ef4444' ?>; z-index: 12;"></div>
|
||||
<div style="position: absolute; left: <?= $posTheorique ?>%; top: 54px; transform: translateX(-50%); font-size: 0.85rem; font-weight: bold; color: <?= $solde_theorique >= 0 ? '#8b5cf6' : '#ef4444' ?>; white-space: nowrap;"><?= tr('bud_bar_theoretical') ?? 'Théorique' ?></div>
|
||||
|
||||
<?php
|
||||
$overlapMax = abs($posMax - $posActuel) < 35;
|
||||
$topMaxText = $overlapMax ? '-60px' : '-35px';
|
||||
$heightMaxLine = $overlapMax ? '65px' : '40px';
|
||||
$topMaxLine = $overlapMax ? '-40px' : '-15px';
|
||||
?>
|
||||
<div style="position: absolute; left: <?= $posMax ?>%; top: <?= $topMaxLine ?>; height: <?= $heightMaxLine ?>; width: 2px; background: #cbd5e1; z-index: 10;"></div>
|
||||
|
||||
<div style="position: absolute; left: <?= $posMax ?>%; top: <?= $topMaxText ?>; transform: translateX(calc(-100% + 10px)); font-size: 0.8rem; font-weight: bold; color: #64748b; white-space: nowrap; cursor: help;" title="<?= sprintf(tr('bud_capacity_tooltip'), number_format($solde_initial, 0), number_format($salaires_retenus, 0), number_format($rentrees_autres, 0)) ?>">
|
||||
<?= tr('bud_bar_max_cap') ?? 'Capacité Max' ?> : <?= number_format($capacite_max, 0, ',', ' ') ?> € ℹ️
|
||||
</div>
|
||||
<div class="bud-scale-bar" style="left: <?= $leftActuel ?>%; width: <?= $widthActuel ?>%; border-radius: <?= $solde_actuel >= 0 ? '0 12px 12px 0' : '12px' ?>;"></div>
|
||||
|
||||
<?php if ($solde_theorique < 0): ?>
|
||||
<div class="bud-scale-bar--danger" style="left: <?= $leftDanger ?>%; width: <?= $widthDanger ?>%;"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="bud-scale-marker bud-scale-marker--zero" style="left: <?= $posZero ?>%;"></div>
|
||||
<div class="bud-scale-label bud-scale-label--zero" style="left: <?= $posZero ?>%;">0 €</div>
|
||||
|
||||
<div class="bud-scale-marker bud-scale-marker--actual" style="left: <?= $posActuel ?>%;"></div>
|
||||
<div class="bud-scale-label bud-scale-label--actual" style="left: <?= $posActuel ?>%;"><?= tr('bud_bar_actual') ?? 'Actuel' ?></div>
|
||||
|
||||
<?php $colorTheo = $solde_theorique >= 0 ? 'var(--primary)' : 'var(--danger)'; ?>
|
||||
<div class="bud-scale-marker bud-scale-marker--theo" style="left: <?= $posTheorique ?>%; background: <?= $colorTheo ?>;"></div>
|
||||
<div class="bud-scale-label bud-scale-label--theo" style="left: <?= $posTheorique ?>%; color: <?= $colorTheo ?>;"><?= tr('bud_bar_theoretical') ?? 'Théorique' ?></div>
|
||||
|
||||
<?php
|
||||
$overlapMax = abs($posMax - $posActuel) < 35;
|
||||
$topMaxText = $overlapMax ? '-60px' : '-35px';
|
||||
$heightMaxLine = $overlapMax ? '65px' : '40px';
|
||||
$topMaxLine = $overlapMax ? '-40px' : '-15px';
|
||||
?>
|
||||
<div class="bud-scale-marker bud-scale-marker--max" style="left: <?= $posMax ?>%; top: <?= $topMaxLine ?>; height: <?= $heightMaxLine ?>;"></div>
|
||||
|
||||
<div class="bud-scale-label bud-scale-label--max" style="left: <?= $posMax ?>%; top: <?= $topMaxText ?>; transform: translateX(calc(-100% + 10px));" title="<?= sprintf(tr('bud_capacity_tooltip'), number_format($solde_initial, 0), number_format($salaires_retenus, 0), number_format($rentrees_autres, 0)) ?>">
|
||||
<?= tr('bud_bar_max_cap') ?? 'Capacité Max' ?> : <?= number_format($capacite_max, 0, ',', ' ') ?> € ℹ️
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="categories-grid" style="display:grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap:24px;">
|
||||
<?php foreach ($categoriesConfig as $key => $conf): ?>
|
||||
<div class="cat-card" <?= $isClosed ? 'style="opacity:0.8;"' : '' ?>>
|
||||
<div class="cat-card-header" style="background:<?= $conf['color'] ?>15; border-bottom:1px solid <?= $conf['color'] ?>30;">
|
||||
<div class="cat-card-title-group">
|
||||
<h3 style="margin:0; font-size:1rem; color:<?= $conf['color'] ?>;"><?= $conf['label'] ?></h3>
|
||||
<div class="cat-card-subtitle">
|
||||
<?php $logic = getDisplayLogic($totals[$key], $conf['budget'], $conf['type']); echo $logic['text']; ?>
|
||||
</div>
|
||||
<div class="bud-categories-grid">
|
||||
<?php foreach ($categoriesConfig as $key => $conf): ?>
|
||||
<div class="cat-card" <?= $isClosed ? 'style="opacity:0.8;"' : '' ?>>
|
||||
<div class="cat-card-header" style="background:<?= $conf['color'] ?>15; border-bottom:1px solid <?= $conf['color'] ?>30;">
|
||||
<div class="cat-card-title-group">
|
||||
<h3 style="margin:0; font-size:1rem; color:<?= $conf['color'] ?>;"><?= $conf['label'] ?></h3>
|
||||
<div class="cat-card-subtitle">
|
||||
<?php $logic = getDisplayLogic($totals[$key], $conf['budget'], $conf['type']); echo $logic['text']; ?>
|
||||
</div>
|
||||
<?php if (!$isClosed): ?>
|
||||
<button class="btn-add-item" style="color:<?= $conf['color'] ?>;" onclick="openAddModal('<?= $key ?>', '<?= addslashes(strip_tags($conf['label'])) ?>')">+</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php $barCol = ($conf['db_type'] === 'Income') ? '#10b981' : ($logic['isOver'] ? '#ef4444' : $conf['color']); ?>
|
||||
<div style="background:#f1f5f9; height:4px; width:100%;">
|
||||
<div style="width:<?= $logic['pct'] ?>%; background:<?= $barCol ?>; height:100%;"></div>
|
||||
</div>
|
||||
|
||||
<div style="flex:1; max-height:300px; overflow-y:auto; padding:0;">
|
||||
<?php if (empty($expensesByCategory[$key])): ?>
|
||||
<div style="padding:20px; text-align:center; color:#cbd5e1; font-style:italic; font-size:0.85rem;"><?= tr('bud_no_lines') ?></div>
|
||||
<?php else: ?>
|
||||
<table style="width:100%; border-collapse:collapse; font-size:0.85rem;">
|
||||
<?php foreach ($expensesByCategory[$key] as $exp): ?>
|
||||
<tr style="border-bottom:1px solid #f8fafc;">
|
||||
<td style="padding:10px 15px; color:#94a3b8;"><?= date('d/m', strtotime($exp['date_exp'])) ?></td>
|
||||
<td style="padding:10px 5px; font-weight:500;">
|
||||
<?= htmlspecialchars($exp['label']) ?>
|
||||
</td>
|
||||
<td style="padding:10px 15px; text-align:right; font-weight:600; color:<?= $exp['amount'] > 0 ? '#10b981' : '#1e293b' ?>;">
|
||||
<?= $exp['amount'] > 0 ? '+' : '-' ?><?= number_format(abs($exp['amount']), 2) ?>
|
||||
</td>
|
||||
<?php if (!$isClosed): ?>
|
||||
<td style="width:70px; padding-right:10px; text-align:right; white-space:nowrap;">
|
||||
<button class="btn-icon-action edit" onclick='openEditModal(<?= json_encode($exp, JSON_HEX_APOS | JSON_HEX_QUOT) ?>)' title="<?= tr('edit') ?>">✏️</button>
|
||||
<button class="btn-icon-action delete btn-safe-click" onclick="deleteExpense(<?= $exp['id'] ?>)" title="<?= tr('delete') ?>">🗑️</button>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (!$isClosed): ?>
|
||||
<button class="btn-add-item" style="color:<?= $conf['color'] ?>;" onclick="openAddModal('<?= $key ?>', '<?= addslashes(strip_tags($conf['label'])) ?>')">+</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php $barCol = ($conf['db_type'] === 'Income') ? 'var(--success)' : ($logic['isOver'] ? 'var(--danger)' : $conf['color']); ?>
|
||||
<div style="background:var(--bg-page); height:4px; width:100%;">
|
||||
<div style="width:<?= $logic['pct'] ?>%; background:<?= $barCol ?>; height:100%;"></div>
|
||||
</div>
|
||||
|
||||
<div style="flex:1; max-height:300px; overflow-y:auto; padding:0;">
|
||||
<?php if (empty($expensesByCategory[$key])): ?>
|
||||
<div style="padding:20px; text-align:center; color:var(--text-muted); font-style:italic; font-size:0.85rem;"><?= tr('bud_no_lines') ?></div>
|
||||
<?php else: ?>
|
||||
<table class="bud-expense-table">
|
||||
<?php foreach ($expensesByCategory[$key] as $exp): ?>
|
||||
<tr class="bud-expense-row">
|
||||
<td class="bud-expense-date"><?= date('d/m', strtotime($exp['date_exp'])) ?></td>
|
||||
<td class="bud-expense-label">
|
||||
<?= htmlspecialchars($exp['label']) ?>
|
||||
</td>
|
||||
<td class="bud-expense-amount" style="color:<?= $exp['amount'] > 0 ? 'var(--success)' : 'var(--text-main)' ?>;">
|
||||
<?= $exp['amount'] > 0 ? '+' : '-' ?><?= number_format(abs($exp['amount']), 2) ?>
|
||||
</td>
|
||||
<?php if (!$isClosed): ?>
|
||||
<td class="bud-expense-actions">
|
||||
<button class="btn-icon-action edit" onclick='openEditModal(<?= json_encode($exp, JSON_HEX_APOS | JSON_HEX_QUOT) ?>)' title="<?= tr('edit') ?>">✏️</button>
|
||||
<button class="btn-icon-action delete btn-safe-click" onclick="deleteExpense(<?= $exp['id'] ?>)" title="<?= tr('delete') ?>">🗑️</button>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+256
-105
@@ -33,10 +33,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!verify_csrf($_POST['csrf_token'] ?? null)) {
|
||||
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'error' => 'Session invalide (CSRF). Rechargez la page.']);
|
||||
echo json_encode(['success' => false, 'error' => tr('error_csrf')]);
|
||||
exit;
|
||||
}
|
||||
$error = "Session invalide (CSRF). Rechargez la page.";
|
||||
$error = tr('error_csrf');
|
||||
} else {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
@@ -44,12 +44,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$all = ['calendar', 'budget', 'holidays', 'gifts', 'garage', 'memo', 'todo', 'liste', 'calendar_ios', 'printvault', 'planka'];
|
||||
$enabled = array_values(array_filter($all, fn($m) => isset($_POST['mod_' . $m])));
|
||||
if (empty($enabled)) {
|
||||
$error = "Vous devez garder au moins un module actif.";
|
||||
$error = tr('set_err_min_module');
|
||||
} else {
|
||||
$meta_pdo->prepare("UPDATE families SET enabled_modules = ? WHERE id = ?")
|
||||
->execute([json_encode($enabled), $family_id]);
|
||||
$_SESSION['enabled_modules'] = $enabled;
|
||||
$success = "Modules mis à jour avec succès.";
|
||||
$success = tr('set_success_modules');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_SESSION['app_lang'] = $lang;
|
||||
$meta_pdo->prepare("UPDATE users SET lang = ? WHERE id = ?")
|
||||
->execute([$lang, $user_id]);
|
||||
$success = "Langue mise à jour.";
|
||||
$success = tr('set_success_lang');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$color = trim($_POST['color'] ?? '#0891b2');
|
||||
|
||||
if (!$display_name) {
|
||||
$error = "Le prénom ne peut pas être vide.";
|
||||
$error = tr('set_err_empty_name');
|
||||
} else {
|
||||
$meta_pdo->prepare("UPDATE users SET display_name = ? WHERE id = ?")
|
||||
->execute([$display_name, $user_id]);
|
||||
@@ -80,7 +80,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
$_SESSION['user']['display_name'] = $display_name;
|
||||
$success = "Profil mis à jour avec succès.";
|
||||
$success = tr('set_success_profile');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,15 +94,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$row = $row->fetch();
|
||||
|
||||
if (!password_verify($current, $row['password_hash'])) {
|
||||
$error = "Mot de passe actuel incorrect.";
|
||||
$error = tr('set_err_wrong_pwd');
|
||||
} elseif (strlen($new) < 6) {
|
||||
$error = "Le nouveau mot de passe doit faire au moins 6 caractères.";
|
||||
$error = tr('set_err_pwd_length');
|
||||
} elseif ($new !== $confirm) {
|
||||
$error = "Les mots de passe ne correspondent pas.";
|
||||
$error = tr('set_err_pwd_match');
|
||||
} else {
|
||||
$meta_pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?")
|
||||
->execute([password_hash($new, PASSWORD_BCRYPT), $user_id]);
|
||||
$success = "Mot de passe modifié avec succès.";
|
||||
$success = tr('set_success_pwd');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($name) {
|
||||
$meta_pdo->prepare("UPDATE families SET name = ? WHERE id = ?")
|
||||
->execute([$name, $family_id]);
|
||||
$success = "Nom de l'espace mis à jour.";
|
||||
$success = tr('set_success_family');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$new_code = bin2hex(random_bytes(8));
|
||||
$meta_pdo->prepare("UPDATE families SET invite_code = ? WHERE id = ?")
|
||||
->execute([$new_code, $family_id]);
|
||||
$success = "Nouveau code d'invitation généré.";
|
||||
$success = tr('set_success_invite');
|
||||
}
|
||||
|
||||
if ($action === 'upload_home_bg' && $family_id) {
|
||||
@@ -129,14 +129,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($file && $file['error'] === 0) {
|
||||
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, ['jpg','jpeg','png','webp'])) {
|
||||
$error = "Format non supporté (jpg, png, webp).";
|
||||
$error = tr('set_err_bg_format');
|
||||
} else {
|
||||
$dest = $upload_dir . 'home_bg_' . $family_id . '.' . $ext;
|
||||
foreach (glob($upload_dir . 'home_bg_' . $family_id . '.*') as $old) @unlink($old);
|
||||
if (move_uploaded_file($file['tmp_name'], $dest)) {
|
||||
$success = "Image d'accueil mise à jour.";
|
||||
$success = tr('set_success_bg');
|
||||
} else {
|
||||
$error = "Erreur lors de l'upload.";
|
||||
$error = tr('set_err_upload');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,13 +144,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
if ($action === 'reset_home_bg' && $family_id) {
|
||||
foreach (glob('/uploads/home_bg_' . $family_id . '.*') as $old) @unlink($old);
|
||||
$success = "Image d'accueil réinitialisée.";
|
||||
$success = tr('set_success_bg_reset');
|
||||
}
|
||||
|
||||
if ($action === 'grocery_history_max' && $family_id) {
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
if (!isset($pdo)) {
|
||||
$error = "Base famille indisponible.";
|
||||
$error = tr('set_err_db_unavailable');
|
||||
} else {
|
||||
$n = (int) ($_POST['history_max'] ?? 20);
|
||||
$n = max(1, min(50, $n));
|
||||
@@ -171,7 +171,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$calendarUrl = trim($_POST['icloud_calendar_url'] ?? '');
|
||||
|
||||
if (!$username || !$appPassword || !$calendarUrl) {
|
||||
$error = "Merci de renseigner identifiant iCloud, mot de passe d'app et URL CalDAV.";
|
||||
$error = tr('set_err_ios_missing');
|
||||
} else {
|
||||
try {
|
||||
$resolvedUrl = hh_icloud_resolve_calendar_url_if_needed($username, $appPassword, $calendarUrl);
|
||||
@@ -181,13 +181,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
VALUES (?, 'icloud_caldav', ?, ?, ?, 'connected', NOW())
|
||||
ON DUPLICATE KEY UPDATE username=VALUES(username), secret_encrypted=VALUES(secret_encrypted), calendar_url=VALUES(calendar_url), status='connected', updated_at=NOW()
|
||||
")->execute([$user_id, $username, $encrypted, $resolvedUrl]);
|
||||
$msg = "Connexion calendrier iOS enregistrée.";
|
||||
$msg = tr('set_success_ios');
|
||||
if (rtrim($resolvedUrl, '/') !== rtrim($calendarUrl, '/')) {
|
||||
$msg .= " URL du calendrier détectée automatiquement.";
|
||||
$msg .= tr('set_success_ios_auto');
|
||||
}
|
||||
$success = $msg;
|
||||
} catch (\Throwable $e) {
|
||||
$error = "Impossible d'enregistrer la connexion iOS: " . $e->getMessage();
|
||||
$error = tr('set_err_ios_save') . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$row->execute([$user_id]);
|
||||
$integration = $row->fetch();
|
||||
if (!$integration) {
|
||||
$error = "Aucune connexion iOS configurée.";
|
||||
$error = tr('set_err_ios_none');
|
||||
} else {
|
||||
try {
|
||||
$pwd = hh_normalize_apple_app_password(hh_decrypt_secret($integration['secret_encrypted']));
|
||||
@@ -209,31 +209,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$meta_pdo->prepare("UPDATE user_calendar_integrations SET calendar_url = ? WHERE id = ?")
|
||||
->execute([$resolvedUrl, $integration['id']]);
|
||||
}
|
||||
$success = "Connexion iCloud CalDAV valide (HTTP $code).";
|
||||
$success = sprintf(tr('set_success_ios_test'), $code);
|
||||
} else {
|
||||
$error = "Test connexion échoué (HTTP $code). Vérifiez vos identifiants.";
|
||||
$error = sprintf(tr('set_err_ios_test'), $code);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$error = "Test connexion impossible: " . $e->getMessage();
|
||||
$error = tr('set_err_ios_test_fail') . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === 'calendar_ios_disconnect') {
|
||||
$meta_pdo->prepare("DELETE FROM user_calendar_integrations WHERE user_id = ? AND provider='icloud_caldav'")->execute([$user_id]);
|
||||
$success = "Connexion iOS supprimée.";
|
||||
$success = tr('set_success_ios_del');
|
||||
}
|
||||
|
||||
// --- GESTION DES PROFILS RATTACHÉS (Enfants, Nounous, etc.) ---
|
||||
if ($action === 'add_attached_profile') {
|
||||
$name = trim($_POST['profile_name'] ?? '');
|
||||
$role = trim($_POST['profile_role'] ?? 'child');
|
||||
$color = trim($_POST['profile_color'] ?? '#3b82f6');
|
||||
|
||||
if ($name) {
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
if (isset($pdo)) {
|
||||
$stmt = $pdo->prepare("INSERT INTO pf_people (name, role) VALUES (?, ?)");
|
||||
$stmt->execute([$name, $role]);
|
||||
$stmt = $pdo->prepare("INSERT INTO pf_people (name, role, color) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$name, $role, $color]);
|
||||
$success = "Profil de {$name} ajouté avec succès.";
|
||||
}
|
||||
} else {
|
||||
@@ -241,15 +242,60 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === 'update_attached_profile_color') {
|
||||
$profile_id = (int)($_POST['profile_id'] ?? 0);
|
||||
$color = trim($_POST['color'] ?? '#3b82f6');
|
||||
if ($profile_id > 0) {
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
if (isset($pdo)) {
|
||||
$stmt = $pdo->prepare("UPDATE pf_people SET color = ? WHERE id = ? AND user_id IS NULL");
|
||||
$stmt->execute([$color, $profile_id]);
|
||||
$success = "Couleur mise à jour.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === 'delete_attached_profile') {
|
||||
$profile_id = (int)($_POST['profile_id'] ?? 0);
|
||||
if ($profile_id > 0) {
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
if (isset($pdo)) {
|
||||
// On protège les vrais utilisateurs en s'assurant que user_id est NULL
|
||||
$stmt = $pdo->prepare("DELETE FROM pf_people WHERE id = ? AND user_id IS NULL");
|
||||
$stmt->execute([$profile_id]);
|
||||
$success = "Profil supprimé.";
|
||||
$success = tr('set_success_profile_del');
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($action === 'update_profile_color') {
|
||||
$profile_id = (int)($_POST['profile_id'] ?? 0);
|
||||
$color = trim($_POST['color'] ?? '#0891b2');
|
||||
if ($profile_id > 0) {
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
if (isset($pdo)) {
|
||||
$stmt = $pdo->prepare("UPDATE pf_people SET color = ? WHERE id = ? AND user_id IS NULL");
|
||||
$stmt->execute([$color, $profile_id]);
|
||||
$success = tr('set_success_color_profile');
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($action === 'update_attached_profile_color') {
|
||||
$profile_id = (int)($_POST['profile_id'] ?? 0);
|
||||
$color = trim($_POST['color'] ?? '');
|
||||
|
||||
// Vérification stricte format Hexadécimal
|
||||
if ($profile_id > 0 && preg_match('/^#[a-f0-9]{6}$/i', $color)) {
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
if (isset($pdo)) {
|
||||
// user_id IS NULL garantit qu'on ne modifie pas un vrai compte Parent par accident
|
||||
$stmt = $pdo->prepare("UPDATE pf_people SET color = ? WHERE id = ? AND user_id IS NULL");
|
||||
$stmt->execute([$color, $profile_id]);
|
||||
|
||||
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
}
|
||||
$success = tr('set_success_color_update');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,9 +335,8 @@ if ($family_id) {
|
||||
$mem->execute([$family_id]);
|
||||
$members = $mem->fetchAll();
|
||||
|
||||
// Récupération des profils rattachés (ceux qui n'ont pas de compte de connexion, donc user_id IS NULL)
|
||||
if (isset($pdo)) {
|
||||
$stmtAttached = $pdo->query("SELECT id, name, role FROM pf_people WHERE user_id IS NULL ORDER BY role ASC, name ASC");
|
||||
$stmtAttached = $pdo->query("SELECT id, name, role, color FROM pf_people WHERE user_id IS NULL ORDER BY role ASC, name ASC");
|
||||
$attached_profiles = $stmtAttached->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
@@ -310,17 +355,17 @@ if ($family_id && isset($pdo)) {
|
||||
} catch (\Throwable $e) {}
|
||||
}
|
||||
|
||||
$pageTitle = "Paramètres — HouseHub";
|
||||
$pageTitle = tr('set_title_page');
|
||||
$activePage = "settings";
|
||||
require __DIR__ . '/header.php';
|
||||
?>
|
||||
|
||||
<div class="pf-container pf-settings-page">
|
||||
|
||||
<h1 class="pf-settings-title">⚙️ Paramètres</h1>
|
||||
<h1 class="pf-settings-title"><?= tr('set_title_page') ?></h1>
|
||||
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2">🌐 Langue / Language</h2>
|
||||
<h2 class="pf-card-h2"><?= tr('set_title_lang') ?></h2>
|
||||
<form method="post" class="pf-lang-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="set_lang">
|
||||
@@ -347,8 +392,8 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<?php if ($family_id): ?>
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight">🧩 Modules actifs</h2>
|
||||
<p class="pf-muted-note">Choisissez les modules visibles dans la navigation (partagé avec tous les membres de l'espace).</p>
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight"><?= tr('set_title_modules') ?></h2>
|
||||
<p class="pf-muted-note"><?= tr('set_desc_modules') ?></p>
|
||||
<?php
|
||||
$enabledMods = $_SESSION['enabled_modules'] ?? ['calendar','budget','holidays','gifts','calendar_ios'];
|
||||
$allModules = [
|
||||
@@ -389,13 +434,13 @@ require __DIR__ . '/header.php';
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight">📝 <?= htmlspecialchars(tr('liste_settings_title')) ?></h2>
|
||||
<p class="pf-muted-note"><?= htmlspecialchars(tr('liste_settings_intro')) ?></p>
|
||||
<form method="post" class="pf-stack-md" style="margin-top:1rem">
|
||||
<form method="post" class="pf-stack-md pf-mt-md">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="grocery_history_max">
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label" for="history_max"><?= htmlspecialchars(tr('liste_settings_history_max')) ?></label>
|
||||
<input type="number" name="history_max" id="history_max" class="pf-input" style="max-width:120px" min="1" max="50" step="1" value="<?= (int) $groceryHistoryMaxSetting ?>" required>
|
||||
<p class="pf-muted-note" style="margin-top:0.35rem"><?= htmlspecialchars(tr('liste_settings_history_hint')) ?></p>
|
||||
<input type="number" name="history_max" id="history_max" class="pf-input pf-input-sm" min="1" max="50" step="1" value="<?= (int) $groceryHistoryMaxSetting ?>" required>
|
||||
<p class="pf-muted-note pf-mt-xs"><?= htmlspecialchars(tr('liste_settings_history_hint')) ?></p>
|
||||
</div>
|
||||
<button type="submit" class="pf-btn"><?= htmlspecialchars(tr('btn_save')) ?></button>
|
||||
</form>
|
||||
@@ -403,21 +448,21 @@ require __DIR__ . '/header.php';
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight">📱 Intégration Calendrier iOS (CalDAV)</h2>
|
||||
<p class="pf-muted-note">Configurez ici votre calendrier iCloud pour synchroniser les événements créés dans HouseHub.</p>
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight"><?= tr('set_title_ios') ?></h2>
|
||||
<p class="pf-muted-note"><?= tr('set_desc_ios') ?></p>
|
||||
<form method="post" class="pf-stack-md">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="calendar_ios_save">
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Identifiant Apple (email iCloud)</label>
|
||||
<input type="text" name="icloud_username" class="pf-input" value="<?= htmlspecialchars($calendarIntegration['username'] ?? '') ?>" placeholder="nom@icloud.com" required>
|
||||
<label class="pf-label"><?= tr('set_label_apple_id') ?></label>
|
||||
<input type="text" name="icloud_username" class="pf-input" value="<?= htmlspecialchars($calendarIntegration['username'] ?? '') ?>" placeholder="<?= tr('placeholder_apple_id') ?>" required>
|
||||
</div>
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Mot de passe d'app Apple</label>
|
||||
<input type="password" name="icloud_app_password" class="pf-input" placeholder="xxxx-xxxx-xxxx-xxxx" required>
|
||||
<label class="pf-label"><?= tr('set_label_apple_pwd') ?></label>
|
||||
<input type="password" name="icloud_app_password" class="pf-input" placeholder="<?= tr('placeholder_apple_pwd') ?>" required>
|
||||
</div>
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">URL du calendrier CalDAV</label>
|
||||
<label class="pf-label"><?= tr('set_label_apple_url') ?></label>
|
||||
<input type="url" name="icloud_calendar_url" class="pf-input" value="<?= htmlspecialchars($calendarIntegration['calendar_url'] ?? '') ?>" placeholder="https://caldav.icloud.com/..." required>
|
||||
</div>
|
||||
<div class="pf-flex-gap-8">
|
||||
@@ -429,12 +474,12 @@ require __DIR__ . '/header.php';
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="calendar_ios_test">
|
||||
<button type="submit" class="pf-btn btn-secondary">Tester la connexion</button>
|
||||
<button type="submit" class="pf-btn btn-secondary"><?= tr('set_btn_test_ios') ?></button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="calendar_ios_disconnect">
|
||||
<button type="submit" class="pf-btn btn-secondary">Déconnecter</button>
|
||||
<button type="submit" class="pf-btn btn-secondary"><?= tr('set_btn_disconnect_ios') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -445,13 +490,13 @@ require __DIR__ . '/header.php';
|
||||
$has_custom_bg = $bg_file !== null;
|
||||
?>
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight">🖼️ Image d'accueil</h2>
|
||||
<p class="pf-muted-note">Photo de fond affichée sur la page d'accueil (partagée avec tous les membres).</p>
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight"><?= tr('set_title_bg') ?></h2>
|
||||
<p class="pf-muted-note"><?= tr('set_desc_bg') ?></p>
|
||||
|
||||
<?php if ($has_custom_bg): ?>
|
||||
<div class="pf-home-bg-preview">
|
||||
<img src="/home-bg.php" alt="Fond actuel">
|
||||
<span class="pf-badge-overlay">Image personnalisée</span>
|
||||
<span class="pf-badge-overlay"><?= tr('set_badge_custom_bg') ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -459,10 +504,10 @@ require __DIR__ . '/header.php';
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="upload_home_bg">
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Nouvelle image (jpg, png, webp — max 10 Mo)</label>
|
||||
<label class="pf-label"><?= tr('set_label_new_bg') ?></label>
|
||||
<input type="file" name="home_bg" class="pf-input" accept="image/jpeg,image/png,image/webp" required>
|
||||
</div>
|
||||
<button type="submit" class="pf-btn pf-shrink-0">Enregistrer</button>
|
||||
<button type="submit" class="pf-btn pf-shrink-0"><?= tr('btn_save') ?></button>
|
||||
</form>
|
||||
|
||||
<?php if ($has_custom_bg): ?>
|
||||
@@ -470,32 +515,49 @@ require __DIR__ . '/header.php';
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="reset_home_bg">
|
||||
<button type="submit" class="pf-btn btn-secondary pf-btn-sm-text"
|
||||
onclick="return confirm('Revenir à l\'image par défaut ?')">🔄 Remettre l'image par défaut</button>
|
||||
onclick="return confirm('<?= addslashes(tr('set_confirm_reset_bg')) ?>')"><?= tr('set_btn_reset_bg') ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2">👤 Mon profil</h2>
|
||||
<h2 class="pf-card-h2"><?= tr('set_title_profile') ?></h2>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="update_profile">
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Nom d'utilisateur</label>
|
||||
<label class="pf-label"><?= tr('set_label_username') ?></label>
|
||||
<input type="text" class="pf-input pf-input--disabled-muted" value="<?= htmlspecialchars($user['username']) ?>" disabled>
|
||||
</div>
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Prénom / Pseudo affiché</label>
|
||||
<label class="pf-label"><?= tr('set_label_display_name') ?></label>
|
||||
<input type="text" name="display_name" class="pf-input" value="<?= htmlspecialchars($user['display_name']) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label" for="profile_color">Couleur thématique de mon profil</label>
|
||||
<div class="pf-color-picker-group">
|
||||
<input type="color" name="color" id="profile_color" class="pf-input-color" value="<?= htmlspecialchars($user_color) ?>">
|
||||
<span class="pf-muted-note">Cette couleur sera utilisée pour vos grilles de budget, calendriers et indicateurs personnels.</span>
|
||||
|
||||
<!-- Le champ caché qui va stocker la valeur -->
|
||||
<input type="hidden" name="color" id="profile_color" value="<?= htmlspecialchars($user_color) ?>">
|
||||
|
||||
<!-- Le panel de couleurs prédéfinies -->
|
||||
<div class="pf-color-palette" id="color-palette">
|
||||
<?php
|
||||
$palette = ['#ef4444', '#f97316', '#f59e0b', '#84cc16', '#10b981', '#14b8a6', '#06b6d4', '#0ea5e9', '#3b82f6', '#6366f1', '#8b5cf6', '#d946ef', '#f43f5e', '#64748b'];
|
||||
foreach ($palette as $c):
|
||||
$isSelected = (strtolower($user_color) === $c) ? 'selected' : '';
|
||||
?>
|
||||
<button type="button"
|
||||
class="pf-color-swatch <?= $isSelected ?>"
|
||||
style="background-color: <?= $c ?>;"
|
||||
data-color="<?= $c ?>"
|
||||
onclick="selectProfileColor(this, '<?= $c ?>')">
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<span class="pf-muted-note pf-profile-color-note"><?= tr('set_desc_profile_color') ?></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
|
||||
@@ -503,65 +565,65 @@ require __DIR__ . '/header.php';
|
||||
</section>
|
||||
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2">🔒 Changer le mot de passe</h2>
|
||||
<h2 class="pf-card-h2"><?= tr('set_title_pwd') ?></h2>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="change_password">
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Mot de passe actuel</label>
|
||||
<label class="pf-label"><?= tr('set_label_cur_pwd') ?></label>
|
||||
<input type="password" name="current_password" class="pf-input" required placeholder="••••••">
|
||||
</div>
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Nouveau mot de passe</label>
|
||||
<label class="pf-label"><?= tr('set_label_new_pwd') ?></label>
|
||||
<input type="password" name="new_password" class="pf-input" required placeholder="••••••" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Confirmer</label>
|
||||
<label class="pf-label"><?= tr('set_label_confirm_pwd') ?></label>
|
||||
<input type="password" name="confirm_password" class="pf-input" required placeholder="••••••" autocomplete="new-password">
|
||||
</div>
|
||||
<button type="submit" class="pf-btn">Modifier</button>
|
||||
<button type="submit" class="pf-btn"><?= tr('set_btn_edit') ?></button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<?php if ($family): ?>
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2">🏠 Mon espace familial</h2>
|
||||
<h2 class="pf-card-h2"><?= tr('set_title_family') ?></h2>
|
||||
|
||||
<form method="post" class="pf-mb-section">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="update_family_name">
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label">Nom de l'espace</label>
|
||||
<label class="pf-label"><?= tr('set_label_family_name') ?></label>
|
||||
<div class="pf-flex-gap-8">
|
||||
<input type="text" name="family_name" class="pf-input" value="<?= htmlspecialchars($family['name']) ?>" required>
|
||||
<button type="submit" class="pf-btn pf-shrink-0">Enregistrer</button>
|
||||
<button type="submit" class="pf-btn pf-shrink-0"><?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="pf-dashed-panel">
|
||||
<p class="pf-muted-note pf-muted-note--tight">Code d'invitation — partagez-le pour inviter quelqu'un</p>
|
||||
<p class="pf-muted-note pf-muted-note--tight"><?= tr('set_desc_invite') ?></p>
|
||||
<div class="pf-invite-row">
|
||||
<code id="invite-code" class="pf-invite-code"
|
||||
onclick="copyCode()" title="Cliquer pour copier">
|
||||
onclick="copyCode()" title="<?= tr('set_title_copy') ?>">
|
||||
<?= htmlspecialchars($family['invite_code']) ?>
|
||||
</code>
|
||||
<span id="copy-msg" class="pf-copy-msg">✓ Copié !</span>
|
||||
<span id="copy-msg" class="pf-copy-msg"><?= tr('set_msg_copied') ?></span>
|
||||
<form method="post" class="pf-ml-auto">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="regen_invite">
|
||||
<button type="submit" class="pf-btn btn-secondary pf-btn-sm-text"
|
||||
onclick="return confirm('Regénérer le code ? L\'ancien sera invalidé.')">
|
||||
🔄 Nouveau code
|
||||
onclick="return confirm('<?= addslashes(tr('set_confirm_new_code')) ?>')">
|
||||
<?= tr('set_btn_new_code') ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<p class="pf-invite-footnote">
|
||||
Lien d'inscription : <strong><?= $_SERVER['HTTP_HOST'] ?>/register.php</strong>
|
||||
<?= tr('set_desc_register_link') ?> <strong><?= $_SERVER['HTTP_HOST'] ?>/register.php</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 class="pf-members-heading">Membres (<?= count($members) ?>)</h3>
|
||||
<h3 class="pf-members-heading"><?= tr('set_title_members') ?> (<?= count($members) ?>)</h3>
|
||||
<div class="pf-stack-sm">
|
||||
<?php foreach ($members as $m): ?>
|
||||
<div class="pf-member-card">
|
||||
@@ -569,7 +631,7 @@ require __DIR__ . '/header.php';
|
||||
<strong><?= htmlspecialchars($m['display_name']) ?></strong>
|
||||
<span class="pf-muted-inline">@<?= htmlspecialchars($m['username']) ?></span>
|
||||
<?php if ($m['is_admin']): ?>
|
||||
<span class="pf-admin-badge">Admin</span>
|
||||
<span class="pf-admin-badge"><?= tr('set_badge_admin') ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<span class="pf-muted-tiny"><?= date('d/m/Y', strtotime($m['created_at'])) ?></span>
|
||||
@@ -577,54 +639,79 @@ require __DIR__ . '/header.php';
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<h3 class="pf-members-heading" style="margin-top: 2rem;">🧸 Profils rattachés</h3>
|
||||
<p class="pf-muted-note pf-muted-note--tight" style="margin-bottom: 1rem;">Membres gérés par le foyer (Enfants, Nounous, Proches) n'ayant pas d'accès direct.</p>
|
||||
<h3 class="pf-members-heading pf-mt-lg">🧸 <?= tr('settings_attached_profiles_title') ?></h3>
|
||||
<p class="pf-muted-note pf-muted-note--tight pf-mb-md"><?= tr('settings_attached_profiles_desc') ?></p>
|
||||
|
||||
<div class="pf-stack-sm" style="margin-bottom: 1.5rem;">
|
||||
<div class="pf-stack-sm" style="margin-bottom: 1.5rem;">
|
||||
<?php foreach ($attached_profiles as $p):
|
||||
$roleBadge = 'Inconnu';
|
||||
if (in_array($p['role'], ['child', 'enfant'])) $roleBadge = '👶 Enfant';
|
||||
elseif ($p['role'] === 'helper') $roleBadge = '💼 Intervenant';
|
||||
elseif ($p['role'] === 'relative') $roleBadge = '👵 Proche';
|
||||
$roleBadge = tr('role_unknown');
|
||||
if (in_array($p['role'], ['child', 'enfant'])) $roleBadge = tr('role_child_icon');
|
||||
elseif ($p['role'] === 'helper') $roleBadge = tr('role_helper_icon');
|
||||
elseif ($p['role'] === 'relative') $roleBadge = tr('role_relative_icon');
|
||||
|
||||
$pColor = htmlspecialchars($p['color'] ?? '#3b82f6');
|
||||
?>
|
||||
<div class="pf-member-card" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div class="pf-member-card" style="display: flex; justify-content: space-between; align-items: center; border-left: 4px solid <?= $pColor ?>;">
|
||||
<div>
|
||||
<strong><?= htmlspecialchars($p['name']) ?></strong>
|
||||
<span class="pf-muted-inline"><?= $roleBadge ?></span>
|
||||
</div>
|
||||
<form method="post" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="delete_attached_profile">
|
||||
<input type="hidden" name="profile_id" value="<?= $p['id'] ?>">
|
||||
<button type="submit" class="btn-icon-action delete" title="Supprimer" style="background: none; border: none; cursor: pointer; font-size: 1.2rem; padding: 4px;" onclick="return confirm('Supprimer définitivement ce profil ?')">🗑️</button>
|
||||
</form>
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
|
||||
<div class="pf-color-dropdown">
|
||||
<button type="button" class="pf-color-trigger" style="background-color: <?= $pColor ?>;" title="<?= tr('change_color') ?>" onclick="toggleColorMenu(this)"></button>
|
||||
<div class="pf-color-menu">
|
||||
<?php foreach(['#ef4444', '#f97316', '#f59e0b', '#84cc16', '#10b981', '#14b8a6', '#06b6d4', '#0ea5e9', '#3b82f6', '#6366f1', '#8b5cf6', '#d946ef', '#f43f5e', '#64748b'] as $c): ?>
|
||||
<button type="button" class="pf-color-swatch" style="background-color: <?= $c ?>;" onclick="updateAttachedProfileColor(<?= $p['id'] ?>, '<?= $c ?>', this)"></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" style="margin:0;">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="delete_attached_profile">
|
||||
<input type="hidden" name="profile_id" value="<?= $p['id'] ?>">
|
||||
<button type="submit" class="btn-icon-action delete" title="<?= tr('btn_delete') ?>" style="background: none; border: none; cursor: pointer; font-size: 1.2rem; padding: 4px;" onclick="return confirm('<?= addslashes(tr('confirm_delete_profile')) ?>')">🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (empty($attached_profiles)): ?>
|
||||
<div class="pf-muted-note" style="padding: 10px; text-align: center; border: 1px dashed var(--border-light); border-radius: 8px;">Aucun profil rattaché.</div>
|
||||
<div class="pf-muted-note" style="padding: 10px; text-align: center; border: 1px dashed var(--border-light); border-radius: 8px;"><?= tr('settings_no_attached_profiles') ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<form method="post" style="display: flex; gap: 8px; background: var(--bg-soft); padding: 12px; border-radius: 8px; border: 1px dashed var(--border-light); align-items: flex-end;">
|
||||
<form method="post" style="display: flex; gap: 8px; background: var(--bg-soft); padding: 12px; border-radius: 8px; border: 1px dashed var(--border-light); align-items: flex-end; flex-wrap: wrap;">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="add_attached_profile">
|
||||
|
||||
<div class="pf-form-group" style="margin-bottom: 0; flex: 2;">
|
||||
<label class="pf-label" style="font-size: 0.8rem; margin-bottom: 4px;">Prénom</label>
|
||||
<input type="text" name="profile_name" class="pf-input" placeholder="Ex: Pol, Carole..." required>
|
||||
<div class="pf-form-group" style="margin-bottom: 0; flex: 2; min-width: 120px;">
|
||||
<label class="pf-label" style="font-size: 0.8rem; margin-bottom: 4px;"><?= tr('label_firstname') ?></label>
|
||||
<input type="text" name="profile_name" class="pf-input" placeholder="<?= tr('placeholder_firstname_ex') ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="pf-form-group" style="margin-bottom: 0; flex: 1.5;">
|
||||
<label class="pf-label" style="font-size: 0.8rem; margin-bottom: 4px;">Rôle</label>
|
||||
<div class="pf-form-group" style="margin-bottom: 0; flex: 1.5; min-width: 120px;">
|
||||
<label class="pf-label" style="font-size: 0.8rem; margin-bottom: 4px;"><?= tr('label_role') ?></label>
|
||||
<select name="profile_role" class="pf-input" required>
|
||||
<option value="child">👶 Enfant</option>
|
||||
<option value="helper">💼 Intervenant</option>
|
||||
<option value="relative">👵 Proche</option>
|
||||
<option value="child"><?= tr('role_child_icon') ?></option>
|
||||
<option value="helper"><?= tr('role_helper_icon') ?></option>
|
||||
<option value="relative"><?= tr('role_relative_icon') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pf-form-group" style="margin-bottom: 0; display: flex; flex-direction: column; align-items: flex-start;">
|
||||
<label class="pf-label" style="font-size: 0.8rem; margin-bottom: 4px;"><?= tr('label_color') ?></label>
|
||||
<div style="display: flex; gap: 4px; flex-wrap: wrap; padding-bottom: 4px;">
|
||||
<?php foreach(['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#64748b'] as $i => $c): ?>
|
||||
<label class="pf-radio-swatch" style="background-color: <?= $c ?>;" title="<?= $c ?>">
|
||||
<input type="radio" name="profile_color" value="<?= $c ?>" <?= $i === 0 ? 'checked' : '' ?> style="display:none;">
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="pf-btn pf-shrink-0" style="padding: 8px 16px;">Ajouter</button>
|
||||
<button type="submit" class="pf-btn pf-shrink-0" style="padding: 8px 16px; margin-bottom: 2px;"><?= tr('btn_add') ?></button>
|
||||
</form>
|
||||
|
||||
</section>
|
||||
@@ -632,7 +719,7 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<?php if (!empty($_SESSION['user']['is_admin'])): ?>
|
||||
<div class="pf-settings-admin-link">
|
||||
<a href="/admin/">→ Panneau d'administration</a>
|
||||
<a href="/admin/"><?= tr('set_link_admin') ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -660,7 +747,7 @@ function copyCode() {
|
||||
});
|
||||
}
|
||||
|
||||
// --- GESTION DES NOTIFICATIONS TOAST (Remplaçant l'ancien bandeau vert) ---
|
||||
// --- GESTION DES NOTIFICATIONS TOAST ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
<?php if ($success): ?>
|
||||
if (typeof showToast === 'function') {
|
||||
@@ -678,6 +765,70 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
<?php endif; ?>
|
||||
});
|
||||
|
||||
|
||||
|
||||
function selectProfileColor(btn, hexColor) {
|
||||
// 1. Mettre à jour l'input caché
|
||||
document.getElementById('profile_color').value = hexColor;
|
||||
|
||||
// 2. Mettre à jour l'UI (enlever la classe selected partout, l'ajouter sur le bouton cliqué)
|
||||
const swatches = document.querySelectorAll('#color-palette .pf-color-swatch');
|
||||
swatches.forEach(el => el.classList.remove('selected'));
|
||||
btn.classList.add('selected');
|
||||
}
|
||||
|
||||
function toggleColorMenu(btn) {
|
||||
// Fermer tous les autres popovers
|
||||
document.querySelectorAll('.pf-color-menu').forEach(menu => {
|
||||
if (menu !== btn.nextElementSibling) menu.classList.remove('is-open');
|
||||
});
|
||||
// Ouvrir/Fermer celui cliqué
|
||||
btn.nextElementSibling.classList.toggle('is-open');
|
||||
}
|
||||
|
||||
// Fermer le menu si on clique en dehors
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.pf-color-dropdown')) {
|
||||
document.querySelectorAll('.pf-color-menu').forEach(menu => menu.classList.remove('is-open'));
|
||||
}
|
||||
});
|
||||
|
||||
// --- MISE À JOUR DYNAMIQUE COULEUR PROFIL RATTACHÉ ---
|
||||
async function updateAttachedProfileColor(profileId, newColor, btnElement) {
|
||||
const fd = new FormData();
|
||||
fd.append('action', 'update_attached_profile_color');
|
||||
fd.append('profile_id', profileId);
|
||||
fd.append('color', newColor);
|
||||
fd.append('csrf_token', window.CSRF_TOKEN); // Sécurité absolue
|
||||
|
||||
try {
|
||||
const res = await pachaFetch('/settings.php', { method: 'POST', body: fd });
|
||||
if (res && res.success) {
|
||||
if (typeof showToast === 'function') {
|
||||
showToast(window.I18N['color_updated'] || 'Couleur mise à jour !', 'success');
|
||||
}
|
||||
|
||||
const dropdown = btnElement.closest('.pf-color-dropdown');
|
||||
if (dropdown) {
|
||||
// Màj visuelle de la pastille déclencheur
|
||||
dropdown.querySelector('.pf-color-trigger').style.backgroundColor = newColor;
|
||||
dropdown.querySelector('.pf-color-menu').classList.remove('is-open');
|
||||
}
|
||||
|
||||
// Màj de la bordure gauche de la carte
|
||||
const card = btnElement.closest('.pf-member-card');
|
||||
if (card) {
|
||||
card.style.borderLeftColor = newColor;
|
||||
}
|
||||
} else {
|
||||
if (typeof showToast === 'function') showToast(window.I18N['error_occured'] || 'Erreur', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (typeof showToast === 'function') showToast(window.I18N['error_occured'] || 'Erreur', 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require __DIR__ . '/footer.php'; ?>
|
||||
Reference in new issue
Block a user