trad
This commit is contained in:
+11
-11
@@ -1,12 +1,13 @@
|
||||
<?php
|
||||
require __DIR__ . '/includes/auth.php';
|
||||
require __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/i18n.php';
|
||||
require_login('/login.php');
|
||||
|
||||
// Gestion de l'onglet actif (par défaut 'recap')
|
||||
// Gestion de l'onglet actif (par défaut 'suivi')
|
||||
$tab = $_GET['tab'] ?? 'suivi';
|
||||
|
||||
$pageTitle = "PachaFamily - Budget";
|
||||
$pageTitle = tr('budget_page_title');
|
||||
$activePage = "budget";
|
||||
$pageCss = "/modules/budget/budget.css";
|
||||
|
||||
@@ -15,27 +16,27 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<div class="pf-container">
|
||||
<div class="pf-hero" style="text-align: center; margin-bottom: 30px;">
|
||||
<h1 style="margin-bottom: 20px;">Gestion du Budget</h1>
|
||||
<h1 style="margin-bottom: 20px;"><?= tr('budget_main_header') ?></h1>
|
||||
|
||||
<nav class="budget-tabs-container">
|
||||
<a href="?tab=suivi" class="tab-item <?= $tab == 'suivi' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">🗓️</span>
|
||||
<span>Suivi Mensuel</span>
|
||||
<span><?= tr('budget_tab_tracking') ?></span>
|
||||
</a>
|
||||
|
||||
<a href="?tab=budget_prev" class="tab-item <?= $tab == 'budget_prev' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">🎯</span>
|
||||
<span>Budget 2026</span>
|
||||
<span><?= tr('budget_tab_prev') ?></span>
|
||||
</a>
|
||||
|
||||
<a href="?tab=epargne" class="tab-item <?= $tab == 'epargne' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">🐷</span>
|
||||
<span>Épargne</span>
|
||||
<span><?= tr('budget_tab_savings') ?></span>
|
||||
</a>
|
||||
|
||||
<a href="?tab=recap" class="tab-item <?= $tab == 'recap' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">📊</span>
|
||||
<span>Récapitulatif</span>
|
||||
<span><?= tr('budget_tab_recap') ?></span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -43,7 +44,6 @@ require __DIR__ . '/header.php';
|
||||
<section class="pf-section">
|
||||
<?php
|
||||
// ROUTAGE DYNAMIQUE
|
||||
// Sécurisation basique pour éviter d'inclure n'importe quoi
|
||||
$allowedTabs = ['recap', 'suivi', 'epargne', 'budget_prev'];
|
||||
|
||||
if (in_array($tab, $allowedTabs)) {
|
||||
@@ -53,12 +53,12 @@ require __DIR__ . '/header.php';
|
||||
include $viewPath;
|
||||
} else {
|
||||
echo "<div style='text-align:center; padding:50px; color:#ef4444;'>";
|
||||
echo "<h3>Oups ! Fichier introuvable.</h3>";
|
||||
echo "<p>Vérifiez que le fichier <code>views/" . htmlspecialchars($tab) . ".php</code> existe bien.</p>";
|
||||
echo "<h3>" . tr('budget_err_file_not_found') . "</h3>";
|
||||
echo "<p>" . sprintf(tr('budget_err_file_detail'), htmlspecialchars($tab)) . "</p>";
|
||||
echo "</div>";
|
||||
}
|
||||
} else {
|
||||
echo "<p>Page non autorisée.</p>";
|
||||
echo "<p>" . tr('budget_err_not_authorized') . "</p>";
|
||||
}
|
||||
?>
|
||||
</section>
|
||||
|
||||
+56
-71
@@ -6,9 +6,9 @@ error_reporting(E_ALL);
|
||||
require __DIR__ . '/includes/auth.php';
|
||||
require_login();
|
||||
require __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/i18n.php';
|
||||
|
||||
|
||||
$pageTitle = "PachaFamily - Family Calendar";
|
||||
$pageTitle = tr('fc_page_title');
|
||||
$activePage = "family-calendar";
|
||||
$bodyClass = "pf-family-calendar";
|
||||
$pageCss = "/modules/family-calendar/family-calendar.css";
|
||||
@@ -16,20 +16,18 @@ $pageCss = "/modules/family-calendar/family-calendar.css";
|
||||
require __DIR__ . '/header.php';
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<div class="pf-container" style="max-width:100%; padding:0;">
|
||||
<div class="fc-header-row">
|
||||
<h1>Family Calendar</h1>
|
||||
<h1><?= tr('fc_main_header') ?></h1>
|
||||
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button id="btnOpenSnapshotModal" class="pf-btn-icon-text">
|
||||
<span class="icon">⚖️</span>
|
||||
<span class="text">Corriger les soldes</span>
|
||||
<span class="text"><?= tr('fc_btn_correct_balances') ?></span>
|
||||
</button>
|
||||
<button id="btnOpenHolidays" class="pf-btn-icon-text">
|
||||
<span class="icon">🏖️</span>
|
||||
<span class="text">Voir les vacances scolaires</span>
|
||||
<span class="text"><?= tr('fc_btn_school_holidays') ?></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,20 +35,19 @@ require __DIR__ . '/header.php';
|
||||
<div id="modalHolidays" class="fc-modal-overlay" style="display: none;">
|
||||
<div class="fc-modal-content">
|
||||
<div class="fc-modal-header">
|
||||
<h2>Vacances Scolaires (Zone C)</h2>
|
||||
<h2><?= tr('fc_modal_holidays_title') ?></h2>
|
||||
<button id="btnCloseHolidays" class="fc-modal-close">×</button>
|
||||
</div>
|
||||
<div class="fc-modal-body">
|
||||
<table id="schoolHolidaysTable" class="fc-holidays-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Période</th>
|
||||
<th>Du</th>
|
||||
<th>Au</th>
|
||||
<th><?= tr('fc_col_period') ?></th>
|
||||
<th><?= tr('fc_col_from') ?></th>
|
||||
<th><?= tr('fc_col_to') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,20 +56,20 @@ require __DIR__ . '/header.php';
|
||||
<div id="modalSnapshot" class="fc-modal-overlay" style="display: none;">
|
||||
<div class="fc-modal-content" style="max-width: 400px;">
|
||||
<div class="fc-modal-header">
|
||||
<h2>Ajuster un solde</h2>
|
||||
<h2><?= tr('fc_modal_snap_title') ?></h2>
|
||||
<button id="btnCloseSnapshot" class="fc-modal-close">×</button>
|
||||
</div>
|
||||
<div class="fc-modal-body" style="padding: 24px;">
|
||||
<form id="formSnapshot">
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label class="pf-label">Personne</label>
|
||||
<label class="pf-label"><?= tr('fc_label_person') ?></label>
|
||||
<select id="snapPerson" class="pf-input" required>
|
||||
<option value="2">Alex</option>
|
||||
<option value="3">Laia</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label class="pf-label">Type de congé</label>
|
||||
<label class="pf-label"><?= tr('fc_label_leave_type') ?></label>
|
||||
<select id="snapType" class="pf-input" required>
|
||||
<option value="CP">CP</option>
|
||||
<option value="JRA">JRA</option>
|
||||
@@ -80,14 +77,14 @@ require __DIR__ . '/header.php';
|
||||
</select>
|
||||
</div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label class="pf-label">Date d'application</label>
|
||||
<input type="date" id="snapDate" class="pf-input" required title="Choisissez de préférence le 1er jour d'un mois">
|
||||
<label class="pf-label"><?= tr('fc_label_apply_date') ?></label>
|
||||
<input type="date" id="snapDate" class="pf-input" required title="<?= tr('fc_snap_date_hint') ?>">
|
||||
</div>
|
||||
<div style="margin-bottom: 24px;">
|
||||
<label class="pf-label">Solde restant à cette date</label>
|
||||
<input type="number" step="0.5" id="snapBalance" class="pf-input" placeholder="ex: 12.5" required>
|
||||
<label class="pf-label"><?= tr('fc_label_remaining_balance') ?></label>
|
||||
<input type="number" step="0.5" id="snapBalance" class="pf-input" placeholder="<?= tr('fc_placeholder_balance') ?>" required>
|
||||
</div>
|
||||
<button type="submit" class="pf-btn" style="width: 100%;">Enregistrer le correctif</button>
|
||||
<button type="submit" class="pf-btn" style="width: 100%;"><?= tr('fc_btn_save_snap') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,9 +94,9 @@ require __DIR__ . '/header.php';
|
||||
<div class="fc-month-calendar-wrapper">
|
||||
<div class="fc-month-header">
|
||||
<div class="fc-view-controls">
|
||||
<button class="fc-view-button fc-view-button--active" data-view="1month">1 mois</button>
|
||||
<button class="fc-view-button" data-view="2months">2 mois</button>
|
||||
<button class="fc-view-button" data-view="year">Année</button>
|
||||
<button class="fc-view-button fc-view-button--active" data-view="1month"><?= tr('fc_view_1m') ?></button>
|
||||
<button class="fc-view-button" data-view="2months"><?= tr('fc_view_2m') ?></button>
|
||||
<button class="fc-view-button" data-view="year"><?= tr('fc_view_year') ?></button>
|
||||
</div>
|
||||
<h3 id="fc-current-month-year"></h3>
|
||||
<div class="fc-nav-controls">
|
||||
@@ -107,7 +104,6 @@ require __DIR__ . '/header.php';
|
||||
<button id="fc-next-month" class="fc-nav-button">›</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fc-calendar-container">
|
||||
<div id="fc-month-calendar" class="fc-month-calendar"></div>
|
||||
<div id="fc-month-selectionMenu" class="fc-selection-menu" style="display:none;"></div>
|
||||
@@ -115,11 +111,9 @@ require __DIR__ . '/header.php';
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section class="pf-section">
|
||||
<div class="fc-week-header">
|
||||
<h2>Planning hebdo</h2>
|
||||
<h2><?= tr('fc_weekly_planning') ?></h2>
|
||||
<div class="fc-week-nav-controls">
|
||||
<button id="fc-prev-school-year" class="fc-nav-button">‹</button>
|
||||
<span id="fc-current-school-year-label"></span>
|
||||
@@ -129,28 +123,21 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<div id="planningTable-wrapper">
|
||||
<table id="planningTable">
|
||||
<colgroup>
|
||||
<col class="col-month"><col class="col-month">
|
||||
<col class="col-day"><col class="col-day"><col class="col-day"><col class="col-day"><col class="col-day">
|
||||
<col class="col-total"><col class="col-total"><col class="col-total"><col class="col-total"><col class="col-total"><col class="col-total">
|
||||
<col class="col-alex-sub"><col class="col-alex-sub"><col class="col-alex-sub"><col class="col-alex-sub"><col class="col-alex-sub"><col class="col-alex-sub">
|
||||
<col class="col-laia-sub"><col class="col-laia-sub"><col class="col-laia-sub"><col class="col-laia-sub"><col class="col-laia-sub"><col class="col-laia-sub">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="3" class="col-month col-sticky-mois">Mois</th>
|
||||
<th rowspan="3" class="col-month col-sticky-mois">Sem.</th>
|
||||
<th rowspan="3" class="col-day">Lun</th>
|
||||
<th rowspan="3" class="col-day">Mar</th>
|
||||
<th rowspan="3" class="col-day">Mer</th>
|
||||
<th rowspan="3" class="col-day">Jeu</th>
|
||||
<th rowspan="3" class="col-day">Ven</th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span>Off Carole</span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span>Extra Off</span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span>Centre</span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span>Avis</span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span>Pep Malade</span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span>Présence</span></th>
|
||||
<th rowspan="3" class="col-month col-sticky-mois"><?= tr('fc_col_month') ?></th>
|
||||
<th rowspan="3" class="col-month col-sticky-mois"><?= tr('fc_col_week_short') ?></th>
|
||||
<th rowspan="3" class="col-day"><?= tr('day_mon') ?></th>
|
||||
<th rowspan="3" class="col-day"><?= tr('day_tue') ?></th>
|
||||
<th rowspan="3" class="col-day"><?= tr('day_wed') ?></th>
|
||||
<th rowspan="3" class="col-day"><?= tr('day_thu') ?></th>
|
||||
<th rowspan="3" class="col-day"><?= tr('day_fri') ?></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_off_carole') ?></span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_extra_off') ?></span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_centre') ?></span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_avis') ?></span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_pep_sick') ?></span></th>
|
||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_presence') ?></span></th>
|
||||
<th colspan="6" class="col-alex header-group">ALEX</th>
|
||||
<th colspan="6" class="col-laia header-group">LAIA</th>
|
||||
</tr>
|
||||
@@ -159,12 +146,12 @@ require __DIR__ . '/header.php';
|
||||
<th colspan="2" class="col-laia-sub">CP</th><th colspan="2" class="col-laia-sub">JRA</th><th colspan="2" class="col-laia-sub">JA</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="col-alex-sub col-alex-av">Av.</th><th class="col-alex-sub col-alex-use">Use</th>
|
||||
<th class="col-alex-sub col-alex-av">Av.</th><th class="col-alex-sub col-alex-use">Use</th>
|
||||
<th class="col-alex-sub col-alex-av">Av.</th><th class="col-alex-sub col-alex-use">Use</th>
|
||||
<th class="col-laia-sub col-laia-av">Av.</th><th class="col-laia-sub col-laia-use">Use</th>
|
||||
<th class="col-laia-sub col-laia-av">Av.</th><th class="col-laia-sub col-laia-use">Use</th>
|
||||
<th class="col-laia-sub col-laia-av">Av.</th><th class="col-laia-sub col-laia-use">Use</th>
|
||||
<th class="col-alex-sub col-alex-av"><?= tr('fc_col_av') ?></th><th class="col-alex-sub col-alex-use"><?= tr('fc_col_use') ?></th>
|
||||
<th class="col-alex-sub col-alex-av"><?= tr('fc_col_av') ?></th><th class="col-alex-sub col-alex-use"><?= tr('fc_col_use') ?></th>
|
||||
<th class="col-alex-sub col-alex-av"><?= tr('fc_col_av') ?></th><th class="col-alex-sub col-alex-use"><?= tr('fc_col_use') ?></th>
|
||||
<th class="col-laia-sub col-laia-av"><?= tr('fc_col_av') ?></th><th class="col-laia-sub col-laia-use"><?= tr('fc_col_use') ?></th>
|
||||
<th class="col-laia-sub col-laia-av"><?= tr('fc_col_av') ?></th><th class="col-laia-sub col-laia-use"><?= tr('fc_col_use') ?></th>
|
||||
<th class="col-laia-sub col-laia-av"><?= tr('fc_col_av') ?></th><th class="col-laia-sub col-laia-use"><?= tr('fc_col_use') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="planningBody"></tbody>
|
||||
@@ -175,39 +162,37 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<section class="pf-section pf-section--bottom-panels">
|
||||
<div class="fc-bottom-grid">
|
||||
|
||||
<div class="pf-card">
|
||||
<h2 class="pf-card-title">Légende</h2>
|
||||
<h2 class="pf-card-title"><?= tr('fc_legend_title') ?></h2>
|
||||
<div class="pf-card-body">
|
||||
<div class="pf-legend-grid">
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-school-holiday"></div><span>Vacances Scolaires</span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-public-holiday"></div><span>Férié</span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-off-carole"></div><span>Off Carole</span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-extra-off-carole"></div><span>Extra Off</span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-centre"></div><span>Centre</span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-avis"></div><span>Avis</span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-pep-sick"></div><span>Pep Malade</span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-school-holiday"></div><span><?= tr('leg_school_holidays') ?></span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-public-holiday"></div><span><?= tr('leg_public_holiday') ?></span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-off-carole"></div><span><?= tr('leg_off_carole') ?></span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-extra-off-carole"></div><span><?= tr('leg_extra_off') ?></span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-centre"></div><span><?= tr('leg_centre') ?></span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-avis"></div><span><?= tr('leg_avis') ?></span></div>
|
||||
<div class="pf-legend-item"><div class="pf-legend-color fc-legend-pep-sick"></div><span><?= tr('leg_pep_sick') ?></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-card">
|
||||
<div class="pf-card-title fc-summary-header">
|
||||
<span>Récapitulatif</span>
|
||||
<span><?= tr('fc_summary_title') ?></span>
|
||||
<div class="fc-summary-controls">
|
||||
<select id="summType" class="fc-summ-select"> <option value="year">Année</option>
|
||||
<option value="month">Mois</option>
|
||||
</select>
|
||||
<select id="summValue" class="fc-summ-select"> </select>
|
||||
<select id="summType" class="fc-summ-select">
|
||||
<option value="year"><?= tr('fc_summ_year') ?></option>
|
||||
<option value="month"><?= tr('fc_summ_month') ?></option>
|
||||
</select>
|
||||
<select id="summValue" class="fc-summ-select"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-card-body">
|
||||
<div id="globalSummary" class="fc-summary-vertical">
|
||||
<div id="globalSummary" class="fc-summary-vertical"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
+68
-73
@@ -4,13 +4,14 @@
|
||||
require __DIR__ . '/includes/auth.php';
|
||||
require_login('/login.php');
|
||||
require __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/i18n.php'; // Toujours s'assurer que tr() est dispo
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) { session_start(); }
|
||||
|
||||
// --- 1. CONFIGURATION & DONNÉES ---
|
||||
|
||||
$year = (int)date('Y');
|
||||
$pageTitle = "PachaFamily - Llista de regals";
|
||||
$pageTitle = tr('gift_page_title');
|
||||
$activePage = "gift-list";
|
||||
$bodyClass = "pf-gift-list";
|
||||
$pageCss = "/modules/gift-list/gift-list.css";
|
||||
@@ -42,14 +43,15 @@ $adultsByChildForAnniv = [
|
||||
'Guim' => $baseAdults,
|
||||
];
|
||||
|
||||
// Labels & Icônes
|
||||
// Labels & Icônes (Utilisation des clés de traduction pour l'affichage)
|
||||
$allOccasionLabels = [
|
||||
'TIO' => 'Tió',
|
||||
'NOEL' => 'Nadal',
|
||||
'ROIS' => 'Reis',
|
||||
'ANNIV' => 'Anniversary',
|
||||
'SANT' => 'Sant',
|
||||
'TIO' => tr('gift_occ_tio'),
|
||||
'NOEL' => tr('gift_occ_noel'),
|
||||
'ROIS' => tr('gift_occ_rois'),
|
||||
'ANNIV' => tr('gift_occ_anniv'),
|
||||
'SANT' => tr('gift_occ_sant'),
|
||||
];
|
||||
|
||||
$occasionIcons = [
|
||||
'TIO' => '/modules/gift-list/assets/img/tio.png',
|
||||
'NOEL' => '/modules/gift-list/assets/img/santa.png',
|
||||
@@ -62,7 +64,6 @@ $tableGifts = 'pf_gifts';
|
||||
|
||||
// --- 2. RÉCUPÉRATION DES DONNÉES ---
|
||||
|
||||
// Préparation requête
|
||||
$inMarks = implode(',', array_fill(0, count($allowedOccasions), '?'));
|
||||
$sql = "SELECT * FROM {$tableGifts} WHERE year = ? AND occasion IN ($inMarks) ORDER BY adult_name, child_name, occasion, created_at";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
@@ -70,26 +71,21 @@ $params = array_merge([$year], $allowedOccasions);
|
||||
$stmt->execute($params);
|
||||
$gifts = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
|
||||
// Indexation [occasion][child][adult] pour la matrice
|
||||
$byOccasion = [];
|
||||
foreach ($gifts as $gift) {
|
||||
$byOccasion[$gift['occasion']][$gift['child_name']][$gift['adult_name']][] = $gift;
|
||||
}
|
||||
|
||||
// Occasions à afficher
|
||||
$occasionsToShow = array_values(array_intersect(array_keys($allOccasionLabels), $allowedOccasions));
|
||||
|
||||
// --- 3. CALCUL TRICOUNT (Backend) ---
|
||||
// On pré-calcule ici pour alléger la vue HTML
|
||||
// --- 3. CALCUL TRICOUNT ---
|
||||
|
||||
$people = $baseAdults;
|
||||
$adultsInDb = array_column($gifts, 'adult_name');
|
||||
$payersInDb = array_column($gifts, 'payer_name'); // Peut contenir des NULL si colonne vide, mais array_unique gère
|
||||
// Nettoyage et fusion des participants
|
||||
$payersInDb = array_column($gifts, 'payer_name');
|
||||
$people = array_values(array_unique(array_merge($people, $adultsInDb, $payersInDb)));
|
||||
$people = array_filter($people); // Enlève les vides éventuels
|
||||
$people = array_filter($people);
|
||||
|
||||
// Matrice de dettes
|
||||
$matrix = [];
|
||||
foreach ($people as $p1) {
|
||||
foreach ($people as $p2) $matrix[$p1][$p2] = 0.0;
|
||||
@@ -100,7 +96,6 @@ foreach ($gifts as $g) {
|
||||
$payer = $g['payer_name'] ?? $g['adult_name'];
|
||||
$amt = (float)$g['amount'];
|
||||
|
||||
// Si payé par quelqu'un d'autre que le bénéficiaire (l'adulte responsable)
|
||||
if ($amt > 0 && $adult && $payer && $adult !== $payer) {
|
||||
if (isset($matrix[$adult][$payer])) {
|
||||
$matrix[$adult][$payer] += $amt;
|
||||
@@ -108,7 +103,6 @@ foreach ($gifts as $g) {
|
||||
}
|
||||
}
|
||||
|
||||
// Résolution des dettes (Liquidations)
|
||||
$settlements = [];
|
||||
$countPeople = count($people);
|
||||
for ($i = 0; $i < $countPeople; $i++) {
|
||||
@@ -132,18 +126,18 @@ require __DIR__ . '/header.php';
|
||||
<div class="pf-container cl-view-<?= htmlspecialchars($currentView) ?>">
|
||||
|
||||
<div class="cl-titlebar">
|
||||
<h1>Llista de regals <?= htmlspecialchars($year) ?></h1>
|
||||
<div class="cl-view-switch" aria-label="Canvia la vista">
|
||||
<a href="?view=nadal" class="cl-view-btn <?= $currentView === 'nadal' ? 'is-active' : '' ?>">Nadal</a>
|
||||
<a href="?view=anniversary" class="cl-view-btn <?= $currentView === 'anniversary' ? 'is-active' : '' ?>">Anniversary</a>
|
||||
<h1><?= sprintf(tr('gift_main_title'), htmlspecialchars($year)) ?></h1>
|
||||
<div class="cl-view-switch" aria-label="<?= tr('gift_aria_change_view') ?>">
|
||||
<a href="?view=nadal" class="cl-view-btn <?= $currentView === 'nadal' ? 'is-active' : '' ?>"><?= tr('gift_view_nadal') ?></a>
|
||||
<a href="?view=anniversary" class="cl-view-btn <?= $currentView === 'anniversary' ? 'is-active' : '' ?>"><?= tr('gift_view_anniv') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="pf-section pf-section--panel">
|
||||
<h2>Vista per festa</h2>
|
||||
<h2><?= tr('gift_view_by_party') ?></h2>
|
||||
|
||||
<?php if (empty($gifts)): ?>
|
||||
<p class="cl-legend">No hi ha cap regal registrat per a <?= htmlspecialchars($year) ?> en aquesta vista.</p>
|
||||
<p class="cl-legend"><?= sprintf(tr('gift_no_gifts'), htmlspecialchars($year)) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($occasionsToShow as $occCode): ?>
|
||||
@@ -158,12 +152,10 @@ require __DIR__ . '/header.php';
|
||||
<div class="cl-occasion-children-tables">
|
||||
<?php foreach ($children as $childName): ?>
|
||||
<?php
|
||||
// Détermine quels adultes participent pour cet enfant
|
||||
$adultsForChild = ($currentView === 'anniversary')
|
||||
? ($adultsByChildForAnniv[$childName] ?? $baseAdults)
|
||||
: $baseAdults;
|
||||
|
||||
// Prépare listes/total par adulte
|
||||
$lists = [];
|
||||
$totals = [];
|
||||
foreach ($adultsForChild as $adultName) {
|
||||
@@ -171,7 +163,6 @@ require __DIR__ . '/header.php';
|
||||
$totals[$adultName] = array_sum(array_column($lists[$adultName], 'amount'));
|
||||
}
|
||||
|
||||
// Calcul hauteur tableau
|
||||
$counts = array_map('count', $lists);
|
||||
$maxRowsChild = !empty($counts) ? max($counts) : 0;
|
||||
?>
|
||||
@@ -185,7 +176,7 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<caption>
|
||||
<?= htmlspecialchars($childName) ?>
|
||||
<button type="button" class="cl-child-add-btn" title="Afegeix un regal"
|
||||
<button type="button" class="cl-child-add-btn" title="<?= tr('gift_add_gift') ?>"
|
||||
data-year="<?= $year ?>"
|
||||
data-child="<?= htmlspecialchars($childName) ?>"
|
||||
data-occasion="<?= htmlspecialchars($occCode) ?>"
|
||||
@@ -239,7 +230,7 @@ require __DIR__ . '/header.php';
|
||||
<div class="cl-gift-right">
|
||||
<span class="cl-gift-amount">(<?= number_format($amt, 0, ',', ' ') ?> €)</span>
|
||||
<span class="cl-gift-actions">
|
||||
<button type="button" class="cl-gift-action-btn cl-gift-edit" aria-label="Edita"
|
||||
<button type="button" class="cl-gift-action-btn cl-gift-edit" aria-label="<?= tr('edit') ?>"
|
||||
data-id="<?= $giftId ?>"
|
||||
data-year="<?= $year ?>"
|
||||
data-child="<?= htmlspecialchars($childName) ?>"
|
||||
@@ -251,7 +242,7 @@ require __DIR__ . '/header.php';
|
||||
data-link="<?= htmlspecialchars($gift['product_link'] ?? '') ?>">
|
||||
✎
|
||||
</button>
|
||||
<button type="button" class="cl-gift-action-btn cl-gift-delete" aria-label="Eliminar" data-id="<?= $giftId ?>">
|
||||
<button type="button" class="cl-gift-action-btn cl-gift-delete" aria-label="<?= tr('delete') ?>" data-id="<?= $giftId ?>">
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
@@ -259,7 +250,7 @@ require __DIR__ . '/header.php';
|
||||
</div>
|
||||
|
||||
<?php if (!empty($payer) && $payer !== $gift['adult_name']): ?>
|
||||
<small style="color:#b91c1c; font-style:italic; display:block; font-size:0.75em;">(pagat per <?= htmlspecialchars($payer) ?>)</small>
|
||||
<small style="color:#b91c1c; font-style:italic; display:block; font-size:0.75em;">(<?= sprintf(tr('gift_paid_by'), htmlspecialchars($payer)) ?>)</small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
@@ -279,9 +270,8 @@ require __DIR__ . '/header.php';
|
||||
</section>
|
||||
|
||||
<section class="pf-section pf-section--panel">
|
||||
<h2>Resum del pressupost</h2>
|
||||
<h2><?= tr('gift_summary_title') ?></h2>
|
||||
<?php
|
||||
// Calcul agrégé SQL pour vérification
|
||||
$stmtSum = $pdo->prepare("SELECT adult_name, child_name, occasion, SUM(amount) AS total FROM {$tableGifts} WHERE year = ? AND occasion IN ($inMarks) GROUP BY adult_name, child_name, occasion ORDER BY adult_name, child_name, occasion");
|
||||
$stmtSum->execute($params);
|
||||
$sums = $stmtSum->fetchAll(PDO::FETCH_ASSOC);
|
||||
@@ -289,7 +279,7 @@ require __DIR__ . '/header.php';
|
||||
<div class="cl-budget-wrapper">
|
||||
<table class="pf-table pf-table--compact">
|
||||
<thead>
|
||||
<tr><th>Adult</th><th>Infant</th><th>Festa</th><th>Total</th></tr>
|
||||
<tr><th><?= tr('gift_col_adult') ?></th><th><?= tr('gift_col_child') ?></th><th><?= tr('gift_col_party') ?></th><th>Total</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($sums as $row): ?>
|
||||
@@ -311,7 +301,7 @@ require __DIR__ . '/header.php';
|
||||
<table class="pf-table pf-table--compact cl-debt-matrix">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cl-matrix-corner"><span>Deutor ↓</span><span>Creditor →</span></th>
|
||||
<th class="cl-matrix-corner"><span><?= tr('gift_debtor') ?> ↓</span><span><?= tr('gift_creditor') ?> →</span></th>
|
||||
<?php foreach ($people as $p): ?><th><?= htmlspecialchars($p) ?></th><?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -334,24 +324,24 @@ require __DIR__ . '/header.php';
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top:16px; font-size:1.1rem; color:#374151;">Liquidacions</h3>
|
||||
<h3 style="margin-top:16px; font-size:1.1rem; color:#374151;"><?= tr('gift_liquidations') ?></h3>
|
||||
<?php if (empty($settlements)): ?>
|
||||
<p class="cl-legend">Cap deute pendent.</p>
|
||||
<p class="cl-legend"><?= tr('gift_no_debt') ?></p>
|
||||
<?php else: ?>
|
||||
<ul class="hol-list">
|
||||
<?php foreach ($settlements as $s): ?>
|
||||
<li><strong><?= htmlspecialchars($s['from']) ?></strong> ha de pagar <strong><?= number_format($s['amount'], 2, ',', ' ') ?> €</strong> a <?= htmlspecialchars($s['to']) ?></li>
|
||||
<li><strong><?= htmlspecialchars($s['from']) ?></strong> <?= tr('gift_owes') ?> <strong><?= number_format($s['amount'], 2, ',', ' ') ?> €</strong> <?= tr('gift_to') ?> <?= htmlspecialchars($s['to']) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="pf-section pf-section--panel">
|
||||
<h2>Llista detallada de regals</h2>
|
||||
<h2><?= tr('gift_detailed_list') ?></h2>
|
||||
<div class="cl-detail-wrapper">
|
||||
<table class="pf-table pf-table--compact">
|
||||
<thead>
|
||||
<tr><th>Adult</th><th>Infant</th><th>Festa</th><th>Regal</th><th>€</th><th>Enllaç</th></tr>
|
||||
<tr><th><?= tr('gift_col_adult') ?></th><th><?= tr('gift_col_child') ?></th><th><?= tr('gift_col_party') ?></th><th><?= tr('gift_col_gift') ?></th><th>€</th><th><?= tr('gift_col_link') ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($gifts as $g): ?>
|
||||
@@ -375,6 +365,14 @@ require __DIR__ . '/header.php';
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Pont de traduction JS
|
||||
window.I18N = {
|
||||
...window.I18N,
|
||||
'gift_modal_title_add': "<?= tr('gift_modal_title_add') ?>",
|
||||
'gift_modal_title_edit': "<?= tr('gift_modal_title_edit') ?>",
|
||||
'gift_confirm_delete': "<?= tr('gift_confirm_delete') ?>"
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const modal = document.getElementById('cl-gift-modal');
|
||||
const backdrop = modal ? modal.querySelector('.cl-modal-backdrop') : null;
|
||||
@@ -383,9 +381,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
function toggleModal(show) {
|
||||
if (!modal) return;
|
||||
modal.classList.toggle('cl-open', show);
|
||||
if(show) document.body.classList.add('no-scroll');
|
||||
else document.body.classList.remove('no-scroll');
|
||||
}
|
||||
|
||||
// Gestion des options de select (Adultes dynamiques selon l'enfant)
|
||||
function populateSelects(adults) {
|
||||
const selects = [document.getElementById('clm-adult'), document.getElementById('clm-payer')];
|
||||
selects.forEach(sel => {
|
||||
@@ -409,7 +408,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
populateSelects(adults);
|
||||
|
||||
// Valeurs par défaut
|
||||
document.getElementById('clm-action').value = 'create';
|
||||
document.getElementById('clm-id').value = '';
|
||||
document.getElementById('clm-year').value = d.year;
|
||||
@@ -420,7 +418,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('clm-amount').value = '';
|
||||
document.getElementById('clm-link').value = '';
|
||||
|
||||
document.getElementById('cl-modal-title').textContent = `Afegeix un regal per ${d.child}`;
|
||||
document.getElementById('cl-modal-title').textContent = tr('gift_modal_title_add').replace('%s', d.child);
|
||||
toggleModal(true);
|
||||
});
|
||||
});
|
||||
@@ -431,12 +429,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
if (!btn) return;
|
||||
|
||||
const d = btn.dataset;
|
||||
// Pour l'édition, on s'assure que l'adulte actuel est dans la liste (même si pas standard)
|
||||
const adultSelect = document.getElementById('clm-adult');
|
||||
const payerSelect = document.getElementById('clm-payer');
|
||||
|
||||
// On ne recharge pas toute la liste adults ici car complexe à récupérer du DOM,
|
||||
// on ajoute juste l'option si manquante.
|
||||
[adultSelect, payerSelect].forEach(sel => {
|
||||
const val = (sel === adultSelect) ? d.adult : (d.payer || d.adult);
|
||||
if (!Array.from(sel.options).some(o => o.value === val)) {
|
||||
@@ -458,7 +453,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('clm-amount').value = d.amount;
|
||||
document.getElementById('clm-link').value = d.link;
|
||||
|
||||
document.getElementById('cl-modal-title').textContent = `Edita el regal`;
|
||||
document.getElementById('cl-modal-title').textContent = tr('gift_modal_title_edit');
|
||||
toggleModal(true);
|
||||
});
|
||||
|
||||
@@ -467,7 +462,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const btn = e.target.closest('.cl-gift-delete');
|
||||
if (!btn) return;
|
||||
|
||||
if (confirm('Vols eliminar aquest regal?')) {
|
||||
if (confirm(tr('gift_confirm_delete'))) {
|
||||
const form = document.getElementById('cl-delete-form');
|
||||
document.getElementById('cld-id').value = btn.dataset.id;
|
||||
form.submit();
|
||||
@@ -483,9 +478,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
<div id="cl-gift-modal" class="cl-modal" aria-hidden="true">
|
||||
<div class="cl-modal-backdrop"></div>
|
||||
<div class="cl-modal-dialog" role="dialog" aria-modal="true" aria-labelledby="cl-modal-title">
|
||||
<div class="cl-modal-dialog pf-modal-content" role="dialog" aria-modal="true" aria-labelledby="cl-modal-title">
|
||||
<form method="post" action="/modules/gift-list/save-gift.php" class="cl-modal-form">
|
||||
<h3 id="cl-modal-title">Regal</h3>
|
||||
<h3 id="cl-modal-title" style="margin-top:0;"><?= tr('gift_col_gift') ?></h3>
|
||||
|
||||
<input type="hidden" name="year" id="clm-year">
|
||||
<input type="hidden" name="child_name" id="clm-child">
|
||||
@@ -493,34 +488,34 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
<input type="hidden" name="action" id="clm-action">
|
||||
<input type="hidden" name="gift_id" id="clm-id">
|
||||
|
||||
<label class="clm-label">
|
||||
Adult
|
||||
<select name="adult_name" id="clm-adult" required></select>
|
||||
</label>
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label"><?= tr('gift_col_adult') ?></label>
|
||||
<select name="adult_name" id="clm-adult" class="pf-input" required></select>
|
||||
</div>
|
||||
|
||||
<label class="clm-label">
|
||||
Pagat per
|
||||
<select name="payer_name" id="clm-payer" required></select>
|
||||
</label>
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label"><?= tr('gift_modal_payer') ?></label>
|
||||
<select name="payer_name" id="clm-payer" class="pf-input" required></select>
|
||||
</div>
|
||||
|
||||
<label class="clm-label">
|
||||
Nom del regal
|
||||
<input type="text" name="gift_description" id="clm-gift" placeholder="p. ex., Lego Star Wars" required>
|
||||
</label>
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label"><?= tr('gift_modal_gift_name') ?></label>
|
||||
<input type="text" name="gift_description" id="clm-gift" class="pf-input" placeholder="<?= tr('gift_modal_ph_name') ?>" required>
|
||||
</div>
|
||||
|
||||
<label class="clm-label">
|
||||
Preu (€)
|
||||
<input type="number" name="amount" id="clm-amount" placeholder="49.99" step="0.01" min="0">
|
||||
</label>
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label"><?= tr('gift_modal_price') ?></label>
|
||||
<input type="number" name="amount" id="clm-amount" class="pf-input" placeholder="49.99" step="0.01" min="0">
|
||||
</div>
|
||||
|
||||
<label class="clm-label">
|
||||
Enllaç (opcional)
|
||||
<input type="url" name="product_link" id="clm-link" placeholder="https://...">
|
||||
</label>
|
||||
<div class="form-group" style="margin-bottom:25px;">
|
||||
<label class="pf-label"><?= tr('gift_modal_link') ?></label>
|
||||
<input type="url" name="product_link" id="clm-link" class="pf-input" placeholder="https://...">
|
||||
</div>
|
||||
|
||||
<div class="cl-modal-actions">
|
||||
<button type="button" class="clm-cancel">Cancel·la</button>
|
||||
<button type="submit" class="clm-ok">Guardar</button>
|
||||
<div class="modal-footer" style="padding-top:15px; border-top:1px solid #e2e8f0; display:flex; justify-content:flex-end; gap:10px;">
|
||||
<button type="button" class="clm-cancel pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="clm-ok pf-btn"><?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
+26
-8
@@ -4,7 +4,10 @@ if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
// 1. Inclusion du moteur de traduction
|
||||
require_once __DIR__ . '/includes/i18n.php';
|
||||
|
||||
// Petite fonction pour changer de langue sans perdre les paramètres de l'URL (ex: id=5)
|
||||
/**
|
||||
* Génère une URL avec le paramètre de langue mis à jour
|
||||
* sans perdre les autres paramètres (ex: id, tab, etc.)
|
||||
*/
|
||||
function getLangUrl($newLang) {
|
||||
$params = $_GET;
|
||||
$params['lang'] = $newLang;
|
||||
@@ -52,13 +55,13 @@ $currentLang = $_SESSION['app_lang'] ?? 'fr'; // Récupération de la langue act
|
||||
<div class="pf-user-badge">
|
||||
<?= htmlspecialchars($_SESSION['user']['display_name'] ?? $_SESSION['user']['username']) ?>
|
||||
</div>
|
||||
<a href="/logout.php" class="pf-logout-btn">Déconnexion</a>
|
||||
<a href="/logout.php" class="pf-logout-btn"><?= tr('btn_logout') ?></a>
|
||||
<?php else: ?>
|
||||
<a href="/login.php" class="pf-nav-link">Connexion</a>
|
||||
<a href="/login.php" class="pf-nav-link"><?= tr('btn_login') ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<button class="pf-burger-btn" aria-label="Menu">☰</button>
|
||||
<button class="pf-burger-btn" aria-label="<?= tr('aria_menu') ?>">☰</button>
|
||||
</header>
|
||||
|
||||
<div class="pf-mobile-menu">
|
||||
@@ -67,24 +70,39 @@ $currentLang = $_SESSION['app_lang'] ?? 'fr'; // Récupération de la langue act
|
||||
<a href="/budget.php" class="pf-mobile-nav-link"><?= tr('menu_budget') ?></a>
|
||||
<a href="/holidays.php" class="pf-mobile-nav-link"><?= tr('menu_holidays') ?></a>
|
||||
<a href="/gift-list.php" class="pf-mobile-nav-link"><?= tr('menu_gifts') ?></a>
|
||||
<hr style="width: 80%; border: 0; border-top: 1px solid #eee; margin: 10px 0;">
|
||||
<?php if (isset($_SESSION['user'])): ?>
|
||||
<a href="/logout.php" class="pf-mobile-nav-link pf-mobile-logout">Se déconnecter</a>
|
||||
<a href="/logout.php" class="pf-mobile-nav-link pf-mobile-logout"><?= tr('btn_logout') ?></a>
|
||||
<?php else: ?>
|
||||
<a href="/login.php" class="pf-mobile-nav-link">Se connecter</a>
|
||||
<a href="/login.php" class="pf-mobile-nav-link"><?= tr('btn_login') ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<main class="pf-main">
|
||||
|
||||
<script>
|
||||
// Script simple pour le menu mobile
|
||||
window.I18N_LANG = '<?= $currentLang === "ca" ? "ca-ES" : "fr-FR" ?>';
|
||||
window.I18N = <?php echo json_encode($current_translations_array ?? []); ?>;
|
||||
|
||||
function tr(key) {
|
||||
return window.I18N[key] || key;
|
||||
}
|
||||
|
||||
// Gestion du menu mobile
|
||||
const burgerBtn = document.querySelector('.pf-burger-btn');
|
||||
const mobileMenu = document.querySelector('.pf-mobile-menu');
|
||||
|
||||
if(burgerBtn) {
|
||||
if(burgerBtn && mobileMenu) {
|
||||
burgerBtn.addEventListener('click', () => {
|
||||
const isOpen = mobileMenu.classList.toggle('is-open');
|
||||
burgerBtn.textContent = isOpen ? '✕' : '☰';
|
||||
|
||||
// Bonus sécurité : empêcher le scroll si le menu est ouvert
|
||||
if (isOpen) {
|
||||
document.body.classList.add('no-scroll');
|
||||
} else {
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
+7
-3
@@ -4,14 +4,18 @@
|
||||
require __DIR__ . '/includes/auth.php';
|
||||
require_login('/login.php');
|
||||
require __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/i18n.php'; // Toujours s'assurer de l'accès à tr()
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) { session_start(); }
|
||||
|
||||
// 1. On récupère la vue demandée (par défaut 'list')
|
||||
$tab = $_GET['tab'] ?? 'list';
|
||||
|
||||
// 2. Configuration des variables pour le header
|
||||
$pageTitle = ($tab === 'holiday_detail') ? "PachaFamily - Détail du voyage" : "PachaFamily - Mes Vacances";
|
||||
// 2. Configuration des variables pour le header (Titres traduits)
|
||||
$pageTitle = ($tab === 'holiday_detail')
|
||||
? tr('hdl_title_detail')
|
||||
: tr('hdl_title_list');
|
||||
|
||||
$activePage = "holidays";
|
||||
$bodyClass = "pf-holidays";
|
||||
$pageCss = "/modules/holidays/holidays.css";
|
||||
@@ -27,7 +31,7 @@ if ($tab === 'holiday_detail' && isset($_GET['id'])) {
|
||||
require __DIR__ . '/modules/holidays/views/list.php';
|
||||
}
|
||||
|
||||
// 4. Inclusion du JS global du module (s'applique aux deux vues)
|
||||
// 4. Inclusion du JS global du module (Pont i18n déjà géré dans le header)
|
||||
echo '<script src="/modules/holidays/holidays.js"></script>';
|
||||
|
||||
require __DIR__ . '/footer.php';
|
||||
+422
-20
@@ -1,15 +1,215 @@
|
||||
<?php
|
||||
// Dictionnaire Catalan
|
||||
return [
|
||||
// ==========================================
|
||||
// GLOBAL & NAVIGATION
|
||||
// ==========================================
|
||||
'menu_home' => 'Inici',
|
||||
'menu_budget' => 'Pressupost',
|
||||
'menu_calendar' => 'Calendari',
|
||||
'menu_gifts' => 'Regals',
|
||||
'menu_budget' => 'Pressupost',
|
||||
'menu_holidays' => 'Viatges',
|
||||
'outbound_trip' => 'Anada',
|
||||
'return_trip' => 'Tornada',
|
||||
'menu_gifts' => 'Regals',
|
||||
'nav_home' => 'Inici',
|
||||
'nav_calendar' => 'Calendari',
|
||||
'nav_budget' => 'Pressupost',
|
||||
'nav_holidays' => 'Viatges',
|
||||
'nav_gifts' => 'Regals',
|
||||
'aria_menu' => 'Menú mòbil',
|
||||
|
||||
// Boutons et actions globales
|
||||
'actions' => 'Accions',
|
||||
'edit' => 'Modificar',
|
||||
'delete' => 'Suprimir',
|
||||
'save' => 'Desar',
|
||||
'cancel' => 'Cancel·lar',
|
||||
'btn_save' => 'Guardar',
|
||||
'btn_cancel' => 'Cancel·lar',
|
||||
'btn_delete' => 'Eliminar',
|
||||
'btn_close' => 'Tancar',
|
||||
'btn_back' => '◀ Tornar',
|
||||
'btn_edit_bases' => '⚙️ Modificar les bases',
|
||||
|
||||
// Mots génériques & Erreurs
|
||||
'error' => 'Error.',
|
||||
'confirm_delete' => 'Estàs segur que vols suprimir aquest element?',
|
||||
|
||||
// Mois & Jours
|
||||
'month_01' => 'Gener', 'month_02' => 'Febrer', 'month_03' => 'Març',
|
||||
'month_04' => 'Abril', 'month_05' => 'Maig', 'month_06' => 'Juny',
|
||||
'month_07' => 'Juliol','month_08' => 'Agost', 'month_09' => 'Setembre',
|
||||
'month_10' => 'Octubre','month_11' => 'Novembre','month_12' => 'Desembre',
|
||||
'day_mon' => 'Dl', 'day_tue' => 'Dt', 'day_wed' => 'Dc',
|
||||
'day_thu' => 'Dj', 'day_fri' => 'Dv', 'day_sat' => 'Ds', 'day_sun' => 'Dg',
|
||||
|
||||
// ==========================================
|
||||
// AUTHENTIFICATION / LOGIN
|
||||
// ==========================================
|
||||
'login_title' => 'Connexió - PachaFamily',
|
||||
'login_header' => 'Connexió',
|
||||
'label_username' => "Nom d'usuari",
|
||||
'label_password' => 'Contrasenya',
|
||||
'placeholder_username' => 'ex: pacha',
|
||||
'btn_login' => 'Connexió',
|
||||
'btn_login_submit' => 'Connectar-se',
|
||||
'logout' => 'Tancar sessió',
|
||||
'btn_logout' => 'Tancar sessió',
|
||||
'error_missing_fields' => 'Camps obligatoris.',
|
||||
'error_invalid_credentials' => 'Identificadors incorrectes.',
|
||||
|
||||
// ==========================================
|
||||
// ACCUEIL / INDEX
|
||||
// ==========================================
|
||||
'home_title' => 'PachaFamily - Inici',
|
||||
'home_welcome' => 'Benvinguts a PachaFamily',
|
||||
'home_subtitle' => "Centre de control de l'organització familiar.",
|
||||
'home_logged_as' => 'Connectat com a',
|
||||
'home_modules_title' => 'Mòduls',
|
||||
'mod_calendar_name' => 'Calendari Familiar',
|
||||
'mod_calendar_desc' => "Gestioneu les vacances, les modalitats de cura i l'organització setmanal.",
|
||||
'mod_holidays_name' => 'Vacances',
|
||||
'mod_holidays_desc' => 'Planifiqueu les vostres properes escapades, destinacions i idees de viatge.',
|
||||
'mod_gifts_name' => 'Regals',
|
||||
'mod_gifts_desc' => "Planifiqueu els regals per a cada infant, per Nadal i els aniversaris.",
|
||||
'mod_budget_name' => 'Pressupost',
|
||||
'mod_budget_desc' => 'Seguiment de despeses fixes, ingressos i balanç del compte familiar.',
|
||||
'cta_open' => 'Obrir',
|
||||
'cta_explore' => 'Explorar',
|
||||
'cta_view_lists' => 'Veure les llistes',
|
||||
'cta_manage' => 'Gestionar',
|
||||
|
||||
// ==========================================
|
||||
// FAMILY CALENDAR
|
||||
// ==========================================
|
||||
'fc_page_title' => 'PachaFamily - Calendari',
|
||||
'fc_main_header' => 'Calendari Familiar',
|
||||
'fc_btn_correct_balances' => 'Corregir els saldos',
|
||||
'fc_btn_school_holidays' => 'Vacances escolars',
|
||||
'fc_modal_holidays_title' => 'Vacances Escolars (Zona C)',
|
||||
'fc_modal_snap_title' => 'Ajustar un saldo',
|
||||
'fc_col_period' => 'Període',
|
||||
'fc_col_from' => 'Des del',
|
||||
'fc_col_to' => 'Fins al',
|
||||
'fc_label_person' => 'Persona',
|
||||
'fc_label_leave_type' => 'Tipus de permís',
|
||||
'fc_label_apply_date' => "Data d'aplicació",
|
||||
'fc_snap_date_hint' => "Trieu preferiblement el primer dia d'un mes",
|
||||
'fc_label_remaining_balance'=> 'Saldo restant en aquesta data',
|
||||
'fc_placeholder_balance' => 'ex: 12.5',
|
||||
'fc_btn_save_snap' => 'Desar el correctiu',
|
||||
'fc_view_1m' => '1 mes',
|
||||
'fc_view_2m' => '2 mesos',
|
||||
'fc_view_year' => 'Any',
|
||||
'fc_weekly_planning' => 'Planificació setmanal',
|
||||
'fc_col_month' => 'Mes',
|
||||
'fc_col_week_short' => 'Set.',
|
||||
'fc_col_av' => 'Av.',
|
||||
'fc_col_use' => 'Use',
|
||||
'fc_legend_title' => 'Llegenda',
|
||||
'fc_summary_title' => 'Resum',
|
||||
'fc_summ_year' => 'Any',
|
||||
'fc_summ_month' => 'Mes',
|
||||
'fc_school_year' => 'Curs escolar',
|
||||
'fc_unit_days' => 'dies',
|
||||
'fc_menu_carole' => 'Permisos Carole',
|
||||
'fc_clear' => 'Esborrar',
|
||||
'fc_menu_kids_leaves' => 'Permisos nens',
|
||||
|
||||
'leg_school_holidays' => 'Vacances Escolars',
|
||||
'leg_public_holiday' => 'Festiu',
|
||||
'leg_off_carole' => 'Off Carole',
|
||||
'leg_extra_off' => 'Extra Off',
|
||||
'leg_centre' => 'Casal',
|
||||
'leg_avis' => 'Avís',
|
||||
'leg_pep_sick' => 'Pep Malalt',
|
||||
'leg_presence' => 'Presència',
|
||||
|
||||
'vac_toussaint' => 'Vacances de Tots Sants',
|
||||
'vac_noel' => 'Vacances de Nadal',
|
||||
'vac_hiver' => "Vacances d'Hivern",
|
||||
'vac_printemps' => 'Vacances de Primavera',
|
||||
'vac_ascension' => "Pont de l'Ascensió",
|
||||
'vac_ete' => "Vacances d'Estiu",
|
||||
'btn_off' => 'Off',
|
||||
'btn_extra' => 'Extra',
|
||||
|
||||
// ==========================================
|
||||
// HOLIDAYS (VOYAGES)
|
||||
// ==========================================
|
||||
'hdl_title_list' => 'PachaFamily - Les meves vacances',
|
||||
'hdl_title_detail' => 'PachaFamily - Detall del viatge',
|
||||
'hdl_main_title' => 'Les meves vacances',
|
||||
'hdl_filter_all' => 'Mostrar-ho tot',
|
||||
'hdl_filter_year' => 'Any',
|
||||
'hdl_left_to_pay_label' => 'Resta per pagar',
|
||||
'hdl_btn_create' => 'Crear un viatge',
|
||||
'hdl_no_active_trips' => 'Cap viatge en curs per aquest període. Planifiquem alguna cosa!',
|
||||
'hdl_history_title' => 'Historial',
|
||||
'hdl_dates_to_define' => 'Dates per definir',
|
||||
'hdl_total_budget' => 'Pressupost Total',
|
||||
'hdl_paid' => 'Pagat',
|
||||
'hdl_saved' => 'Finançat',
|
||||
'hdl_left_to_pay' => 'Resta per pagar',
|
||||
'hdl_status_booked' => 'Confirmat',
|
||||
'hdl_status_planned' => 'Planificat',
|
||||
'hdl_status_draft' => 'Esborrany',
|
||||
'hdl_status_passed' => 'Passat',
|
||||
'hdl_status_archived' => 'Arxivat',
|
||||
'hdl_modal_title' => 'Planificar el viatge',
|
||||
'hdl_label_name' => 'Nom del viatge',
|
||||
'hdl_ph_name' => 'Ex: Octubre - Portugal',
|
||||
'hdl_label_status' => 'Estat',
|
||||
'hdl_label_period' => 'Període (Text lliure)',
|
||||
'hdl_ph_period' => 'Ex: Octubre 2026',
|
||||
'hdl_label_from' => 'Des del',
|
||||
'hdl_label_to' => 'Fins al',
|
||||
'hdl_cat_transport' => 'Transport',
|
||||
'hdl_add_transport' => 'Afegir un transport',
|
||||
'hdl_cat_accommodation' => 'Allotjament',
|
||||
'hdl_add_accommodation' => 'Afegir un allotjament',
|
||||
'hdl_cat_activity' => 'Activitat',
|
||||
'hdl_add_activity' => 'Afegir una activitat',
|
||||
'hdl_label_budget_food' => 'Pressupost Menjar i Beguda (€)',
|
||||
'hdl_label_budget_extras' => 'Pressupost Extras (€)',
|
||||
'hdl_label_notes' => 'Notes',
|
||||
'hdl_ph_notes' => 'Idees soltes...',
|
||||
'hdl_label_budget_food_extras' => 'Pressupost Menjar i Extres',
|
||||
'hdl_map_itinerary' => 'Itinerari',
|
||||
'hdl_outbound' => 'Anada',
|
||||
'hdl_return' => 'Tornada',
|
||||
'hdl_btn_add_step' => 'Afegir una etapa',
|
||||
'hdl_step_details' => 'Detalls del roadtrip',
|
||||
'hdl_no_steps' => 'Cap etapa definida. Comenceu per afegir-ne una!',
|
||||
'hdl_general_costs' => 'Despeses generals i Pressupostos',
|
||||
'hdl_food_bev' => 'Menjar i Beguda',
|
||||
'hdl_extras' => 'Extres',
|
||||
'hdl_view_planning' => 'Veure la planificació',
|
||||
'hdl_passing_point' => 'Punt de pas (sense despeses)',
|
||||
'hdl_search_location' => 'Cercar un lloc',
|
||||
'hdl_ph_search' => 'Ciutat, hotel, adreça...',
|
||||
'hdl_label_step_name' => "Nom de l'etapa",
|
||||
'hdl_label_arrival' => 'Arribada',
|
||||
'hdl_label_departure' => 'Sortida',
|
||||
'hdl_planned_expenses' => 'Despeses previstes per a aquesta etapa',
|
||||
'hdl_btn_add_expense' => '+ Afegir una despesa',
|
||||
'hdl_save_fav' => 'Desar als meus preferits',
|
||||
'hdl_planning_title' => 'Planificació de l’etapa',
|
||||
'hdl_to_pay' => 'Per pagar',
|
||||
'hdl_js_search_loading' => 'Cerca en curs...',
|
||||
'hdl_js_no_result' => 'No s’han trobat resultats.',
|
||||
'hdl_js_network_error' => 'Error de connexió al servei de mapes.',
|
||||
'hdl_js_confirm_del_trip' => 'Estàs segur que vols suprimir aquest viatge sencer?',
|
||||
'hdl_js_confirm_del_step' => 'Suprimir aquesta etapa?',
|
||||
'hdl_js_step_label' => 'Etapa',
|
||||
'hdl_js_ph_expense_name' => 'Ex: Hotel, Peatge...',
|
||||
'hdl_quick_edit_title' => 'Modificació ràpida',
|
||||
'hdl_js_edit_step' => 'Modificar l’etapa',
|
||||
'hdl_js_missing_dates_title'=> 'Dates mancants',
|
||||
'hdl_js_missing_dates_msg' => 'Cal definir una data d’arribada i sortida per organitzar la planificació.',
|
||||
'hdl_to_place' => 'Per situar',
|
||||
|
||||
// Anciennes clés Holidays non préfixées (conservades)
|
||||
'outbound_trip' => 'Anada',
|
||||
'return_trip' => 'Tornada',
|
||||
'period' => 'Període',
|
||||
'budget_food' => 'Pressupost Menjar i Extres',
|
||||
'total_cost' => 'Cost Total Estimat',
|
||||
@@ -17,16 +217,10 @@ return [
|
||||
'provisioned' => 'Provisionat',
|
||||
'left_to_pay' => 'Pendent de pagament',
|
||||
'map_itinerary' => '🗺️ Itinerari',
|
||||
'outbound_trip' => 'Anada',
|
||||
'return_trip' => 'Tornada',
|
||||
'place_step' => '📍 Afegir una etapa',
|
||||
'step_details' => '📝 Detall de les etapes',
|
||||
'no_steps' => 'Cap etapa planificada.',
|
||||
'travel_notes' => '📝 Notes del viatge',
|
||||
'edit' => 'Modificar',
|
||||
|
||||
// --- Paraules diverses & Tooltips ---
|
||||
'error' => 'Error.',
|
||||
'to_be_defined' => 'A definir',
|
||||
'funded' => 'Finançat (Provisió)',
|
||||
'view_planning' => 'Veure el planning',
|
||||
@@ -34,8 +228,6 @@ return [
|
||||
'from_date' => 'Del',
|
||||
'to_date' => 'al',
|
||||
'passing_point' => 'Punt de pas (Cap despesa)',
|
||||
|
||||
// --- Modal : Afegir / Editar una etapa ---
|
||||
'search_location' => 'Cercar una ubicació geogràfica',
|
||||
'search_placeholder' => 'Ex: París, Ibis Barcelona...',
|
||||
'step_name' => 'Nom de l\'etapa',
|
||||
@@ -44,15 +236,7 @@ return [
|
||||
'planned_expenses' => 'Despeses previstes',
|
||||
'add_expense' => '+ Afegir una despesa',
|
||||
'save_favorite' => 'Guardar als meus preferits',
|
||||
|
||||
// --- Botons genèrics ---
|
||||
'btn_delete' => 'Eliminar',
|
||||
'btn_cancel' => 'Cancel·lar',
|
||||
'btn_save' => 'Guardar',
|
||||
'btn_close' => 'Tancar',
|
||||
'planning_title' => 'Planning',
|
||||
|
||||
// --- Javascript (holidays.js) ---
|
||||
'modal_plan_trip' => 'Planificar el viatge',
|
||||
'modal_quick_edit' => 'Edició ràpida',
|
||||
'err_modal_missing' => 'Error: La modal holidayModal no es troba.',
|
||||
@@ -75,4 +259,222 @@ return [
|
||||
'missing_dates_msg' => 'Si us plau, modifica aquesta etapa (✏️) i omple la seva <strong>Data d\'arribada i de sortida</strong>.',
|
||||
'to_place' => '📥 A col·locar',
|
||||
|
||||
// ==========================================
|
||||
// BUDGET (GLOBAL & CATEGORIES)
|
||||
// ==========================================
|
||||
'budget_page_title' => 'PachaFamily - Pressupost',
|
||||
'budget_main_header' => 'Gestió del Pressupost',
|
||||
'budget_tab_tracking' => 'Seguiment Mensual',
|
||||
'budget_tab_prev' => 'Pressupost 2026',
|
||||
'budget_tab_savings' => 'Estalvis',
|
||||
'budget_tab_recap' => 'Resum',
|
||||
'budget_err_file_not_found' => 'Vaja! Fitxer no trobat.',
|
||||
'budget_err_file_detail' => 'Comproveu que el fitxer views/%s.php existeix.',
|
||||
'budget_err_not_authorized' => 'Pàgina no autoritzada.',
|
||||
'cat_income' => 'Ingressos',
|
||||
'cat_fmcg' => 'Compres (FMCG)',
|
||||
'cat_fuel' => 'Benzina',
|
||||
'cat_school' => 'Escola / Llar d’infants',
|
||||
'cat_fixed' => 'Despeses Fixes',
|
||||
'cat_others' => 'Altres / Imprevistos',
|
||||
'cat_savings' => 'Estalvis',
|
||||
|
||||
// --- BUDGET : SUIVI ---
|
||||
'bud_rem_school' => 'Escola (Resta estimada)',
|
||||
'bud_rem_fmcg' => 'Compres (Resta estimada)',
|
||||
'bud_rem_fuel' => 'Benzina (Resta estimada)',
|
||||
'bud_closed_archived' => 'Aquest mes està tancat i arxivat.',
|
||||
'bud_budget_of' => 'Pressupost:',
|
||||
'bud_confirm_close' => 'Tancar aquest mes? Això congelarà els saldos actuals i canviarà el seguiment per defecte al mes vinent.',
|
||||
'bud_close_btn' => 'Tancar',
|
||||
'bud_confirm_reopen' => 'Reobrir aquest mes? Els seus saldos es recalcularan en directe.',
|
||||
'bud_reopen_btn' => 'Reobrir',
|
||||
'bud_active_month_btn' => 'Mes Actiu',
|
||||
'bud_bank_balance' => 'Saldo bancari',
|
||||
'bud_frozen' => 'Congelat',
|
||||
'bud_current' => 'Actual',
|
||||
'bud_update_btn' => 'Act.',
|
||||
'bud_theoretical_balance' => 'Saldo teòric final de mes',
|
||||
'bud_upcoming_charges' => 'Càrrecs pendents',
|
||||
'bud_all_paid' => 'Tot està pagat!',
|
||||
'bud_capacity_tooltip' => 'Càlcul: Report Mes Anterior (%s€) + Sous (%s€) + Altres ingressos (%s€)',
|
||||
'bud_max_capacity' => 'Capacitat Màx',
|
||||
'bud_actual' => 'Actual',
|
||||
'bud_theoretical' => 'Teòric',
|
||||
'bud_import_csv' => 'Importar CSV',
|
||||
'bud_csv_file' => 'Fitxer CSV (Punt i coma)',
|
||||
'bud_preview' => 'Previsualitzar',
|
||||
'bud_validate_import' => 'Validar importació',
|
||||
'bud_assign_month' => 'Assignar al mes',
|
||||
'bud_op_date' => 'Data Operació',
|
||||
'bud_label' => 'Concepte',
|
||||
'bud_amount' => 'Import',
|
||||
'bud_category' => 'Categoria',
|
||||
'bud_ignore' => 'Ignorar',
|
||||
'bud_to_define' => 'Per definir',
|
||||
'bud_is_charge' => 'Despesa?',
|
||||
'bud_is_income' => 'Ingrés?',
|
||||
'btn_import' => 'Importar',
|
||||
'bud_no_lines' => 'Cap línia.',
|
||||
'bud_confirm_delete' => 'Suprimir aquesta línia?',
|
||||
'bud_new_transaction' => 'Nova transacció',
|
||||
'bud_assign_to_month' => 'Assignar al mes de gestió',
|
||||
'bud_real_date' => 'Data real de l\'operació',
|
||||
'bud_type' => 'Tipus',
|
||||
'bud_type_expense' => 'Despesa',
|
||||
'bud_type_income' => 'Ingrés / Reemborsament',
|
||||
'bud_beneficiary' => 'Beneficiari',
|
||||
'bud_fixed_charge' => 'Despesa Fixa',
|
||||
'bud_expected_income' => 'Ingrés Esperat',
|
||||
'bud_amount_eur' => 'Import (€)',
|
||||
'bud_update_balance' => 'Act. Saldo Bancari',
|
||||
'bud_balance_eur' => 'Saldo (€)',
|
||||
'bud_add_title' => 'Afegir',
|
||||
'bud_edit_title' => 'Modificar la transacció',
|
||||
'bud_to_define_js' => 'per definir',
|
||||
|
||||
// --- BUDGET : RECAP ---
|
||||
'bud_recap_monthly_title' => 'Resum Mensual',
|
||||
'bud_recap_btn_add' => 'Afegir una despesa / ingrés',
|
||||
'bud_planned_amount' => 'Import Previst',
|
||||
'bud_day' => 'Dia',
|
||||
'bud_month_state' => 'Estat del mes',
|
||||
'bud_est_short' => 'Est.',
|
||||
'bud_reg_planned_in' => 'Regularització prevista al %s',
|
||||
'bud_bonus' => 'Bonificació',
|
||||
'bud_overrun' => 'Excés',
|
||||
'bud_remaining' => 'Resta',
|
||||
'bud_exact_amount' => 'Import exacte',
|
||||
'bud_per_month_short' => 'És a dir',
|
||||
'bud_month_short' => 'mes',
|
||||
'bud_freq_mensuel' => 'Mensual',
|
||||
'bud_freq_annuel' => 'Anual',
|
||||
'bud_state_validated' => 'Validat',
|
||||
'bud_state_partial' => 'Parcial',
|
||||
'bud_state_waiting' => 'En espera',
|
||||
'bud_total_income_smoothed' => 'Total Ingressos (Prorratejats)',
|
||||
'bud_total_expenses_smoothed'=> 'Total Despeses (Prorratejades)',
|
||||
'bud_theoretical_balance_recap'=> 'Equilibri teòric',
|
||||
'bud_recap_footer_note' => 'L\'estat del pagament s\'actualitza automàticament en funció de les operacions importades a la pestanya "Seguiment Mensual".',
|
||||
'bud_recap_modal_add' => 'Afegir un element',
|
||||
'bud_recap_modal_edit' => 'Modificar',
|
||||
'bud_label_name' => 'Nom',
|
||||
'bud_ph_item_name' => 'Ex: Lloguer, Sou...',
|
||||
'bud_label_keywords' => 'Paraules clau (Detecció Auto)',
|
||||
'bud_ph_keywords' => 'Ex: NETFLIX, PRIME VIDEO (separades per coma)',
|
||||
'bud_help_keywords' => 'Si una despesa del mes conté aquesta paraula, la línia passarà a "Validat".',
|
||||
'bud_label_day' => 'Dia (1-31)',
|
||||
'bud_cat_expense' => 'Despesa',
|
||||
'bud_label_frequency' => 'Freqüència',
|
||||
'bud_label_amount_type' => 'Tipus d\'import',
|
||||
'bud_type_fixed' => 'Fix (Factura)',
|
||||
'bud_type_variable' => 'Variable (Estimació)',
|
||||
'bud_label_regularization' => 'Regularització',
|
||||
'bud_reg_none' => 'Cap',
|
||||
'bud_recap_confirm_delete' => 'Segur que vols suprimir aquest element?',
|
||||
'bud_err_delete' => 'Error en suprimir.',
|
||||
|
||||
// --- BUDGET : EPARGNE ---
|
||||
'bud_sav_add_month' => 'Introduir un mes',
|
||||
'bud_sav_add_one_month' => '+1 Mes',
|
||||
'bud_sav_no_data' => 'Cap dada per a %s.',
|
||||
'bud_sav_post_month' => 'Concepte / Mes',
|
||||
'bud_sav_from_date' => 'Des del %s',
|
||||
'bud_sav_edit_modal' => 'Modificar amb Modal',
|
||||
'bud_sav_delete_month' => 'Suprimir tot el mes',
|
||||
'bud_sav_total_bank' => 'Total Banc',
|
||||
'bud_sav_extra' => 'Extra',
|
||||
'bud_sav_modal_title_add' => 'Introduir un mes',
|
||||
'bud_sav_modal_title_edit' => 'Modificar:',
|
||||
'bud_sav_month_concerned' => 'Mes afectat',
|
||||
'bud_sav_total_bank_eur' => 'Total al Banc (€)',
|
||||
'bud_sav_ventilation' => 'Desglossament',
|
||||
'bud_sav_adj_help' => 'Utilitzeu l\'ajust (+/-) per recalcular automàticament.',
|
||||
'bud_sav_add_line' => 'Línia',
|
||||
'bud_sav_current' => 'Actual',
|
||||
'bud_sav_adjust' => 'Ajust (+/-)',
|
||||
'bud_sav_new' => 'Nou',
|
||||
'bud_sav_sum_mode_title' => 'Activar la suma ràpida',
|
||||
'bud_sav_selection' => 'Selecció:',
|
||||
'bud_sav_ph_name' => 'Nom (ex: Vacances)',
|
||||
'bud_sav_saving' => 'Desant...',
|
||||
'bud_err_tech' => 'S\'ha produït un error tècnic.',
|
||||
'bud_sav_confirm_delete_month' => 'Suprimir TOT el mes de %m per a %o?',
|
||||
'bud_sav_prompt_duplicate' => "Vols copiar les dades de %s a %t1?\n\nIntrodueix el nou TOTAL al banc (€) per a %t2:",
|
||||
'bud_err_server' => 'Error del servidor: ',
|
||||
'bud_err_network_dup' => 'Error de xarxa en duplicar.',
|
||||
|
||||
// --- BUDGET : PREVISIONNEL ---
|
||||
'bud_prev_incomes' => 'Ingressos',
|
||||
'bud_prev_person' => 'Persona',
|
||||
'bud_prev_salary' => 'Sou',
|
||||
'bud_prev_monthly_payment' => 'Mensualitat',
|
||||
'bud_prev_joint_account' => 'Cte Comú',
|
||||
'bud_prev_func_expenses' => 'Desp. Func.',
|
||||
'bud_prev_perso_savings' => 'Est. Perso',
|
||||
'bud_prev_family_savings' => 'Est. Família',
|
||||
'bud_prev_available' => 'Dispo',
|
||||
'bud_prev_perso_remaining' => 'Restant Perso',
|
||||
'bud_prev_budget_alloc' => 'Distribució Pressupostària',
|
||||
'bud_prev_today' => 'Avui',
|
||||
'bud_prev_new_line' => 'Nova Línia',
|
||||
'bud_prev_remaining' => 'Restant',
|
||||
'bud_prev_confirm_del_line' => 'Suprimir aquesta línia i tot el seu historial?',
|
||||
'bud_prev_notes_for' => 'Notes per a',
|
||||
'bud_prev_saved' => 'Desat',
|
||||
'bud_prev_notes_ph' => 'Escriu les teves observacions, recordatoris o comentaris per a aquest mes aquí...',
|
||||
'bud_prev_save_note' => 'Desar la nota',
|
||||
'bud_prev_transfers_to_make' => 'Transferències a fer',
|
||||
'bud_prev_destination' => 'Destinació',
|
||||
'bud_prev_done' => 'FET',
|
||||
'bud_prev_validate' => 'Validar',
|
||||
'bud_prev_grand_total' => 'GRAN TOTAL',
|
||||
'bud_prev_new_line_title' => 'Nova Línia de Pressupost',
|
||||
'bud_prev_edit_line_title' => 'Modificar la línia',
|
||||
'bud_prev_label_target' => 'Objectiu (Destinació)',
|
||||
'bud_prev_choose' => 'Tria',
|
||||
'bud_prev_link_holiday' => 'Enllaçar a un viatge (Opcional)',
|
||||
'bud_prev_no_link' => 'No associar',
|
||||
'bud_prev_err_no_history' => 'Impossible: no hi ha historial disponible per copiar.',
|
||||
'bud_prev_confirm_copy' => "Vols copiar les dades de %s a %t?\n\n⚠️ Això sobreescriurà tots els valors ja presents per a %t.",
|
||||
'bud_prev_confirm_transfers' => "Confirmes que %p ha fet totes les transferències per a %m?\n\nAixò actualitzarà l'Estalvi automàticament.",
|
||||
|
||||
// ==========================================
|
||||
// CADEAUX (GIFTS)
|
||||
// ==========================================
|
||||
'gift_page_title' => 'PachaFamily - Llista de regals',
|
||||
'gift_occ_tio' => 'Tió',
|
||||
'gift_occ_noel' => 'Nadal',
|
||||
'gift_occ_rois' => 'Reis',
|
||||
'gift_occ_anniv' => 'Aniversari',
|
||||
'gift_occ_sant' => 'Sant',
|
||||
'gift_main_title' => 'Llista de regals %s',
|
||||
'gift_aria_change_view' => 'Canvia la vista',
|
||||
'gift_view_nadal' => 'Nadal',
|
||||
'gift_view_anniv' => 'Aniversaris',
|
||||
'gift_view_by_party' => 'Vista per festa',
|
||||
'gift_no_gifts' => 'No hi ha cap regal registrat per a %s en aquesta vista.',
|
||||
'gift_add_gift' => 'Afegeix un regal',
|
||||
'gift_paid_by' => 'pagat per %s',
|
||||
'gift_summary_title' => 'Resum del pressupost',
|
||||
'gift_col_adult' => 'Adult',
|
||||
'gift_col_child' => 'Infant',
|
||||
'gift_col_party' => 'Festa',
|
||||
'gift_debtor' => 'Deutor',
|
||||
'gift_creditor' => 'Creditor',
|
||||
'gift_liquidations' => 'Liquidacions',
|
||||
'gift_no_debt' => 'Cap deute pendent.',
|
||||
'gift_owes' => 'ha de pagar',
|
||||
'gift_to' => 'a',
|
||||
'gift_detailed_list' => 'Llista detallada de regals',
|
||||
'gift_col_gift' => 'Regal',
|
||||
'gift_col_link' => 'Enllaç',
|
||||
'gift_modal_title_add' => 'Afegeix un regal per a %s',
|
||||
'gift_modal_title_edit' => 'Edita el regal',
|
||||
'gift_modal_payer' => 'Pagat per',
|
||||
'gift_modal_gift_name' => 'Nom del regal',
|
||||
'gift_modal_ph_name' => 'p. ex., Lego Star Wars',
|
||||
'gift_modal_price' => 'Preu (€)',
|
||||
'gift_modal_link' => 'Enllaç (opcional)',
|
||||
'gift_confirm_delete' => 'Vols eliminar aquest regal?',
|
||||
];
|
||||
+422
-19
@@ -1,14 +1,215 @@
|
||||
<?php
|
||||
// Dictionnaire Français
|
||||
return [
|
||||
// ==========================================
|
||||
// GLOBAL & NAVIGATION
|
||||
// ==========================================
|
||||
'menu_home' => 'Accueil',
|
||||
'menu_budget' => 'Budget',
|
||||
'menu_calendar' => 'Calendrier',
|
||||
'menu_gifts' => 'Cadeaux',
|
||||
'menu_budget' => 'Budget',
|
||||
'menu_holidays' => 'Voyages',
|
||||
'outbound_trip' => 'Aller',
|
||||
'menu_gifts' => 'Cadeaux',
|
||||
'nav_home' => 'Accueil',
|
||||
'nav_calendar' => 'Calendrier',
|
||||
'nav_budget' => 'Budget',
|
||||
'nav_holidays' => 'Voyages',
|
||||
'nav_gifts' => 'Cadeaux',
|
||||
'aria_menu' => 'Menu mobile',
|
||||
|
||||
// Boutons et actions globales
|
||||
'actions' => 'Actions',
|
||||
'edit' => 'Modifier',
|
||||
'delete' => 'Supprimer',
|
||||
'save' => 'Enregistrer',
|
||||
'cancel' => 'Annuler',
|
||||
'btn_save' => 'Enregistrer',
|
||||
'btn_cancel' => 'Annuler',
|
||||
'btn_delete' => 'Supprimer',
|
||||
'btn_close' => 'Fermer',
|
||||
'btn_back' => '◀ Retour',
|
||||
'btn_edit_bases' => '⚙️ Modifier les bases',
|
||||
|
||||
// Mots génériques & Erreurs
|
||||
'error' => 'Erreur.',
|
||||
'confirm_delete' => 'Es-tu sûr de vouloir supprimer cet élément ?',
|
||||
|
||||
// Mois & Jours
|
||||
'month_01' => 'Janvier', 'month_02' => 'Février', 'month_03' => 'Mars',
|
||||
'month_04' => 'Avril', 'month_05' => 'Mai', 'month_06' => 'Juin',
|
||||
'month_07' => 'Juillet', 'month_08' => 'Août', 'month_09' => 'Septembre',
|
||||
'month_10' => 'Octobre', 'month_11' => 'Novembre','month_12' => 'Décembre',
|
||||
'day_mon' => 'Lun', 'day_tue' => 'Mar', 'day_wed' => 'Mer',
|
||||
'day_thu' => 'Jeu', 'day_fri' => 'Ven', 'day_sat' => 'Sam', 'day_sun' => 'Dim',
|
||||
|
||||
// ==========================================
|
||||
// AUTHENTIFICATION / LOGIN
|
||||
// ==========================================
|
||||
'login_title' => 'Connexion - PachaFamily',
|
||||
'login_header' => 'Connexion',
|
||||
'label_username' => 'Identifiant',
|
||||
'label_password' => 'Mot de passe',
|
||||
'placeholder_username' => 'ex: pacha',
|
||||
'btn_login' => 'Connexion',
|
||||
'btn_login_submit' => 'Se connecter',
|
||||
'logout' => 'Déconnexion',
|
||||
'btn_logout' => 'Déconnexion',
|
||||
'error_missing_fields' => 'Champs obligatoires.',
|
||||
'error_invalid_credentials' => 'Identifiants incorrects.',
|
||||
|
||||
// ==========================================
|
||||
// ACCUEIL / INDEX
|
||||
// ==========================================
|
||||
'home_title' => 'PachaFamily - Accueil',
|
||||
'home_welcome' => 'Bienvenue sur PachaFamily',
|
||||
'home_subtitle' => "Centre de contrôle de l'organisation familiale.",
|
||||
'home_logged_as' => 'Connecté en tant que',
|
||||
'home_modules_title' => 'Modules',
|
||||
'mod_calendar_name' => 'Calendrier Familial',
|
||||
'mod_calendar_desc' => "Gérez les congés, les modes de garde et l'organisation hebdomadaire.",
|
||||
'mod_holidays_name' => 'Vacances',
|
||||
'mod_holidays_desc' => 'Planifiez vos prochaines escapades, destinations et idées de voyage.',
|
||||
'mod_gifts_name' => 'Cadeaux',
|
||||
'mod_gifts_desc' => 'Planifiez les cadeaux pour chaque enfant, pour Noël et les anniversaires.',
|
||||
'mod_budget_name' => 'Budget',
|
||||
'mod_budget_desc' => 'Suivi des dépenses fixes, revenus et équilibre du compte familial.',
|
||||
'cta_open' => 'Ouvrir',
|
||||
'cta_explore' => 'Explorer',
|
||||
'cta_view_lists' => 'Voir les listes',
|
||||
'cta_manage' => 'Gérer',
|
||||
|
||||
// ==========================================
|
||||
// FAMILY CALENDAR
|
||||
// ==========================================
|
||||
'fc_page_title' => 'PachaFamily - Calendrier',
|
||||
'fc_main_header' => 'Calendrier Familial',
|
||||
'fc_btn_correct_balances' => 'Corriger les soldes',
|
||||
'fc_btn_school_holidays' => 'Vacances scolaires',
|
||||
'fc_modal_holidays_title' => 'Vacances Scolaires (Zone C)',
|
||||
'fc_modal_snap_title' => 'Ajuster un solde',
|
||||
'fc_col_period' => 'Période',
|
||||
'fc_col_from' => 'Du',
|
||||
'fc_col_to' => 'Au',
|
||||
'fc_label_person' => 'Personne',
|
||||
'fc_label_leave_type' => 'Type de congé',
|
||||
'fc_label_apply_date' => "Date d'application",
|
||||
'fc_snap_date_hint' => "Choisissez de préférence le 1er jour d'un mois",
|
||||
'fc_label_remaining_balance'=> 'Solde restant à cette date',
|
||||
'fc_placeholder_balance' => 'ex: 12.5',
|
||||
'fc_btn_save_snap' => 'Enregistrer le correctif',
|
||||
'fc_view_1m' => '1 mois',
|
||||
'fc_view_2m' => '2 mois',
|
||||
'fc_view_year' => 'Année',
|
||||
'fc_weekly_planning' => 'Planning hebdomadaire',
|
||||
'fc_col_month' => 'Mois',
|
||||
'fc_col_week_short' => 'Sem.',
|
||||
'fc_col_av' => 'Av.',
|
||||
'fc_col_use' => 'Use',
|
||||
'fc_legend_title' => 'Légende',
|
||||
'fc_summary_title' => 'Récapitulatif',
|
||||
'fc_summ_year' => 'Année',
|
||||
'fc_summ_month' => 'Mois',
|
||||
'fc_school_year' => 'Année scolaire',
|
||||
'fc_unit_days' => 'jours',
|
||||
'fc_menu_carole' => 'Congés Carole',
|
||||
'fc_clear' => 'Effacer',
|
||||
'fc_menu_kids_leaves' => 'Congés Enfants',
|
||||
|
||||
'leg_school_holidays' => 'Vacances Scolaires',
|
||||
'leg_public_holiday' => 'Férié',
|
||||
'leg_off_carole' => 'Off Carole',
|
||||
'leg_extra_off' => 'Extra Off',
|
||||
'leg_centre' => 'Centre',
|
||||
'leg_avis' => 'Avis',
|
||||
'leg_pep_sick' => 'Pep Malade',
|
||||
'leg_presence' => 'Présence',
|
||||
|
||||
'vac_toussaint' => 'Vacances de la Toussaint',
|
||||
'vac_noel' => 'Vacances de Noël',
|
||||
'vac_hiver' => "Vacances d'Hiver",
|
||||
'vac_printemps' => 'Vacances de Printemps',
|
||||
'vac_ascension' => 'Pont de l’Ascension',
|
||||
'vac_ete' => "Vacances d'Été",
|
||||
'btn_off' => 'Off',
|
||||
'btn_extra' => 'Extra',
|
||||
|
||||
// ==========================================
|
||||
// HOLIDAYS (VOYAGES)
|
||||
// ==========================================
|
||||
'hdl_title_list' => 'PachaFamily - Mes Vacances',
|
||||
'hdl_title_detail' => 'PachaFamily - Détail du voyage',
|
||||
'hdl_main_title' => 'Mes Vacances',
|
||||
'hdl_filter_all' => 'Tout afficher',
|
||||
'hdl_filter_year' => 'Année',
|
||||
'hdl_left_to_pay_label' => 'Reste à payer',
|
||||
'hdl_btn_create' => 'Créer un voyage',
|
||||
'hdl_no_active_trips' => 'Aucun voyage en cours pour cette période. Planifions quelque chose !',
|
||||
'hdl_history_title' => 'Historique',
|
||||
'hdl_dates_to_define' => 'Dates à définir',
|
||||
'hdl_total_budget' => 'Budget Total',
|
||||
'hdl_paid' => 'Payé',
|
||||
'hdl_saved' => 'Financé',
|
||||
'hdl_left_to_pay' => 'Reste à payer',
|
||||
'hdl_status_booked' => 'Confirmé',
|
||||
'hdl_status_planned' => 'Planifié',
|
||||
'hdl_status_draft' => 'Brouillon',
|
||||
'hdl_status_passed' => 'Terminé',
|
||||
'hdl_status_archived' => 'Archivé',
|
||||
'hdl_modal_title' => 'Planifier le voyage',
|
||||
'hdl_label_name' => 'Nom du voyage',
|
||||
'hdl_ph_name' => 'Ex: Octobre - Portugal',
|
||||
'hdl_label_status' => 'Statut',
|
||||
'hdl_label_period' => 'Période (Texte libre)',
|
||||
'hdl_ph_period' => 'Ex: Octobre 2026',
|
||||
'hdl_label_from' => 'Du',
|
||||
'hdl_label_to' => 'Au',
|
||||
'hdl_cat_transport' => 'Transport',
|
||||
'hdl_add_transport' => 'Ajouter un transport',
|
||||
'hdl_cat_accommodation' => 'Hébergement',
|
||||
'hdl_add_accommodation' => 'Ajouter un hébergement',
|
||||
'hdl_cat_activity' => 'Activité',
|
||||
'hdl_add_activity' => 'Ajouter une activité',
|
||||
'hdl_label_budget_food' => 'Budget Food & Bev (€)',
|
||||
'hdl_label_budget_extras' => 'Budget Extras (€)',
|
||||
'hdl_label_notes' => 'Notes',
|
||||
'hdl_ph_notes' => 'Idées en vrac...',
|
||||
'hdl_label_budget_food_extras' => 'Budget Food & Extras',
|
||||
'hdl_map_itinerary' => 'Itinéraire',
|
||||
'hdl_outbound' => 'Aller',
|
||||
'hdl_return' => 'Retour',
|
||||
'hdl_btn_add_step' => 'Ajouter une étape',
|
||||
'hdl_step_details' => 'Détails du roadtrip',
|
||||
'hdl_no_steps' => 'Aucune étape définie. Commencez par en ajouter une !',
|
||||
'hdl_general_costs' => 'Frais généraux & Budgets',
|
||||
'hdl_food_bev' => 'Food & Bev',
|
||||
'hdl_extras' => 'Extras',
|
||||
'hdl_view_planning' => 'Voir le planning',
|
||||
'hdl_passing_point' => 'Point de passage (sans frais)',
|
||||
'hdl_search_location' => 'Chercher un lieu',
|
||||
'hdl_ph_search' => 'Ville, hôtel, adresse...',
|
||||
'hdl_label_step_name' => "Nom de l'étape",
|
||||
'hdl_label_arrival' => 'Arrivée',
|
||||
'hdl_label_departure' => 'Départ',
|
||||
'hdl_planned_expenses' => 'Dépenses prévues pour cette étape',
|
||||
'hdl_btn_add_expense' => '+ Ajouter une dépense',
|
||||
'hdl_save_fav' => 'Enregistrer dans mes favoris',
|
||||
'hdl_planning_title' => 'Planning de l’étape',
|
||||
'hdl_to_pay' => 'À payer',
|
||||
'hdl_js_search_loading' => 'Recherche en cours...',
|
||||
'hdl_js_no_result' => 'Aucun résultat trouvé.',
|
||||
'hdl_js_network_error' => 'Erreur de connexion au service de carte.',
|
||||
'hdl_js_confirm_del_trip' => 'Es-tu sûr de vouloir supprimer ce voyage entier ?',
|
||||
'hdl_js_confirm_del_step' => 'Supprimer cette étape ?',
|
||||
'hdl_js_step_label' => 'Étape',
|
||||
'hdl_js_ph_expense_name' => 'Ex: Hôtel, Péage...',
|
||||
'hdl_quick_edit_title' => 'Modification rapide',
|
||||
'hdl_js_edit_step' => 'Modifier l’étape',
|
||||
'hdl_js_missing_dates_title'=> 'Dates manquantes',
|
||||
'hdl_js_missing_dates_msg' => 'Veuillez définir une date d’arrivée et de départ pour organiser le planning.',
|
||||
'hdl_to_place' => 'À placer',
|
||||
|
||||
// Anciennes clés Holidays non préfixées (conservées pour compatibilité si utilisées)
|
||||
'outbound_trip' => 'Aller',
|
||||
'return_trip' => 'Retour',
|
||||
'period' => 'Période',
|
||||
'budget_food' => 'Budget Food & Extras',
|
||||
'total_cost' => 'Coût Total Estimé',
|
||||
@@ -16,16 +217,10 @@ return [
|
||||
'provisioned' => 'Provisionné',
|
||||
'left_to_pay' => 'Reste à payer',
|
||||
'map_itinerary' => '🗺️ Itinéraire',
|
||||
'outbound_trip' => 'Aller',
|
||||
'return_trip' => 'Retour',
|
||||
'place_step' => '📍 Placer une étape',
|
||||
'step_details' => '📝 Détail des étapes',
|
||||
'no_steps' => 'Aucune étape planifiée.',
|
||||
'travel_notes' => '📝 Notes du voyage',
|
||||
'edit' => 'Modifier',
|
||||
|
||||
// --- Mots divers & Infobulles ---
|
||||
'error' => 'Erreur.',
|
||||
'to_be_defined' => 'À définir',
|
||||
'funded' => 'Financé (Provision)',
|
||||
'view_planning' => 'Voir le planning',
|
||||
@@ -33,8 +228,6 @@ return [
|
||||
'from_date' => 'Du',
|
||||
'to_date' => 'au',
|
||||
'passing_point' => 'Point de passage (Aucune dépense)',
|
||||
|
||||
// --- Modale : Placer / Éditer une étape ---
|
||||
'search_location' => 'Rechercher un lieu géographique',
|
||||
'search_placeholder' => 'Ex: Paris, Ibis Barcelone...',
|
||||
'step_name' => 'Nom de l\'étape',
|
||||
@@ -43,15 +236,7 @@ return [
|
||||
'planned_expenses' => 'Dépenses prévues',
|
||||
'add_expense' => '+ Ajouter une dépense',
|
||||
'save_favorite' => 'Sauvegarder dans mes favoris',
|
||||
|
||||
// --- Boutons génériques ---
|
||||
'btn_delete' => 'Supprimer',
|
||||
'btn_cancel' => 'Annuler',
|
||||
'btn_save' => 'Enregistrer',
|
||||
'btn_close' => 'Fermer',
|
||||
'planning_title' => 'Planning',
|
||||
|
||||
// --- Javascript (holidays.js) ---
|
||||
'modal_plan_trip' => 'Planifier le voyage',
|
||||
'modal_quick_edit' => 'Modification rapide',
|
||||
'err_modal_missing' => 'Erreur : La modale holidayModal est introuvable.',
|
||||
@@ -74,4 +259,222 @@ return [
|
||||
'missing_dates_msg' => 'Veuillez modifier cette étape (✏️) et renseigner sa <strong>Date d\'arrivée et de départ</strong>.',
|
||||
'to_place' => '📥 À Placer',
|
||||
|
||||
// ==========================================
|
||||
// BUDGET (GLOBAL & CATEGORIES)
|
||||
// ==========================================
|
||||
'budget_page_title' => 'PachaFamily - Budget',
|
||||
'budget_main_header' => 'Gestion du Budget',
|
||||
'budget_tab_tracking' => 'Suivi Mensuel',
|
||||
'budget_tab_prev' => 'Budget 2026',
|
||||
'budget_tab_savings' => 'Épargne',
|
||||
'budget_tab_recap' => 'Récapitulatif',
|
||||
'budget_err_file_not_found' => 'Oups ! Fichier introuvable.',
|
||||
'budget_err_file_detail' => 'Vérifiez que le fichier views/%s.php existe bien.',
|
||||
'budget_err_not_authorized' => 'Page non autorisée.',
|
||||
'cat_income' => 'Revenus',
|
||||
'cat_fmcg' => 'Courses (FMCG)',
|
||||
'cat_fuel' => 'Essence',
|
||||
'cat_school' => 'École / Garde',
|
||||
'cat_fixed' => 'Charges Fixes',
|
||||
'cat_others' => 'Autres / Imprévus',
|
||||
'cat_savings' => 'Épargne',
|
||||
|
||||
// --- BUDGET : SUIVI ---
|
||||
'bud_rem_school' => 'École (Reste estimé)',
|
||||
'bud_rem_fmcg' => 'Courses (Reste estimé)',
|
||||
'bud_rem_fuel' => 'Essence (Reste estimé)',
|
||||
'bud_closed_archived' => 'Ce mois est clôturé et archivé.',
|
||||
'bud_budget_of' => 'Budget :',
|
||||
'bud_confirm_close' => 'Clôturer ce mois ? Cela figera les soldes actuels et basculera le suivi par défaut sur le mois prochain.',
|
||||
'bud_close_btn' => 'Clôturer',
|
||||
'bud_confirm_reopen' => 'Rouvrir ce mois ? Ses soldes seront recalculés en direct.',
|
||||
'bud_reopen_btn' => 'Rouvrir',
|
||||
'bud_active_month_btn' => 'Mois Actif',
|
||||
'bud_bank_balance' => 'Solde bancaire',
|
||||
'bud_frozen' => 'Figé',
|
||||
'bud_current' => 'Actuel',
|
||||
'bud_update_btn' => 'MàJ',
|
||||
'bud_theoretical_balance' => 'Solde théorique fin de mois',
|
||||
'bud_upcoming_charges' => 'Charges à venir',
|
||||
'bud_all_paid' => 'Tout est payé !',
|
||||
'bud_capacity_tooltip' => 'Calcul : Report Mois Précédent (%s€) + Salaires (%s€) + Autres rentrées (%s€)',
|
||||
'bud_max_capacity' => 'Capacité Max',
|
||||
'bud_actual' => 'Actuel',
|
||||
'bud_theoretical' => 'Théorique',
|
||||
'bud_import_csv' => 'Importer CSV',
|
||||
'bud_csv_file' => 'Fichier CSV (Point-virgule)',
|
||||
'bud_preview' => 'Prévisualiser',
|
||||
'bud_validate_import' => 'Valider l\'importation',
|
||||
'bud_assign_month' => 'Imputer au mois',
|
||||
'bud_op_date' => 'Date Opération',
|
||||
'bud_label' => 'Libellé',
|
||||
'bud_amount' => 'Montant',
|
||||
'bud_category' => 'Catégorie',
|
||||
'bud_ignore' => 'Ignorer',
|
||||
'bud_to_define' => 'À définir',
|
||||
'bud_is_charge' => 'Charge ?',
|
||||
'bud_is_income' => 'Revenu ?',
|
||||
'btn_import' => 'Importer',
|
||||
'bud_no_lines' => 'Aucune ligne.',
|
||||
'bud_confirm_delete' => 'Supprimer cette ligne ?',
|
||||
'bud_new_transaction' => 'Nouvelle transaction',
|
||||
'bud_assign_to_month' => 'Imputer au mois de gestion',
|
||||
'bud_real_date' => 'Date réelle de l\'opération',
|
||||
'bud_type' => 'Type',
|
||||
'bud_type_expense' => 'Dépense',
|
||||
'bud_type_income' => 'Revenu / Remboursement',
|
||||
'bud_beneficiary' => 'Bénéficiaire',
|
||||
'bud_fixed_charge' => 'Charge Fixe',
|
||||
'bud_expected_income' => 'Revenu Attendu',
|
||||
'bud_amount_eur' => 'Montant (€)',
|
||||
'bud_update_balance' => 'MàJ Solde Bancaire',
|
||||
'bud_balance_eur' => 'Solde (€)',
|
||||
'bud_add_title' => 'Ajouter',
|
||||
'bud_edit_title' => 'Modifier la transaction',
|
||||
'bud_to_define_js' => 'à définir',
|
||||
|
||||
// --- BUDGET : RECAP ---
|
||||
'bud_recap_monthly_title' => 'Récapitulatif Mensuel',
|
||||
'bud_recap_btn_add' => 'Ajouter un frais / revenu',
|
||||
'bud_planned_amount' => 'Montant Prévu',
|
||||
'bud_day' => 'Jour',
|
||||
'bud_month_state' => 'État du mois',
|
||||
'bud_est_short' => 'Est.',
|
||||
'bud_reg_planned_in' => 'Régul. prévue en %s',
|
||||
'bud_bonus' => 'Bonus',
|
||||
'bud_overrun' => 'Dépassement',
|
||||
'bud_remaining' => 'Reste',
|
||||
'bud_exact_amount' => 'Montant exact',
|
||||
'bud_per_month_short' => 'Soit',
|
||||
'bud_month_short' => 'mois',
|
||||
'bud_freq_mensuel' => 'Mensuel',
|
||||
'bud_freq_annuel' => 'Annuel',
|
||||
'bud_state_validated' => 'Validé',
|
||||
'bud_state_partial' => 'Partiel',
|
||||
'bud_state_waiting' => 'En attente',
|
||||
'bud_total_income_smoothed' => 'Total Revenus (Lissés)',
|
||||
'bud_total_expenses_smoothed'=> 'Total Dépenses (Lissées)',
|
||||
'bud_theoretical_balance_recap'=> 'Équilibre théorique',
|
||||
'bud_recap_footer_note' => 'L\'état de paiement se met à jour automatiquement en fonction des opérations importées dans l\'onglet "Suivi Mensuel".',
|
||||
'bud_recap_modal_add' => 'Ajouter un élément',
|
||||
'bud_recap_modal_edit' => 'Modifier',
|
||||
'bud_label_name' => 'Nom',
|
||||
'bud_ph_item_name' => 'Ex: Loyer, Salaire...',
|
||||
'bud_label_keywords' => 'Mots-clés (Détection Auto)',
|
||||
'bud_ph_keywords' => 'Ex: NETFLIX, PRIME VIDEO (séparés par virgule)',
|
||||
'bud_help_keywords' => 'Si une dépense du mois contient ce mot, la ligne passera en "Validé".',
|
||||
'bud_label_day' => 'Jour (1-31)',
|
||||
'bud_cat_expense' => 'Dépense (Frais)',
|
||||
'bud_label_frequency' => 'Fréquence',
|
||||
'bud_label_amount_type' => 'Type de montant',
|
||||
'bud_type_fixed' => 'Fixe (Facture)',
|
||||
'bud_type_variable' => 'Variable (Estimation)',
|
||||
'bud_label_regularization' => 'Régularisation',
|
||||
'bud_reg_none' => 'Aucune',
|
||||
'bud_recap_confirm_delete' => 'Voulez-vous vraiment supprimer cet élément ?',
|
||||
'bud_err_delete' => 'Erreur lors de la suppression.',
|
||||
|
||||
// --- BUDGET : EPARGNE ---
|
||||
'bud_sav_add_month' => 'Saisir un mois',
|
||||
'bud_sav_add_one_month' => '+1 Mois',
|
||||
'bud_sav_no_data' => 'Aucune donnée pour %s.',
|
||||
'bud_sav_post_month' => 'Poste / Mois',
|
||||
'bud_sav_from_date' => 'Dès le %s',
|
||||
'bud_sav_edit_modal' => 'Modifier avec Modale',
|
||||
'bud_sav_delete_month' => 'Supprimer tout le mois',
|
||||
'bud_sav_total_bank' => 'Total Banque',
|
||||
'bud_sav_extra' => 'Extra',
|
||||
'bud_sav_modal_title_add' => 'Saisir un mois',
|
||||
'bud_sav_modal_title_edit' => 'Modifier :',
|
||||
'bud_sav_month_concerned' => 'Mois concerné',
|
||||
'bud_sav_total_bank_eur' => 'Total en Banque (€)',
|
||||
'bud_sav_ventilation' => 'Ventilation',
|
||||
'bud_sav_adj_help' => 'Utilisez l\'ajustement (+/-) pour recalculer automatiquement.',
|
||||
'bud_sav_add_line' => 'Ligne',
|
||||
'bud_sav_current' => 'Actuel',
|
||||
'bud_sav_adjust' => 'Ajust (+/-)',
|
||||
'bud_sav_new' => 'Nouveau',
|
||||
'bud_sav_sum_mode_title' => 'Activer l\'addition rapide',
|
||||
'bud_sav_selection' => 'Sélection :',
|
||||
'bud_sav_ph_name' => 'Nom (ex: Vacances)',
|
||||
'bud_sav_saving' => 'Enregistrement...',
|
||||
'bud_err_tech' => 'Une erreur technique est survenue.',
|
||||
'bud_sav_confirm_delete_month' => 'Supprimer TOUT le mois de %m pour %o ?',
|
||||
'bud_sav_prompt_duplicate' => "Voulez-vous copier les données de %s vers %t1 ?\n\nSaisissez le nouveau TOTAL en banque (€) pour %t2 :",
|
||||
'bud_err_server' => 'Erreur serveur : ',
|
||||
'bud_err_network_dup' => 'Erreur réseau lors de la duplication.',
|
||||
|
||||
// --- BUDGET : PREVISIONNEL ---
|
||||
'bud_prev_incomes' => 'Revenus',
|
||||
'bud_prev_person' => 'Personne',
|
||||
'bud_prev_salary' => 'Salaire',
|
||||
'bud_prev_monthly_payment' => 'Mensualité',
|
||||
'bud_prev_joint_account' => 'Cpt Commun',
|
||||
'bud_prev_func_expenses' => 'Frais Func.',
|
||||
'bud_prev_perso_savings' => 'Eco Perso',
|
||||
'bud_prev_family_savings' => 'Eco Family',
|
||||
'bud_prev_available' => 'Dispo',
|
||||
'bud_prev_perso_remaining' => 'Restant Perso',
|
||||
'bud_prev_budget_alloc' => 'Répartition Budgétaire',
|
||||
'bud_prev_today' => 'Auj.',
|
||||
'bud_prev_new_line' => 'Nouvelle Ligne',
|
||||
'bud_prev_remaining' => 'Restant',
|
||||
'bud_prev_confirm_del_line' => 'Supprimer cette ligne et tout son historique ?',
|
||||
'bud_prev_notes_for' => 'Notes pour',
|
||||
'bud_prev_saved' => 'Enregistré',
|
||||
'bud_prev_notes_ph' => 'Écrivez vos remarques, rappels ou commentaires pour ce mois ici...',
|
||||
'bud_prev_save_note' => 'Enregistrer la note',
|
||||
'bud_prev_transfers_to_make' => 'Virements à effectuer',
|
||||
'bud_prev_destination' => 'Destination',
|
||||
'bud_prev_done' => 'FAIT',
|
||||
'bud_prev_validate' => 'Valider',
|
||||
'bud_prev_grand_total' => 'GRAND TOTAL',
|
||||
'bud_prev_new_line_title' => 'Nouvelle Ligne de Budget',
|
||||
'bud_prev_edit_line_title' => 'Modifier la ligne',
|
||||
'bud_prev_label_target' => 'Cible (Destination)',
|
||||
'bud_prev_choose' => 'Choisir',
|
||||
'bud_prev_link_holiday' => 'Lier à un voyage (Optionnel)',
|
||||
'bud_prev_no_link' => 'Ne pas associer',
|
||||
'bud_prev_err_no_history' => 'Impossible : pas d\'historique disponible pour copier.',
|
||||
'bud_prev_confirm_copy' => "Voulez-vous copier les données de %s vers %t ?\n\n⚠️ Cela écrasera toutes les valeurs déjà présentes pour %t.",
|
||||
'bud_prev_confirm_transfers' => "Confirmer que %p a bien effectué tous ses virements pour %m ?\n\nCela mettra à jour l'Épargne automatiquement.",
|
||||
|
||||
// ==========================================
|
||||
// CADEAUX (GIFTS)
|
||||
// ==========================================
|
||||
'gift_page_title' => 'PachaFamily - Liste de cadeaux',
|
||||
'gift_occ_tio' => 'Tió',
|
||||
'gift_occ_noel' => 'Noël',
|
||||
'gift_occ_rois' => 'Rois',
|
||||
'gift_occ_anniv' => 'Anniversaire',
|
||||
'gift_occ_sant' => 'Saint',
|
||||
'gift_main_title' => 'Liste de cadeaux %s',
|
||||
'gift_aria_change_view' => 'Changer la vue',
|
||||
'gift_view_nadal' => 'Noël',
|
||||
'gift_view_anniv' => 'Anniversaires',
|
||||
'gift_view_by_party' => 'Vue par fête',
|
||||
'gift_no_gifts' => 'Aucun cadeau enregistré pour %s dans cette vue.',
|
||||
'gift_add_gift' => 'Ajouter un cadeau',
|
||||
'gift_paid_by' => 'payé par %s',
|
||||
'gift_summary_title' => 'Résumé du budget',
|
||||
'gift_col_adult' => 'Adulte',
|
||||
'gift_col_child' => 'Enfant',
|
||||
'gift_col_party' => 'Fête',
|
||||
'gift_debtor' => 'Débiteur',
|
||||
'gift_creditor' => 'Créancier',
|
||||
'gift_liquidations' => 'Liquidations',
|
||||
'gift_no_debt' => 'Aucune dette en cours.',
|
||||
'gift_owes' => 'doit',
|
||||
'gift_to' => 'à',
|
||||
'gift_detailed_list' => 'Liste détaillée des cadeaux',
|
||||
'gift_col_gift' => 'Cadeau',
|
||||
'gift_col_link' => 'Lien',
|
||||
'gift_modal_title_add' => 'Ajouter un cadeau pour %s',
|
||||
'gift_modal_title_edit' => 'Modifier le cadeau',
|
||||
'gift_modal_payer' => 'Payé par',
|
||||
'gift_modal_gift_name' => 'Nom du cadeau',
|
||||
'gift_modal_ph_name' => 'ex: Lego Star Wars',
|
||||
'gift_modal_price' => 'Prix (€)',
|
||||
'gift_modal_link' => 'Lien (optionnel)',
|
||||
'gift_confirm_delete' => 'Voulez-vous vraiment supprimer ce cadeau ?',
|
||||
];
|
||||
@@ -3,8 +3,11 @@
|
||||
require __DIR__ . '/includes/auth.php';
|
||||
require_login('/login.php');
|
||||
|
||||
// Inclusion i18n (déjà fait dans header, mais par sécurité si besoin de tr() avant)
|
||||
require_once __DIR__ . '/includes/i18n.php';
|
||||
|
||||
// Configuration de la page
|
||||
$pageTitle = "PachaFamily - Accueil";
|
||||
$pageTitle = tr('home_title');
|
||||
$activePage = "home";
|
||||
$bodyClass = "pf-home"; // Important pour l'image de fond définie dans home.css
|
||||
$pageCss = "/modules/home/home.css";
|
||||
@@ -15,57 +18,57 @@ require __DIR__ . '/header.php';
|
||||
<div class="pf-container">
|
||||
|
||||
<div class="pf-hero">
|
||||
<h1>Bienvenue sur PachaFamily</h1>
|
||||
<p>Centre de contrôle de l'organisation familiale.</p>
|
||||
<h1><?= tr('home_welcome') ?></h1>
|
||||
<p><?= tr('home_subtitle') ?></p>
|
||||
|
||||
<?php if (isset($_SESSION['user'])): ?>
|
||||
<div class="pf-user-info" style="margin-top: 12px; font-weight: 500;">
|
||||
Connecté en tant que
|
||||
<?= tr('home_logged_as') ?>
|
||||
<strong><?= htmlspecialchars($_SESSION['user']['display_name'] ?? $_SESSION['user']['username']) ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<section class="pf-section">
|
||||
<h2 style="color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.6);">Modules</h2>
|
||||
<h2 style="color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.6);"><?= tr('home_modules_title') ?></h2>
|
||||
|
||||
<div class="pf-modules-grid">
|
||||
|
||||
<a href="/family-calendar.php" class="pf-module-card">
|
||||
<div class="pf-card-icon">📅</div>
|
||||
<h3 class="pf-card-title">Family calendar</h3>
|
||||
<h3 class="pf-card-title"><?= tr('mod_calendar_name') ?></h3>
|
||||
<div class="pf-card-desc">
|
||||
Gérez les congés, les modes de garde et l'organisation hebdomadaire.
|
||||
<?= tr('mod_calendar_desc') ?>
|
||||
</div>
|
||||
<span class="pf-card-cta">Ouvrir</span>
|
||||
<span class="pf-card-cta"><?= tr('cta_open') ?></span>
|
||||
</a>
|
||||
|
||||
<a href="/holidays.php" class="pf-module-card">
|
||||
<div class="pf-card-icon">🏖️</div>
|
||||
<h3 class="pf-card-title">Vacances</h3>
|
||||
<h3 class="pf-card-title"><?= tr('mod_holidays_name') ?></h3>
|
||||
<div class="pf-card-desc">
|
||||
Planifiez vos prochaines escapades, destinations et idées de voyage.
|
||||
<?= tr('mod_holidays_desc') ?>
|
||||
</div>
|
||||
<span class="pf-card-cta">Explorer</span>
|
||||
<span class="pf-card-cta"><?= tr('cta_explore') ?></span>
|
||||
</a>
|
||||
|
||||
<a href="/gift-list.php" class="pf-module-card">
|
||||
<div class="pf-card-icon">🎁</div>
|
||||
<h3 class="pf-card-title">Cadeaux</h3>
|
||||
<h3 class="pf-card-title"><?= tr('mod_gifts_name') ?></h3>
|
||||
<div class="pf-card-desc">
|
||||
Planifiez les cadeaux pour chaque enfant, pour Noël, les anniversaires et autres.
|
||||
<?= tr('mod_gifts_desc') ?>
|
||||
</div>
|
||||
<span class="pf-card-cta">Voir les listes</span>
|
||||
<span class="pf-card-cta"><?= tr('cta_view_lists') ?></span>
|
||||
</a>
|
||||
|
||||
<a href="/budget.php" class="pf-module-card">
|
||||
<div class="pf-card-icon">💰</div>
|
||||
<h3 class="pf-card-title">Budget</h3>
|
||||
<h3 class="pf-card-title"><?= tr('mod_budget_name') ?></h3>
|
||||
<div class="pf-card-desc">
|
||||
Suivi des dépenses fixes, revenus et équilibre du compte familial.
|
||||
<?= tr('mod_budget_desc') ?>
|
||||
</div>
|
||||
<span class="pf-card-cta">Gérer</span>
|
||||
</a>
|
||||
<span class="pf-card-cta"><?= tr('cta_manage') ?></span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
session_start();
|
||||
require __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/i18n.php'; // Toujours s'assurer que tr() est dispo
|
||||
|
||||
$pageTitle = "Connexion";
|
||||
$pageTitle = tr('login_title');
|
||||
$activePage = "login";
|
||||
|
||||
if (isset($_SESSION['user'])) {
|
||||
@@ -17,7 +18,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (!$username || !$password) {
|
||||
$error = "Champs obligatoires.";
|
||||
$error = tr('error_missing_fields');
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT id, username, password_hash, display_name FROM pf_users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
@@ -33,7 +34,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
header('Location: ' . $redirectTo);
|
||||
exit;
|
||||
} else {
|
||||
$error = "Identifiants incorrects.";
|
||||
$error = tr('error_invalid_credentials');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +43,7 @@ require __DIR__ . '/header.php';
|
||||
?>
|
||||
|
||||
<div class="pf-login-container">
|
||||
<h1 style="text-align:center; font-size:1.8rem;">Connexion</h1>
|
||||
<h1 style="text-align:center; font-size:1.8rem;"><?= tr('login_header') ?></h1>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="pf-error"><?= htmlspecialchars($error) ?></div>
|
||||
@@ -50,16 +51,16 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<form method="post" action="/login.php<?= isset($_GET['redirect']) ? '?redirect=' . urlencode($_GET['redirect']) : '' ?>">
|
||||
<div class="pf-form-group">
|
||||
<label for="username">Identifiant</label>
|
||||
<input type="text" id="username" name="username" required autofocus placeholder="ex: pacha">
|
||||
<label for="username"><?= tr('label_username') ?></label>
|
||||
<input type="text" id="username" name="username" required autofocus placeholder="<?= tr('placeholder_username') ?>">
|
||||
</div>
|
||||
|
||||
<div class="pf-form-group">
|
||||
<label for="password">Mot de passe</label>
|
||||
<label for="password"><?= tr('label_password') ?></label>
|
||||
<input type="password" id="password" name="password" required placeholder="••••••">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="pf-btn">Se connecter</button>
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_login_submit') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ $nextMonthLink = date('Y-m-01', strtotime("+1 month", $focusTs));
|
||||
$cycleConfigs = [];
|
||||
$stmtNotes = $pdo->query("SELECT reference_id, content FROM pf_notes WHERE note_type = 'month_config'");
|
||||
while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
// Convertit "03-2026" en "2026-03-01" (Notre Tiroir)
|
||||
$parts = explode('-', $row['reference_id']);
|
||||
if (count($parts) == 2) {
|
||||
$mKey = $parts[1] . '-' . $parts[0] . '-01';
|
||||
@@ -81,25 +80,32 @@ $currentNote = $stmtNote->fetchColumn();
|
||||
|
||||
// 7. Récupération des vacances actives
|
||||
$activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN ('draft', 'planned', 'booked') ORDER BY start_date ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Helper function pour les mois
|
||||
function getTranslatedMonthName($dateString) {
|
||||
$m = date('m', strtotime($dateString));
|
||||
$y = date('Y', strtotime($dateString));
|
||||
return tr('month_' . $m) . ' ' . $y;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="prev-container">
|
||||
|
||||
<div>
|
||||
<div class="prev-section-header">
|
||||
<h2>Revenus <?= $currentYear ?></h2>
|
||||
<h2><?= tr('bud_prev_incomes') ?> <?= $currentYear ?></h2>
|
||||
</div>
|
||||
|
||||
<table class="prev-salary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Personne</th>
|
||||
<th>Salaire</th>
|
||||
<th>Mensualité<br><small style="font-weight:normal; text-transform:none;">(Cpt Commun)</small></th>
|
||||
<th>Frais Func.</th>
|
||||
<th>Eco Perso</th>
|
||||
<th style="background:#f0f9ff; color:#0284c7;">Eco Family<br><small style="font-weight:normal; text-transform:none;">(Dispo)</small></th>
|
||||
<th>Restant Perso</th>
|
||||
<th><?= tr('bud_prev_person') ?></th>
|
||||
<th><?= tr('bud_prev_salary') ?></th>
|
||||
<th><?= tr('bud_prev_monthly_payment') ?><br><small style="font-weight:normal; text-transform:none;">(<?= tr('bud_prev_joint_account') ?>)</small></th>
|
||||
<th><?= tr('bud_prev_func_expenses') ?></th>
|
||||
<th><?= tr('bud_prev_perso_savings') ?></th>
|
||||
<th style="background:#f0f9ff; color:#0284c7;"><?= tr('bud_prev_family_savings') ?><br><small style="font-weight:normal; text-transform:none;">(<?= tr('bud_prev_available') ?>)</small></th>
|
||||
<th><?= tr('bud_prev_perso_remaining') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -124,8 +130,6 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -133,20 +137,19 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
<div>
|
||||
<div class="prev-section-header">
|
||||
<div style="display:flex; gap:10px; align-items:center;">
|
||||
<h2>Répartition Budgétaire</h2>
|
||||
<h2><?= tr('bud_prev_budget_alloc') ?></h2>
|
||||
<div class="nav-group">
|
||||
<a href="?tab=budget_prev&focus_date=<?= $prevMonthLink ?>" class="btn-nav">◀</a>
|
||||
<a href="?tab=budget_prev&focus_date=<?= date('Y-m-01') ?>" class="btn-nav">Auj.</a>
|
||||
<a href="?tab=budget_prev&focus_date=<?= date('Y-m-01') ?>" class="btn-nav"><?= tr('bud_prev_today') ?></a>
|
||||
<a href="?tab=budget_prev&focus_date=<?= $nextMonthLink ?>" class="btn-nav">▶</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:10px;">
|
||||
<button class="pf-btn btn-secondary" onclick="duplicateMonth()">
|
||||
🔁 +1 Mois
|
||||
🔁 <?= tr('bud_sav_add_one_month') ?>
|
||||
</button>
|
||||
|
||||
<button class="pf-btn" onclick="document.getElementById('addCatModal').style.display='flex'">
|
||||
+ Nouvelle Ligne
|
||||
<button class="pf-btn" onclick="document.getElementById('addCatModal').style.display='flex'; document.body.classList.add('no-scroll');">
|
||||
+ <?= tr('bud_prev_new_line') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,21 +164,18 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
$cls = $isCurrent ? 'current' : '';
|
||||
?>
|
||||
<th colspan="3" class="th-month <?= $cls ?>">
|
||||
<?= date('F Y', strtotime($month)) ?>
|
||||
<span style="text-transform:capitalize;"><?= getTranslatedMonthName($month) ?></span>
|
||||
<?php
|
||||
// AFFICHAGE DU CYCLE CONFIGURÉ
|
||||
if (isset($cycleConfigs[$month]) && !empty($cycleConfigs[$month]['start_date'])) {
|
||||
$cStart = date('d/m', strtotime($cycleConfigs[$month]['start_date']));
|
||||
echo "<div style='font-size:0.75rem; font-weight:normal; color:#64748b; margin-top:2px;'>Dès le $cStart</div>";
|
||||
echo "<div style='font-size:0.75rem; font-weight:normal; color:#64748b; margin-top:2px;'>" . sprintf(tr('bud_sav_from_date'), $cStart) . "</div>";
|
||||
}
|
||||
?>
|
||||
</th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="col-sticky header-cell">
|
||||
|
||||
</th>
|
||||
<th class="col-sticky header-cell"></th>
|
||||
<?php foreach ($months as $month): ?>
|
||||
<th class="th-sub">Global</th>
|
||||
<th class="th-sub txt-alex">Alex</th>
|
||||
@@ -194,7 +194,7 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
</tr>
|
||||
|
||||
<tr class="row-restant">
|
||||
<td class="col-sticky" style="text-align:right !important;">Restant (Eco Family)</td>
|
||||
<td class="col-sticky" style="text-align:right !important;"><?= tr('bud_prev_remaining') ?> (Eco Family)</td>
|
||||
<?php foreach ($months as $m): ?>
|
||||
<td style="border-left:2px solid #cbd5e1; background:#e2e8f0;">-</td>
|
||||
<td class="val-ok sum-target" id="restant_alloc_alex_<?= $m ?>">0</td>
|
||||
@@ -222,7 +222,7 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
<div class="row-actions">
|
||||
<button type="button"
|
||||
class="btn-icon-action edit"
|
||||
title="Modifier"
|
||||
title="<?= tr('edit') ?>"
|
||||
data-id="<?= $cat['id'] ?>"
|
||||
data-name="<?= htmlspecialchars($cat['name']) ?>"
|
||||
data-target="<?= htmlspecialchars($cat['target']) ?>"
|
||||
@@ -232,8 +232,8 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
</button>
|
||||
|
||||
<a href="?tab=budget_prev&id=<?= $cat['id'] ?>&action=delete_category"
|
||||
onclick="return confirm('Supprimer cette ligne et tout son historique ?')"
|
||||
class="btn-icon-action delete" title="Supprimer">
|
||||
onclick="return confirm('<?= tr('bud_prev_confirm_del_line') ?>')"
|
||||
class="btn-icon-action delete" title="<?= tr('delete') ?>">
|
||||
×
|
||||
</a>
|
||||
</div>
|
||||
@@ -271,10 +271,10 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
<div style="margin: 20px 0; background: white; padding: 20px; border-radius: 12px; box-shadow: var(--shadow-sm); border: 1px solid #e2e8f0;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||
<h3 style="margin: 0; font-size: 1.1rem; color: #1e293b;">
|
||||
📝 Notes pour <?= date('F Y', strtotime($focusDate)) ?>
|
||||
📝 <?= tr('bud_prev_notes_for') ?> <span style="text-transform:capitalize;"><?= getTranslatedMonthName($focusDate) ?></span>
|
||||
</h3>
|
||||
<span id="note-save-indicator" style="font-size:0.85rem; color:#10b981; font-weight:bold; opacity:0; transition:opacity 0.3s;">
|
||||
✓ Enregistré
|
||||
✓ <?= tr('bud_prev_saved') ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -282,21 +282,19 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
id="monthNoteArea"
|
||||
class="pf-input"
|
||||
rows="3"
|
||||
placeholder="Écrivez vos remarques, rappels ou commentaires pour ce mois ici..."
|
||||
placeholder="<?= tr('bud_prev_notes_ph') ?>"
|
||||
style="width: 100%; resize: vertical; border-color: #cbd5e1; background: #f8fafc; margin-bottom: 10px;"
|
||||
><?= htmlspecialchars((string)$currentNote) ?></textarea>
|
||||
|
||||
<div style="text-align: right;">
|
||||
<button type="button" class="pf-btn" style="width: auto; display: inline-flex;"
|
||||
onclick="saveGenericNote('budget_prev', '<?= $focusDate ?>', document.getElementById('monthNoteArea').value)">
|
||||
Enregistrer la note
|
||||
<?= tr('bud_prev_save_note') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<?php
|
||||
<?php
|
||||
$focusMonth = $months[0];
|
||||
|
||||
$targetsOrder = ['vers commune', 'vers L.Pol', 'vers L.Pep', 'vers L.Perso'];
|
||||
@@ -335,24 +333,24 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
<div class="recap-wrapper">
|
||||
<div class="recap-card">
|
||||
<div class="recap-header">
|
||||
Virements à effectuer - <?= date('F Y', strtotime($focusMonth)) ?>
|
||||
<?= tr('bud_prev_transfers_to_make') ?> - <span style="text-transform:capitalize;"><?= getTranslatedMonthName($focusMonth) ?></span>
|
||||
</div>
|
||||
|
||||
<table class="recap-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align:left;">Destination</th>
|
||||
<th style="text-align:left;"><?= tr('bud_prev_destination') ?></th>
|
||||
|
||||
<th class="col-alex">
|
||||
<div style="display:flex; flex-direction:column; align-items:center; gap:5px;">
|
||||
<span>ALEX</span>
|
||||
<?php if($isValidatedAlex): ?>
|
||||
<div style="background:#10b981; color:white; padding:4px 8px; border-radius:4px; font-size:0.7rem; display:flex; align-items:center; gap:4px;">
|
||||
✓ FAIT
|
||||
✓ <?= tr('bud_prev_done') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<button onclick="validateTransfers('Alex', '<?= $focusMonth ?>')" class="pf-btn btn-small" style="background:white; color:#0891b2; border:1px solid #0891b2; font-size:0.7rem; padding:2px 8px; height:auto;">
|
||||
Valider
|
||||
<?= tr('bud_prev_validate') ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -363,11 +361,11 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
<span>LAIA</span>
|
||||
<?php if($isValidatedLaia): ?>
|
||||
<div style="background:#10b981; color:white; padding:4px 8px; border-radius:4px; font-size:0.7rem; display:flex; align-items:center; gap:4px;">
|
||||
✓ FAIT
|
||||
✓ <?= tr('bud_prev_done') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<button onclick="validateTransfers('Laia', '<?= $focusMonth ?>')" class="pf-btn btn-small" style="background:white; color:#d97706; border:1px solid #d97706; font-size:0.7rem; padding:2px 8px; height:auto;">
|
||||
Valider
|
||||
<?= tr('bud_prev_validate') ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -390,7 +388,7 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="row-grand-total">
|
||||
<td>GRAND TOTAL</td>
|
||||
<td><?= tr('bud_prev_grand_total') ?></td>
|
||||
<td class="col-alex sum-target" id="grand_total_alex">0 €</td>
|
||||
<td class="col-laia sum-target" id="grand_total_laia">0 €</td>
|
||||
<td class="col-global sum-target" id="grand_total_global">0 €</td>
|
||||
@@ -399,25 +397,24 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="addCatModal" class="pf-modal" style="display:none; position:fixed; inset:0; z-index:9999; background:rgba(15, 23, 42, 0.6); backdrop-filter:blur(4px); align-items:center; justify-content:center;">
|
||||
<div class="pf-modal-content" style="background:white; width:95%; max-width:400px; border-radius:20px; box-shadow:0 20px 25px -5px rgba(0,0,0,0.1); padding:30px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 class="pf-modal-title" style="margin:0; border:none; font-size:1.2rem;">Nouvelle Ligne de Budget</h3>
|
||||
<button onclick="document.getElementById('addCatModal').style.display='none'" style="border:none; background:none; font-size:1.5rem; cursor:pointer; color:var(--text-muted);">×</button>
|
||||
<h3 class="pf-modal-title" style="margin:0; border:none; font-size:1.2rem;"><?= tr('bud_prev_new_line_title') ?></h3>
|
||||
<button onclick="document.getElementById('addCatModal').style.display='none'; document.body.classList.remove('no-scroll');" style="border:none; background:none; font-size:1.5rem; cursor:pointer; color:var(--text-muted);">×</button>
|
||||
</div>
|
||||
<form action="/modules/budget/includes/api/save-budget.php" method="POST">
|
||||
<input type="hidden" name="action" value="add_category">
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Nom (ex: Vacances)</label>
|
||||
<label class="pf-label"><?= tr('bud_prev_label_name') ?></label>
|
||||
<input type="text" name="name" class="pf-input" required>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Cible (Destination)</label>
|
||||
<label class="pf-label"><?= tr('bud_prev_label_target') ?></label>
|
||||
<select name="target" class="pf-input" required>
|
||||
<option value="" disabled selected>-- Choisir --</option>
|
||||
<option value="" disabled selected>-- <?= tr('bud_prev_choose') ?> --</option>
|
||||
<option value="vers L.Pol">vers L.Pol</option>
|
||||
<option value="vers L.Pep">vers L.Pep</option>
|
||||
<option value="vers L.Perso">vers L.Perso</option>
|
||||
@@ -425,17 +422,17 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:20px;">
|
||||
<label class="pf-label" style="color:#8b5cf6;">🌴 Lier à un voyage (Optionnel)</label>
|
||||
<label class="pf-label" style="color:#8b5cf6;">🌴 <?= tr('bud_prev_link_holiday') ?></label>
|
||||
<select name="holiday_id" class="pf-input" style="border-color:#8b5cf6; background:#f5f3ff;">
|
||||
<option value="">-- Ne pas associer --</option>
|
||||
<option value="">-- <?= tr('bud_prev_no_link') ?> --</option>
|
||||
<?php foreach ($activeHolidays as $hol): ?>
|
||||
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="document.getElementById('addCatModal').style.display='none'" class="pf-btn btn-secondary">Annuler</button>
|
||||
<button type="submit" class="pf-btn">Ajouter</button>
|
||||
<button type="button" onclick="document.getElementById('addCatModal').style.display='none'; document.body.classList.remove('no-scroll');" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn"><?= tr('bud_add_title') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -444,19 +441,19 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
<div id="editCatModal" class="pf-modal">
|
||||
<div class="pf-modal-content" style="max-width:400px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 class="pf-modal-title" style="margin:0; border:none; font-size:1.2rem;">Modifier la ligne</h3>
|
||||
<button onclick="document.getElementById('editCatModal').style.display='none'" style="border:none; background:none; font-size:1.5rem; cursor:pointer; color:var(--text-muted);">×</button>
|
||||
<h3 class="pf-modal-title" style="margin:0; border:none; font-size:1.2rem;"><?= tr('bud_prev_edit_line_title') ?></h3>
|
||||
<button onclick="document.getElementById('editCatModal').style.display='none'; document.body.classList.remove('no-scroll');" style="border:none; background:none; font-size:1.5rem; cursor:pointer; color:var(--text-muted);">×</button>
|
||||
</div>
|
||||
<form action="/modules/budget/includes/api/save-budget.php" method="POST">
|
||||
<input type="hidden" name="action" value="update_category">
|
||||
<input type="hidden" name="cat_id" id="edit_cat_id">
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Nom</label>
|
||||
<label class="pf-label"><?= tr('bud_label_name') ?></label>
|
||||
<input type="text" name="name" id="edit_cat_name" class="pf-input" required>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Cible (Destination)</label>
|
||||
<label class="pf-label"><?= tr('bud_prev_label_target') ?></label>
|
||||
<select name="target" id="edit_cat_target" class="pf-input" required>
|
||||
<option value="vers L.Pol">vers L.Pol</option>
|
||||
<option value="vers L.Pep">vers L.Pep</option>
|
||||
@@ -465,17 +462,17 @@ $activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:20px;">
|
||||
<label class="pf-label" style="color:#8b5cf6;">🌴 Lier à un voyage (Optionnel)</label>
|
||||
<label class="pf-label" style="color:#8b5cf6;">🌴 <?= tr('bud_prev_link_holiday') ?></label>
|
||||
<select name="holiday_id" id="edit_cat_holiday" class="pf-input" style="border-color:#8b5cf6; background:#f5f3ff;">
|
||||
<option value="">-- Ne pas associer --</option>
|
||||
<option value="">-- <?= tr('bud_prev_no_link') ?> --</option>
|
||||
<?php foreach ($activeHolidays as $hol): ?>
|
||||
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="document.getElementById('editCatModal').style.display='none'" class="pf-btn btn-secondary">Annuler</button>
|
||||
<button type="submit" class="pf-btn">Enregistrer</button>
|
||||
<button type="button" onclick="document.getElementById('editCatModal').style.display='none'; 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>
|
||||
@@ -564,15 +561,28 @@ body.sum-mode-active .sum-target {
|
||||
}
|
||||
</style>
|
||||
|
||||
<button id="fabSumMode" class="pf-fab-sum" onclick="toggleSumMode()" title="Activer l'addition rapide">
|
||||
<button id="fabSumMode" class="pf-fab-sum" onclick="toggleSumMode()" title="<?= tr('bud_sav_sum_mode_title') ?>">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"></path><line x1="8" y1="12" x2="16" y2="12"></line><line x1="12" y1="8" x2="12" y2="16"></line></svg>
|
||||
</button>
|
||||
|
||||
<div id="sumResultBar" class="pf-sum-bar">
|
||||
<span class="pf-sum-label">Sélection :</span>
|
||||
<span class="pf-sum-label"><?= tr('bud_sav_selection') ?></span>
|
||||
<span id="sumResultValue" class="pf-sum-value">0 €</span>
|
||||
<button onclick="toggleSumMode()" class="pf-sum-close" title="Fermer">×</button>
|
||||
<button onclick="toggleSumMode()" class="pf-sum-close" title="<?= tr('btn_close') ?>">×</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.I18N = {
|
||||
...window.I18N,
|
||||
'bud_prev_err_no_history': "<?= tr('bud_prev_err_no_history') ?>",
|
||||
'bud_prev_confirm_copy': "<?= tr('bud_prev_confirm_copy') ?>",
|
||||
'bud_prev_confirm_transfers': "<?= tr('bud_prev_confirm_transfers') ?>",
|
||||
'bud_err_tech': "<?= tr('bud_err_tech') ?>"
|
||||
};
|
||||
|
||||
const currentLang = document.documentElement.lang === "ca" ? "ca-ES" : "fr-FR";
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function openEditModal(btn) {
|
||||
const id = btn.getAttribute('data-id');
|
||||
@@ -586,6 +596,7 @@ function openEditModal(btn) {
|
||||
document.getElementById('edit_cat_holiday').value = holiday;
|
||||
|
||||
document.getElementById('editCatModal').style.display = 'flex';
|
||||
document.body.classList.add('no-scroll');
|
||||
}
|
||||
|
||||
const currentYear = <?= $currentYear ?>;
|
||||
@@ -600,7 +611,7 @@ function updateSalary(person, input) {
|
||||
const ecoF = parseFloat(row.querySelector('[data-field="eco_family"]').value) || 0;
|
||||
|
||||
const restant = salary - (mens + frais + ecoP + ecoF);
|
||||
document.getElementById('restant_' + person).innerText = Math.round(restant).toLocaleString('fr-FR') + ' €';
|
||||
document.getElementById('restant_' + person).innerText = Math.round(restant).toLocaleString(currentLang) + ' €';
|
||||
|
||||
saveData('update_salary_config', { year: currentYear, person: person, field: input.dataset.field, value: input.value });
|
||||
recalcAllAllocations();
|
||||
@@ -611,29 +622,24 @@ function updateAlloc(month, catId, personField, input) {
|
||||
recalcAllAllocations();
|
||||
}
|
||||
|
||||
function getMonthName(dateStr) {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString('fr-FR', { month: 'long' }).toUpperCase();
|
||||
}
|
||||
|
||||
function duplicateMonth() {
|
||||
const targetDateStr = months[0];
|
||||
const sourceDateStr = months[1];
|
||||
|
||||
if (!sourceDateStr) {
|
||||
alert("Impossible : pas d'historique disponible pour copier.");
|
||||
alert(tr("bud_prev_err_no_history"));
|
||||
return;
|
||||
}
|
||||
|
||||
const formatMonth = (d) => {
|
||||
let str = new Date(d).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
|
||||
let str = new Date(d).toLocaleDateString(currentLang, { month: 'long', year: 'numeric' });
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
};
|
||||
|
||||
const sourceName = formatMonth(sourceDateStr);
|
||||
const targetName = formatMonth(targetDateStr);
|
||||
|
||||
const message = `Voulez-vous copier les données de ${sourceName} vers ${targetName} ?\n\n⚠️ Cela écrasera toutes les valeurs déjà présentes pour ${targetName}.`;
|
||||
const message = tr("bud_prev_confirm_copy").replace('%s', sourceName).replace('%t', targetName);
|
||||
|
||||
if(!confirm(message)) return;
|
||||
|
||||
@@ -699,13 +705,13 @@ function recalcAllAllocations() {
|
||||
const elRestLaia = document.getElementById('restant_alloc_laia_' + m);
|
||||
|
||||
elRestAlex.innerText = Math.round(restAlex) + ' €';
|
||||
elRestAlex.className = 'val-' + (restAlex >= 0 ? 'ok' : 'ko') + ' sum-target'; // On maintient la classe sum-target !
|
||||
elRestAlex.className = 'val-' + (restAlex >= 0 ? 'ok' : 'ko') + ' sum-target';
|
||||
|
||||
elRestLaia.innerText = Math.round(restLaia) + ' €';
|
||||
elRestLaia.className = 'val-' + (restLaia >= 0 ? 'ok' : 'ko') + ' sum-target';
|
||||
});
|
||||
updateSummaryTable();
|
||||
if(isSumModeActive) updateSumResult(); // Force recalcul si la calculatrice est ouverte
|
||||
if(isSumModeActive) updateSumResult();
|
||||
}
|
||||
|
||||
function updateSummaryTable() {
|
||||
@@ -739,18 +745,18 @@ function updateSummaryTable() {
|
||||
const laiaSum = dataByTarget[targetName] ? dataByTarget[targetName].laia : 0;
|
||||
const globalSum = alexSum + laiaSum;
|
||||
|
||||
row.cells[1].innerText = Math.round(alexSum).toLocaleString('fr-FR') + ' €';
|
||||
row.cells[2].innerText = Math.round(laiaSum).toLocaleString('fr-FR') + ' €';
|
||||
row.cells[3].innerText = Math.round(globalSum).toLocaleString('fr-FR') + ' €';
|
||||
row.cells[1].innerText = Math.round(alexSum).toLocaleString(currentLang) + ' €';
|
||||
row.cells[2].innerText = Math.round(laiaSum).toLocaleString(currentLang) + ' €';
|
||||
row.cells[3].innerText = Math.round(globalSum).toLocaleString(currentLang) + ' €';
|
||||
|
||||
grandTotalAlex += alexSum;
|
||||
grandTotalLaia += laiaSum;
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('grand_total_alex').innerText = Math.round(grandTotalAlex).toLocaleString('fr-FR') + ' €';
|
||||
document.getElementById('grand_total_laia').innerText = Math.round(grandTotalLaia).toLocaleString('fr-FR') + ' €';
|
||||
document.getElementById('grand_total_global').innerText = Math.round(grandTotalAlex + grandTotalLaia).toLocaleString('fr-FR') + ' €';
|
||||
document.getElementById('grand_total_alex').innerText = Math.round(grandTotalAlex).toLocaleString(currentLang) + ' €';
|
||||
document.getElementById('grand_total_laia').innerText = Math.round(grandTotalLaia).toLocaleString(currentLang) + ' €';
|
||||
document.getElementById('grand_total_global').innerText = Math.round(grandTotalAlex + grandTotalLaia).toLocaleString(currentLang) + ' €';
|
||||
}
|
||||
|
||||
function saveData(action, data) {
|
||||
@@ -761,7 +767,8 @@ function saveData(action, data) {
|
||||
}
|
||||
|
||||
function validateTransfers(person, month) {
|
||||
if (!confirm(`Confirmer que ${person} a bien effectué tous ses virements pour ${month} ?\n\nCela mettra à jour l'Épargne automatiquement.`)) return;
|
||||
const msg = tr("bud_prev_confirm_transfers").replace('%p', person).replace('%m', month);
|
||||
if (!confirm(msg)) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'validate_transfers');
|
||||
@@ -777,7 +784,7 @@ function validateTransfers(person, month) {
|
||||
if(data.success) window.location.reload();
|
||||
else alert("Erreur: " + data.error);
|
||||
})
|
||||
.catch(e => alert("Erreur technique"));
|
||||
.catch(e => alert(tr("bud_err_tech")));
|
||||
}
|
||||
|
||||
function saveGenericNote(noteType, refId, content) {
|
||||
@@ -845,7 +852,6 @@ function toggleSumMode() {
|
||||
|
||||
function extractNumberFromText(text) {
|
||||
if (!text) return 0;
|
||||
// Remplace les virgules par des points et supprime tout ce qui n'est pas chiffre, point ou signe moins
|
||||
const cleanText = text.replace(',', '.').replace(/[^\d.-]/g, '');
|
||||
return parseFloat(cleanText) || 0;
|
||||
}
|
||||
@@ -862,13 +868,12 @@ function updateSumResult() {
|
||||
total += val;
|
||||
});
|
||||
|
||||
document.getElementById('sumResultValue').innerText = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }).format(total);
|
||||
document.getElementById('sumResultValue').innerText = new Intl.NumberFormat(currentLang, { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }).format(total);
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!isSumModeActive) return;
|
||||
|
||||
// Cible les inputs type number OU les cellules (td/th/div) ayant la classe .sum-target
|
||||
const targetElement = e.target.closest('input[type="number"], .sum-target');
|
||||
|
||||
if (targetElement) {
|
||||
@@ -884,7 +889,7 @@ document.addEventListener('click', function(e) {
|
||||
|
||||
updateSumResult();
|
||||
}
|
||||
}, true); // Le 'true' intercepte le clic avant le focus normal
|
||||
}, true);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', recalcAllAllocations);
|
||||
</script>
|
||||
@@ -13,6 +13,13 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
$cycleConfigs[$mKey] = json_decode($row['content'], true);
|
||||
}
|
||||
}
|
||||
|
||||
// Récupération sécurisée du nom des mois (utilise les clés globales existantes)
|
||||
function getMonthName($dateString) {
|
||||
$m = date('m', strtotime($dateString));
|
||||
$y = date('Y', strtotime($dateString));
|
||||
return tr('month_' . $m) . ' ' . $y;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="budget-view">
|
||||
@@ -48,7 +55,7 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
$ownerTextClass = '';
|
||||
if ($currentOwner === 'Alex') $ownerTextClass = 'txt-alex';
|
||||
elseif ($currentOwner === 'Laia') $ownerTextClass = 'txt-laia';
|
||||
else $ownerTextClass = 'txt-global'; // Pour Pol et Pep (ou autre)
|
||||
else $ownerTextClass = 'txt-global'; // Pour Pol et Pep
|
||||
?>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; margin-top: <?= ($requestedOwner === 'Nens' && $currentOwner !== 'Pol') ? '40px' : '0' ?>;">
|
||||
@@ -65,11 +72,11 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<?php if (!empty($months)): ?>
|
||||
<button onclick="duplicateLastMonth('<?= $months[0] ?>', '<?= $currentOwner ?>')" class="pf-btn btn-secondary">
|
||||
🔁 +1 Mois
|
||||
🔁 <?= tr('bud_sav_add_one_month') ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<button onclick="openCustomSavingsModal('<?= $currentOwner ?>')" class="pf-btn">
|
||||
+ Saisir un mois
|
||||
+ <?= tr('bud_sav_add_month') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,32 +84,32 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
<div class="table-responsive" style="background:white; border-radius:16px; box-shadow:var(--shadow-sm); border:1px solid #e2e8f0;">
|
||||
<?php if (empty($months)): ?>
|
||||
<div style="padding: 30px; text-align: center; color: #64748b;">
|
||||
<p>Aucune donnée pour <?= htmlspecialchars($currentOwner) ?>.</p>
|
||||
<p><?= sprintf(tr('bud_sav_no_data'), htmlspecialchars($currentOwner)) ?></p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<table class="pf-table savings-table nens-table theme-<?= strtolower($currentOwner) ?>" style="margin-top:0; box-shadow:none; border-radius:16px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-col" style="background:#f8fafc;">Poste / Mois</th>
|
||||
<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"><?= date('M Y', strtotime($month)) ?></span>
|
||||
<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;'>Dès le $cStart</span>";
|
||||
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" title="Modifier avec Modale"
|
||||
<button class="btn-icon-small" title="<?= tr('bud_sav_edit_modal') ?>"
|
||||
data-json="<?= htmlspecialchars(json_encode($data[$month] ?? []), ENT_QUOTES, 'UTF-8') ?>"
|
||||
onclick='editCustomSavingsMonth("<?= $month ?>", "<?= $currentOwner ?>", JSON.parse(this.getAttribute("data-json")))'>
|
||||
✏️
|
||||
</button>
|
||||
<button class="btn-icon-small" title="Supprimer tout le mois"
|
||||
<button class="btn-icon-small" title="<?= tr('bud_sav_delete_month') ?>"
|
||||
onclick="deleteEntireMonth('<?= $month ?>', '<?= $currentOwner ?>')"
|
||||
style="color: #ef4444; border-color: #fca5a5; background: #fef2f2;">
|
||||
🗑️
|
||||
@@ -115,7 +122,7 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="row-total">
|
||||
<td class="sticky-col"><strong>Total Banque</strong></td>
|
||||
<td class="sticky-col"><strong><?= tr('bud_sav_total_bank') ?></strong></td>
|
||||
<?php foreach ($months as $month):
|
||||
$val = $data[$month]['TOTAL_BANQUE'] ?? 0;
|
||||
?>
|
||||
@@ -154,7 +161,7 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
<?php endforeach; ?>
|
||||
|
||||
<tr class="row-extres">
|
||||
<td class="sticky-col"><strong>Extra</strong></td>
|
||||
<td class="sticky-col"><strong><?= tr('bud_sav_extra') ?></strong></td>
|
||||
<?php foreach ($months as $month):
|
||||
$total = $data[$month]['TOTAL_BANQUE'] ?? 0;
|
||||
$sum = 0;
|
||||
@@ -176,8 +183,8 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
<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;">Saisir un mois</h3>
|
||||
<button type="button" onclick="document.getElementById('savingsModal').style.display='none'" style="border:none; background:none; font-size:1.8rem; cursor:pointer; color:#64748b; line-height:1;">×</button>
|
||||
<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>
|
||||
|
||||
<form action="/modules/budget/includes/api/save-savings.php" method="POST" id="savingsForm">
|
||||
@@ -187,12 +194,12 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
||||
<div style="display:flex; gap:15px; margin-bottom:20px;">
|
||||
<div class="form-group" style="flex:1; margin:0;">
|
||||
<label class="pf-label">Mois concerné</label>
|
||||
<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;">
|
||||
<label class="pf-label">Total en Banque (€)</label>
|
||||
<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;">
|
||||
</div>
|
||||
</div>
|
||||
@@ -201,17 +208,17 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px;">
|
||||
<div>
|
||||
<h4 style="margin:0; font-size:1rem; color:#1e293b;">Ventilation</h4>
|
||||
<span style="font-size:0.8rem; color:#64748b;">Utilisez l'ajustement (+/-) pour recalculer automatiquement.</span>
|
||||
<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>
|
||||
</div>
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="addCustomEpargneLine()" style="padding:4px 10px; height:auto; width:auto; font-size:0.9rem;">+ Ligne</button>
|
||||
<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>
|
||||
</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;">Catégorie</div>
|
||||
<div style="width:100px;">Actuel</div>
|
||||
<div style="width:90px;">Ajust (+/-)</div>
|
||||
<div style="width:100px;">Nouveau</div>
|
||||
<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>
|
||||
|
||||
@@ -219,22 +226,41 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
</div>
|
||||
|
||||
<div style="margin-top:25px; display:flex; justify-content:flex-end; gap:10px;">
|
||||
<button type="button" onclick="document.getElementById('savingsModal').style.display='none'" class="pf-btn btn-secondary" style="width:auto; margin:0;">Annuler</button>
|
||||
<button type="submit" class="pf-btn" style="width:auto; margin:0;">Enregistrer</button>
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="fabSumMode" class="pf-fab-sum" onclick="toggleSumMode()" title="Activer l'addition rapide">
|
||||
<button id="fabSumMode" class="pf-fab-sum" onclick="toggleSumMode()" title="<?= tr('bud_sav_sum_mode_title') ?>">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"></path><line x1="8" y1="12" x2="16" y2="12"></line><line x1="12" y1="8" x2="12" y2="16"></line></svg>
|
||||
</button>
|
||||
|
||||
<div id="sumResultBar" class="pf-sum-bar">
|
||||
<span class="pf-sum-label">Sélection :</span>
|
||||
<span class="pf-sum-label"><?= tr('bud_sav_selection') ?></span>
|
||||
<span id="sumResultValue" class="pf-sum-value">0 €</span>
|
||||
<button onclick="toggleSumMode()" class="pf-sum-close" title="Fermer">×</button>
|
||||
<button onclick="toggleSumMode()" class="pf-sum-close" title="<?= tr('btn_close') ?>">×</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.I18N = {
|
||||
...window.I18N,
|
||||
'bud_sav_ph_name' : "<?= tr('bud_sav_ph_name') ?>",
|
||||
'bud_sav_modal_title_edit' : "<?= tr('bud_sav_modal_title_edit') ?>",
|
||||
'bud_sav_modal_title_add' : "<?= tr('bud_sav_modal_title_add') ?>",
|
||||
'bud_sav_saving' : "<?= tr('bud_sav_saving') ?>",
|
||||
'bud_err_tech' : "<?= tr('bud_err_tech') ?>",
|
||||
'bud_sav_confirm_delete_month' : "<?= tr('bud_sav_confirm_delete_month') ?>",
|
||||
'bud_sav_prompt_duplicate' : "<?= tr('bud_sav_prompt_duplicate') ?>",
|
||||
'bud_err_server' : "<?= tr('bud_err_server') ?>",
|
||||
'bud_err_network_dup' : "<?= tr('bud_err_network_dup') ?>"
|
||||
};
|
||||
|
||||
// Détection de la langue pour le JS
|
||||
const currentLang = document.documentElement.lang === "ca" ? "ca-ES" : "fr-FR";
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// ============================================================================
|
||||
// 1. GESTION DE L'ÉDITION INVISIBLE EN DIRECT (Input Classique)
|
||||
@@ -243,7 +269,6 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
||||
function updateEpargneCell(month, category, owner, inputEl) {
|
||||
const val = parseFloat(inputEl.value) || 0;
|
||||
|
||||
// 1. Sauvegarde silencieuse en Ajax
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'update_single_entry');
|
||||
formData.append('month_date', month);
|
||||
@@ -254,9 +279,8 @@ function updateEpargneCell(month, category, owner, inputEl) {
|
||||
fetch('/modules/budget/includes/api/save-savings.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).catch(err => alert("Erreur lors de la sauvegarde"));
|
||||
}).catch(err => alert(tr("bud_err_tech")));
|
||||
|
||||
// 2. Recalcul visuel de l'Extra instantané
|
||||
const totalInput = document.querySelector(`.total-input-${owner}-${month}`);
|
||||
const totalVal = parseFloat(totalInput.value) || 0;
|
||||
|
||||
@@ -269,11 +293,10 @@ function updateEpargneCell(month, category, owner, inputEl) {
|
||||
const extraCell = document.getElementById(`extra_${owner}_${month}`);
|
||||
|
||||
if (extraCell) {
|
||||
extraCell.innerText = Math.round(extra).toLocaleString('fr-FR') + ' €';
|
||||
extraCell.innerText = Math.round(extra).toLocaleString(currentLang) + ' €';
|
||||
extraCell.style.color = extra >= 0 ? '#10b981' : '#ef4444';
|
||||
}
|
||||
|
||||
// Si la calculatrice est active, on met à jour son résultat aussi
|
||||
if(isSumModeActive) updateSumResult();
|
||||
}
|
||||
|
||||
@@ -290,7 +313,7 @@ function addCustomEpargneLine(catName = '', amount = '') {
|
||||
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="Nom (ex: Vacances)" oninput="updateCustomFieldName(this)" style="padding:6px; font-size:0.9rem;" required>
|
||||
<input type="text" class="pf-input cat-name-input" value="${catName}" placeholder="${tr("bud_sav_ph_name")}" oninput="updateCustomFieldName(this)" style="padding:6px; font-size:0.9rem;" required>
|
||||
</div>
|
||||
|
||||
<div style="width:100px;">
|
||||
@@ -337,8 +360,8 @@ function editCustomSavingsMonth(monthDate, owner, rowData) {
|
||||
document.getElementById('sav_month').value = ym;
|
||||
|
||||
const dateObj = new Date(monthDate);
|
||||
const monthName = dateObj.toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
|
||||
document.getElementById('savingsModalTitle').innerText = "Modifier : " + monthName + " (" + owner + ")";
|
||||
const monthName = dateObj.toLocaleDateString(currentLang, { month: 'long', year: 'numeric' });
|
||||
document.getElementById('savingsModalTitle').innerText = tr("bud_sav_modal_title_edit") + " " + monthName + " (" + owner + ")";
|
||||
|
||||
document.getElementById('sav_total').value = rowData['TOTAL_BANQUE'] || '';
|
||||
|
||||
@@ -352,6 +375,7 @@ function editCustomSavingsMonth(monthDate, owner, rowData) {
|
||||
if (container.children.length === 0) addCustomEpargneLine();
|
||||
|
||||
document.getElementById('savingsModal').style.display = 'flex';
|
||||
document.body.classList.add('no-scroll');
|
||||
}
|
||||
|
||||
function openCustomSavingsModal(owner) {
|
||||
@@ -359,18 +383,22 @@ function openCustomSavingsModal(owner) {
|
||||
document.getElementById('sav_month').value = '';
|
||||
document.getElementById('sav_total').value = '';
|
||||
|
||||
document.getElementById('savingsModalTitle').innerText = "Saisir un mois (" + owner + ")";
|
||||
document.getElementById('savingsModalTitle').innerText = tr("bud_sav_modal_title_add") + " (" + owner + ")";
|
||||
|
||||
const container = document.getElementById('linesContainer');
|
||||
container.innerHTML = '';
|
||||
addCustomEpargneLine();
|
||||
|
||||
document.getElementById('savingsModal').style.display = 'flex';
|
||||
document.body.classList.add('no-scroll');
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('savingsModal');
|
||||
if (event.target == modal) modal.style.display = 'none';
|
||||
if (event.target == modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
}
|
||||
|
||||
const savingsForm = document.getElementById('savingsForm');
|
||||
@@ -385,7 +413,7 @@ if (savingsForm) {
|
||||
|
||||
const submitBtn = this.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.innerText;
|
||||
submitBtn.innerText = "Enregistrement...";
|
||||
submitBtn.innerText = tr('bud_sav_saving');
|
||||
submitBtn.disabled = true;
|
||||
|
||||
const formData = new FormData(this);
|
||||
@@ -395,7 +423,7 @@ if (savingsForm) {
|
||||
.then(text => { window.location.reload(); })
|
||||
.catch(error => {
|
||||
console.error("Erreur:", error);
|
||||
alert("Une erreur technique est survenue.");
|
||||
alert(tr("bud_err_tech"));
|
||||
submitBtn.innerText = originalText;
|
||||
submitBtn.disabled = false;
|
||||
});
|
||||
@@ -403,14 +431,16 @@ if (savingsForm) {
|
||||
}
|
||||
|
||||
function deleteEntireMonth(monthDate, owner) {
|
||||
if (!confirm(`Supprimer TOUT le mois de ${monthDate} pour ${owner} ?`)) return;
|
||||
const msg = tr('bud_sav_confirm_delete_month').replace('%m', monthDate).replace('%o', owner);
|
||||
if (!confirm(msg)) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("action", "delete_month_global");
|
||||
formData.append("month_date", monthDate);
|
||||
formData.append("owner", owner);
|
||||
fetch("/modules/budget/includes/api/save-savings.php", { method: "POST", body: formData })
|
||||
.then(() => window.location.reload())
|
||||
.catch(err => alert("Erreur lors de la suppression."));
|
||||
.catch(err => alert("<?= tr('bud_err_delete') ?>"));
|
||||
}
|
||||
|
||||
function duplicateLastMonth(lastMonthDate, owner) {
|
||||
@@ -421,7 +451,7 @@ function duplicateLastMonth(lastMonthDate, owner) {
|
||||
let nextMonthStr = `${year}-${month}-01`;
|
||||
|
||||
const formatMonth = (d) => {
|
||||
let str = new Date(d).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
|
||||
let str = new Date(d).toLocaleDateString(currentLang, { month: 'long', year: 'numeric' });
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
};
|
||||
|
||||
@@ -433,7 +463,10 @@ function duplicateLastMonth(lastMonthDate, owner) {
|
||||
defaultTotal = cycleConfigs[nextMonthStr].start_balance;
|
||||
}
|
||||
|
||||
const message = `Voulez-vous copier les données de ${sourceName} vers ${targetName} ?\n\nSaisissez le nouveau TOTAL en banque (€) pour ${targetName} :`;
|
||||
const message = tr('bud_sav_prompt_duplicate')
|
||||
.replace('%s', sourceName)
|
||||
.replace('%t1', targetName)
|
||||
.replace('%t2', targetName);
|
||||
|
||||
let newTotal = prompt(message, defaultTotal);
|
||||
|
||||
@@ -451,15 +484,14 @@ function duplicateLastMonth(lastMonthDate, owner) {
|
||||
try {
|
||||
const d = JSON.parse(text);
|
||||
if (d.success) window.location.reload();
|
||||
else alert("Erreur serveur : " + (d.error || "Inconnue"));
|
||||
else alert(tr('bud_err_server') + (d.error || "Inconnue"));
|
||||
} catch(e) {
|
||||
console.log("Avertissement : La réponse serveur n'était pas du JSON. Rechargement...");
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
alert("Erreur réseau lors de la duplication.");
|
||||
alert(tr("bud_err_network_dup"));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -509,7 +541,7 @@ function updateSumResult() {
|
||||
total += val;
|
||||
});
|
||||
|
||||
document.getElementById('sumResultValue').innerText = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }).format(total);
|
||||
document.getElementById('sumResultValue').innerText = new Intl.NumberFormat(currentLang, { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }).format(total);
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
|
||||
@@ -17,8 +17,12 @@ $currentMonth = isset($_GET['m']) ? str_pad((int)$_GET['m'], 2, '0', STR_PAD_LEF
|
||||
$currentYear = isset($_GET['y']) ? (int)$_GET['y'] : date('Y', strtotime($defaultActiveMonth));
|
||||
$viewMonthDate = "$currentYear-$currentMonth-01";
|
||||
|
||||
// Conservation du tableau d'origine pour la correspondance en base de données
|
||||
$moisFr = ['', 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
|
||||
$currentMonthName = $moisFr[(int)$currentMonth] . ' ' . $currentYear;
|
||||
|
||||
// Affichage dynamique traduit
|
||||
$monthTranslationKey = 'month_' . str_pad((int)$currentMonth, 2, '0', STR_PAD_LEFT);
|
||||
$currentMonthName = tr($monthTranslationKey) . ' ' . $currentYear;
|
||||
|
||||
// 4. Récupération des Dépenses Réelles du mois (Basé sur le Mois de Gestion !)
|
||||
$stmtExp = $pdo->prepare("SELECT amount, label, category, budget_item_id FROM pf_expenses WHERE gestion_month = ?");
|
||||
@@ -31,20 +35,20 @@ $totalRevenus = 0;
|
||||
|
||||
<div class="budget-view">
|
||||
<div class="view-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h2 style="margin:0;">Récapitulatif Mensuel</h2>
|
||||
<button onclick="openRecapModal('add')" class="pf-btn">+ Ajouter un frais / revenu</button>
|
||||
<h2 style="margin:0;"><?= tr('bud_recap_monthly_title') ?></h2>
|
||||
<button onclick="openRecapModal('add')" class="pf-btn">+ <?= tr('bud_recap_btn_add') ?></button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive" style="background:white; border-radius:16px; box-shadow:var(--shadow-sm); border:1px solid #e2e8f0; overflow:hidden;">
|
||||
<table class="pf-table" style="margin:0; box-shadow:none;">
|
||||
<thead style="background:#f8fafc;">
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Montant Prévu</th>
|
||||
<th>Type</th>
|
||||
<th>Jour</th>
|
||||
<th>État du mois (<?= $currentMonthName ?>)</th>
|
||||
<th style="text-align:right;">Actions</th>
|
||||
<th><?= tr('bud_label_name') ?></th>
|
||||
<th><?= tr('bud_planned_amount') ?></th>
|
||||
<th><?= tr('bud_type') ?></th>
|
||||
<th><?= tr('bud_day') ?></th>
|
||||
<th><?= tr('bud_month_state') ?> (<?= $currentMonthName ?>)</th>
|
||||
<th style="text-align:right;"><?= tr('actions') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -109,7 +113,7 @@ $totalRevenus = 0;
|
||||
<tr class="<?= $rowClass ?>" style="border-bottom:1px solid #f1f5f9;">
|
||||
<td style="padding:15px;">
|
||||
<strong><?= htmlspecialchars($item['name']) ?></strong>
|
||||
<?= $item['is_estimate'] ? ' <small style="color:#64748b;">(Est.)</small>' : '' ?>
|
||||
<?= $item['is_estimate'] ? ' <small style="color:#64748b;">('.tr('bud_est_short').')</small>' : '' ?>
|
||||
|
||||
<?php if(!empty($item['mapping_keywords'])): ?>
|
||||
<span title="<?= htmlspecialchars($item['mapping_keywords']) ?>" style="font-size:0.7rem; cursor:help;">🔗</span>
|
||||
@@ -117,7 +121,7 @@ $totalRevenus = 0;
|
||||
|
||||
<?php if(!empty($item['reg_month'])): ?>
|
||||
<div style="font-size:0.75rem; color:#94a3b8; font-style:italic; margin-top:2px;">
|
||||
📅 Régul. prévue en <?= htmlspecialchars($item['reg_month']) ?>
|
||||
📅 <?= sprintf(tr('bud_reg_planned_in'), tr('month_'.str_pad(array_search($item['reg_month'], $moisFr), 2, '0', STR_PAD_LEFT))) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
@@ -128,30 +132,29 @@ $totalRevenus = 0;
|
||||
<?php if ($hasMatchingExpense): ?>
|
||||
<?php
|
||||
// Le "Gap" est la différence visuelle.
|
||||
// S'il y a plus de $realAbs que prévu, c'est un dépassement ou un bonus.
|
||||
$gap = $realAbs - $targetAbs;
|
||||
|
||||
if ($gap > 0.05): ?>
|
||||
<div style="font-size:0.75rem; color:#ef4444; font-weight:bold;">
|
||||
<?= $item['category'] === 'income' ? 'Bonus :' : 'Dépassement :' ?> +<?= number_format($gap, 2, ',', ' ') ?> €
|
||||
<?= $item['category'] === 'income' ? tr('bud_bonus') : tr('bud_overrun') ?> : +<?= number_format($gap, 2, ',', ' ') ?> €
|
||||
</div>
|
||||
<?php elseif ($gap < -0.05): ?>
|
||||
<div style="font-size:0.75rem; color:#f59e0b; font-weight:normal;">
|
||||
Reste : <?= number_format(abs($gap), 2, ',', ' ') ?> €
|
||||
<?= tr('bud_remaining') ?> : <?= number_format(abs($gap), 2, ',', ' ') ?> €
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="font-size:0.75rem; color:#10b981; font-weight:normal;">
|
||||
Montant exact ✓
|
||||
<?= tr('bud_exact_amount') ?> ✓
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($item['type'] === 'Annuel'): ?>
|
||||
<div style="font-size:0.75rem; color:#94a3b8; font-weight:normal;">Soit <?= number_format($amountToAdd, 2, ',', ' ') ?>/mois</div>
|
||||
<div style="font-size:0.75rem; color:#94a3b8; font-weight:normal;"><?= tr('bud_per_month_short') ?> <?= number_format($amountToAdd, 2, ',', ' ') ?>/<?= tr('bud_month_short') ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td style="padding:15px;">
|
||||
<span class="badge-type <?= strtolower($item['type']) ?>" style="background:#e2e8f0; padding:4px 8px; border-radius:12px; font-size:0.8rem; font-weight:600; color:#475569;">
|
||||
<?= $item['type'] ?>
|
||||
<?= tr('bud_freq_'.strtolower($item['type'])) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td style="padding:15px; color:#64748b; font-weight:bold;"><?= $item['payment_day'] ? $item['payment_day'] : '-' ?></td>
|
||||
@@ -159,23 +162,23 @@ $totalRevenus = 0;
|
||||
<td style="padding:15px;">
|
||||
<?php if ($isAutoChecked): ?>
|
||||
<div style="display:inline-flex; align-items:center; gap:5px; background:#f0fdf4; color:#16a34a; padding:4px 10px; border-radius:20px; font-size:0.85rem; border:1px solid #bbf7d0;">
|
||||
<span>✓</span> Validé
|
||||
<span>✓</span> <?= tr('bud_state_validated') ?>
|
||||
</div>
|
||||
<?php elseif($hasMatchingExpense): ?>
|
||||
<div style="display:inline-flex; align-items:center; gap:5px; background:#fffbeb; color:#d97706; padding:4px 10px; border-radius:20px; font-size:0.85rem; border:1px solid #fde68a;">
|
||||
<span>⏳</span> Partiel
|
||||
<span>⏳</span> <?= tr('bud_state_partial') ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="display:inline-flex; align-items:center; gap:5px; background:#f8fafc; color:#94a3b8; padding:4px 10px; border-radius:20px; font-size:0.85rem; border:1px solid #e2e8f0;">
|
||||
<span>○</span> En attente
|
||||
<span>○</span> <?= tr('bud_state_waiting') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<td style="padding:15px; text-align:right;">
|
||||
<div class="action-buttons" style="display:flex; gap:5px; justify-content:flex-end;">
|
||||
<button class="btn-icon" onclick='editRecapItem(<?= htmlspecialchars(json_encode($item), ENT_QUOTES, 'UTF-8') ?>)' title="Modifier" style="background:none; border:none; cursor:pointer; font-size:1.1rem; filter:grayscale(1); transition:0.2s;">✏️</button>
|
||||
<button class="btn-icon" onclick="deleteRecapItem(<?= $item['id'] ?>)" title="Supprimer" style="background:none; border:none; cursor:pointer; font-size:1.1rem; filter:grayscale(1); transition:0.2s;">🗑️</button>
|
||||
<button class="btn-icon" onclick='editRecapItem(<?= htmlspecialchars(json_encode($item), ENT_QUOTES, 'UTF-8') ?>)' title="<?= tr('edit') ?>" style="background:none; border:none; cursor:pointer; font-size:1.1rem; filter:grayscale(1); transition:0.2s;">✏️</button>
|
||||
<button class="btn-icon" onclick="deleteRecapItem(<?= $item['id'] ?>)" title="<?= tr('delete') ?>" style="background:none; border:none; cursor:pointer; font-size:1.1rem; filter:grayscale(1); transition:0.2s;">🗑️</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -183,18 +186,18 @@ $totalRevenus = 0;
|
||||
</tbody>
|
||||
<tfoot style="background:#f8fafc;">
|
||||
<tr>
|
||||
<td colspan="1" style="padding:15px;"><strong>Total Revenus (Lissés)</strong></td>
|
||||
<td colspan="1" style="padding:15px;"><strong><?= tr('bud_total_income_smoothed') ?></strong></td>
|
||||
<td colspan="5" style="padding:15px; color:#10b981;"><strong>+ <?= number_format($totalRevenus, 2, ',', ' ') ?> €</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1" style="padding:15px;"><strong>Total Dépenses (Lissées)</strong></td>
|
||||
<td colspan="1" style="padding:15px;"><strong><?= tr('bud_total_expenses_smoothed') ?></strong></td>
|
||||
<td colspan="5" style="padding:15px; color:#ef4444;"><strong>- <?= number_format($totalDepenses, 2, ',', ' ') ?> €</strong></td>
|
||||
</tr>
|
||||
<tr style="border-top: 2px solid #e2e8f0; background: white;">
|
||||
<td colspan="1" style="padding:15px; font-size:1.1rem;"><strong>Équilibre théorique</strong></td>
|
||||
<td colspan="1" style="padding:15px; font-size:1.1rem;"><strong><?= tr('bud_theoretical_balance_recap') ?></strong></td>
|
||||
<?php $balance = $totalRevenus - $totalDepenses; ?>
|
||||
<td colspan="5" style="padding:15px; font-size: 1.3em;" class="<?= $balance >= 0 ? 'text-success' : 'text-danger' ?>">
|
||||
<strong style="color:<?= $balance >= 0 ? '#10b981' : '#ef4444' ?>;"><?= number_format($balance, 2, ',', ' ') ?> € / mois</strong>
|
||||
<strong style="color:<?= $balance >= 0 ? '#10b981' : '#ef4444' ?>;"><?= number_format($balance, 2, ',', ' ') ?> € / <?= tr('bud_month_short') ?></strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@@ -202,7 +205,7 @@ $totalRevenus = 0;
|
||||
</div>
|
||||
|
||||
<div class="budget-note" style="margin-top:15px; font-size:0.85rem; color:#64748b;">
|
||||
<p>* L'état de paiement se met à jour automatiquement en fonction des opérations importées dans l'onglet "Suivi Mensuel".</p>
|
||||
<p>* <?= tr('bud_recap_footer_note') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -210,7 +213,7 @@ $totalRevenus = 0;
|
||||
<div class="pf-modal-content" style="background:white; width:95%; max-width:500px; border-radius:20px; box-shadow:0 20px 25px -5px rgba(0,0,0,0.1); padding:30px; position:relative;">
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 id="recapModalTitle" style="margin:0; font-size:1.2rem; color:#1e293b;">Ajouter un élément</h3>
|
||||
<h3 id="recapModalTitle" style="margin:0; font-size:1.2rem; color:#1e293b;"><?= tr('bud_recap_modal_add') ?></h3>
|
||||
<button type="button" onclick="closeRecapModal()" style="border:none; background:none; font-size:1.8rem; cursor:pointer; color:#64748b; line-height:1;">×</button>
|
||||
</div>
|
||||
|
||||
@@ -219,67 +222,70 @@ $totalRevenus = 0;
|
||||
<input type="hidden" name="id" id="item_id">
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Nom</label>
|
||||
<input type="text" name="name" id="item_name" required class="pf-input" placeholder="Ex: Loyer, Salaire..." style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;"><?= tr('bud_label_name') ?></label>
|
||||
<input type="text" name="name" id="item_name" required class="pf-input" placeholder="<?= tr('bud_ph_item_name') ?>" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px;">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Mots-clés (Détection Auto)</label>
|
||||
<input type="text" name="mapping_keywords" id="item_keywords" class="pf-input" placeholder="Ex: NETFLIX, PRIME VIDEO (séparés par virgule)" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px; background:#f0f9ff; border-color:#bae6fd;">
|
||||
<small style="color:#64748b; font-size:0.75rem;">Si une dépense du mois contient ce mot, la ligne passera en "Validé".</small>
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;"><?= tr('bud_label_keywords') ?></label>
|
||||
<input type="text" name="mapping_keywords" id="item_keywords" class="pf-input" placeholder="<?= tr('bud_ph_keywords') ?>" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px; background:#f0f9ff; border-color:#bae6fd;">
|
||||
<small style="color:#64748b; font-size:0.75rem;"><?= tr('bud_help_keywords') ?></small>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:15px; margin-bottom:15px;">
|
||||
<div style="flex:1;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Montant (€)</label>
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;"><?= tr('bud_amount_eur') ?></label>
|
||||
<input type="number" step="0.01" name="amount" id="item_amount" required class="pf-input" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px;">
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Jour (1-31)</label>
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;"><?= tr('bud_label_day') ?></label>
|
||||
<input type="number" min="1" max="31" name="payment_day" id="item_day" class="pf-input" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:15px; margin-bottom:15px;">
|
||||
<div style="flex:1;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Catégorie</label>
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;"><?= tr('bud_category') ?></label>
|
||||
<select name="category" id="item_category" class="pf-input" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px; background:white;">
|
||||
<option value="expense">Dépense (Frais)</option>
|
||||
<option value="income">Revenu (Salaire)</option>
|
||||
<option value="expense"><?= tr('bud_cat_expense') ?></option>
|
||||
<option value="income"><?= tr('bud_cat_income') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Fréquence</label>
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;"><?= tr('bud_label_frequency') ?></label>
|
||||
<select name="type" id="item_type" class="pf-input" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px; background:white;">
|
||||
<option value="Mensuel">Mensuel</option>
|
||||
<option value="Annuel">Annuel</option>
|
||||
<option value="Mensuel"><?= tr('bud_freq_mensuel') ?></option>
|
||||
<option value="Annuel"><?= tr('bud_freq_annuel') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:15px; margin-bottom:25px;">
|
||||
<div style="flex:1;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Type de montant</label>
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;"><?= tr('bud_label_amount_type') ?></label>
|
||||
<select name="is_estimate" id="item_is_estimate" class="pf-input" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px; background:white;">
|
||||
<option value="0">Fixe (Facture)</option>
|
||||
<option value="1">Variable (Estimation)</option>
|
||||
<option value="0"><?= tr('bud_type_fixed') ?></option>
|
||||
<option value="1"><?= tr('bud_type_variable') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Régularisation</label>
|
||||
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;"><?= tr('bud_label_regularization') ?></label>
|
||||
<select name="reg_month" id="item_reg_month" class="pf-input" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px; background:white;">
|
||||
<option value="">Aucune</option>
|
||||
<option value=""><?= tr('bud_reg_none') ?></option>
|
||||
<?php
|
||||
$mois = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
|
||||
foreach($mois as $m) echo "<option value='$m'>$m</option>";
|
||||
foreach($moisFr as $index => $m) {
|
||||
if($index == 0) continue;
|
||||
// On injecte la valeur en français (pour la DB) mais on affiche la version traduite
|
||||
echo "<option value='$m'>" . tr('month_'.str_pad($index, 2, '0', STR_PAD_LEFT)) . "</option>";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:flex-end; gap:10px;">
|
||||
<button type="button" onclick="closeRecapModal()" class="pf-btn btn-secondary" style="width:auto; margin:0; background:#f1f5f9; color:#475569; border:none; padding:10px 20px; border-radius:8px; font-weight:600; cursor:pointer;">Annuler</button>
|
||||
<button type="submit" class="pf-btn" style="width:auto; margin:0; background:#2563eb; color:white; border:none; padding:10px 20px; border-radius:8px; font-weight:600; cursor:pointer;">Enregistrer</button>
|
||||
<button type="button" onclick="closeRecapModal()" class="pf-btn btn-secondary" style="width:auto; margin:0; background:#f1f5f9; color:#475569; border:none; padding:10px 20px; border-radius:8px; font-weight:600; cursor:pointer;"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn" style="width:auto; margin:0; background:#2563eb; color:white; border:none; padding:10px 20px; border-radius:8px; font-weight:600; cursor:pointer;"><?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -290,26 +296,29 @@ window.onclick = function(event) {
|
||||
const modal = document.getElementById('budgetRecapModal');
|
||||
if (event.target == modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
}
|
||||
|
||||
function openRecapModal(mode) {
|
||||
if (mode === "add") {
|
||||
document.getElementById("recapModalTitle").innerText = "Ajouter un élément";
|
||||
document.getElementById("recapModalTitle").innerText = "<?= tr('bud_recap_modal_add') ?>";
|
||||
document.getElementById("item_id").value = "";
|
||||
document.getElementById("recapForm").reset();
|
||||
}
|
||||
document.getElementById("budgetRecapModal").style.display = "flex";
|
||||
document.body.classList.add('no-scroll');
|
||||
}
|
||||
|
||||
function closeRecapModal() {
|
||||
document.getElementById("budgetRecapModal").style.display = "none";
|
||||
document.body.classList.remove('no-scroll');
|
||||
}
|
||||
|
||||
function editRecapItem(item) {
|
||||
const data = typeof item === "string" ? JSON.parse(item) : item;
|
||||
|
||||
document.getElementById("recapModalTitle").innerText = "Modifier : " + data.name;
|
||||
document.getElementById("recapModalTitle").innerText = "<?= tr('bud_recap_modal_edit') ?> : " + data.name;
|
||||
document.getElementById("item_id").value = data.id;
|
||||
document.getElementById("item_name").value = data.name;
|
||||
document.getElementById("item_keywords").value = data.mapping_keywords || '';
|
||||
@@ -322,10 +331,11 @@ function editRecapItem(item) {
|
||||
document.getElementById("item_is_estimate").value = data.is_estimate;
|
||||
|
||||
document.getElementById("budgetRecapModal").style.display = "flex";
|
||||
document.body.classList.add('no-scroll');
|
||||
}
|
||||
|
||||
function deleteRecapItem(id) {
|
||||
if (confirm("Voulez-vous vraiment supprimer cet élément ?")) {
|
||||
if (confirm("<?= tr('bud_recap_confirm_delete') ?>")) {
|
||||
const formData = new FormData();
|
||||
formData.append("action", "delete");
|
||||
formData.append("id", id);
|
||||
@@ -336,7 +346,7 @@ function deleteRecapItem(id) {
|
||||
}).then(() => {
|
||||
window.location.reload();
|
||||
}).catch(err => {
|
||||
alert("Erreur lors de la suppression.");
|
||||
alert("<?= tr('bud_err_delete') ?>");
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
// 1. GESTION DU MOIS ACTIF ET DE LA NAVIGATION
|
||||
// ============================================================================
|
||||
|
||||
// A. Déterminer quel est le mois "ouvert" par défaut
|
||||
$stmtActive = $pdo->query("SELECT content FROM pf_notes WHERE note_type = 'active_gestion_month' LIMIT 1");
|
||||
$defaultActiveMonth = $stmtActive->fetchColumn();
|
||||
if (!$defaultActiveMonth) {
|
||||
$defaultActiveMonth = date('Y-m-01');
|
||||
}
|
||||
|
||||
// B. Mois affiché à l'écran
|
||||
$viewM = isset($_GET['m']) ? str_pad((int)$_GET['m'], 2, '0', STR_PAD_LEFT) : date('m', strtotime($defaultActiveMonth));
|
||||
$viewY = isset($_GET['y']) ? (int)$_GET['y'] : date('Y', strtotime($defaultActiveMonth));
|
||||
$viewMonthDate = "$viewY-$viewM-01";
|
||||
@@ -168,21 +166,18 @@ if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] == 0) {
|
||||
// 3. RECUPERATION DES DONNEES ET CALCULS
|
||||
// ============================================================================
|
||||
|
||||
// A. Vérifier si le mois affiché est clôturé
|
||||
$stmtCheckClose = $pdo->prepare("SELECT content FROM pf_notes WHERE note_type = 'month_closure' AND reference_id = ?");
|
||||
$stmtCheckClose->execute([$viewMonthDate]);
|
||||
$closureJson = $stmtCheckClose->fetchColumn();
|
||||
$monthState = $closureJson ? json_decode($closureJson, true) : null;
|
||||
$isClosed = ($monthState && isset($monthState['is_closed']) && $monthState['is_closed'] === true);
|
||||
|
||||
// B. Récupérer le solde bancaire (Snapshot actuel)
|
||||
$snapshot = ['date' => date('Y-m-d'), 'amount' => 0];
|
||||
$snapStmt = $pdo->query("SELECT * FROM pf_bank_snapshots ORDER BY id DESC LIMIT 1");
|
||||
if ($s = $snapStmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$snapshot = ['date' => $s['snapshot_date'], 'amount' => (float)$s['amount']];
|
||||
}
|
||||
|
||||
// C. Récupérer le solde final du mois PRÉCÉDENT pour avoir la base de ce mois-ci
|
||||
$stmtPrevClose = $pdo->prepare("SELECT content FROM pf_notes WHERE note_type = 'month_closure' AND reference_id = ?");
|
||||
$stmtPrevClose->execute([$prevDate]);
|
||||
$prevClosureJson = $stmtPrevClose->fetchColumn();
|
||||
@@ -193,7 +188,6 @@ if ($solde_initial === 0 && !$prevMonthState) {
|
||||
$solde_initial = $snapshot['amount'];
|
||||
}
|
||||
|
||||
// D. Les Dépenses assignées à ce MOIS DE GESTION
|
||||
$stmt = $pdo->prepare("SELECT * FROM pf_expenses WHERE gestion_month = ? ORDER BY date_exp DESC");
|
||||
$stmt->execute([$viewMonthDate]);
|
||||
$allExpenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
@@ -201,13 +195,11 @@ $allExpenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$paidItemIds = array_column(array_filter($allExpenses, fn($e) => !empty($e['budget_item_id'])), 'budget_item_id');
|
||||
$realExpensesLabels = array_column(array_filter($allExpenses, fn($e) => $e['amount'] < 0), 'label');
|
||||
|
||||
// E. Lecture du Budget
|
||||
$budget_fmcg = 0; $budget_school = 0; $budget_essence = 0; $budget_frais = 0; $budget_income_prevu = 0;
|
||||
$total_income = 0; $total_expenses_prevues = 0;
|
||||
$reste_a_venir_calc = 0;
|
||||
$fixedChargesList = []; $incomeList = []; $pending_charges = [];
|
||||
|
||||
// CORRECTION BUG N°2 : On supprime la vérification "is_checked" de la base, on ne se fie qu'aux dépenses réelles du mois.
|
||||
$stmt = $pdo->query("SELECT id, name, amount, type, category, is_estimate, payment_day, mapping_keywords FROM pf_budget_items ORDER BY name ASC");
|
||||
while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$absAmount = abs((float)$item['amount']);
|
||||
@@ -249,26 +241,25 @@ while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
||||
$budget_autres = max(0, $total_income - $total_expenses_prevues);
|
||||
|
||||
// TRADUCTION DES CATÉGORIES
|
||||
$categoriesConfig = [
|
||||
'Income' => ['type'=>'credit', 'label'=>'Revenus', 'budget'=>$budget_income_prevu, 'color'=>'#10b981', 'suggestions'=>[]],
|
||||
'FMCG' => ['type'=>'debit', 'label'=>'Courses (FMCG)', 'budget'=>$budget_fmcg, 'color'=>'#3b82f6', 'suggestions'=>['Action', 'Carrefour', 'Lidl']],
|
||||
'Essence' => ['type'=>'debit', 'label'=>'Essence', 'budget'=>$budget_essence, 'color'=>'#f59e0b', 'suggestions'=>['Audi', 'Polo']],
|
||||
'School' => ['type'=>'debit', 'label'=>'École / Garde', 'budget'=>$budget_school, 'color'=>'#10b981', 'suggestions'=>[]],
|
||||
'Frais' => ['type'=>'debit', 'label'=>'Charges Fixes', 'budget'=>$budget_frais, 'color'=>'#ef4444', 'suggestions'=>[]],
|
||||
'Autres' => ['type'=>'debit', 'label'=>'Autres / Imprévus', 'budget'=>$budget_autres, 'color'=>'#64748b', 'suggestions'=>['Restaurant', 'Cadeau']],
|
||||
'LivretA' => ['type'=>'debit', 'label'=>'Epargne', 'budget'=>0, 'color'=>'#8b5cf6', 'suggestions'=>['Virement']]
|
||||
'Income' => ['type'=>'credit', 'label'=>tr('cat_income'), 'budget'=>$budget_income_prevu, 'color'=>'#10b981', 'suggestions'=>[]],
|
||||
'FMCG' => ['type'=>'debit', 'label'=>tr('cat_fmcg'), 'budget'=>$budget_fmcg, 'color'=>'#3b82f6', 'suggestions'=>['Action', 'Carrefour', 'Lidl']],
|
||||
'Essence' => ['type'=>'debit', 'label'=>tr('cat_fuel'), 'budget'=>$budget_essence, 'color'=>'#f59e0b', 'suggestions'=>['Audi', 'Polo']],
|
||||
'School' => ['type'=>'debit', 'label'=>tr('cat_school'), 'budget'=>$budget_school, 'color'=>'#10b981', 'suggestions'=>[]],
|
||||
'Frais' => ['type'=>'debit', 'label'=>tr('cat_fixed'), 'budget'=>$budget_frais, 'color'=>'#ef4444', 'suggestions'=>[]],
|
||||
'Autres' => ['type'=>'debit', 'label'=>tr('cat_others'), 'budget'=>$budget_autres, 'color'=>'#64748b', 'suggestions'=>['Restaurant', 'Cadeau']],
|
||||
'LivretA' => ['type'=>'debit', 'label'=>tr('cat_savings'),'budget'=>0, 'color'=>'#8b5cf6', 'suggestions'=>['Virement']]
|
||||
];
|
||||
|
||||
$totals = array_fill_keys(array_keys($categoriesConfig), 0);
|
||||
$expensesByCategory = array_fill_keys(array_keys($categoriesConfig), []);
|
||||
|
||||
$total_rentrees = 0;
|
||||
$depenses_reelles = 0;
|
||||
|
||||
foreach ($allExpenses as $exp) {
|
||||
$cat = $exp['category'];
|
||||
if (!isset($totals[$cat])) $cat = 'Autres';
|
||||
|
||||
$val = (float)$exp['amount'];
|
||||
|
||||
if ($cat === 'Income') { $totals[$cat] += $val; }
|
||||
@@ -286,28 +277,26 @@ foreach ($allExpenses as $exp) {
|
||||
}
|
||||
}
|
||||
|
||||
// 1. École
|
||||
// Reste à venir dynamiques
|
||||
$ecole_depense = isset($totals['School']) ? $totals['School'] : 0;
|
||||
$reste_ecole = max(0, $budget_school - $ecole_depense);
|
||||
if ($reste_ecole > 0) {
|
||||
$reste_a_venir_calc += $reste_ecole;
|
||||
$pending_charges[] = ['name' => 'École (Reste estimé)', 'amount' => $reste_ecole];
|
||||
$pending_charges[] = ['name' => tr('bud_rem_school'), 'amount' => $reste_ecole];
|
||||
}
|
||||
|
||||
// 2. Courses (FMCG)
|
||||
$fmcg_depense = isset($totals['FMCG']) ? $totals['FMCG'] : 0;
|
||||
$reste_fmcg = max(0, $budget_fmcg - $fmcg_depense);
|
||||
if ($reste_fmcg > 0) {
|
||||
$reste_a_venir_calc += $reste_fmcg;
|
||||
$pending_charges[] = ['name' => 'Courses (Reste estimé)', 'amount' => $reste_fmcg];
|
||||
$pending_charges[] = ['name' => tr('bud_rem_fmcg'), 'amount' => $reste_fmcg];
|
||||
}
|
||||
|
||||
// 3. Essence
|
||||
$essence_depense = isset($totals['Essence']) ? $totals['Essence'] : 0;
|
||||
$reste_essence = max(0, $budget_essence - $essence_depense);
|
||||
if ($reste_essence > 0) {
|
||||
$reste_a_venir_calc += $reste_essence;
|
||||
$pending_charges[] = ['name' => 'Essence (Reste estimé)', 'amount' => $reste_essence];
|
||||
$pending_charges[] = ['name' => tr('bud_rem_fuel'), 'amount' => $reste_essence];
|
||||
}
|
||||
|
||||
// F. Calculs des KPIs finaux
|
||||
@@ -315,7 +304,6 @@ $rentrees_salaires_reels = $totals['Income'] ?? 0;
|
||||
$rentrees_autres = $total_rentrees - $rentrees_salaires_reels;
|
||||
$salaires_retenus = max($rentrees_salaires_reels, $budget_income_prevu);
|
||||
|
||||
// Capacité Max : Solde fin de mois précédent + Salaire retenu + Autres rentrées
|
||||
$capacite_max_calc = $solde_initial + $salaires_retenus + $rentrees_autres;
|
||||
$solde_theorique_calc = ($solde_initial + $total_rentrees) - $depenses_reelles - $reste_a_venir_calc;
|
||||
|
||||
@@ -347,8 +335,13 @@ function getDisplayLogic($spent, $bg, $type) {
|
||||
return ['pct' => $pct, 'isOver' => $isOver, 'text' => $text];
|
||||
}
|
||||
|
||||
$moisFr = ['', 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
|
||||
$monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
// Noms des mois traduits
|
||||
$monthNames = [
|
||||
1 => tr('month_01'), 2 => tr('month_02'), 3 => tr('month_03'), 4 => tr('month_04'),
|
||||
5 => tr('month_05'), 6 => tr('month_06'), 7 => tr('month_07'), 8 => tr('month_08'),
|
||||
9 => tr('month_09'), 10 => tr('month_10'), 11 => tr('month_11'), 12 => tr('month_12')
|
||||
];
|
||||
$monthName = $monthNames[(int)$viewM] . ' ' . $viewY;
|
||||
?>
|
||||
|
||||
<div class="budget-view">
|
||||
@@ -357,37 +350,37 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
|
||||
<?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;">
|
||||
🔒 Ce mois est clôturé et archivé.
|
||||
🔒 <?= 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;">Budget : <?= $monthName ?></h2>
|
||||
<h2 style="margin:0; font-size:1.3rem; color:#0f172a; text-transform:capitalize;"><?= tr('bud_budget_of') ?> <?= $monthName ?></h2>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:10px;">
|
||||
<?php if (!$isClosed): ?>
|
||||
<form method="POST" onsubmit="return confirm('Clôturer ce mois ? Cela figera les soldes actuels et basculera le suivi par défaut sur le mois prochain.');">
|
||||
<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;">🔒 Clôturer</button>
|
||||
<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('Rouvrir ce mois ? Ses soldes seront recalculés en direct.');">
|
||||
<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;">🔓 Rouvrir</button>
|
||||
<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">Mois Actif</a>
|
||||
<a href="<?= $defaultLink ?>" class="suivi-btn-nav"><?= tr('bud_active_month_btn') ?></a>
|
||||
<a href="<?= $nextLink ?>" class="suivi-btn-nav">▶</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -395,17 +388,17 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
|
||||
<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;">Solde bancaire <?= $isClosed ? "(Figé)" : "(Actuel)" ?></div>
|
||||
<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>
|
||||
<?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'">✏️ MàJ</button>
|
||||
<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>
|
||||
|
||||
<div style="padding:15px; background:#f8fafc; border-radius:12px; border:1px solid #e2e8f0;">
|
||||
<div style="font-size:0.85rem; color:#64748b; margin-bottom:4px;">Solde théorique fin de mois</div>
|
||||
<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>
|
||||
@@ -413,7 +406,7 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
|
||||
<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>Charges à venir</span>
|
||||
<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;">
|
||||
@@ -421,7 +414,7 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
</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;">Tout est payé !</div>
|
||||
<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;">
|
||||
@@ -438,12 +431,12 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
<div style="position: absolute; top: 0px; left: 10px; font-size: 0.8rem; color: #64748b; font-weight: 700;">0 €</div>
|
||||
|
||||
<div style="position: absolute; top: 0px; right: 10px; font-size: 0.8rem; color: #64748b; font-weight: 700; text-align:right; cursor:help;"
|
||||
title="Calcul : Report Mois Précédent (<?= number_format($solde_initial, 0) ?>€) + Salaires (<?= number_format($salaires_retenus, 0) ?>€) + Autres rentrées (<?= number_format($rentrees_autres, 0) ?>€)">
|
||||
Capacité Max : <?= number_format($capacite_max, 0, ',', ' ') ?> € ℹ️
|
||||
title="<?= sprintf(tr('bud_capacity_tooltip'), number_format($solde_initial, 0), number_format($salaires_retenus, 0), number_format($rentrees_autres, 0)) ?>">
|
||||
<?= tr('bud_max_capacity') ?> : <?= number_format($capacite_max, 0, ',', ' ') ?> € ℹ️
|
||||
</div>
|
||||
|
||||
<div style="position: absolute; top: 12px; left: <?= $pct_actuel ?>%; transform: translateX(-50%); text-align: center; z-index: 10;">
|
||||
<div style="color: #334155; font-size: 0.75rem; font-weight: 800; white-space: nowrap; margin-bottom: 2px;">Actuel</div>
|
||||
<div style="color: #334155; font-size: 0.75rem; font-weight: 800; white-space: nowrap; margin-bottom: 2px;"><?= tr('bud_actual') ?></div>
|
||||
<div style="width: 3px; height: 18px; background: #334155; margin: 0 auto; border-radius: 2px;"></div>
|
||||
</div>
|
||||
<div style="height: 24px; background: #e2e8f0; border-radius: 12px; display: flex; overflow: hidden; position: relative; box-shadow: inset 0 2px 4px rgba(0,0,0,0.05);">
|
||||
@@ -452,37 +445,37 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
</div>
|
||||
<div style="position: absolute; top: 54px; left: <?= $pct_theorique ?>%; transform: translateX(-50%); text-align: center; z-index: 10;">
|
||||
<div style="width: 3px; height: 18px; background: #8b5cf6; margin: 0 auto 2px auto; border-radius: 2px;"></div>
|
||||
<div style="color: #8b5cf6; font-size: 0.75rem; font-weight: 800; white-space: nowrap;">Théorique</div>
|
||||
<div style="color: #8b5cf6; font-size: 0.75rem; font-weight: 800; white-space: nowrap;"><?= tr('bud_theoretical') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!$isClosed): ?>
|
||||
<div style="display:flex; gap:10px; margin-bottom:20px;">
|
||||
<button class="pf-btn btn-secondary" onclick="toggleDiv('importCsvForm')">📂 Importer CSV</button>
|
||||
<button class="pf-btn btn-secondary" onclick="toggleDiv('importCsvForm')">📂 <?= tr('bud_import_csv') ?></button>
|
||||
</div>
|
||||
|
||||
<div id="importCsvForm" style="display:<?= $showPreview ? 'block' : 'none' ?>; background:#f8fafc; padding:20px; border-radius:12px; margin-bottom:20px;">
|
||||
<?php if (!$showPreview): ?>
|
||||
<form method="POST" enctype="multipart/form-data" style="display:flex; gap:10px; align-items:end; flex-wrap:wrap;">
|
||||
<div style="flex:1; min-width:250px;">
|
||||
<label class="pf-label">Fichier CSV (Point-virgule)</label>
|
||||
<label class="pf-label"><?= tr('bud_csv_file') ?></label>
|
||||
<input type="file" name="csv_file" accept=".csv" class="pf-input">
|
||||
</div>
|
||||
<button type="submit" class="pf-btn" style="width:auto;">Prévisualiser</button>
|
||||
<button type="submit" class="pf-btn" style="width:auto;"><?= tr('bud_preview') ?></button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<form method="POST" id="formMapping">
|
||||
<input type="hidden" name="action" value="save_import">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; flex-wrap:wrap; gap:10px;">
|
||||
<h3 style="margin:0;">Valider l'importation</h3>
|
||||
<h3 style="margin:0;"><?= tr('bud_validate_import') ?></h3>
|
||||
<span id="missingCount" style="color:#ef4444; background:#fee2e2; padding:4px 10px; border-radius:12px; font-weight:bold; font-size:0.85rem; display:none;"></span>
|
||||
</div>
|
||||
|
||||
<div style="max-height:350px; overflow-y:auto; background:white; border:1px solid #eee; border-radius:8px;">
|
||||
<table class="pf-table" style="margin:0;">
|
||||
<thead style="position:sticky; top:0; z-index:10;">
|
||||
<tr><th><input type="checkbox" onclick="toggleAll(this)" checked></th><th>Imputer au mois</th><th>Date Opération</th><th>Libellé</th><th>Montant</th><th>Catégorie</th></tr>
|
||||
<tr><th><input type="checkbox" onclick="toggleAll(this)" checked></th><th><?= tr('bud_assign_month') ?></th><th><?= tr('bud_op_date') ?></th><th><?= tr('bud_label') ?></th><th><?= tr('bud_amount') ?></th><th><?= tr('bud_category') ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($csvData as $idx => $row):
|
||||
@@ -507,17 +500,17 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
<td>
|
||||
<div style="display:flex; gap:5px;">
|
||||
<select name="lines[<?= $idx ?>][cat]" class="pf-input line-select" onchange="handleLineCatChange(this)" <?= $dis ?> style="flex:1;">
|
||||
<option value="">-- <?= $isCrd ? 'Ignorer' : 'À définir' ?> --</option>
|
||||
<option value="">-- <?= $isCrd ? tr('bud_ignore') : tr('bud_to_define') ?> --</option>
|
||||
<?php foreach ($categoriesConfig as $k => $c): ?>
|
||||
<option value="<?= $k ?>" <?= ($row['cat']===$k)?'selected':'' ?>><?= $c['label'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="lines[<?= $idx ?>][budget_item_id]" class="pf-input select-frais" onchange="checkValidation()" style="display:none; flex:1;" disabled>
|
||||
<option value="">-- Charge ? --</option>
|
||||
<option value="">-- <?= tr('bud_is_charge') ?> --</option>
|
||||
<?php foreach ($fixedChargesList as $fc): ?><option value="<?= $fc['id'] ?>"><?= htmlspecialchars($fc['name']) ?></option><?php endforeach; ?>
|
||||
</select>
|
||||
<select name="lines[<?= $idx ?>][budget_item_id]" class="pf-input select-income" onchange="checkValidation()" style="display:none; flex:1;" disabled>
|
||||
<option value="">-- Revenu ? --</option>
|
||||
<option value="">-- <?= tr('bud_is_income') ?> --</option>
|
||||
<?php foreach ($incomeList as $inc): ?><option value="<?= $inc['id'] ?>"><?= htmlspecialchars($inc['name']) ?></option><?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
@@ -528,8 +521,8 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
</table>
|
||||
</div>
|
||||
<div style="margin-top:15px; display:flex; justify-content:flex-end; gap:10px;">
|
||||
<a href="?tab=suivi" class="pf-btn btn-secondary" style="width:auto;">Annuler</a>
|
||||
<button type="submit" id="btnImport" class="pf-btn" style="width:auto;">Importer</button>
|
||||
<a href="?tab=suivi" class="pf-btn btn-secondary" style="width:auto;"><?= tr('btn_cancel') ?></a>
|
||||
<button type="submit" id="btnImport" class="pf-btn" style="width:auto;"><?= tr('btn_import') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
@@ -558,7 +551,7 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
|
||||
<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;">Aucune ligne.</div>
|
||||
<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): ?>
|
||||
@@ -572,8 +565,8 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
</td>
|
||||
<?php if (!$isClosed): ?>
|
||||
<td style="width:60px; padding-right:10px; text-align:right; white-space:nowrap;">
|
||||
<button onclick='openEditModal(<?= json_encode($exp) ?>)' style="background:none; border:none; cursor:pointer; font-size:1rem; margin-right:8px;">✏️</button>
|
||||
<a href="?tab=suivi&delete_expense=<?= $exp['id'] ?>" onclick="return confirm('Supprimer ?')" style="color:#ef4444; text-decoration:none; font-size:1.4rem;">×</a>
|
||||
<button onclick='openEditModal(<?= json_encode($exp, JSON_HEX_APOS | JSON_HEX_QUOT) ?>)' style="background:none; border:none; cursor:pointer; font-size:1rem; margin-right:8px;">✏️</button>
|
||||
<a href="?tab=suivi&delete_expense=<?= $exp['id'] ?>" onclick="return confirm('<?= tr('bud_confirm_delete') ?>')" style="color:#ef4444; text-decoration:none; font-size:1.4rem;">×</a>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
@@ -589,25 +582,25 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
<div id="manualExpenseModal" class="pf-modal">
|
||||
<div class="pf-modal-content" style="max-width:400px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 style="margin:0;" id="modalTitle">Nouvelle transaction</h3>
|
||||
<button type="button" onclick="closeSuiviModal('manualExpenseModal')" style="border:none; background:none; font-size:1.8rem; cursor:pointer;">×</button>
|
||||
<h3 style="margin:0;" id="modalTitle"><?= tr('bud_new_transaction') ?></h3>
|
||||
<button type="button" onclick="closeSuiviModal('manualExpenseModal')" class="pf-modal-close">×</button>
|
||||
</div>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="save_expense_manual">
|
||||
<input type="hidden" name="expense_id" id="modalExpenseId">
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Imputer au mois de gestion :</label>
|
||||
<label class="pf-label"><?= tr('bud_assign_to_month') ?> :</label>
|
||||
<input type="month" name="gestion_month" id="modalGestionMonth" class="pf-input" style="font-weight:bold; color:#2563eb;" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Date réelle de l'opération</label>
|
||||
<label class="pf-label"><?= tr('bud_real_date') ?></label>
|
||||
<input type="date" name="date" id="modalDate" class="pf-input" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Catégorie</label>
|
||||
<label class="pf-label"><?= tr('bud_category') ?></label>
|
||||
<select name="category" id="modalCatSelect" class="pf-input" onchange="handleModalCatChange(this)">
|
||||
<?php foreach($categoriesConfig as $key => $conf): ?>
|
||||
<option value="<?= $key ?>"><?= $conf['label'] ?></option>
|
||||
@@ -616,21 +609,21 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Type</label>
|
||||
<label class="pf-label"><?= tr('bud_type') ?></label>
|
||||
<select name="is_credit" id="modalIsCredit" class="pf-input">
|
||||
<option value="0">Dépense (-)</option>
|
||||
<option value="1">Revenu / Remboursement (+)</option>
|
||||
<option value="0"><?= tr('bud_type_expense') ?> (-)</option>
|
||||
<option value="1"><?= tr('bud_type_income') ?> (+)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="blockInputText" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Libellé</label>
|
||||
<label class="pf-label"><?= tr('bud_label') ?></label>
|
||||
<input type="text" name="label" class="pf-input" id="modalLabelInput" list="modalSuggestions" autocomplete="off">
|
||||
<datalist id="modalSuggestions"></datalist>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="blockInputSelect" style="margin-bottom:15px; display:none;">
|
||||
<label class="pf-label">Bénéficiaire</label>
|
||||
<label class="pf-label"><?= tr('bud_beneficiary') ?></label>
|
||||
<select name="label_select" id="schoolSelect" class="pf-input">
|
||||
<option value="Ecole Pol">Ecole Pol</option>
|
||||
<option value="Carole">Carole</option>
|
||||
@@ -638,7 +631,7 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="blockInputFrais" style="margin-bottom:15px; display:none;">
|
||||
<label class="pf-label">Charge Fixe</label>
|
||||
<label class="pf-label"><?= tr('bud_fixed_charge') ?></label>
|
||||
<select name="budget_item_id" id="fraisSelect" class="pf-input" disabled>
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<?php foreach ($fixedChargesList as $fc): ?><option value="<?= $fc['id'] ?>"><?= htmlspecialchars($fc['name']) ?></option><?php endforeach; ?>
|
||||
@@ -646,7 +639,7 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="blockInputIncome" style="margin-bottom:15px; display:none;">
|
||||
<label class="pf-label">Revenu Attendu</label>
|
||||
<label class="pf-label"><?= tr('bud_expected_income') ?></label>
|
||||
<select name="budget_item_id" id="incomeSelect" class="pf-input" disabled>
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<?php foreach ($incomeList as $inc): ?><option value="<?= $inc['id'] ?>"><?= htmlspecialchars($inc['name']) ?></option><?php endforeach; ?>
|
||||
@@ -654,13 +647,13 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label">Montant (€)</label>
|
||||
<label class="pf-label"><?= tr('bud_amount_eur') ?></label>
|
||||
<input type="number" step="0.01" name="amount" id="modalAmount" class="pf-input" placeholder="0.00" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:20px; display:flex; justify-content:flex-end; gap:10px;">
|
||||
<button type="button" onclick="closeSuiviModal('manualExpenseModal')" class="pf-btn btn-secondary" style="width:auto;">Annuler</button>
|
||||
<button type="submit" class="pf-btn" style="width:auto;">Enregistrer</button>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="closeSuiviModal('manualExpenseModal')" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -668,14 +661,14 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
|
||||
<div id="snapshotModal" class="pf-modal">
|
||||
<div class="pf-modal-content" style="max-width:350px;">
|
||||
<h3 style="margin-top:0;">MàJ Solde Bancaire</h3>
|
||||
<h3 style="margin-top:0;"><?= tr('bud_update_balance') ?></h3>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="save_snapshot">
|
||||
<input type="date" name="snapshot_date" class="pf-input" value="<?= date('Y-m-d') ?>" style="margin-bottom:10px;" required>
|
||||
<input type="number" step="0.01" name="snapshot_amount" class="pf-input" placeholder="Solde (€)" style="margin-bottom:15px;" required>
|
||||
<div style="display:flex; justify-content:flex-end; gap:10px;">
|
||||
<button type="button" onclick="closeSuiviModal('snapshotModal')" class="pf-btn btn-secondary" style="width:auto;">Annuler</button>
|
||||
<button type="submit" class="pf-btn" style="width:auto;">Sauvegarder</button>
|
||||
<input type="number" step="0.01" name="snapshot_amount" class="pf-input" placeholder="<?= tr('bud_balance_eur') ?>" style="margin-bottom:15px;" required>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="closeSuiviModal('snapshotModal')" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -684,14 +677,13 @@ $monthName = $moisFr[(int)$viewM] . ' ' . $viewY;
|
||||
<script>
|
||||
const activeViewMonth = '<?= substr($viewMonthDate, 0, 7) ?>';
|
||||
function toggleDiv(id) { const el = document.getElementById(id); el.style.display = (el.style.display === 'none') ? 'block' : 'none'; }
|
||||
function openSuiviModal(id) { document.getElementById(id).style.display = 'flex'; }
|
||||
function closeSuiviModal(id) { document.getElementById(id).style.display = 'none'; }
|
||||
function openSuiviModal(id) { document.getElementById(id).style.display = 'flex'; document.body.classList.add('no-scroll'); }
|
||||
function closeSuiviModal(id) { document.getElementById(id).style.display = 'none'; document.body.classList.remove('no-scroll');}
|
||||
|
||||
const suggestions = <?= json_encode(array_map(fn($c) => $c['suggestions'], $categoriesConfig)) ?>;
|
||||
|
||||
function handleModalCatChange(select) {
|
||||
const catKey = select.value;
|
||||
const catText = select.options[select.selectedIndex].text.toLowerCase();
|
||||
|
||||
document.getElementById('blockInputText').style.display = 'none';
|
||||
document.getElementById('blockInputSelect').style.display = 'none';
|
||||
@@ -716,10 +708,10 @@ function handleModalCatChange(select) {
|
||||
|
||||
function openAddModal(catKey, catLabel) {
|
||||
openSuiviModal('manualExpenseModal');
|
||||
document.getElementById('modalTitle').innerText = "Ajouter : " + catLabel;
|
||||
document.getElementById('modalTitle').innerText = "<?= tr('bud_add_title') ?> : " + catLabel;
|
||||
document.getElementById('modalExpenseId').value = "";
|
||||
document.getElementById('modalDate').value = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('modalGestionMonth').value = activeViewMonth; // Force le mois de gestion actuel
|
||||
document.getElementById('modalGestionMonth').value = activeViewMonth;
|
||||
document.getElementById('modalLabelInput').value = "";
|
||||
document.getElementById('modalAmount').value = "";
|
||||
const catSelect = document.getElementById('modalCatSelect'); catSelect.value = catKey; handleModalCatChange(catSelect);
|
||||
@@ -728,7 +720,7 @@ function openAddModal(catKey, catLabel) {
|
||||
|
||||
function openEditModal(e) {
|
||||
openSuiviModal('manualExpenseModal');
|
||||
document.getElementById('modalTitle').innerText = "Modifier la transaction";
|
||||
document.getElementById('modalTitle').innerText = "<?= tr('bud_edit_title') ?>";
|
||||
document.getElementById('modalExpenseId').value = e.id;
|
||||
document.getElementById('modalDate').value = e.date_exp;
|
||||
document.getElementById('modalGestionMonth').value = e.gestion_month ? e.gestion_month.substring(0, 7) : activeViewMonth;
|
||||
@@ -748,7 +740,9 @@ function handleLineCatChange(select) {
|
||||
else if (select.value === 'Income') { iSel.style.display = 'block'; iSel.disabled = false; }
|
||||
checkValidation();
|
||||
}
|
||||
|
||||
function toggleAll(src) { document.querySelectorAll('.line-checkbox:not([disabled])').forEach(c => c.checked = src.checked); checkValidation(); }
|
||||
|
||||
function checkValidation() {
|
||||
let miss = 0;
|
||||
document.querySelectorAll('.line-checkbox:checked').forEach(cb => {
|
||||
@@ -757,8 +751,9 @@ function checkValidation() {
|
||||
if (!v) { miss++; row.style.background = '#fff1f2'; } else row.style.background = '';
|
||||
});
|
||||
const btn = document.getElementById('btnImport'); const msg = document.getElementById('missingCount');
|
||||
if(miss>0) { btn.disabled = true; btn.style.opacity=0.5; msg.style.display='inline'; msg.innerText=miss+' à définir'; }
|
||||
if(miss>0) { btn.disabled = true; btn.style.opacity=0.5; msg.style.display='inline'; msg.innerText = miss + ' <?= tr('bud_to_define_js') ?>'; }
|
||||
else { btn.disabled = false; btn.style.opacity=1; msg.style.display='none'; }
|
||||
}
|
||||
|
||||
if(document.getElementById('formMapping')) { document.querySelectorAll('.line-select').forEach(s => handleLineCatChange(s)); checkValidation(); }
|
||||
</script>
|
||||
@@ -105,7 +105,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
setupModalUI() {
|
||||
const headerH2 = document.querySelector(".fc-modal-header h2");
|
||||
if (headerH2 && !document.getElementById("holidayYearSelect")) {
|
||||
headerH2.innerHTML = `Vacances Scolaires (Zone C)
|
||||
headerH2.innerHTML = `${tr("fc_modal_holidays_title")}
|
||||
<select id="holidayYearSelect" style="margin-left:15px; font-size:1rem; padding:4px; border-radius:4px; border:1px solid #cbd5e1;">
|
||||
<option value="${this.currentSchoolYearStart - 1}">${this.currentSchoolYearStart - 1} - ${this.currentSchoolYearStart}</option>
|
||||
<option value="${this.currentSchoolYearStart}" selected>${this.currentSchoolYearStart} - ${this.currentSchoolYearStart + 1}</option>
|
||||
@@ -273,20 +273,20 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
(1000 * 60 * 60 * 24),
|
||||
) + 1;
|
||||
|
||||
let name = "Vacances scolaires";
|
||||
let name = tr("leg_school_holidays");
|
||||
|
||||
if (m === 10 || m === 11) {
|
||||
name = "Vacances de la Toussaint";
|
||||
name = tr("vac_toussaint");
|
||||
} else if (m === 12 || m === 1) {
|
||||
name = "Vacances de Noël";
|
||||
name = tr("vac_noel");
|
||||
} else if (m === 2 || m === 3) {
|
||||
name = "Vacances d'Hiver";
|
||||
name = tr("vac_hiver");
|
||||
} else if (m === 4 || (m === 5 && durationDays > 6)) {
|
||||
name = "Vacances de Printemps";
|
||||
name = tr("vac_printemps");
|
||||
} else if (m === 5 && durationDays <= 6) {
|
||||
name = "Pont de Mai (Ascension)";
|
||||
name = tr("vac_ascension");
|
||||
} else if (m === 7 || m === 8) {
|
||||
name = "Vacances d'Été";
|
||||
name = tr("vac_ete");
|
||||
}
|
||||
|
||||
html += `
|
||||
@@ -690,22 +690,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const y = this.currentMonth.getFullYear();
|
||||
const m = this.currentMonth.getMonth();
|
||||
|
||||
const lang = window.I18N_LANG || "fr-FR"; // Assure-toi d'avoir cette variable ou utilise une déduction
|
||||
const titleEl = document.querySelector("#fc-current-month-year");
|
||||
if (titleEl) {
|
||||
if (this.viewMode === "year") {
|
||||
titleEl.textContent = `Année scolaire ${this.currentSchoolYearStart} – ${this.currentSchoolYearStart + 1}`;
|
||||
titleEl.textContent = `${tr("fc_school_year")} ${this.currentSchoolYearStart} – ${this.currentSchoolYearStart + 1}`;
|
||||
} else if (this.viewMode === "2months") {
|
||||
const nextM = new Date(y, m + 1, 1);
|
||||
const m1 = new Intl.DateTimeFormat("fr-FR", { month: "long" }).format(
|
||||
const m1 = new Intl.DateTimeFormat(lang, { month: "long" }).format(
|
||||
this.currentMonth,
|
||||
);
|
||||
const m2 = new Intl.DateTimeFormat("fr-FR", {
|
||||
const m2 = new Intl.DateTimeFormat(lang, {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}).format(nextM);
|
||||
titleEl.textContent = `${m1} - ${m2}`;
|
||||
} else {
|
||||
titleEl.textContent = new Intl.DateTimeFormat("fr-FR", {
|
||||
titleEl.textContent = new Intl.DateTimeFormat(lang, {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}).format(this.currentMonth);
|
||||
@@ -1261,54 +1262,56 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
if (!menu) return;
|
||||
|
||||
this._currentBulkInfo = { dates };
|
||||
|
||||
// Gestion de la date affichée (Singulier/Pluriel)
|
||||
const dateLabel =
|
||||
dates.length > 1
|
||||
? `${dates.length} jours`
|
||||
: new Date(dates[0]).toLocaleDateString("fr-FR");
|
||||
? `${dates.length} ${tr("fc_unit_days")}`
|
||||
: new Date(dates[0]).toLocaleDateString(window.I18N_LANG || "fr-FR");
|
||||
|
||||
// Uniformisation : On définit l'icône SVG une seule fois
|
||||
// Icône SVG Poubelle (Uniformisée)
|
||||
const trashSvg = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>`;
|
||||
|
||||
// En-tête principal avec la date
|
||||
// Fonction utilitaire interne pour les en-têtes de section avec suppression
|
||||
const buildHeader = (title, action, cat) => `
|
||||
<div class="fc-menu-header">
|
||||
<strong>${title}</strong>
|
||||
${action ? `<button class="fc-menu-clear-icon" title="${tr("fc_clear")}" data-action="${action}" ${cat ? `data-cat="${cat}"` : ""}>${trashSvg}</button>` : ""}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 1. En-tête du menu (Date ou nombre de jours)
|
||||
let html = `<div class="fc-menu-section" style="border-bottom: none; padding-bottom: 0;">
|
||||
<strong style="font-size:0.85rem; color:var(--text-main); margin-bottom: 4px;">${dateLabel}</strong>
|
||||
</div>`;
|
||||
|
||||
// Fonction utilitaire pour créer une ligne de titre avec l'icône SVG
|
||||
const buildHeader = (title, action, cat) => `
|
||||
<div class="fc-menu-header">
|
||||
<strong>${title}</strong>
|
||||
${action ? `<button class="fc-menu-clear-icon" title="Effacer" data-action="${action}" ${cat ? `data-cat="${cat}"` : ""}>${trashSvg}</button>` : ""}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Section Carole
|
||||
// 2. Section Carole (Congés)
|
||||
html += `<div class="fc-menu-section">
|
||||
${buildHeader("Congés Carole", "clear-type", "CONGE")}
|
||||
${buildHeader(tr("fc_menu_carole"), "clear-type", "CONGE")}
|
||||
<div class="fc-menu-grid">
|
||||
<button class="fc-menu-btn" data-action="add" data-type="OFF_CAROLE" data-person="Carole">Off</button>
|
||||
<button class="fc-menu-btn" data-action="add" data-type="EXTRA_OFF_CAROLE" data-person="Carole">Extra</button>
|
||||
<button class="fc-menu-btn" data-action="add" data-type="OFF_CAROLE" data-person="Carole">${tr("btn_off")}</button>
|
||||
<button class="fc-menu-btn" data-action="add" data-type="EXTRA_OFF_CAROLE" data-person="Carole">${tr("btn_extra")}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Section Garde
|
||||
// 3. Section Garde (Centre / Avis)
|
||||
html += `<div class="fc-menu-section">
|
||||
${buildHeader("Garde", "clear-type", "GARDE")}
|
||||
${buildHeader(tr("leg_centre"), "clear-type", "GARDE")}
|
||||
<div class="fc-menu-grid">
|
||||
<button class="fc-menu-btn" data-action="add" data-type="CENTRE">Centre</button>
|
||||
<button class="fc-menu-btn" data-action="add" data-type="AVIS">Avis</button>
|
||||
<button class="fc-menu-btn" data-action="add" data-type="CENTRE">${tr("leg_centre")}</button>
|
||||
<button class="fc-menu-btn" data-action="add" data-type="AVIS">${tr("leg_avis")}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Section Pep
|
||||
// 4. Section Pep (Maladie)
|
||||
html += `<div class="fc-menu-section">
|
||||
${buildHeader("Pep", "clear-type", "PEP")}
|
||||
<button class="fc-menu-btn" data-action="add" data-type="PEP_SICK" style="width:100%">Pep Malade 🤒</button>
|
||||
<button class="fc-menu-btn" data-action="add" data-type="PEP_SICK" style="width:100%">${tr("leg_pep_sick")} 🤒</button>
|
||||
</div>`;
|
||||
|
||||
// --- SECTION ALEX & LAIA AVEC SUPPRESSION INDÉPENDANTE, UNIFORMISÉE ET ALIGNÉE À CÔTÉ (INLINE) ---
|
||||
// 5. Section Enfants (Alex & Laia)
|
||||
html += `<div class="fc-menu-section">
|
||||
${buildHeader("Congés Enfants", null, null)}
|
||||
${buildHeader(tr("fc_menu_kids_leaves"), null, null)}
|
||||
<div class="fc-menu-leaves-table">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -1316,13 +1319,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
<th>
|
||||
<div class="fc-th-inline">
|
||||
Alex
|
||||
<button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="2" title="Effacer Alex">${trashSvg}</button>
|
||||
<button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="2" title="${tr("fc_clear")} Alex">${trashSvg}</button>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="fc-th-inline">
|
||||
Laia
|
||||
<button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="3" title="Effacer Laia">${trashSvg}</button>
|
||||
<button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="3" title="${tr("fc_clear")} Laia">${trashSvg}</button>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
@@ -1335,6 +1338,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
<td><button class="fc-menu-btn" data-action="add-leave" data-pid="3" data-type="${t}">${t}</button></td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
html += ` </tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1342,7 +1346,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
menu.innerHTML = html;
|
||||
|
||||
// Positionnement intelligent du menu
|
||||
// Positionnement (Gestion des bords d'écran)
|
||||
const menuWidth = 240;
|
||||
let left = x + 10;
|
||||
if (left + menuWidth > window.innerWidth) left = x - menuWidth - 10;
|
||||
|
||||
@@ -1188,3 +1188,11 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover {
|
||||
color: #64748b;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.highlight-step {
|
||||
transition:
|
||||
box-shadow 0.3s,
|
||||
transform 0.3s;
|
||||
box-shadow: 0 0 0 3px var(--primary);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
@@ -32,10 +32,12 @@ function openHolidayModal(mode) {
|
||||
document.getElementById("list_activity").innerHTML = "";
|
||||
|
||||
if (mode === "add") {
|
||||
document.getElementById("modalTitle").innerText = tr("modal_plan_trip");
|
||||
document.getElementById("modalTitle").innerText = tr("hdl_modal_title");
|
||||
btnDelete.style.display = "none";
|
||||
} else {
|
||||
document.getElementById("modalTitle").innerText = tr("modal_quick_edit");
|
||||
document.getElementById("modalTitle").innerText = tr(
|
||||
"hdl_quick_edit_title",
|
||||
);
|
||||
btnDelete.style.display = "block";
|
||||
}
|
||||
|
||||
@@ -120,23 +122,14 @@ function addItem(category, name = "", amount = "", isPaid = 0) {
|
||||
|
||||
div.innerHTML = `
|
||||
<input type="hidden" name="items[cat][]" value="${category}">
|
||||
|
||||
<input type="text" name="items[name][]" class="pf-input"
|
||||
placeholder="${tr("placeholder_title")}" value="${name}"
|
||||
style="flex: 2; padding: 8px; font-size:0.9rem;" required>
|
||||
|
||||
<input type="number" step="0.01" name="items[amount][]" class="pf-input"
|
||||
placeholder="${tr("placeholder_price")}" value="${amount}"
|
||||
style="width: 80px; text-align: right; padding: 8px; font-size:0.9rem;">
|
||||
|
||||
<label title="${tr("already_paid")}" style="display: flex; align-items: center; cursor: pointer; padding: 0 5px;">
|
||||
<input type="text" name="items[name][]" class="pf-input" placeholder="${tr("hdl_js_ph_expense_name")}" value="${name}" style="flex: 2; padding: 8px; font-size:0.9rem;" required>
|
||||
<input type="number" step="0.01" name="items[amount][]" class="pf-input" placeholder="0.00" value="${amount}" style="width: 80px; text-align: right; padding: 8px; font-size:0.9rem;">
|
||||
<label title="${tr("hdl_paid")}" style="display: flex; align-items: center; cursor: pointer; padding: 0 5px;">
|
||||
<input type="checkbox" ${checkedAttr} onchange="this.nextElementSibling.value = this.checked ? 1 : 0" style="margin:0;">
|
||||
<input type="hidden" name="items[paid][]" value="${isPaid}">
|
||||
<span style="font-size:0.75rem; margin-left:4px; font-weight:bold; color:#64748b;">${tr("paid")}</span>
|
||||
<span style="font-size:0.75rem; margin-left:4px; font-weight:bold; color:#64748b;">${tr("hdl_paid")}</span>
|
||||
</label>
|
||||
|
||||
<button type="button" onclick="this.parentElement.remove()" title="${tr("remove_line")}"
|
||||
style="width: 28px; height: 28px; border: none; background: #fee2e2; color: #ef4444; border-radius: 6px; cursor: pointer; font-weight: bold; display:flex; align-items:center; justify-content:center;">
|
||||
<button type="button" onclick="this.parentElement.remove()" title="${tr("btn_delete")}" style="width: 28px; height: 28px; border: none; background: #fee2e2; color: #ef4444; border-radius: 6px; cursor: pointer; font-weight: bold; display:flex; align-items:center; justify-content:center;">
|
||||
×
|
||||
</button>
|
||||
`;
|
||||
@@ -145,7 +138,7 @@ function addItem(category, name = "", amount = "", isPaid = 0) {
|
||||
}
|
||||
|
||||
function deleteHoliday() {
|
||||
if (!confirm(tr("confirm_delete_trip"))) return;
|
||||
if (!confirm(tr("hdl_js_confirm_del_trip"))) return;
|
||||
const form = document.getElementById("holidayForm");
|
||||
const input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
@@ -161,6 +154,7 @@ let map = null;
|
||||
|
||||
function toggleMap() {
|
||||
const modal = document.getElementById("hol-map-modal");
|
||||
if (!modal) return;
|
||||
if (modal.style.display === "flex") {
|
||||
modal.style.display = "none";
|
||||
} else {
|
||||
@@ -246,7 +240,7 @@ function initDetailMap() {
|
||||
|
||||
marker.bindPopup(`
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:0.75rem; color:#64748b; margin-bottom:2px; font-weight:bold;">${tr("step_number")} ${index + 1}</div>
|
||||
<div style="font-size:0.75rem; color:#64748b; margin-bottom:2px; font-weight:bold;">${tr("hdl_js_step_label")} ${index + 1}</div>
|
||||
<strong style="font-size:1rem; color:#0f172a;">${pt.location_name}</strong><br>
|
||||
<span style="font-weight:bold; color:${color};">${parseFloat(pt.total_amount).toFixed(2)} €</span>
|
||||
</div>
|
||||
@@ -358,7 +352,6 @@ function openCheckpointModal(mode, data = null) {
|
||||
|
||||
container.innerHTML = "";
|
||||
|
||||
// Nettoyage des champs de date et de la barre de recherche
|
||||
if (document.getElementById("cp_start_date"))
|
||||
document.getElementById("cp_start_date").value = "";
|
||||
if (document.getElementById("cp_end_date"))
|
||||
@@ -366,11 +359,10 @@ function openCheckpointModal(mode, data = null) {
|
||||
document.getElementById("searchPlaceInput").value = "";
|
||||
document.getElementById("searchResults").innerHTML = "";
|
||||
|
||||
// MODIFICATION : La barre de recherche (et les favoris) est TOUJOURS visible !
|
||||
searchBlock.style.display = "block";
|
||||
|
||||
if (mode === "add") {
|
||||
document.getElementById("cpModalTitle").innerText = tr("place_new_step");
|
||||
document.getElementById("cpModalTitle").innerText = tr("hdl_btn_add_step");
|
||||
formBlock.style.display = "none";
|
||||
btnDel.style.display = "none";
|
||||
document.getElementById("cp_old_sort_order").value = "";
|
||||
@@ -378,7 +370,7 @@ function openCheckpointModal(mode, data = null) {
|
||||
addCpExpenseLine();
|
||||
document.getElementById("cp_is_return").checked = false;
|
||||
} else if (mode === "edit" && data) {
|
||||
document.getElementById("cpModalTitle").innerText = tr("edit_step");
|
||||
document.getElementById("cpModalTitle").innerText = tr("hdl_js_edit_step");
|
||||
formBlock.style.display = "block";
|
||||
btnDel.style.display = "block";
|
||||
|
||||
@@ -422,7 +414,7 @@ function searchPlace() {
|
||||
if (q.length < 3) return;
|
||||
|
||||
const resultsDiv = document.getElementById("searchResults");
|
||||
resultsDiv.innerHTML = `<span style="color:#64748b; font-size:0.85rem;">${tr("search_in_progress")}</span>`;
|
||||
resultsDiv.innerHTML = `<span style="color:#64748b; font-size:0.85rem;">${tr("hdl_js_search_loading")}</span>`;
|
||||
|
||||
fetch(
|
||||
"/modules/holidays/includes/api/geocode.php?limit=5&q=" +
|
||||
@@ -432,7 +424,7 @@ function searchPlace() {
|
||||
.then((data) => {
|
||||
resultsDiv.innerHTML = "";
|
||||
if (data.error || !data.results || data.results.length === 0) {
|
||||
resultsDiv.innerHTML = `<span style="color:#ef4444; font-size:0.85rem;">${tr("no_result_found")}</span>`;
|
||||
resultsDiv.innerHTML = `<span style="color:#ef4444; font-size:0.85rem;">${tr("hdl_js_no_result")}</span>`;
|
||||
return;
|
||||
}
|
||||
data.results.forEach((place) => {
|
||||
@@ -449,7 +441,7 @@ function searchPlace() {
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
resultsDiv.innerHTML = `<span style="color:#ef4444; font-size:0.85rem;">${tr("network_error")}</span>`;
|
||||
resultsDiv.innerHTML = `<span style="color:#ef4444; font-size:0.85rem;">${tr("hdl_js_network_error")}</span>`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -478,23 +470,23 @@ function addCpExpenseLine(
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="hol-form-inner">
|
||||
<select name="items[cat][]" class="pf-input hol-form-select" title="${tr("category")}">
|
||||
<select name="items[cat][]" class="pf-input hol-form-select">
|
||||
<option value="accommodation" ${category === "accommodation" ? "selected" : ""}>🏨</option>
|
||||
<option value="transport" ${category === "transport" ? "selected" : ""}>🚗</option>
|
||||
<option value="activity" ${category === "activity" ? "selected" : ""}>🎫</option>
|
||||
</select>
|
||||
<input type="text" name="items[name][]" class="pf-input hol-form-text" placeholder="${tr("placeholder_label")}" value="${name}">
|
||||
<input type="text" name="items[name][]" class="pf-input hol-form-text" placeholder="${tr("hdl_js_ph_expense_name")}" value="${name}">
|
||||
<input type="number" step="0.01" name="items[amount][]" class="pf-input hol-form-number" placeholder="0.00" value="${amount}">
|
||||
|
||||
<label class="hol-form-paid-label" title="${tr("already_paid")}">
|
||||
<label class="hol-form-paid-label" title="${tr("hdl_paid")}">
|
||||
<input type="checkbox" ${isChecked} onchange="this.nextElementSibling.value = this.checked ? 1 : 0">
|
||||
<input type="hidden" name="items[paid][]" value="${isPaid}">
|
||||
<span class="hol-form-paid-text">${tr("paid")}</span>
|
||||
<span class="hol-form-paid-text">${tr("hdl_paid")}</span>
|
||||
</label>
|
||||
<button type="button" class="btn-remove-expense" onclick="this.parentElement.parentElement.remove()" title="${tr("btn_delete")}">×</button>
|
||||
</div>
|
||||
<div class="hol-form-subrow">
|
||||
<input type="text" name="items[notes][]" class="pf-input hol-form-notes-input hol-form-notes-full" placeholder="${tr("placeholder_notes_link")}" value="${notes}">
|
||||
<input type="text" name="items[notes][]" class="pf-input hol-form-notes-input hol-form-notes-full" placeholder="${tr("hdl_ph_notes")}" value="${notes}">
|
||||
</div>
|
||||
<input type="hidden" name="items[id][]" value="${itemId}">
|
||||
<input type="hidden" name="items[date][]" value="${itemDate}">
|
||||
@@ -505,7 +497,7 @@ function addCpExpenseLine(
|
||||
}
|
||||
|
||||
function deleteCheckpoint() {
|
||||
if (!confirm(tr("confirm_delete_step"))) return;
|
||||
if (!confirm(tr("hdl_js_confirm_del_step"))) return;
|
||||
const form = document.getElementById("formCheckpoint");
|
||||
const input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
@@ -592,16 +584,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// ============================================================================
|
||||
let selectedItemIdForMove = null;
|
||||
|
||||
function closePlanningModal() {
|
||||
document.getElementById("planningModal").style.display = "none";
|
||||
document.body.classList.remove("no-scroll");
|
||||
}
|
||||
|
||||
function openPlanningModal(step) {
|
||||
document.getElementById("planningModalTitle").innerText =
|
||||
tr("planning_of") + step.location_name;
|
||||
tr("hdl_planning_title") + " : " + step.location_name;
|
||||
const container = document.getElementById("planningContainer");
|
||||
selectedItemIdForMove = null;
|
||||
|
||||
let validItems = step.items.filter((it) => it.name !== "PF_TECHNICAL_POINT");
|
||||
|
||||
if (!step.step_start_date || !step.step_end_date) {
|
||||
container.innerHTML = `<div style="text-align:center; padding:40px;"><h3>${tr("missing_dates_title")}</h3><p style="color:#64748b;">${tr("missing_dates_msg")}</p></div>`;
|
||||
container.innerHTML = `<div style="text-align:center; padding:40px;"><h3>${tr("hdl_js_missing_dates_title")}</h3><p style="color:#64748b;">${tr("hdl_js_missing_dates_msg")}</p></div>`;
|
||||
document.getElementById("planningModal").style.display = "flex";
|
||||
return;
|
||||
}
|
||||
@@ -619,15 +616,13 @@ function openPlanningModal(step) {
|
||||
<div class="hol-unmapped-zone" id="unmapped-pool"
|
||||
ondragover="allowDrop(event)" ondrop="handleDropEvent(event, '', '')"
|
||||
onclick="handleZoneTap(event, '', '')">
|
||||
<div class="hol-unmapped-title" style="width:100%;">📥 ${tr("to_place")}</div>
|
||||
<div class="hol-unmapped-title" style="width:100%;">📥 ${tr("hdl_to_place")}</div>
|
||||
</div>
|
||||
<div class="hol-calendar-zone">
|
||||
`;
|
||||
|
||||
datesToDisplay.forEach((dateStr) => {
|
||||
const dObj = new Date(dateStr);
|
||||
|
||||
// NOUVEAU : On utilise la langue actuelle du navigateur pour traduire les jours !
|
||||
const dayName = dObj.toLocaleDateString(currentLang, { weekday: "short" });
|
||||
const dayNum = dObj.toLocaleDateString(currentLang, {
|
||||
day: "numeric",
|
||||
|
||||
@@ -3,32 +3,34 @@
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
|
||||
if ($id === 0) { echo "<div class='pf-section'><p><p>".tr('error')."</p></p></div>"; exit; }
|
||||
if ($id === 0) {
|
||||
echo "<div class='pf-section'><p>".tr('err_invalid_holiday_id')."</p></div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Récupération des données du voyage
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT h.*,
|
||||
(COALESCE(h.budget_food, 0) + COALESCE(h.budget_extra, 0) + COALESCE((SELECT SUM(amount) FROM pf_holidays_items WHERE holiday_id = h.id), 0)) as total_cost,
|
||||
(
|
||||
(SELECT COALESCE(SUM(ABS(amount)), 0) FROM pf_expenses WHERE holiday_id = h.id) +
|
||||
(SELECT COALESCE(SUM(amount), 0) FROM pf_holidays_items WHERE holiday_id = h.id AND is_paid = 1)
|
||||
) as total_paid,
|
||||
((SELECT COALESCE(SUM(ABS(amount)), 0) FROM pf_expenses WHERE holiday_id = h.id) + (SELECT COALESCE(SUM(amount), 0) FROM pf_holidays_items WHERE holiday_id = h.id AND is_paid = 1)) as total_paid,
|
||||
(SELECT COALESCE(SUM(amount), 0) FROM pf_savings WHERE holiday_id = h.id) as total_saved
|
||||
FROM pf_holidays h WHERE h.id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$holiday = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Récupération des items (étapes et frais généraux)
|
||||
$stmtItems = $pdo->prepare("SELECT * FROM pf_holidays_items WHERE holiday_id = ? ORDER BY sort_order ASC, id ASC");
|
||||
$stmtItems->execute([$id]);
|
||||
$items = $stmtItems->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Favoris pour le géocodage
|
||||
$stmtFav = $pdo->query("SELECT content FROM pf_notes WHERE note_type = 'holiday_favorites'");
|
||||
$favorites = json_decode($stmtFav->fetchColumn() ?: '[]', true);
|
||||
|
||||
$steps = [];
|
||||
$generalItems = [];
|
||||
|
||||
// C'est ici qu'il manquait sûrement une accolade !
|
||||
foreach ($items as $it) {
|
||||
if ($it['location_name'] !== null) {
|
||||
$orderKey = $it['sort_order'];
|
||||
@@ -54,6 +56,7 @@ foreach ($items as $it) {
|
||||
ksort($steps);
|
||||
$mapPoints = array_values($steps);
|
||||
|
||||
// Affichage de la date
|
||||
$dateDisplay = htmlspecialchars($holiday['period_hint'] ?? '');
|
||||
if (empty($dateDisplay) && $holiday['start_date']) {
|
||||
$dateDisplay = date('d/m/Y', strtotime($holiday['start_date']));
|
||||
@@ -78,7 +81,7 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<a href="?tab=list" class="pf-btn btn-secondary pf-btn-small" style="width: fit-content; text-decoration: none;"><?= tr('btn_back') ?></a>
|
||||
<div style="display: flex; align-items: center; gap: 15px;">
|
||||
<h1 style="margin: 0; font-size: 1.8rem;"><?= htmlspecialchars($holiday['title']) ?></h1>
|
||||
<span class="hol-badge-status"><?= strtoupper($holiday['status']) ?></span>
|
||||
<span class="hol-badge-status"><?= tr('hdl_status_' . $holiday['status']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -92,57 +95,56 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hol-summary-card" style="width: 100%; display: block; clear: both;">
|
||||
<div class="hol-summary-card">
|
||||
<div class="hol-summary-grid">
|
||||
<div class="hol-summary-item">
|
||||
<div class="hol-summary-label"><?= tr('period') ?></div>
|
||||
<div class="hol-summary-value"><?= $dateDisplay ?: tr('to_be_defined') ?></div>
|
||||
<div class="hol-summary-label"><?= tr('hdl_label_period') ?></div>
|
||||
<div class="hol-summary-value"><?= $dateDisplay ?: tr('hdl_dates_to_define') ?></div>
|
||||
</div>
|
||||
<div class="hol-summary-item">
|
||||
<div class="hol-summary-label"><?= tr('budget_food') ?></div>
|
||||
<div class="hol-summary-label"><?= tr('hdl_label_budget_food_extras') ?></div>
|
||||
<div class="hol-summary-value">🍔 <?= number_format($holiday['budget_food'], 0) ?> € | 🎁 <?= number_format($holiday['budget_extra'], 0) ?> €</div>
|
||||
</div>
|
||||
<div class="hol-summary-item">
|
||||
<div class="hol-summary-label"><?= tr('total_cost') ?></div>
|
||||
<div class="hol-summary-label"><?= tr('hdl_total_budget') ?></div>
|
||||
<div class="hol-summary-value total"><?= number_format($cost, 0, ',', ' ') ?> €</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hol-progress-bar">
|
||||
<div class="hol-progress-paid" style="width:<?= $pctPaid ?>%;" title="<?= tr('paid') ?>"></div>
|
||||
<div class="hol-progress-saved" style="width:<?= $pctSaved ?>%;" title="<?= tr('funded') ?>"></div>
|
||||
<div class="hol-progress-paid" style="width:<?= $pctPaid ?>%;" title="<?= tr('hdl_paid') ?>"></div>
|
||||
<div class="hol-progress-saved" style="width:<?= $pctSaved ?>%;" title="<?= tr('hdl_saved') ?>"></div>
|
||||
</div>
|
||||
<div class="hol-progress-labels">
|
||||
<span class="hol-label-paid">✓ <?= tr('paid') ?> : <?= number_format($paid, 0, ',', ' ') ?> €</span>
|
||||
<span class="hol-label-saved">💼 <?= tr('provisioned') ?> : <?= number_format($saved, 0, ',', ' ') ?> €</span>
|
||||
<span class="hol-label-left">⏳ <?= tr('left_to_pay') ?> : <?= number_format($leftToPay, 0, ',', ' ') ?> €</span>
|
||||
<span class="hol-label-paid">✓ <?= tr('hdl_paid') ?> : <?= number_format($paid, 0, ',', ' ') ?> €</span>
|
||||
<span class="hol-label-saved">💼 <?= tr('hdl_saved') ?> : <?= number_format($saved, 0, ',', ' ') ?> €</span>
|
||||
<span class="hol-label-left">⏳ <?= tr('hdl_left_to_pay') ?> : <?= number_format($leftToPay, 0, ',', ' ') ?> €</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hol-layout-grid">
|
||||
|
||||
<div class="hol-panel">
|
||||
<div class="hol-panel-header" style="flex-wrap: wrap; gap: 10px;">
|
||||
<div class="hol-panel-header-group">
|
||||
<h3 style="margin:0;"><?= tr('map_itinerary') ?></h3>
|
||||
<h3 style="margin:0;"><?= tr('hdl_map_itinerary') ?></h3>
|
||||
<div class="hol-map-legend">
|
||||
<span class="hol-legend-item" title="<?= tr('outbound_trip') ?>"><span class="hol-legend-color aller"></span> <?= tr('outbound_trip') ?></span>
|
||||
<span class="hol-legend-item" title="<?= tr('return_trip') ?>"><span class="hol-legend-color retour"></span> <?= tr('return_trip') ?></span>
|
||||
<span class="hol-legend-item" title="<?= tr('hdl_outbound') ?>"><span class="hol-legend-color aller"></span> <?= tr('hdl_outbound') ?></span>
|
||||
<span class="hol-legend-item" title="<?= tr('hdl_return') ?>"><span class="hol-legend-color retour"></span> <?= tr('hdl_return') ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="pf-btn pf-btn-small" onclick="openCheckpointModal('add')"><?= tr('place_step') ?></button>
|
||||
<button class="pf-btn pf-btn-small" onclick="openCheckpointModal('add')"><?= tr('hdl_btn_add_step') ?></button>
|
||||
</div>
|
||||
<div id="tripMap" style="flex:1; width:100%; background:#f1f5f9;"></div>
|
||||
</div>
|
||||
|
||||
<div class="hol-panel">
|
||||
<div class="hol-panel-header">
|
||||
<h3 style="margin:0;"><?= tr('step_details') ?></h3>
|
||||
<h3 style="margin:0;"><?= tr('hdl_step_details') ?></h3>
|
||||
</div>
|
||||
|
||||
<div class="hol-panel-body">
|
||||
<?php if (empty($steps)): ?>
|
||||
<p style="color:var(--text-muted); font-style:italic; text-align:center; margin-top:40px;"><?= tr('no_steps') ?></p>
|
||||
<p style="color:var(--text-muted); font-style:italic; text-align:center; margin-top:40px;"><?= tr('hdl_no_steps') ?></p>
|
||||
<?php else: ?>
|
||||
|
||||
<?php if (!empty($generalItems) || $holiday['budget_food'] > 0 || $holiday['budget_extra'] > 0): ?>
|
||||
@@ -150,7 +152,7 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<div class="hol-cp-header" style="background: transparent;">
|
||||
<div class="hol-cp-info-group">
|
||||
<span style="font-size:1.4rem;">🌍</span>
|
||||
<div class="hol-cp-title">Frais généraux & Budgets</div>
|
||||
<div class="hol-cp-title"><?= tr('hdl_general_costs') ?></div>
|
||||
</div>
|
||||
<?php
|
||||
$generalTotal = $holiday['budget_food'] + $holiday['budget_extra'];
|
||||
@@ -158,20 +160,20 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
?>
|
||||
<div class="hol-cp-actions-group">
|
||||
<div class="hol-cp-total"><?= number_format($generalTotal, 2, ',', ' ') ?> €</div>
|
||||
<button onclick='editHoliday(JSON.parse(document.getElementById("holidayDataJson").textContent))' class="btn-icon-small" title="<?= tr('edit') ?>">⚙️</button>
|
||||
<button onclick='editHoliday(JSON.parse(document.getElementById("holidayDataJson").textContent))' class="btn-icon-small" title="<?= tr('btn_edit') ?>">⚙️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hol-cp-body">
|
||||
<?php if ($holiday['budget_food'] > 0): ?>
|
||||
<div class="hol-expense-wrapper"><div class="hol-expense-main">
|
||||
<span class="hol-expense-name" style="color:#64748b;">🍔 Food & Bev</span>
|
||||
<span class="hol-expense-name" style="color:#64748b;">🍔 <?= tr('hdl_food_bev') ?></span>
|
||||
<span><strong class="hol-expense-amount"><?= number_format($holiday['budget_food'], 2, ',', ' ') ?> €</strong><span class="status-pending">⏳</span></span>
|
||||
</div></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($holiday['budget_extra'] > 0): ?>
|
||||
<div class="hol-expense-wrapper"><div class="hol-expense-main">
|
||||
<span class="hol-expense-name" style="color:#64748b;">🎁 Extras</span>
|
||||
<span class="hol-expense-name" style="color:#64748b;">🎁 <?= tr('hdl_extras') ?></span>
|
||||
<span><strong class="hol-expense-amount"><?= number_format($holiday['budget_extra'], 2, ',', ' ') ?> €</strong><span class="status-pending">⏳</span></span>
|
||||
</div></div>
|
||||
<?php endif; ?>
|
||||
@@ -190,6 +192,7 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($steps as $step): ?>
|
||||
<div id="step-card-<?= $step['sort_order'] ?>" class="hol-checkpoint hol-checkpoint-draggable" draggable="true" data-location="<?= htmlspecialchars($step['location_name']) ?>">
|
||||
<div class="hol-cp-header">
|
||||
@@ -198,19 +201,19 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<div class="hol-cp-title" onclick="panMapTo(<?= $step['lat'] ?>, <?= $step['lng'] ?>)" title="<?= htmlspecialchars($step['location_name']) ?>">
|
||||
📍 <?= htmlspecialchars($step['location_name']) ?>
|
||||
<?php if (!empty($step['is_return'])): ?>
|
||||
<span style="background: #fff7ed; color: #ea580c; padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: bold; margin-left: 5px; border: 1px solid #ffedd5; vertical-align: middle;">🏁 <?= tr('return_trip') ?></span>
|
||||
<span style="background: #fff7ed; color: #ea580c; padding: 2px 6px; border-radius: 4px; font-size: 0.7rem; font-weight: bold; margin-left: 5px; border: 1px solid #ffedd5; vertical-align: middle;">🏁 <?= tr('hdl_return') ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($step['step_start_date']) && !empty($step['step_end_date'])): ?>
|
||||
<div style="font-size:0.75rem; color:#64748b; font-weight:normal; margin-top:2px;">
|
||||
<?= tr('from_date') ?> <?= date('d/m', strtotime($step['step_start_date'])) ?> <?= tr('to_date') ?> <?= date('d/m', strtotime($step['step_end_date'])) ?>
|
||||
<?= tr('hdl_from') ?> <?= date('d/m', strtotime($step['step_start_date'])) ?> <?= tr('hdl_to') ?> <?= date('d/m', strtotime($step['step_end_date'])) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hol-cp-actions-group">
|
||||
<div class="hol-cp-total"><?= number_format($step['total_amount'], 2, ',', ' ') ?> €</div>
|
||||
<button onclick='openPlanningModal(<?= htmlspecialchars(json_encode($step), ENT_QUOTES, "UTF-8") ?>)' class="btn-icon-small" title="<?= tr('view_planning') ?>">📅</button>
|
||||
<button onclick='openCheckpointModal("edit", <?= htmlspecialchars(json_encode($step), ENT_QUOTES, "UTF-8") ?>)' class="btn-icon-small" title="<?= tr('edit') ?>">✏️</button>
|
||||
<button onclick='openPlanningModal(<?= htmlspecialchars(json_encode($step), ENT_QUOTES, "UTF-8") ?>)' class="btn-icon-small" title="<?= tr('hdl_view_planning') ?>">📅</button>
|
||||
<button onclick='openCheckpointModal("edit", <?= htmlspecialchars(json_encode($step), ENT_QUOTES, "UTF-8") ?>)' class="btn-icon-small" title="<?= tr('btn_edit') ?>">✏️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -227,14 +230,14 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<span class="hol-expense-name"><?= $icon ?> <?= htmlspecialchars($it['name']) ?></span>
|
||||
<span>
|
||||
<strong class="hol-expense-amount"><?= number_format($it['amount'], 2, ',', ' ') ?> €</strong>
|
||||
<span class="<?= $it['is_paid'] ? 'status-paid' : 'status-pending' ?>" title="<?= $it['is_paid'] ? tr('paid') : tr('to_pay') ?>"><?= $it['is_paid'] ? '✓' : '⏳' ?></span>
|
||||
<span class="<?= $it['is_paid'] ? 'status-paid' : 'status-pending' ?>" title="<?= $it['is_paid'] ? tr('hdl_paid') : tr('hdl_to_pay') ?>"><?= $it['is_paid'] ? '✓' : '⏳' ?></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ($visibleItemsCount === 0): ?>
|
||||
<div class="hol-empty-step">📍 <?= tr('passing_point') ?></div>
|
||||
<div class="hol-empty-step">📍 <?= tr('hdl_passing_point') ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,12 +245,14 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div> <?php if (!empty($holiday['notes'])): ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($holiday['notes'])): ?>
|
||||
<div class="hol-summary-card" style="margin-top: 24px; padding: 25px; border-left: 5px solid #f59e0b;">
|
||||
<h3 style="margin: 0 0 15px 0; font-size: 1.2rem; color: #0f172a; display: flex; align-items: center; gap: 8px;">
|
||||
<?= tr('travel_notes') ?>
|
||||
📝 <?= tr('hdl_label_notes') ?>
|
||||
</h3>
|
||||
<div style="font-size: 0.95rem; color: #334155; white-space: pre-wrap; line-height: 1.6; font-family: 'Segoe UI', system-ui, sans-serif;">
|
||||
<div style="font-size: 0.95rem; color: #334155; white-space: pre-wrap; line-height: 1.6;">
|
||||
<?= htmlspecialchars($holiday['notes']) ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,30 +260,30 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="checkpointModal" class="pf-modal">
|
||||
<div class="pf-modal-content" style="max-width: 600px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 id="cpModalTitle" style="margin:0;">📍 <?= tr('place_step') ?></h3>
|
||||
<button type="button" onclick="document.getElementById('checkpointModal').style.display='none'; document.body.classList.remove('no-scroll');" style="border:none; background:none; font-size:1.8rem; cursor:pointer;">×</button> </div>
|
||||
<h3 id="cpModalTitle" style="margin:0;">📍 <?= tr('hdl_btn_add_step') ?></h3>
|
||||
<button type="button" onclick="closeCheckpointModal()" class="pf-modal-close">×</button>
|
||||
</div>
|
||||
|
||||
<div id="cpSearchBlock" style="margin-bottom:20px;">
|
||||
<?php if (!empty($favorites)): ?>
|
||||
<div style="margin-bottom:15px; display:flex; gap:8px; flex-wrap:wrap;">
|
||||
<?php foreach($favorites as $fav): ?>
|
||||
<button type="button" class="pf-btn btn-secondary" style="padding:4px 10px; font-size:0.8rem; height:auto; width:auto; border-radius:20px;" onclick="selectPlace(<?= $fav['lat'] ?>, <?= $fav['lng'] ?>, '<?= htmlspecialchars(addslashes($fav['name'])) ?>')">
|
||||
<button type="button" class="pf-btn btn-secondary" style="padding:4px 10px; font-size:0.8rem; border-radius:20px;" onclick="selectPlace(<?= $fav['lat'] ?>, <?= $fav['lng'] ?>, '<?= htmlspecialchars(addslashes($fav['name'])) ?>')">
|
||||
⭐ <?= htmlspecialchars($fav['name']) ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<label class="pf-label"><?= tr('search_location') ?></label>
|
||||
<label class="pf-label"><?= tr('hdl_search_location') ?></label>
|
||||
<div style="display:flex; gap:10px;">
|
||||
<input type="text" id="searchPlaceInput" class="pf-input" placeholder="<?= tr('search_placeholder') ?>" onkeypress="if(event.key === 'Enter') { searchPlace(); return false; }">
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="searchPlace()" style="width:auto; margin:0;">🔍</button>
|
||||
<input type="text" id="searchPlaceInput" class="pf-input" placeholder="<?= tr('hdl_ph_search') ?>" onkeypress="if(event.key === 'Enter') { searchPlace(); return false; }">
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="searchPlace()">🔍</button>
|
||||
</div>
|
||||
<div id="searchResults" style="margin-top:10px; max-height:200px; overflow-y:auto; display:flex; flex-direction:column; gap:5px;"></div>
|
||||
<div id="searchResults" style="margin-top:10px; max-height:200px; overflow-y:auto;"></div>
|
||||
</div>
|
||||
|
||||
<form action="/modules/holidays/includes/api/save_checkpoint.php" method="POST" id="formCheckpoint" style="display:none; border-top:1px solid #e2e8f0; padding-top:20px;">
|
||||
@@ -289,49 +294,46 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<input type="hidden" name="old_sort_order" id="cp_old_sort_order">
|
||||
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label class="pf-label"><?= tr('step_name') ?></label>
|
||||
<label class="pf-label"><?= tr('hdl_label_step_name') ?></label>
|
||||
<input type="text" name="location_name" id="cp_name" class="pf-input" style="font-weight:bold; color:var(--primary);" required>
|
||||
</div>
|
||||
<div style="display:flex; gap:15px; margin-bottom:15px; background:#f8fafc; padding:12px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
<div class="form-group" style="flex:1; margin:0;">
|
||||
<label class="pf-label"><?= tr('arrival_optional') ?></label>
|
||||
|
||||
<div style="display:flex; gap:15px; margin-bottom:15px; background:#f8fafc; padding:12px; border-radius:8px;">
|
||||
<div class="form-group" style="flex:1;">
|
||||
<label class="pf-label"><?= tr('hdl_label_arrival') ?></label>
|
||||
<input type="date" name="step_start_date" id="cp_start_date" class="pf-input">
|
||||
</div>
|
||||
<div class="form-group" style="flex:1; margin:0;">
|
||||
<label class="pf-label"><?= tr('departure_optional') ?></label>
|
||||
<div class="form-group" style="flex:1;">
|
||||
<label class="pf-label"><?= tr('hdl_label_departure') ?></label>
|
||||
<input type="date" name="step_end_date" id="cp_end_date" class="pf-input">
|
||||
</div>
|
||||
<div style="margin-bottom: 15px; padding-left: 5px;">
|
||||
<label style="display:flex; align-items:center; cursor:pointer; font-size:0.9rem; color:#d97706; font-weight:600;">
|
||||
<input type="checkbox" name="is_return" id="cp_is_return" value="1" style="margin-right:8px; width:16px; height:16px;">
|
||||
🏁 <?= tr('return_trip') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label style="display:flex; align-items:center; cursor:pointer; color:#ea580c; font-weight:600;">
|
||||
<input type="checkbox" name="is_return" id="cp_is_return" value="1" style="margin-right:8px;">
|
||||
🏁 <?= tr('hdl_return') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
|
||||
<label class="pf-label" style="margin:0;"><?= tr('planned_expenses') ?></label>
|
||||
<button type="button" class="pf-btn btn-secondary" onclick="addCpExpenseLine()" style="padding:4px 8px; font-size:0.8rem; height:auto; width:auto; margin:0;"><?= tr('add_expense') ?></button>
|
||||
<label class="pf-label" style="margin:0;"><?= tr('hdl_planned_expenses') ?></label>
|
||||
<button type="button" class="pf-btn btn-secondary pf-btn-small" onclick="addCpExpenseLine()"><?= tr('hdl_btn_add_expense') ?></button>
|
||||
</div>
|
||||
|
||||
<div id="cpExpensesContainer" style="margin-bottom:15px; display:flex; flex-direction:column; gap:10px; max-height:350px; overflow-y:auto; overflow-x:hidden;">
|
||||
</div>
|
||||
<div id="cpExpensesContainer" style="margin-bottom:15px;"></div>
|
||||
|
||||
<div style="margin-bottom: 20px; padding-left: 5px;">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<label style="display:flex; align-items:center; cursor:pointer; font-size:0.85rem; color:#475569;">
|
||||
<input type="checkbox" name="save_favorite" value="1" style="margin-right:8px;">
|
||||
⭐ <?= tr('save_favorite') ?>
|
||||
⭐ <?= tr('hdl_save_fav') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; border-top:1px solid #e2e8f0; padding-top:15px;">
|
||||
<div>
|
||||
<button type="button" onclick="deleteCheckpoint()" id="btnDeleteCp" class="pf-btn btn-secondary" style="color:#ef4444; border-color:#fca5a5; display:none; width:auto; margin:0;">🗑️ <?= tr('btn_delete') ?></button>
|
||||
</div>
|
||||
<div style="display:flex; gap:10px;">
|
||||
<button type="button" onclick="document.getElementById('checkpointModal').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>
|
||||
<div class="modal-footer" style="padding-top:15px; border-top:1px solid #e2e8f0;">
|
||||
<button type="button" onclick="deleteCheckpoint()" id="btnDeleteCp" class="pf-btn btn-secondary" style="color:#ef4444; border-color:#fca5a5; display:none;"><?= tr('btn_delete') ?></button>
|
||||
<button type="button" onclick="closeCheckpointModal()" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -340,46 +342,29 @@ $pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
<div id="planningModal" class="pf-modal">
|
||||
<div class="pf-modal-content" style="max-width: 550px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h3 id="planningModalTitle" style="margin:0; color:var(--primary);">📅 <?= tr('planning_title') ?></h3>
|
||||
<button type="button" onclick="document.getElementById('planningModal').style.display='none'; document.body.classList.remove('no-scroll');" style="border:none; background:none; font-size:1.8rem; cursor:pointer;">×</button> </div>
|
||||
|
||||
<div id="planningContainer" style="width: 100%;">
|
||||
|
||||
<div style="margin-top:20px; display:flex; justify-content:flex-end;">
|
||||
<button type="button" onclick="document.getElementById('planningModal').style.display='none'; document.body.classList.remove('no-scroll');" class="pf-btn btn-secondary"><?= tr('btn_close') ?></button>
|
||||
</div>
|
||||
<h3 id="planningModalTitle" style="margin:0; color:var(--primary);">📅 <?= tr('hdl_planning_title') ?></h3>
|
||||
<button type="button" onclick="closePlanningModal()" class="pf-modal-close">×</button>
|
||||
</div>
|
||||
<div id="planningContainer" style="width: 100%;"></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="closePlanningModal()" class="pf-btn btn-secondary"><?= tr('btn_close') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include __DIR__ . '/modal.php'; ?>
|
||||
|
||||
<script>
|
||||
const MAP_POINTS = <?= json_encode($mapPoints) ?>;
|
||||
|
||||
// Le pont i18n pour Javascript
|
||||
window.I18N = <?= json_encode([
|
||||
'modal_plan_trip' => tr('modal_plan_trip'),
|
||||
'modal_quick_edit' => tr('modal_quick_edit'),
|
||||
'err_modal_missing' => tr('err_modal_missing'),
|
||||
'placeholder_title' => tr('placeholder_title'),
|
||||
'placeholder_price' => tr('placeholder_price'),
|
||||
'already_paid' => tr('already_paid'),
|
||||
'paid' => tr('paid'), // Clé déjà existante
|
||||
'remove_line' => tr('remove_line'),
|
||||
'confirm_delete_trip' => tr('confirm_delete_trip'),
|
||||
'step_number' => tr('step_number'),
|
||||
'place_new_step' => tr('place_new_step'),
|
||||
'edit_step' => tr('edit_step'),
|
||||
'search_in_progress' => tr('search_in_progress'),
|
||||
'no_result_found' => tr('no_result_found'),
|
||||
'network_error' => tr('network_error'),
|
||||
'placeholder_label' => tr('placeholder_label'),
|
||||
'btn_delete' => tr('btn_delete'), // Clé déjà existante
|
||||
'placeholder_notes_link' => tr('placeholder_notes_link'),
|
||||
'confirm_delete_step' => tr('confirm_delete_step'),
|
||||
'planning_of' => tr('planning_of'),
|
||||
'missing_dates_title' => tr('missing_dates_title'),
|
||||
'missing_dates_msg' => tr('missing_dates_msg'),
|
||||
'to_place' => tr('to_place')
|
||||
]) ?>;
|
||||
// Le pont i18n pour Javascript (Holidays specific)
|
||||
window.I18N = {
|
||||
...window.I18N,
|
||||
'hdl_js_search_loading': "<?= tr('hdl_js_search_loading') ?>",
|
||||
'hdl_js_no_result': "<?= tr('hdl_js_no_result') ?>",
|
||||
'hdl_js_confirm_del_trip': "<?= tr('hdl_js_confirm_del_trip') ?>",
|
||||
'hdl_js_confirm_del_step': "<?= tr('hdl_js_confirm_del_step') ?>",
|
||||
'hdl_js_step_label': "<?= tr('hdl_js_step_label') ?>",
|
||||
'hdl_js_ph_expense_name': "<?= tr('hdl_js_ph_expense_name') ?>",
|
||||
'hdl_js_ph_amount': "0.00",
|
||||
'hdl_js_delete_line': "<?= tr('btn_delete') ?>"
|
||||
};
|
||||
</script>
|
||||
@@ -4,19 +4,16 @@
|
||||
// 1. Gestion du filtrage par année
|
||||
$selectedYear = $_GET['y'] ?? date('Y');
|
||||
|
||||
// Récupérer les années disponibles dans la BDD pour le menu déroulant
|
||||
$yearStmt = $pdo->query("SELECT DISTINCT YEAR(start_date) as yr FROM pf_holidays WHERE start_date IS NOT NULL ORDER BY yr DESC");
|
||||
$availableYears = $yearStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
if (!in_array(date('Y'), $availableYears)) {
|
||||
$availableYears[] = date('Y');
|
||||
rsort($availableYears); // Trie par ordre décroissant
|
||||
rsort($availableYears);
|
||||
}
|
||||
|
||||
// Construction de la condition de filtrage SQL
|
||||
$whereSQL = "";
|
||||
$params = [];
|
||||
if ($selectedYear !== 'all') {
|
||||
// On affiche les voyages de l'année OU les voyages sans date (pour ne pas perdre les brouillons)
|
||||
$whereSQL = "WHERE YEAR(h.start_date) = ? OR h.start_date IS NULL";
|
||||
$params[] = $selectedYear;
|
||||
}
|
||||
@@ -24,69 +21,53 @@ if ($selectedYear !== 'all') {
|
||||
// 2. Récupération des voyages + Calculs
|
||||
$sql = "
|
||||
SELECT h.*,
|
||||
(
|
||||
COALESCE(h.budget_food, 0) +
|
||||
COALESCE(h.budget_extra, 0) +
|
||||
COALESCE((SELECT SUM(amount) FROM pf_holidays_items WHERE holiday_id = h.id), 0)
|
||||
) as total_cost,
|
||||
(
|
||||
(SELECT COALESCE(SUM(ABS(amount)), 0) FROM pf_expenses WHERE holiday_id = h.id) +
|
||||
(SELECT COALESCE(SUM(amount), 0) FROM pf_holidays_items WHERE holiday_id = h.id AND is_paid = 1)
|
||||
) as total_paid,
|
||||
(COALESCE(h.budget_food, 0) + COALESCE(h.budget_extra, 0) + COALESCE((SELECT SUM(amount) FROM pf_holidays_items WHERE holiday_id = h.id), 0)) as total_cost,
|
||||
((SELECT COALESCE(SUM(ABS(amount)), 0) FROM pf_expenses WHERE holiday_id = h.id) + (SELECT COALESCE(SUM(amount), 0) FROM pf_holidays_items WHERE holiday_id = h.id AND is_paid = 1)) as total_paid,
|
||||
(SELECT COALESCE(SUM(amount), 0) FROM pf_savings WHERE holiday_id = h.id) as total_saved
|
||||
FROM pf_holidays h
|
||||
$whereSQL
|
||||
ORDER BY
|
||||
COALESCE(start_date, '2999-12-31') ASC,
|
||||
FIELD(status, 'booked', 'planned', 'draft', 'passed', 'archived')
|
||||
ORDER BY COALESCE(start_date, '2999-12-31') ASC, FIELD(status, 'booked', 'planned', 'draft', 'passed', 'archived')
|
||||
";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 3. Tri par statut
|
||||
$active = array_filter($holidays, fn($h) => in_array($h['status'], ['draft', 'planned', 'booked']));
|
||||
$history = array_filter($holidays, fn($h) => in_array($h['status'], ['passed', 'archived']));
|
||||
|
||||
// 4. Calcul du Reste à Payer Global (uniquement sur les voyages Actifs affichés)
|
||||
$globalLeftToPay = 0;
|
||||
foreach ($active as $h) {
|
||||
$cost = (float)$h['total_cost'];
|
||||
$paid = (float)$h['total_paid'];
|
||||
$globalLeftToPay += max(0, $cost - $paid);
|
||||
$globalLeftToPay += max(0, (float)$h['total_cost'] - (float)$h['total_paid']);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="pf-holidays">
|
||||
|
||||
<div class="pf-holidays__titlebar" style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:15px; margin-bottom:30px;">
|
||||
|
||||
<div style="display:flex; align-items:center; gap:15px; flex-wrap:wrap;">
|
||||
<h1 style="margin:0;">Mes Vacances ✈️</h1>
|
||||
<h1 style="margin:0;"><?= tr('hdl_main_title') ?> ✈️</h1>
|
||||
|
||||
<select onchange="window.location.href='?tab=list&y='+this.value" class="pf-input" style="width:auto; padding:6px 12px; font-weight:bold; cursor:pointer; border-radius:8px; border:1px solid #cbd5e1; margin:0;">
|
||||
<option value="all" <?= $selectedYear === 'all' ? 'selected' : '' ?>>Tout afficher</option>
|
||||
<select onchange="window.location.href='?tab=list&y='+this.value" class="pf-input" style="width:auto; padding:6px 12px; font-weight:bold; cursor:pointer;">
|
||||
<option value="all" <?= $selectedYear === 'all' ? 'selected' : '' ?>><?= tr('hdl_filter_all') ?></option>
|
||||
<?php foreach($availableYears as $yr): ?>
|
||||
<option value="<?= $yr ?>" <?= (string)$selectedYear === (string)$yr ? 'selected' : '' ?>>Année <?= $yr ?></option>
|
||||
<option value="<?= $yr ?>" <?= (string)$selectedYear === (string)$yr ? 'selected' : '' ?>><?= tr('hdl_filter_year') ?> <?= $yr ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<div style="background:#fff1f2; color:#be123c; padding:6px 12px; border-radius:8px; font-weight:bold; border:1px solid #fecdd3; font-size:0.9rem; display:flex; align-items:center; gap:8px;">
|
||||
<span>⏳ Reste à payer (<?= $selectedYear === 'all' ? 'Global' : $selectedYear ?>) :</span>
|
||||
<span>⏳ <?= tr('hdl_left_to_pay_label') ?> (<?= $selectedYear === 'all' ? tr('hdl_filter_all') : $selectedYear ?>) :</span>
|
||||
<span style="font-size:1rem;"><?= number_format($globalLeftToPay, 0, ',', ' ') ?> €</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="pf-btn" onclick="openHolidayModal('add')" style="margin:0;">+ Créer un voyage</button>
|
||||
<button class="pf-btn" onclick="openHolidayModal('add')" style="margin:0;">+ <?= tr('hdl_btn_create') ?></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<section class="pf-section">
|
||||
<div class="hol-ideas-grid">
|
||||
<?php if (empty($active)): ?>
|
||||
<p style="color:var(--text-muted); font-style:italic;">Aucun voyage en cours pour cette période. Planifions quelque chose !</p>
|
||||
<p style="color:var(--text-muted); font-style:italic;"><?= tr('hdl_no_active_trips') ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($active as $h): ?>
|
||||
@@ -97,7 +78,7 @@ foreach ($active as $h) {
|
||||
|
||||
<?php if (!empty($history)): ?>
|
||||
<section class="pf-section" style="margin-top: 40px; border-top: 1px solid #e2e8f0; padding-top: 20px;">
|
||||
<h3 style="color:var(--text-muted);">Historique</h3>
|
||||
<h3 style="color:var(--text-muted);"><?= tr('hdl_history_title') ?></h3>
|
||||
<div class="hol-ideas-grid" style="opacity: 0.7;">
|
||||
<?php foreach ($history as $h): ?>
|
||||
<?php renderHolidayCard($h, $pdo); ?>
|
||||
@@ -105,13 +86,11 @@ foreach ($active as $h) {
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<?php include __DIR__ . '/modal.php'; ?>
|
||||
|
||||
<?php
|
||||
// FONCTION D'AFFICHAGE DE LA CARTE (Ne pas modifier, elle est déjà optimale)
|
||||
function renderHolidayCard($h, $pdo) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM pf_holidays_items WHERE holiday_id = ?");
|
||||
$stmt->execute([$h['id']]);
|
||||
@@ -135,58 +114,55 @@ function renderHolidayCard($h, $pdo) {
|
||||
$cost = (float)$h['total_cost'];
|
||||
$paid = (float)$h['total_paid'];
|
||||
$saved = (float)$h['total_saved'];
|
||||
|
||||
$leftToPay = max(0, $cost - $paid);
|
||||
|
||||
$pctPaid = $cost > 0 ? min(100, ($paid / $cost) * 100) : 0;
|
||||
$pctSaved = $cost > 0 ? min(100 - $pctPaid, ($saved / $cost) * 100) : 0;
|
||||
|
||||
// Traduction dynamique du statut
|
||||
$statusLabel = tr('hdl_status_' . $h['status']);
|
||||
|
||||
echo "
|
||||
<div class='hol-idea-card' style='display: flex; flex-direction: column;'>
|
||||
<div class='hol-idea-card__head' style='display:flex; justify-content:space-between; align-items:flex-start; margin-bottom: 10px;'>
|
||||
|
||||
<div style='flex:1;'>
|
||||
<h3 style='margin:0; font-size:1.15rem; color:#0f172a;'>
|
||||
<a href='?tab=holiday_detail&id={$h['id']}' style='text-decoration:none; color:inherit;' title='Ouvrir la page du voyage'>
|
||||
<a href='?tab=holiday_detail&id={$h['id']}' style='text-decoration:none; color:inherit;'>
|
||||
".htmlspecialchars($h['title'])."
|
||||
</a>
|
||||
</h3>
|
||||
<span style='font-size:0.7rem; padding:3px 8px; border-radius:12px; font-weight:bold; display:inline-block; margin-top:6px;' class='$statusClass'>
|
||||
".strtoupper($h['status'])."
|
||||
".strtoupper($statusLabel)."
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style='display:flex; gap:5px;'>
|
||||
<button onclick='editHoliday($json)' class='pf-btn btn-secondary' style='padding:6px; height:auto; width:auto; line-height:1;' title='Modification rapide'>✏️</button>
|
||||
<a href='?tab=holiday_detail&id={$h['id']}' class='pf-btn' style='padding:6px; height:auto; width:auto; line-height:1; text-decoration:none;' title='Gérer le voyage'>👁️</a>
|
||||
<button onclick='editHoliday($json)' class='pf-btn btn-secondary' style='padding:6px;'>✏️</button>
|
||||
<a href='?tab=holiday_detail&id={$h['id']}' class='pf-btn' style='padding:6px; text-decoration:none;'>👁️</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class='hol-idea-meta' style='margin-bottom:15px;'>
|
||||
<span>🗓️ ".($dateDisplay ?: 'Dates à définir')."</span>
|
||||
<span>🗓️ ".($dateDisplay ?: tr('hdl_dates_to_define'))."</span>
|
||||
</div>
|
||||
|
||||
<div style='margin-top:auto; padding-top:15px; border-top:1px solid #f1f5f9;'>
|
||||
|
||||
<div style='display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;'>
|
||||
<span style='font-size:0.85rem; color:#64748b;'>Budget Total</span>
|
||||
<span style='font-size:0.85rem; color:#64748b;'>".tr('hdl_total_budget')."</span>
|
||||
<span style='font-size:1.1rem; font-weight:bold; color:#1e293b;'>".number_format($cost, 0, ',', ' ')." €</span>
|
||||
</div>
|
||||
|
||||
<div style='width:100%; height:8px; background:#e2e8f0; border-radius:4px; margin-bottom:12px; display:flex; overflow:hidden;'>
|
||||
<div style='width:{$pctPaid}%; background:#10b981; transition:width 0.3s ease;' title='Payé'></div>
|
||||
<div style='width:{$pctSaved}%; background:#3b82f6; transition:width 0.3s ease;' title='Financé (Provision)'></div>
|
||||
<div style='width:{$pctPaid}%; background:#10b981; transition:width 0.3s ease;'></div>
|
||||
<div style='width:{$pctSaved}%; background:#3b82f6; transition:width 0.3s ease;'></div>
|
||||
</div>
|
||||
|
||||
<div style='display:grid; grid-template-columns: 1fr 1fr; gap:8px; font-size:0.8rem;'>
|
||||
<div style='color:#10b981; font-weight:600;' title='Montant déjà dépensé'>✓ Payé : ".number_format($paid, 0, ',', ' ')." €</div>
|
||||
<div style='color:#3b82f6; font-weight:600; text-align:right;' title='Montant épargné non dépensé'>💼 Financé : ".number_format($saved, 0, ',', ' ')." €</div>
|
||||
<div style='color:#10b981; font-weight:600;'>✓ ".tr('hdl_paid')." : ".number_format($paid, 0, ',', ' ')." €</div>
|
||||
<div style='color:#3b82f6; font-weight:600; text-align:right;'>💼 ".tr('hdl_saved')." : ".number_format($saved, 0, ',', ' ')." €</div>
|
||||
<div style='color:#ef4444; font-weight:700; font-size:0.85rem; grid-column: span 2; padding-top: 4px; border-top: 1px dashed #fca5a5;'>
|
||||
⏳ Reste à payer : ".number_format($leftToPay, 0, ',', ' ')." €
|
||||
⏳ ".tr('hdl_left_to_pay')." : ".number_format($leftToPay, 0, ',', ' ')." €
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div id="holidayModal" class="pf-modal">
|
||||
<div class="pf-modal-content" style="max-width: 800px; width: 95%;">
|
||||
<h3 id="modalTitle" class="pf-modal-title">Planifier le voyage</h3>
|
||||
<h3 id="modalTitle" class="pf-modal-title"><?= tr('hdl_modal_title') ?></h3>
|
||||
|
||||
<form action="/modules/holidays/includes/api/save_holiday.php" method="POST" id="holidayForm">
|
||||
<input type="hidden" name="id" id="inp_id">
|
||||
@@ -8,34 +8,34 @@
|
||||
|
||||
<div class="form-row">
|
||||
<div style="flex: 2;">
|
||||
<label class="pf-label">Nom du voyage</label>
|
||||
<input type="text" name="title" id="inp_title" class="pf-input" placeholder="Ex: Octobre - Portugal" required>
|
||||
<label class="pf-label"><?= tr('hdl_label_name') ?></label>
|
||||
<input type="text" name="title" id="inp_title" class="pf-input" placeholder="<?= tr('hdl_ph_name') ?>" required>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<label class="pf-label">Statut</label>
|
||||
<label class="pf-label"><?= tr('hdl_label_status') ?></label>
|
||||
<select name="status" id="inp_status" class="pf-input">
|
||||
<option value="draft">Brouillon ✏️</option>
|
||||
<option value="planned">Planifié 📅</option>
|
||||
<option value="booked">Réservé ✅</option>
|
||||
<option value="passed">Passé 👋</option>
|
||||
<option value="archived">Archivé 🗄️</option>
|
||||
<option value="draft"><?= tr('hdl_status_draft') ?> ✏️</option>
|
||||
<option value="planned"><?= tr('hdl_status_planned') ?> 📅</option>
|
||||
<option value="booked"><?= tr('hdl_status_booked') ?> ✅</option>
|
||||
<option value="passed"><?= tr('hdl_status_passed') ?> 👋</option>
|
||||
<option value="archived"><?= tr('hdl_status_archived') ?> 🗄️</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div>
|
||||
<label class="pf-label">Période (Texte libre)</label>
|
||||
<input type="text" name="period_hint" id="inp_period" class="pf-input" placeholder="Ex: Octobre 2026">
|
||||
<label class="pf-label"><?= tr('hdl_label_period') ?></label>
|
||||
<input type="text" name="period_hint" id="inp_period" class="pf-input" placeholder="<?= tr('hdl_ph_period') ?>">
|
||||
</div>
|
||||
<div style="display:flex; gap:10px;">
|
||||
<div style="flex:1">
|
||||
<label class="pf-label">Du</label>
|
||||
<input type="date" name="start_date" id="inp_start" class="pf-input" lang="fr">
|
||||
<label class="pf-label"><?= tr('hdl_label_from') ?></label>
|
||||
<input type="date" name="start_date" id="inp_start" class="pf-input">
|
||||
</div>
|
||||
<div style="flex:1">
|
||||
<label class="pf-label">Au</label>
|
||||
<input type="date" name="end_date" id="inp_end" class="pf-input" lang="fr">
|
||||
<label class="pf-label"><?= tr('hdl_label_to') ?></label>
|
||||
<input type="date" name="end_date" id="inp_end" class="pf-input">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,24 +45,24 @@
|
||||
<div class="hol-columns-wrapper">
|
||||
<div class="hol-col">
|
||||
<div class="hol-col-header">
|
||||
<h4 style="color:#2563eb;">🚗 Transport</h4>
|
||||
<button type="button" class="btn-add-item" onclick="addItem('transport')" title="Ajouter un transport">+</button>
|
||||
<h4 style="color:#2563eb;">🚗 <?= tr('hdl_cat_transport') ?></h4>
|
||||
<button type="button" class="btn-add-item" onclick="addItem('transport')" title="<?= tr('hdl_add_transport') ?>">+</button>
|
||||
</div>
|
||||
<div id="list_transport" class="dynamic-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="hol-col">
|
||||
<div class="hol-col-header">
|
||||
<h4 style="color:#059669;">🏨 Hébergement</h4>
|
||||
<button type="button" class="btn-add-item" onclick="addItem('accommodation')" title="Ajouter un hébergement">+</button>
|
||||
<h4 style="color:#059669;">🏨 <?= tr('hdl_cat_accommodation') ?></h4>
|
||||
<button type="button" class="btn-add-item" onclick="addItem('accommodation')" title="<?= tr('hdl_add_accommodation') ?>">+</button>
|
||||
</div>
|
||||
<div id="list_accommodation" class="dynamic-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="hol-col">
|
||||
<div class="hol-col-header">
|
||||
<h4 style="color:#d97706;">🎫 Activité</h4>
|
||||
<button type="button" class="btn-add-item" onclick="addItem('activity')" title="Ajouter une activité">+</button>
|
||||
<h4 style="color:#d97706;">🎫 <?= tr('hdl_cat_activity') ?></h4>
|
||||
<button type="button" class="btn-add-item" onclick="addItem('activity')" title="<?= tr('hdl_add_activity') ?>">+</button>
|
||||
</div>
|
||||
<div id="list_activity" class="dynamic-list"></div>
|
||||
</div>
|
||||
@@ -72,24 +72,24 @@
|
||||
|
||||
<div class="form-row">
|
||||
<div>
|
||||
<label class="pf-label">🍔 Budget Food & Bev (€)</label>
|
||||
<label class="pf-label">🍔 <?= tr('hdl_label_budget_food') ?></label>
|
||||
<input type="number" step="0.01" name="budget_food" id="inp_food" class="pf-input" placeholder="0.00">
|
||||
</div>
|
||||
<div>
|
||||
<label class="pf-label">🎁 Budget Extras (€)</label>
|
||||
<label class="pf-label">🎁 <?= tr('hdl_label_budget_extras') ?></label>
|
||||
<input type="number" step="0.01" name="budget_extra" id="inp_extra" class="pf-input" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="pf-label">Notes</label>
|
||||
<textarea name="notes" id="inp_notes" class="pf-input" rows="2" placeholder="Idées en vrac..."></textarea>
|
||||
<label class="pf-label"><?= tr('hdl_label_notes') ?></label>
|
||||
<textarea name="notes" id="inp_notes" class="pf-input" rows="2" placeholder="<?= tr('hdl_ph_notes') ?>"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="deleteHoliday()" id="btn_delete" class="pf-btn btn-secondary" style="color:#ef4444; border-color:#fca5a5; margin-right:auto; display:none;">Supprimer</button>
|
||||
<button type="button" onclick="closeHolidayModal()" class="pf-btn btn-secondary">Annuler</button>
|
||||
<button type="submit" class="pf-btn">Enregistrer</button>
|
||||
<button type="button" onclick="deleteHoliday()" id="btn_delete" class="pf-btn btn-secondary" style="color:#ef4444; border-color:#fca5a5; margin-right:auto; display:none;"><?= tr('btn_delete') ?></button>
|
||||
<button type="button" onclick="closeHolidayModal()" class="pf-btn btn-secondary"><?= tr('btn_cancel') ?></button>
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user