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>
This commit is contained in:
@@ -110,8 +110,37 @@
|
||||
updateAgentPdfButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Motif proposé par défaut sur une nouvelle plage : le temps de travail, très
|
||||
* majoritaire à la saisie. On se rabat sur le premier motif décomptant le quota
|
||||
* si le référentiel ne contient pas le code TRAVAIL.
|
||||
*/
|
||||
function defaultMotifId() {
|
||||
const motifs = window.PTA_CONFIG?.motifs || [];
|
||||
const motif = motifs.find((item) => String(item.code).toUpperCase() === 'TRAVAIL')
|
||||
|| motifs.find((item) => item.quota)
|
||||
|| motifs[0];
|
||||
return motif ? Number(motif.id) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Un agent sans droit à l'enveloppe de formation ne peut pas recevoir le motif
|
||||
* « Formation ». Le droit vaut tant que l'agent n'est pas chargé : on ne retire
|
||||
* jamais une possibilité sur une donnée absente.
|
||||
*/
|
||||
function agentHasTrainingRight() {
|
||||
return Number(state.currentAgent?.droit_enveloppe_formation ?? 1) !== 0;
|
||||
}
|
||||
|
||||
function isMotifAllowedForAgent(motif) {
|
||||
return agentHasTrainingRight() || String(motif.code).toUpperCase() !== 'FORMATION';
|
||||
}
|
||||
|
||||
function motifOptionsHtml(selectedMotifId = null) {
|
||||
return (window.PTA_CONFIG?.motifs || [])
|
||||
// Un motif interdit reste proposé s'il est déjà celui de la plage :
|
||||
// le retirer changerait silencieusement le motif d'une saisie existante.
|
||||
.filter((motif) => isMotifAllowedForAgent(motif) || Number(motif.id) === Number(selectedMotifId))
|
||||
.map((motif) => {
|
||||
const selected = Number(motif.id) === Number(selectedMotifId) ? ' selected' : '';
|
||||
const quotaLabel = motif.quota ? ' · décompte quota' : ' · hors quota';
|
||||
@@ -122,13 +151,16 @@
|
||||
|
||||
function timeRangeHtml(dayLabel, index = 0, selectedMotifId = null) {
|
||||
const suffix = `${dayLabel} plage ${index + 1}`;
|
||||
const fallbackMotifId = defaultMotifId();
|
||||
const motifId = selectedMotifId ?? fallbackMotifId;
|
||||
const placeholder = fallbackMotifId === null ? '<option value="">Choisir un motif</option>' : '';
|
||||
return `
|
||||
<div class="weekly-time-range">
|
||||
<label class="weekly-range-motif-field">
|
||||
<span>Motif</span>
|
||||
<select class="weekly-range-motif" aria-label="Motif ${escapeHtml(suffix)}">
|
||||
<option value="">Choisir un motif</option>
|
||||
${motifOptionsHtml(selectedMotifId)}
|
||||
${placeholder}
|
||||
${motifOptionsHtml(motifId)}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
@@ -649,6 +681,7 @@
|
||||
state.entries = [];
|
||||
state.otherStructureEntries = [];
|
||||
state.currentStructure = null;
|
||||
state.currentAgent = null;
|
||||
state.planningId = null;
|
||||
state.quota = null;
|
||||
state.savedCurrentCountedMinutes = 0;
|
||||
@@ -710,6 +743,7 @@
|
||||
state.entries = [];
|
||||
state.otherStructureEntries = [];
|
||||
state.currentStructure = null;
|
||||
state.currentAgent = null;
|
||||
state.planningId = null;
|
||||
state.savedCurrentCountedMinutes = 0;
|
||||
setStatus(null);
|
||||
@@ -728,6 +762,7 @@
|
||||
|
||||
state.quota = data.quota || null;
|
||||
state.currentStructure = data.current_structure || null;
|
||||
state.currentAgent = data.agent || null;
|
||||
const calendarByDate = new Map((data.calendar_days || []).map((day) => [day.date, day]));
|
||||
state.weekDays = state.weekDays.map((day) => ({ ...day, ...(calendarByDate.get(day.date) || {}) }));
|
||||
state.otherStructureEntries = (data.other_entries || []).map((entry) => ({
|
||||
@@ -819,6 +854,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isMotifAllowedForAgent(motif)) {
|
||||
errors.push(`${day?.label || row.dataset.date} ${label} : le motif « ${motif.label} » n’est pas ouvert à cet agent, qui n’a pas droit à l’enveloppe de formation.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!start || !end) {
|
||||
errors.push(`${day?.label || row.dataset.date} ${label} : renseignez l’heure de début et l’heure de fin.`);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user