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; } }