pour prod

This commit is contained in:
Loic Masi
2026-08-07 16:13:43 +02:00
commit 5fbf76868f
157 changed files with 24085 additions and 0 deletions

View File

@@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Core\Access;
use App\Core\Controller;
use App\Core\Csrf;
use App\Models\AgentModel;
use App\Models\MotifModel;
use App\Models\StructureModel;
use DateTimeImmutable;
use DateTimeZone;
use PDO;
final class PageController extends Controller
{
private Access $access;
public function __construct(private PDO $pdo)
{
$this->access = new Access($pdo);
}
public function planning(): void
{
$this->access->requirePage('planning');
$forcedStructure = $this->access->editableStructureId();
$permissions = $this->access->permissions();
$scripts = ['planning-editor.js'];
$modules = ['planning'];
if (!empty($permissions['can_use_templates'])) {
$scripts[] = 'week-templates.js';
$modules[] = 'weekTemplates';
}
$this->renderPage('planning', 'Saisie du planning', 'planning', [
'structures' => $this->loadStructures(),
'motifs' => $this->loadMotifs(),
'currentWeek' => $this->currentWeek(),
'scripts' => $scripts,
'modules' => $modules,
'pageConfig' => [
'structureId' => $forcedStructure ?: $this->positiveInt($_GET['structure_id'] ?? null),
'agentId' => $this->positiveInt($_GET['agent_id'] ?? null),
'week' => $this->weekValue($_GET['week'] ?? null),
'focus' => (string) ($_GET['focus'] ?? ''),
'notice' => trim((string) ($_GET['notice'] ?? '')),
'structureLocked' => $forcedStructure !== null,
],
]);
}
public function pending(): void
{
$this->access->requirePage('pending');
$this->renderPage('pending', 'Plannings à valider', 'pending', [
'scripts' => ['pending-validation.js'],
'modules' => ['pendingValidation'],
]);
}
public function coverage(): void
{
$this->access->requirePage('coverage');
$forcedStructure = $this->access->role() === Access::ROLE_RESPONSABLE ? $this->access->structureId() : null;
$this->renderPage('coverage', 'Contrôle de couverture', 'coverage', [
'structures' => $this->loadStructures(),
'currentWeek' => $this->weekValue($_GET['week'] ?? null) ?: $this->currentWeek(),
'scripts' => ['coverage.js'],
'modules' => ['coverage'],
'pageConfig' => [
'structureId' => $forcedStructure ?: $this->positiveInt($_GET['structure_id'] ?? null),
'structureLocked' => $forcedStructure !== null,
],
]);
}
public function structures(): void
{
$this->access->requirePage('structures');
$forcedStructure = $this->access->role() !== Access::ROLE_SERVICE ? $this->access->structureId() : null;
$this->renderPage('structures', 'Planning par lieu', 'structures', [
'structures' => $this->loadStructures(),
'currentWeek' => $this->weekValue($_GET['week'] ?? null) ?: $this->currentWeek(),
'scripts' => ['structure-overview.js'],
'modules' => ['structureOverview'],
'pageConfig' => [
'structureId' => $forcedStructure ?: $this->positiveInt($_GET['structure_id'] ?? null),
'structureLocked' => $forcedStructure !== null,
],
]);
}
public function agents(): void
{
$this->access->requirePage('agents');
$forcedAgent = $this->access->role() === Access::ROLE_AGENT ? $this->access->agentId() : null;
$this->renderPage('agents', $forcedAgent ? 'Mon planning' : 'Planning par agent', 'agents', [
'agents' => $this->loadAgents(),
'structures' => $this->loadStructuresForAgentEditor(),
'motifs' => $this->loadMotifs(),
'scripts' => ['agent-overview.js'],
'modules' => ['agentOverview'],
'pageConfig' => [
'agentId' => $forcedAgent ?: $this->positiveInt($_GET['agent_id'] ?? null),
'month' => $this->monthValue($_GET['month'] ?? null),
'agentLocked' => $forcedAgent !== null,
],
]);
}
public function annual(): void
{
$this->access->requirePage('annual');
$this->renderPage('annual', 'Vue annuelle du PTA', 'annual', [
'agents' => (new AgentModel($this->pdo))->allActive(),
'scripts' => ['annual-overview.js'],
'modules' => ['annualOverview'],
'pageConfig' => [
'agentId' => $this->positiveInt($_GET['agent_id'] ?? null),
'year' => $this->yearValue($_GET['year'] ?? null),
],
]);
}
public function pta(): void
{
$this->access->requirePage('pta');
$this->renderPage('pta', 'PTA annuel par agent', 'pta', [
'agents' => (new AgentModel($this->pdo))->allActive(),
'structures' => (new StructureModel($this->pdo))->allActive(),
'scripts' => ['pta.js'],
'modules' => ['pta'],
'pageConfig' => [
'agentId' => $this->positiveInt($_GET['agent_id'] ?? null),
'date' => $this->dateValue($_GET['date'] ?? null),
],
]);
}
public function teams(): void
{
$this->access->requirePage('teams');
$this->renderPage('teams', 'Équipes des mercredis et vacances', 'teams', [
'agents' => (new AgentModel($this->pdo))->allActive(),
'structures' => (new StructureModel($this->pdo))->allActive(),
'scripts' => ['teams.js'],
'modules' => ['teams'],
'pageConfig' => [
'dateDebut' => $this->dateValue($_GET['date_debut'] ?? null),
'dateFin' => $this->dateValue($_GET['date_fin'] ?? null),
],
]);
}
public function vacations(): void
{
$this->access->requirePage('vacations');
$this->renderPage('vacations', 'Vacances scolaires', 'vacations', [
'scripts' => ['vacation-calendar.js'],
'modules' => ['vacationCalendar'],
]);
}
public function administration(): void
{
$this->access->requirePage('administration');
$this->renderPage('administration', 'Administration', 'administration', [
'agents' => (new AgentModel($this->pdo))->allActive(),
'structures' => (new StructureModel($this->pdo))->allActive(),
'posts' => (new AgentModel($this->pdo))->allPosts(),
'scripts' => ['administration.js', 'coverage.js'],
'modules' => ['administration', 'coverage'],
]);
}
private function renderPage(string $view, string $title, string $activePage, array $data = []): void
{
$profile = $this->access->profile();
$this->render('pages/' . $view, array_merge([
'pageTitle' => $title,
'activePage' => $activePage,
'csrfToken' => Csrf::ensureToken(),
'scripts' => [],
'modules' => [],
'motifs' => [],
'pageConfig' => [],
'accessProfile' => $profile,
'permissions' => $profile['permissions'],
'allowedPages' => $this->access->allowedPages(),
], $data));
}
private function loadStructures(): array
{
$model = new StructureModel($this->pdo);
if ($this->access->role() === Access::ROLE_SERVICE) {
return $model->allActive();
}
$structureId = $this->access->structureId();
if ($structureId === null) {
return [];
}
$structure = $model->findActive($structureId);
return $structure ? [$structure] : [];
}
private function loadStructuresForAgentEditor(): array
{
if ($this->access->role() === Access::ROLE_SERVICE) {
return (new StructureModel($this->pdo))->allActive();
}
return $this->loadStructures();
}
private function loadAgents(): array
{
$model = new AgentModel($this->pdo);
if ($this->access->role() === Access::ROLE_SERVICE) {
return $model->allActive();
}
if ($this->access->role() === Access::ROLE_AGENT) {
$agent = $this->access->agentId() ? $model->findActive((int) $this->access->agentId()) : null;
return $agent ? [$agent] : [];
}
$structureId = $this->access->structureId();
return $structureId ? $model->allVisibleForStructure($structureId) : [];
}
private function loadMotifs(): array
{
return (new MotifModel($this->pdo))->allActive();
}
private function currentWeek(): string
{
return (new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')))->format('o-\\WW');
}
private function positiveInt(mixed $value): ?int
{
$number = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
return $number === false ? null : (int) $number;
}
private function weekValue(mixed $value): ?string
{
$value = trim((string) $value);
return preg_match('/^\\d{4}-W\\d{2}$/', $value) === 1 ? $value : null;
}
private function dateValue(mixed $value): ?string
{
$value = trim((string) $value);
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $value);
return $date && $date->format('Y-m-d') === $value ? $value : null;
}
private function yearValue(mixed $value): ?int
{
$number = filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 2000, 'max_range' => 2100]]);
return $number === false ? null : (int) $number;
}
private function monthValue(mixed $value): ?string
{
$value = trim((string) $value);
return preg_match('/^\\d{4}-(0[1-9]|1[0-2])$/', $value) === 1 ? $value : null;
}
}