430 lines
25 KiB
PHP
430 lines
25 KiB
PHP
<?php
|
||
/**
|
||
* Écran de saisie du planning — le plus dense de l'application.
|
||
* Contient les deux modes exclusifs (éditeur de semaine type et saisie semaine
|
||
* par semaine), le formulaire jour par jour, le tableau de visualisation, le
|
||
* panneau de quota et les modales de semaine type.
|
||
* La numérotation des étapes se resserre quand le rôle n'a pas droit aux
|
||
* semaines types (voir $usesTemplates ci-dessous).
|
||
* Modules JS : `planning`, `weekTemplates`, `weekTemplateEditor`.
|
||
*
|
||
* Partiel de contenu inclus par la vue de page homonyme. Les identifiants HTML
|
||
* posés ici sont les points d'ancrage du module JS : les renommer casse le
|
||
* module sans aucune erreur visible.
|
||
*/
|
||
// 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>
|
||
|