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>
417 lines
24 KiB
PHP
417 lines
24 KiB
PHP
<?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>
|
||
|