Files
pta/app/Views/partials/planning_editor.php
PTA 4201894e28 V7.10 — Tableau de bord, guide, semaine type et enveloppe de formation
Reprise dans le répertoire de production du travail mené dans le worktree
ux-service-enfance :

- tableau de bord Service Enfance (dashboard.php) et navigation regroupée ;
- guide d'utilisation (faq.php) accessible depuis toute la navigation ;
- éditeur de semaine type mis en avant sur l'écran de saisie ;
- droit à l'enveloppe de formation par agent, en remplacement de la case
  « formation déjà prévue dans la répartition annuelle » ;
- correctif de « [object Object] » sur les cartes de quota du PTA ;
- motif de travail par défaut à la saisie, heures travaillées complètes
  dans les PDF.

Le docker-compose.yml de production est conservé : celui du worktree décrit
une pile de test (conteneurs et volume suffixés « test », ports 9080/9081),
qui aurait fait redémarrer l'application sur un volume MariaDB vide.

Les migrations database/migration_v7_9*.sql sont obligatoires ; le service
db-migrate les applique au déploiement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 16:53:04 +02:00

417 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// Les rôles sans droit aux semaines types n'ont qu'un seul mode de saisie :
// le sélecteur de méthode disparaît et la numérotation des étapes se resserre.
$usesTemplates = !empty($permissions['can_use_templates']);
$etapeSaisie = $usesTemplates ? 3 : 2;
$etapePlanning = $usesTemplates ? 4 : 3;
?>
<section id="editor-view" class="app-view page-content">
<?php if (($accessProfile['role'] ?? '') === 'RESPONSABLE_STRUCTURE'): ?>
<div class="message message-info role-page-notice">
Vous pouvez saisir et corriger les horaires et motifs de votre lieu. Les modifications sont enregistrées en <strong>brouillon</strong> ; seul le Service Enfance peut les valider.
</div>
<?php endif; ?>
<section class="card selection-card">
<h2>1. Sélection du planning</h2>
<div class="selection-grid">
<label class="field">
<span>Lieu d’affectation <strong>*</strong></span>
<select id="structure" required>
<option value="">Choisir un lieu d’affectation</option>
<?php foreach ($structures as $structure): ?>
<option value="<?= (int) $structure['id_structure'] ?>">
<?= htmlspecialchars($structure['nom']) ?> — <?= ($structure['type_affectation'] ?? 'PERISCOLAIRE') === 'EXTRASCOLAIRE' ? 'Extrascolaire' : 'Périscolaire' ?>
</option>
<?php endforeach; ?>
</select>
</label>
<label class="field">
<span>Agent <strong>*</strong></span>
<select id="agent" required disabled>
<option value="">Choisir d’abord un lieu d’affectation</option>
</select>
</label>
<label class="field">
<span>Semaine <strong>*</strong></span>
<input id="week" type="week" value="<?= htmlspecialchars($currentWeek) ?>" required disabled>
</label>
</div>
</section>
<?php if ($usesTemplates): ?>
<section class="card editor-mode-card">
<div class="section-heading">
<div>
<h2>2. Choisir la méthode de saisie</h2>
<p class="section-subtitle">Une semaine type se compose une fois puis se duplique sur toute une période. La saisie semaine par semaine reste disponible pour les corrections.</p>
</div>
</div>
<div class="editor-mode-choices" role="tablist" aria-label="Méthode de saisie">
<button id="editor-mode-template" class="editor-mode-choice is-active" type="button"
role="tab" aria-selected="true" aria-controls="template-mode-panel">
<span class="editor-mode-choice-badge">Le plus rapide</span>
<strong>Semaine type</strong>
<small>Composer une semaine de référence, puis la dupliquer sur une période entière.</small>
</button>
<button id="editor-mode-week" class="editor-mode-choice" type="button"
role="tab" aria-selected="false" aria-controls="week-mode-panel">
<strong>Semaine précise</strong>
<small>Saisir ou corriger une seule semaine, jour par jour.</small>
</button>
</div>
</section>
<div id="template-mode-panel" class="editor-mode-panel" role="tabpanel" aria-labelledby="editor-mode-template">
<section class="card template-editor-card">
<div class="section-heading">
<div>
<h2>Éditeur de semaine type</h2>
<p class="section-subtitle">Les plages saisies ici ne sont pas encore planifiées : elles décrivent une semaine de référence, réutilisable autant de fois que nécessaire.</p>
</div>
<div class="totals template-editor-total">
<span>Total de la semaine type</span>
<strong id="template-editor-total">0 h 00</strong>
<small id="template-editor-total-detail">dont 0 h 00 décomptées</small>
</div>
</div>
<div class="template-editor-toolbar">
<label class="field">
<span>Semaine type de l’agent</span>
<select id="template-editor-select" disabled>
<option value="">Nouvelle semaine type</option>
</select>
</label>
<label class="field">
<span>Nom de la semaine type <strong>*</strong></span>
<input id="template-editor-name" type="text" maxlength="120" placeholder="Ex. Semaine scolaire classique" disabled>
</label>
<div class="template-editor-toolbar-actions">
<button id="template-editor-delete" class="button button-danger" type="button" disabled>Supprimer</button>
</div>
</div>
<div id="template-editor-empty" class="empty-state">Choisissez un lieu d’affectation et un agent pour composer sa semaine type.</div>
<div id="template-editor-grid" class="template-editor-grid" hidden></div>
<div id="template-editor-message" class="message" role="status" aria-live="polite"></div>
<div class="template-editor-actions">
<button id="template-editor-save" class="button button-primary" type="button" disabled>
Enregistrer la semaine type
</button>
<button id="template-editor-apply-period" class="button button-success" type="button" disabled>
Dupliquer sur une période…
</button>
<button id="template-editor-apply-week" class="button button-secondary" type="button" disabled>
Appliquer à la semaine sélectionnée…
</button>
</div>
<p class="hint">Enregistrer une semaine type ne modifie aucun planning. Rien n’est planifié tant que vous ne l’avez pas dupliquée sur une période ou appliquée à une semaine.</p>
</section>
</div>
<?php endif; ?>
<div id="week-mode-panel" class="editor-mode-panel"<?= $usesTemplates ? ' hidden' : '' ?>>
<section class="quota-strip" id="editor-quota" hidden>
<article class="quota-card">
<span>Quotité</span>
<strong id="quota-rate">100 %</strong>
</article>
<article class="quota-card">
<span>Quota de la période</span>
<strong id="quota-target">1 607 h 00</strong>
<small id="quota-period">Année civile</small>
</article>
<article class="quota-card">
<span>Déjà affecté</span>
<strong id="quota-used">0 h 00</strong>
<small id="quota-used-detail">Validé + brouillon</small>
</article>
<article class="quota-card quota-card-emphasis">
<span>Reste à affecter</span>
<strong id="quota-remaining">1 607 h 00</strong>
</article>
</section>
<section class="card" id="entry-card" aria-disabled="true">
<div class="section-heading">
<div>
<h2><?= $etapeSaisie ?>. Saisir ou modifier les affectations</h2>
<p class="section-subtitle">Chaque plage horaire possède son propre motif et sa propre règle de décompte du quota annuel.</p>
</div>
</div>
<div class="weekly-entry-toolbar">
<p class="weekly-entry-help">Les horaires déjà enregistrés sont rechargés automatiquement. Modifiez-les directement, puis appliquez les horaires avant d’enregistrer.</p>
</div>
<div class="weekly-entry-scroll">
<table class="weekly-entry-table" aria-label="Affectations du lundi au vendredi">
<thead>
<tr>
<th>Jour</th>
<th>Affectations et plages horaires</th>
</tr>
</thead>
<tbody id="weekly-entry-body">
<tr>
<td colspan="2" class="weekly-entry-placeholder">Sélectionnez d’abord un lieu d’affectation, un agent et une semaine.</td>
</tr>
</tbody>
</table>
</div>
<div class="weekly-entry-actions">
<button id="add-entry" class="button button-primary button-add" type="button" disabled>
Appliquer les horaires renseignés
</button>
</div>
<p class="hint">Chaque plage peut avoir un motif différent : par exemple Travail le matin puis Formation l’après-midi. Les heures sont saisies par pas de 15 minutes. Les créneaux déjà affectés à l’agent dans un autre lieu apparaissent dans le planning global et bloquent toute nouvelle saisie qui les chevaucherait.</p>
</section>
<section class="card">
<div class="section-heading">
<div>
<h2><?= $etapePlanning ?>. Planning global de l’agent</h2>
<p id="week-label" class="section-subtitle">Toutes les affectations de l’agent sur la semaine, quel que soit le lieu d’affectation. Cliquez sur un créneau pour le déplacer ou le dupliquer, ou glissez-le directement vers un autre jour.</p>
</div>
<div class="planning-summary-actions">
<div class="totals">
<span>Total semaine décompté</span>
<strong id="work-total">0 h 00</strong>
</div>
<?php if (!empty($permissions['can_use_templates'])): ?>
<button id="save-week-template" class="button button-secondary" type="button" disabled>
Enregistrer comme semaine type
</button>
<button id="apply-week-template" class="button button-secondary" type="button" disabled>
Importer une semaine type
</button>
<button id="apply-week-template-period" class="button button-secondary" type="button" disabled>
Appliquer sur une période
</button>
<?php endif; ?>
<button id="open-agent-pdf" class="button button-secondary button-pdf" type="button" disabled>
Voir le PDF de l’agent (7 semaines)
</button>
</div>
</div>
<div id="empty-state" class="empty-state">Aucune affectation pour le moment.</div>
<div id="planning-board" class="planning-board" hidden></div>
</section>
<section class="actions-bar">
<div id="message" class="message" role="status" aria-live="polite"></div>
<div class="action-buttons">
<?php if (!empty($permissions['can_edit_draft'])): ?>
<button id="modify-planning" class="button button-warning" type="button" hidden>Modifier le planning</button>
<button id="save-draft" class="button button-secondary" type="button" disabled>Enregistrer le brouillon</button>
<?php endif; ?>
<?php if (!empty($permissions['can_validate'])): ?>
<button id="validate-planning" class="button button-success" type="button" disabled>Valider le planning</button>
<?php endif; ?>
</div>
</section>
</div><!-- /#week-mode-panel -->
</section>
<div id="planning-entry-action-modal" class="modal-backdrop" hidden>
<section class="modal-card planning-entry-action-card" role="dialog" aria-modal="true" aria-labelledby="planning-entry-action-title">
<div class="section-heading modal-heading">
<div>
<h2 id="planning-entry-action-title">Déplacer ou dupliquer un créneau</h2>
<p id="planning-entry-action-summary" class="section-subtitle"></p>
</div>
<button id="close-planning-entry-action-modal" type="button" class="button button-ghost modal-close" aria-label="Fermer">×</button>
</div>
<form id="planning-entry-action-form" class="admin-form">
<fieldset class="entry-action-mode">
<legend>Action à effectuer</legend>
<label class="entry-action-option">
<input type="radio" name="planning-entry-action" value="move" checked>
<span><strong>Déplacer</strong><small>Le créneau change de jour.</small></span>
</label>
<label class="entry-action-option">
<input type="radio" name="planning-entry-action" value="duplicate">
<span><strong>Dupliquer</strong><small>Le créneau d’origine est conservé.</small></span>
</label>
</fieldset>
<label class="field">
<span>Jour cible</span>
<select id="planning-entry-target-date" required></select>
</label>
<p class="hint">Astuce : vous pouvez aussi glisser-déposer directement un créneau vers un autre jour. Maintenez <strong>Ctrl</strong> (ou <strong>⌘</strong> sur Mac) pendant le dépôt pour le dupliquer.</p>
<div id="planning-entry-action-message" class="message" role="status" aria-live="polite"></div>
<div class="admin-form-actions modal-actions">
<button id="cancel-planning-entry-action" type="button" class="button button-secondary">Annuler</button>
<button id="confirm-planning-entry-action" type="submit" class="button button-primary">Appliquer</button>
</div>
</form>
</section>
</div>
<div id="save-week-template-modal" class="modal-backdrop" hidden>
<section class="modal-card week-template-modal-card" role="dialog" aria-modal="true" aria-labelledby="save-week-template-title">
<div class="section-heading modal-heading">
<div>
<h2 id="save-week-template-title">Enregistrer une semaine type</h2>
<p class="section-subtitle">Le modèle mémorisera toutes les affectations de l’agent sur la semaine, tous lieux confondus.</p>
</div>
<button id="close-save-week-template-modal" type="button" class="button button-ghost modal-close" aria-label="Fermer">×</button>
</div>
<form id="save-week-template-form" class="admin-form">
<label class="field">
<span>Nom de la semaine type <strong>*</strong></span>
<input id="week-template-name" type="text" maxlength="120" placeholder="Ex. Semaine scolaire classique" required>
</label>
<div class="week-template-context" id="save-week-template-context"></div>
<div id="save-week-template-message" class="message" role="status" aria-live="polite"></div>
<div class="admin-form-actions modal-actions">
<button id="cancel-save-week-template" type="button" class="button button-secondary">Annuler</button>
<button type="submit" class="button button-primary">Enregistrer le modèle</button>
</div>
</form>
</section>
</div>
<div id="apply-week-template-modal" class="modal-backdrop" hidden>
<section class="modal-card week-template-modal-card" role="dialog" aria-modal="true" aria-labelledby="apply-week-template-title">
<div class="section-heading modal-heading">
<div>
<h2 id="apply-week-template-title">Importer une semaine type</h2>
<p class="section-subtitle">Le modèle sera appliqué à la semaine actuellement sélectionnée pour cet agent.</p>
</div>
<button id="close-apply-week-template-modal" type="button" class="button button-ghost modal-close" aria-label="Fermer">×</button>
</div>
<form id="apply-week-template-form" class="admin-form">
<label class="field">
<span>Semaine type <strong>*</strong></span>
<select id="week-template-select" required>
<option value="">Chargement...</option>
</select>
</label>
<div id="week-template-details" class="week-template-details"></div>
<fieldset class="entry-action-mode week-template-mode">
<legend>Mode d’import</legend>
<label class="entry-action-option">
<input type="radio" name="week-template-mode" value="merge" checked>
<span><strong>Compléter la semaine</strong><small>Conserve les affectations existantes. L’import est refusé en cas de chevauchement.</small></span>
</label>
<label class="entry-action-option">
<input type="radio" name="week-template-mode" value="replace">
<span><strong>Remplacer la semaine</strong><small>Supprime les affectations existantes de l’agent sur cette semaine avant d’appliquer le modèle.</small></span>
</label>
</fieldset>
<div class="week-template-danger" id="week-template-replace-warning" hidden>
Le mode « Remplacer » efface les affectations actuelles de l’agent sur la semaine sélectionnée, tous lieux confondus.
</div>
<div id="apply-week-template-message" class="message" role="status" aria-live="polite"></div>
<div class="admin-form-actions modal-actions week-template-modal-actions">
<button id="delete-week-template" type="button" class="button button-danger" disabled>Supprimer ce modèle</button>
<span class="modal-action-spacer"></span>
<button id="cancel-apply-week-template" type="button" class="button button-secondary">Annuler</button>
<button type="submit" class="button button-primary">Importer le modèle</button>
</div>
</form>
</section>
</div>
<div id="apply-week-template-period-modal" class="modal-backdrop" hidden>
<section class="modal-card week-template-period-modal-card" role="dialog" aria-modal="true" aria-labelledby="apply-week-template-period-title">
<div class="section-heading modal-heading">
<div>
<h2 id="apply-week-template-period-title">Appliquer une semaine type sur une période</h2>
<p class="section-subtitle">Les périodes de vacances déjà validées dans PTA sont prises en compte avant toute création de planning.</p>
</div>
<button id="close-apply-week-template-period-modal" type="button" class="button button-ghost modal-close" aria-label="Fermer">×</button>
</div>
<form id="apply-week-template-period-form" class="admin-form">
<div class="week-template-period-grid">
<label class="field">
<span>Semaine type <strong>*</strong></span>
<select id="week-template-period-select" required>
<option value="">Chargement...</option>
</select>
</label>
<label class="field">
<span>Date de début <strong>*</strong></span>
<input id="week-template-period-start" type="date" required>
</label>
<label class="field">
<span>Date de fin <strong>*</strong></span>
<input id="week-template-period-end" type="date" required>
</label>
</div>
<fieldset class="entry-action-mode week-template-mode">
<legend>Périodes à appliquer</legend>
<label class="entry-action-option">
<input type="radio" name="week-template-period-filter" value="all" checked>
<span><strong>Toutes les semaines</strong><small>Répète la semaine type sur toutes les dates de la période, qu’elles soient scolaires ou pendant les vacances.</small></span>
</label>
<label class="entry-action-option">
<input type="radio" name="week-template-period-filter" value="school">
<span><strong>Uniquement les périodes scolaires</strong><small>Applique le modèle uniquement aux jours situés hors des vacances enregistrées dans PTA.</small></span>
</label>
<label class="entry-action-option">
<input type="radio" name="week-template-period-filter" value="extra">
<span><strong>Uniquement les périodes extrascolaires</strong><small>Applique le modèle uniquement aux jours compris dans les vacances scolaires enregistrées dans PTA.</small></span>
</label>
</fieldset>
<div class="week-template-period-rule-note">
<strong>Calendrier :</strong>
le caractère scolaire ou extrascolaire d’une date est déterminé à partir des périodes de vacances validées dans PTA.
Le choix ci-dessus pilote l’application du modèle ; il n’est pas déduit du lieu d’affectation.
</div>
<fieldset class="entry-action-mode week-template-mode">
<legend>Mode d’application</legend>
<label class="entry-action-option">
<input type="radio" name="week-template-period-mode" value="merge" checked>
<span><strong>Compléter les plannings</strong><small>Conserve les affectations déjà existantes. L’application est bloquée si un chevauchement est détecté.</small></span>
</label>
<label class="entry-action-option">
<input type="radio" name="week-template-period-mode" value="replace">
<span><strong>Remplacer les jours concernés</strong><small>Supprime les affectations existantes uniquement sur les dates où le modèle doit réellement s’appliquer, puis recrée le planning.</small></span>
</label>
</fieldset>
<div class="week-template-danger" id="week-template-period-replace-warning" hidden>
Les journées exclues par le filtre de calendrier ne seront pas effacées. Seules les dates sur lesquelles le modèle crée au moins un créneau seront remplacées.
</div>
<div class="week-template-period-preview-actions">
<button id="preview-week-template-period" type="button" class="button button-secondary">Prévisualiser l’application</button>
</div>
<div id="week-template-period-preview" class="week-template-period-preview" hidden></div>
<div id="apply-week-template-period-message" class="message" role="status" aria-live="polite"></div>
<div class="admin-form-actions modal-actions">
<button id="cancel-apply-week-template-period" type="button" class="button button-secondary">Annuler</button>
<button id="confirm-apply-week-template-period" type="submit" class="button button-primary" disabled>Appliquer sur la période</button>
</div>
</form>
</section>
</div>