V7.9 — Tableau de bord et navigation regroupée pour le Service Enfance
Le Service Enfance ouvrait onze onglets à plat et atterrissait sur l'écran de saisie, alors que son travail commence par le contrôle. Les chiffres qui déclenchent une action vivaient sur trois écrans distincts. - Nouvelle page dashboard.php, réservée au Service Enfance et devenue sa page d'atterrissage : quatre tuiles d'action, les brouillons les plus anciens avec ouverture directe sur la bonne semaine, les plages de couverture les plus déficitaires, et le suivi des quotas annuels par agent. - Navigation regroupée par intention (Piloter / Planifier / Consulter / Paramétrer). Les rôles à quelques pages gardent la barre simple. - Sous-titre propre à chaque page : le bandeau générique n'apprenait rien. - Pastille « À valider » alimentée depuis toutes les pages, et non plus seulement depuis la page qu'elle annonce. Aucune règle métier n'est modifiée : le tableau de bord relit ce que PlanningModel, CoverageModel et QuotaModel calculent déjà, et son endpoint est gardé par requireService(). Le droit d'accès à la page vient de la migration V7.9 ; sans elle, l'onglet n'apparaît pas et homeUrl() se replie sur planning. Vérifié sur la base réelle : les chiffres de quota du tableau de bord sont identiques à ceux de api/annual_overview.php, les trois rôles rendent leurs écrans sans erreur, le rendu JS a été exécuté dans un DOM avec la réponse réelle de l'API, et la page ne produit aucun attribut style inline (CSP). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,10 +28,10 @@ window.PTA_PAGE_CONFIG = <?= json_encode([
|
||||
'params' => $pageConfig,
|
||||
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) ?>;
|
||||
</script>
|
||||
<script src="assets/js/core.js?v=7.8"></script>
|
||||
<script src="assets/js/core.js?v=7.9"></script>
|
||||
<?php foreach ($scripts as $script): ?>
|
||||
<script src="assets/js/<?= htmlspecialchars($script) ?>?v=7.8"></script>
|
||||
<script src="assets/js/<?= htmlspecialchars($script) ?>?v=7.9"></script>
|
||||
<?php endforeach; ?>
|
||||
<script src="assets/js/page-bootstrap.js?v=7.8"></script>
|
||||
<script src="assets/js/page-bootstrap.js?v=7.9"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,22 +1,65 @@
|
||||
<?php
|
||||
/** @var string $pageTitle */
|
||||
/** @var string $pageSubtitle */
|
||||
/** @var string $activePage */
|
||||
/** @var array $allowedPages */
|
||||
/** @var array $accessProfile */
|
||||
/** @var string $csrfToken */
|
||||
|
||||
$isAgent = ($accessProfile['role'] ?? '') === 'AGENT';
|
||||
|
||||
$navigation = [
|
||||
'dashboard' => ['dashboard.php', 'Tableau de bord'],
|
||||
'planning' => ['planning.php', 'Saisie'],
|
||||
'pending' => ['pending.php', 'À valider'],
|
||||
'coverage' => ['coverage.php', 'Couverture'],
|
||||
'structures' => ['structures.php', 'Par lieu'],
|
||||
'agents' => ['agents.php', ($accessProfile['role'] ?? '') === 'AGENT' ? 'Mon planning' : 'Par agent'],
|
||||
'agents' => ['agents.php', $isAgent ? 'Mon planning' : 'Par agent'],
|
||||
'annual' => ['annual.php', 'Vue annuelle'],
|
||||
'pta' => ['pta.php', 'PTA détaillé'],
|
||||
'teams' => ['teams.php', 'Équipes'],
|
||||
'vacations' => ['vacations.php', 'Vacances'],
|
||||
'administration' => ['administration.php', 'Administration'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Les onze onglets du Service Enfance étaient alignés à plat, sans hiérarchie :
|
||||
* rien ne distinguait une tâche quotidienne d'un référentiel modifié deux fois
|
||||
* par an. Ils sont désormais regroupés par intention. Les rôles qui n'ont que
|
||||
* quelques pages gardent une barre simple, sans titres de groupe.
|
||||
*/
|
||||
$navigationGroups = [
|
||||
'Piloter' => ['dashboard', 'pending', 'coverage'],
|
||||
'Planifier' => ['planning', 'teams'],
|
||||
'Consulter' => ['agents', 'structures', 'annual', 'pta'],
|
||||
'Paramétrer' => ['vacations', 'administration'],
|
||||
];
|
||||
|
||||
$visibleGroups = [];
|
||||
$grouped = [];
|
||||
foreach ($navigationGroups as $groupLabel => $keys) {
|
||||
$visibleKeys = array_values(array_filter(
|
||||
$keys,
|
||||
static fn (string $key): bool => in_array($key, $allowedPages, true) && isset($navigation[$key])
|
||||
));
|
||||
if ($visibleKeys !== []) {
|
||||
$visibleGroups[$groupLabel] = $visibleKeys;
|
||||
$grouped = array_merge($grouped, $visibleKeys);
|
||||
}
|
||||
}
|
||||
|
||||
// Filet de sécurité : une page autorisée en base mais oubliée dans les groupes
|
||||
// ci-dessus reste accessible plutôt que de disparaître de la navigation.
|
||||
$ungrouped = array_values(array_filter(
|
||||
$allowedPages,
|
||||
static fn (string $key): bool => isset($navigation[$key]) && !in_array($key, $grouped, true)
|
||||
));
|
||||
if ($ungrouped !== []) {
|
||||
$visibleGroups['Autres'] = $ungrouped;
|
||||
}
|
||||
|
||||
$visiblePageCount = count($grouped) + count($ungrouped);
|
||||
$showGroupLabels = $visiblePageCount > 4;
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
@@ -24,15 +67,15 @@ $navigation = [
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= htmlspecialchars($pageTitle) ?> — PTA</title>
|
||||
<link rel="stylesheet" href="assets/style.css?v=7.8">
|
||||
<link rel="stylesheet" href="assets/style.css?v=7.9">
|
||||
</head>
|
||||
<body>
|
||||
<main class="app-shell">
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<div class="page-header-identity">
|
||||
<p class="eyebrow">PTA</p>
|
||||
<h1><?= htmlspecialchars($pageTitle) ?></h1>
|
||||
<p class="subtitle">Planification annuelle, équipes, quotas et contrôles métier des accueils de loisirs.</p>
|
||||
<p class="subtitle"><?= htmlspecialchars($pageSubtitle ?? '') ?></p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<?php if ($activePage === 'planning'): ?>
|
||||
@@ -41,7 +84,7 @@ $navigation = [
|
||||
<div class="access-profile-badge">
|
||||
<span><?= htmlspecialchars((string) ($accessProfile['role_label'] ?? '')) ?></span>
|
||||
<strong><?= htmlspecialchars((string) ($accessProfile['label'] ?? '')) ?></strong>
|
||||
<?php if (!empty($accessProfile['structure_name']) && ($accessProfile['role'] ?? '') === 'AGENT'): ?>
|
||||
<?php if (!empty($accessProfile['structure_name']) && $isAgent): ?>
|
||||
<small><?= htmlspecialchars((string) $accessProfile['structure_name']) ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -53,12 +96,21 @@ $navigation = [
|
||||
<div class="message message-warning role-access-warning">Cette page n’est pas disponible avec votre rôle actuel.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<nav class="view-tabs" aria-label="Navigation principale">
|
||||
<?php foreach ($navigation as $key => [$href, $label]): ?>
|
||||
<?php if (!in_array($key, $allowedPages, true)) continue; ?>
|
||||
<a class="view-tab<?= $activePage === $key ? ' is-active' : '' ?>" href="<?= htmlspecialchars($href) ?>"<?= $activePage === $key ? ' aria-current="page"' : '' ?>>
|
||||
<?= htmlspecialchars($label) ?>
|
||||
<?php if ($key === 'pending'): ?><span id="pending-validation-tab-badge" class="nav-count-badge" hidden>0</span><?php endif; ?>
|
||||
</a>
|
||||
<nav class="view-tabs<?= $showGroupLabels ? ' view-tabs-grouped' : '' ?>" aria-label="Navigation principale">
|
||||
<?php foreach ($visibleGroups as $groupLabel => $keys): ?>
|
||||
<div class="view-tab-group" role="group"<?= $showGroupLabels ? ' aria-label="' . htmlspecialchars($groupLabel, ENT_QUOTES) . '"' : '' ?>>
|
||||
<?php if ($showGroupLabels): ?>
|
||||
<span class="view-tab-group-label"><?= htmlspecialchars($groupLabel) ?></span>
|
||||
<?php endif; ?>
|
||||
<div class="view-tab-group-items">
|
||||
<?php foreach ($keys as $key): ?>
|
||||
<?php [$href, $label] = $navigation[$key]; ?>
|
||||
<a class="view-tab<?= $activePage === $key ? ' is-active' : '' ?>" href="<?= htmlspecialchars($href) ?>"<?= $activePage === $key ? ' aria-current="page"' : '' ?>>
|
||||
<?= htmlspecialchars($label) ?>
|
||||
<?php if ($key === 'pending'): ?><span id="pending-validation-tab-badge" class="nav-count-badge" hidden>0</span><?php endif; ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
|
||||
4
app/Views/pages/dashboard.php
Normal file
4
app/Views/pages/dashboard.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
require dirname(__DIR__) . '/layouts/header.php';
|
||||
require dirname(__DIR__) . '/partials/dashboard.php';
|
||||
require dirname(__DIR__) . '/layouts/footer.php';
|
||||
@@ -12,7 +12,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Choisir votre accès — PTA</title>
|
||||
<link rel="stylesheet" href="assets/style.css?v=7.8">
|
||||
<link rel="stylesheet" href="assets/style.css?v=7.9">
|
||||
</head>
|
||||
<body class="role-page-body">
|
||||
<main class="role-page-shell">
|
||||
|
||||
164
app/Views/partials/dashboard.php
Normal file
164
app/Views/partials/dashboard.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/** @var string $currentWeek */
|
||||
?>
|
||||
<section id="dashboard-view" class="app-view page-content dashboard-page">
|
||||
|
||||
<section class="card dashboard-context-card">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>Semaine de travail</h2>
|
||||
<p class="section-subtitle" id="dashboard-week-label">Chargement de la semaine en cours…</p>
|
||||
</div>
|
||||
<div class="dashboard-context-actions">
|
||||
<label class="field dashboard-week-field">
|
||||
<span>Semaine analysée</span>
|
||||
<input id="dashboard-week" type="week" value="<?= htmlspecialchars($currentWeek) ?>">
|
||||
</label>
|
||||
<button id="dashboard-refresh" class="button button-secondary" type="button">Actualiser</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="dashboard-context-note" id="dashboard-week-context" hidden></p>
|
||||
</section>
|
||||
|
||||
<div id="dashboard-message" class="message" role="status" aria-live="polite"></div>
|
||||
<div id="dashboard-loading" class="empty-state" hidden>Consolidation des indicateurs…</div>
|
||||
|
||||
<div class="dashboard-tiles" aria-live="polite">
|
||||
<a class="dashboard-tile dashboard-tile-action" id="dashboard-tile-pending" href="pending.php">
|
||||
<span class="dashboard-tile-label">Plannings à valider</span>
|
||||
<strong class="dashboard-tile-value" id="dashboard-pending-count">—</strong>
|
||||
<small class="dashboard-tile-detail" id="dashboard-pending-detail">Brouillons contenant des affectations</small>
|
||||
<span class="dashboard-tile-cta">Ouvrir la file de validation</span>
|
||||
</a>
|
||||
<a class="dashboard-tile dashboard-tile-action" id="dashboard-tile-coverage" href="coverage.php">
|
||||
<span class="dashboard-tile-label">Manques de couverture</span>
|
||||
<strong class="dashboard-tile-value" id="dashboard-coverage-count">—</strong>
|
||||
<small class="dashboard-tile-detail" id="dashboard-coverage-detail">Sur la semaine analysée</small>
|
||||
<span class="dashboard-tile-cta">Voir le détail des plages</span>
|
||||
</a>
|
||||
<a class="dashboard-tile dashboard-tile-action" id="dashboard-tile-quota" href="annual.php">
|
||||
<span class="dashboard-tile-label">Quotas dépassés</span>
|
||||
<strong class="dashboard-tile-value" id="dashboard-quota-count">—</strong>
|
||||
<small class="dashboard-tile-detail" id="dashboard-quota-detail">Agents au-delà de leur quota annuel</small>
|
||||
<span class="dashboard-tile-cta">Ouvrir la vue annuelle</span>
|
||||
</a>
|
||||
<a class="dashboard-tile dashboard-tile-action" id="dashboard-tile-setup" href="administration.php">
|
||||
<span class="dashboard-tile-label">Lieux à paramétrer</span>
|
||||
<strong class="dashboard-tile-value" id="dashboard-setup-count">—</strong>
|
||||
<small class="dashboard-tile-detail" id="dashboard-setup-detail">Sans horaires de couverture attendus</small>
|
||||
<span class="dashboard-tile-cta">Ouvrir l’administration</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-columns">
|
||||
<section class="card dashboard-list-card">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>À valider en priorité</h2>
|
||||
<p class="section-subtitle">Les brouillons dont la semaine est la plus ancienne.</p>
|
||||
</div>
|
||||
<a class="button button-secondary" href="pending.php">Tout voir</a>
|
||||
</div>
|
||||
<div id="dashboard-pending-empty" class="empty-state" hidden>Aucun planning n’attend de validation.</div>
|
||||
<ul class="dashboard-list" id="dashboard-pending-list"></ul>
|
||||
</section>
|
||||
|
||||
<section class="card dashboard-list-card">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>Couverture à renforcer</h2>
|
||||
<p class="section-subtitle">Les plages les plus déficitaires de la semaine analysée.</p>
|
||||
</div>
|
||||
<a class="button button-secondary" id="dashboard-coverage-link" href="coverage.php">Tout voir</a>
|
||||
</div>
|
||||
<div id="dashboard-coverage-empty" class="empty-state" hidden>Aucun manque de couverture sur cette semaine.</div>
|
||||
<ul class="dashboard-list" id="dashboard-coverage-list"></ul>
|
||||
<div id="dashboard-coverage-unconfigured" class="dashboard-inline-note" hidden></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="card dashboard-quota-card">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>Suivi des quotas annuels</h2>
|
||||
<p class="section-subtitle" id="dashboard-quota-subtitle">Heures affectées, validées et brouillon comprises, sur l’année civile.</p>
|
||||
</div>
|
||||
<a class="button button-secondary" href="annual.php">Vue annuelle détaillée</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-quota-summary">
|
||||
<article class="summary-card">
|
||||
<span>Agents suivis</span>
|
||||
<strong id="dashboard-quota-agents">—</strong>
|
||||
<small>actifs</small>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<span>Consommation globale</span>
|
||||
<strong id="dashboard-quota-rate">—</strong>
|
||||
<small id="dashboard-quota-rate-detail">du quota cumulé</small>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<span>Reste à affecter</span>
|
||||
<strong id="dashboard-quota-remaining">—</strong>
|
||||
<small>hors agents déjà au-delà</small>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<span>Sans aucune heure</span>
|
||||
<strong id="dashboard-quota-empty">—</strong>
|
||||
<small>agent(s) non planifié(s)</small>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div id="dashboard-quota-empty-state" class="empty-state" hidden>Aucun agent actif à suivre.</div>
|
||||
<div class="dashboard-quota-table-wrap">
|
||||
<table class="dashboard-quota-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Agent</th>
|
||||
<th>Lieu de rattachement</th>
|
||||
<th>Quota annuel</th>
|
||||
<th>Affecté</th>
|
||||
<th>Consommation</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dashboard-quota-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card dashboard-shortcuts-card">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>Actions fréquentes</h2>
|
||||
<p class="section-subtitle">Les écrans les plus utilisés, accessibles en un clic.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-shortcuts">
|
||||
<a class="dashboard-shortcut" href="planning.php">
|
||||
<strong>Saisir un planning</strong>
|
||||
<small>Un agent, une semaine, un lieu.</small>
|
||||
</a>
|
||||
<a class="dashboard-shortcut" href="structures.php">
|
||||
<strong>Consulter un lieu</strong>
|
||||
<small>Qui travaille où cette semaine.</small>
|
||||
</a>
|
||||
<a class="dashboard-shortcut" href="teams.php">
|
||||
<strong>Composer une équipe</strong>
|
||||
<small>Mercredis et vacances scolaires.</small>
|
||||
</a>
|
||||
<a class="dashboard-shortcut" href="vacations.php">
|
||||
<strong>Mettre à jour les vacances</strong>
|
||||
<small>Périodes scolaires de référence.</small>
|
||||
</a>
|
||||
<a class="dashboard-shortcut" href="pta.php">
|
||||
<strong>Ouvrir un PTA détaillé</strong>
|
||||
<small>Souhaits, contraintes et répartition.</small>
|
||||
</a>
|
||||
<a class="dashboard-shortcut" href="administration.php">
|
||||
<strong>Gérer les référentiels</strong>
|
||||
<small>Agents, lieux, postes, couverture.</small>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
Reference in New Issue
Block a user