Rendre lisibles le statut et la date imposée
All checks were successful
Build / build (push) Successful in 1m2s
All checks were successful
Build / build (push) Successful in 1m2s
Statut : quatre boutons peints de leur texture, si bien que le panneau sert enfin de légende. « En cours » se raye et « terminé » redevient vif — la trame les distingue, la teinte n'a plus à le faire. Date imposée : un cadre tireté cerne la forme entière. Les taquets tombaient sous les poignées de redimensionnement et disparaissaient sur une barre courte. Son libellé passe dans le modèle, ce qui la fait apparaître dans la vue par mois, qui la taisait. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
115
js/detail.js
115
js/detail.js
@@ -16,8 +16,31 @@
|
||||
import { STATUTS, dateValide, modifierPhase } from './model.js';
|
||||
import { creerBlocNotes } from './notes.js';
|
||||
|
||||
/**
|
||||
* Les champs de la phase, et la forme de leur contrôle.
|
||||
*
|
||||
* Une seule liste, parcourue par tout ce qui traite les champs en bloc :
|
||||
* l'affichage, le rafraîchissement et la comparaison. Ajouter `fixed` avait
|
||||
* demandé quatre retouches dispersées, dont on ne savait qu'en les cherchant
|
||||
* qu'elles allaient ensemble ; il n'y a plus qu'une ligne à ajouter ici, et le
|
||||
* cas particulier à écrire s'il y en a un.
|
||||
*
|
||||
* `notes` n'y est pas : son contrôle est bâti par `notes.js` et se remplit par
|
||||
* lui, jamais en écrivant directement dans le `textarea` — l'aperçu markdown ne
|
||||
* suivrait pas. Il reste comparé à part, pour la même raison.
|
||||
*/
|
||||
const CHAMPS = [
|
||||
{ nom: 'name' },
|
||||
{ nom: 'start' },
|
||||
{ nom: 'end' },
|
||||
{ nom: 'status' },
|
||||
{ nom: 'milestone', case: true },
|
||||
{ nom: 'fixed', case: true },
|
||||
];
|
||||
|
||||
export function creerPanneau(refs, rappels) {
|
||||
const { panneau, formulaire, titre, erreur, champFin, notes, supprimer, fermer } = refs;
|
||||
const { panneau, formulaire, titre, erreur, champDates, champFin, notes, supprimer, fermer } =
|
||||
refs;
|
||||
|
||||
// Le bloc pose lui-même un `<textarea name="notes">` dans le formulaire, et
|
||||
// émet un `input` à chaque changement — bouton de la barre d'outils ou case
|
||||
@@ -35,20 +58,51 @@ export function creerPanneau(refs, rappels) {
|
||||
|
||||
function remplir(projet, phase) {
|
||||
titre.textContent = `${projet.name} — ${phase.name}`;
|
||||
formulaire.name.value = phase.name;
|
||||
formulaire.start.value = phase.start;
|
||||
formulaire.end.value = phase.end;
|
||||
formulaire.status.value = STATUTS.includes(phase.status) ? phase.status : 'todo';
|
||||
formulaire.milestone.checked = phase.milestone;
|
||||
formulaire.fixed.checked = phase.fixed;
|
||||
for (const champ of CHAMPS) ecrire(champ, phase);
|
||||
// Ouverture sur l'aperçu si la phase est commentée, sur la saisie sinon :
|
||||
// on ouvre une phase bloquée pour relire pourquoi, une phase vierge pour
|
||||
// y écrire quelque chose.
|
||||
blocNotes.definir(phase.notes, { reinitialiserMode: true });
|
||||
|
||||
peindre(projet);
|
||||
accorderLesChamps(phase);
|
||||
afficherErreur(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Donne au formulaire la couleur du projet qu'il édite : les textures des
|
||||
* quatre boutons de statut s'y peignent, comme la barre qu'on regarde
|
||||
* derrière le panneau. Rejoué au rafraîchissement, faute de quoi recolorer un
|
||||
* projet laisserait le panneau à sa teinte d'ouverture.
|
||||
*/
|
||||
function peindre(projet) {
|
||||
formulaire.style.setProperty('--couleur-projet', projet.color);
|
||||
}
|
||||
|
||||
/** Écrit un champ du modèle dans son contrôle, quelle que soit sa forme. */
|
||||
function ecrire(champ, phase) {
|
||||
const controle = formulaire[champ.nom];
|
||||
if (champ.case) controle.checked = phase[champ.nom];
|
||||
// Les quatre boutons de statut portent le même nom : leur affecter une
|
||||
// valeur inconnue les décocherait tous, et le panneau n'afficherait plus
|
||||
// rien là où le modèle, lui, retombe sur « à venir ».
|
||||
else if (champ.nom === 'status') controle.value = statutAffiche(phase);
|
||||
else controle.value = phase[champ.nom];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ce que le formulaire montre en plus de ses valeurs : les champs qui n'ont
|
||||
* pas lieu d'être, et la marque de ceux qu'on ne décide pas.
|
||||
*
|
||||
* Rejoué à chaque frappe et pas seulement à l'ouverture : les deux cases
|
||||
* changent ce que le reste du panneau donne à voir, et l'effet doit suivre le
|
||||
* clic qui le déclenche.
|
||||
*/
|
||||
function accorderLesChamps(phase) {
|
||||
// Un jalon tient sur une seule date : le champ « fin » n'a rien à dire.
|
||||
champFin.hidden = phase.milestone;
|
||||
afficherErreur(null);
|
||||
// Le cadre tireté de la frise, reporté sur les dates que la case qualifie.
|
||||
champDates.classList.toggle('champ-double--imposee', phase.fixed);
|
||||
}
|
||||
|
||||
/** Lit le formulaire, applique les invariants et remonte le changement. */
|
||||
@@ -94,7 +148,7 @@ export function creerPanneau(refs, rappels) {
|
||||
afficherErreur(null);
|
||||
}
|
||||
|
||||
champFin.hidden = jalon;
|
||||
accorderLesChamps(apres);
|
||||
phaseCourante = apres;
|
||||
|
||||
if (!sontEgales(avant, apres)) {
|
||||
@@ -164,16 +218,15 @@ export function creerPanneau(refs, rappels) {
|
||||
const actif = document.activeElement;
|
||||
phaseCourante = phase;
|
||||
titre.textContent = `${projet.name} — ${phase.name}`;
|
||||
for (const champ of ['name', 'start', 'end', 'status']) {
|
||||
if (formulaire[champ] !== actif) formulaire[champ].value = phase[champ];
|
||||
for (const champ of CHAMPS) {
|
||||
if (!aLeFocus(formulaire[champ.nom], actif)) ecrire(champ, phase);
|
||||
}
|
||||
// Le mode n'est pas réinitialisé : basculer en aperçu sous les doigts de
|
||||
// qui écrit, parce qu'un glisser sur la frise a rafraîchi le panneau,
|
||||
// serait le pire moment pour changer d'état.
|
||||
if (formulaire.notes !== actif) blocNotes.definir(phase.notes);
|
||||
if (formulaire.milestone !== actif) formulaire.milestone.checked = phase.milestone;
|
||||
if (formulaire.fixed !== actif) formulaire.fixed.checked = phase.fixed;
|
||||
champFin.hidden = phase.milestone;
|
||||
peindre(projet);
|
||||
accorderLesChamps(phase);
|
||||
},
|
||||
|
||||
fermer() {
|
||||
@@ -195,13 +248,29 @@ export function creerPanneau(refs, rappels) {
|
||||
}
|
||||
|
||||
function sontEgales(a, b) {
|
||||
return (
|
||||
a.name === b.name &&
|
||||
a.start === b.start &&
|
||||
a.end === b.end &&
|
||||
a.status === b.status &&
|
||||
a.milestone === b.milestone &&
|
||||
a.fixed === b.fixed &&
|
||||
a.notes === b.notes
|
||||
);
|
||||
return CHAMPS.every((champ) => a[champ.nom] === b[champ.nom]) && a.notes === b.notes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Le statut que le formulaire doit montrer.
|
||||
*
|
||||
* Le modèle ramène un statut inconnu à « à venir » sans bloquer le chargement
|
||||
* (model.js) ; le panneau fait le même choix, faute de quoi il n'afficherait
|
||||
* rien là où la frise, elle, dessine une barre.
|
||||
*/
|
||||
function statutAffiche(phase) {
|
||||
return STATUTS.includes(phase.status) ? phase.status : 'todo';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ce contrôle a-t-il le focus ?
|
||||
*
|
||||
* Les quatre boutons de statut partagent un nom, si bien que `formulaire.status`
|
||||
* est la liste des quatre et non l'un d'eux : elle n'est jamais l'élément
|
||||
* focalisé, et sans ce détour le panneau réécrirait le statut sous les flèches
|
||||
* de qui est en train de le choisir.
|
||||
*/
|
||||
function aLeFocus(controle, actif) {
|
||||
if (controle instanceof RadioNodeList) return [...controle].includes(actif);
|
||||
return controle === actif;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user