Ajouter une vue des tâches et jalons groupés par mois
La frise répond mal à « qu'est-ce qui arrive le mois prochain » : la question traverse les projets, alors que la frise les range précisément en couloirs séparés. Il fallait balayer une colonne de dates à travers tous les couloirs en gardant en tête ce qu'on avait vu plus haut — d'où le tableur ressorti à côté. Deux boutons dans la barre d'outils basculent vers une liste qui déroule toutes les phases, projets mêlés, dans l'ordre du calendrier. Chaque ligne se lit quand, chez qui, puis quoi : période, projet et ses tags, intitulé, statut. Une phase est rattachée au seul mois de son début. La répéter dans chaque mois traversé aurait donné un tableau de charge — utile, mais c'est une autre vue, et le doublon coûte la lecture en diagonale qui fait tout l'intérêt de celle-ci. Sa fin est écrite sur la ligne, ce qui suffit à voir qu'elle déborde. Les mois sans rien ne sont pas représentés : l'absence se lit déjà dans le saut d'un titre au suivant. La liste ne s'édite pas, et ce n'est pas une lacune à combler : déplacer une phase demande de voir ce qu'elle chevauche et ce qu'elle repousse, or c'est justement ce voisinage que l'ordre chronologique dissout. Seules les pastilles de tags restent cliquables — filtrer ne change pas les données. Le filtre par tags vaut pour les deux vues, sans rien changer à sa mécanique, si bien qu'on ne se demande jamais si l'une montre plus que l'autre. L'œil (hidden) écarte un projet de la liste comme il écarte ses barres. La vue choisie n'est pas enregistrée, contrairement à la largeur de la colonne des libellés : cette largeur est un réglage d'écran, la vue est un geste de consultation. L'outil s'ouvre donc sur la frise, celle où l'on modifie. Le défilement horizontal de la frise, lui, est mémorisé le temps de l'aller-retour, puisque masquer un élément lui fait perdre son scrollLeft. Deux extractions au passage, chacune parce que deux vues sœurs en ont besoin et qu'aucune n'a à importer l'autre : les noms de mois et le formatage des dates rejoignent model.js, les pastilles de tags un module tags.js. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,21 +13,23 @@
|
||||
|
||||
import {
|
||||
LIBELLES_STATUT,
|
||||
MOIS_COURTS,
|
||||
MOIS_LONGS,
|
||||
ajouterJours,
|
||||
aujourdhui,
|
||||
bornesProjet,
|
||||
bornesReference,
|
||||
cleTag,
|
||||
deriveProjet,
|
||||
dimancheDeLaSemaine,
|
||||
ecartJours,
|
||||
formaterDateLongue,
|
||||
formulerDerive,
|
||||
lundiDeLaSemaine,
|
||||
projetFiltre,
|
||||
semaineISO,
|
||||
teinteTag,
|
||||
versUTC,
|
||||
} from './model.js';
|
||||
import { pastillesTags } from './tags.js';
|
||||
|
||||
/**
|
||||
* Échelle unique : 6 pixels par jour, soit 42 pixels par semaine.
|
||||
@@ -45,16 +47,6 @@ export const PIXELS_PAR_JOUR = 6;
|
||||
*/
|
||||
const LIBELLE_MINI = { annee: 40, mois: 34, semaine: 30 };
|
||||
|
||||
const MOIS_COURTS = [
|
||||
'janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin',
|
||||
'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.',
|
||||
];
|
||||
|
||||
const MOIS_LONGS = [
|
||||
'janvier', 'février', 'mars', 'avril', 'mai', 'juin',
|
||||
'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre',
|
||||
];
|
||||
|
||||
/**
|
||||
* Calcule l'échelle : origine, largeur totale et conversions date <-> pixel.
|
||||
* Toute la géométrie de la frise passe par cet objet.
|
||||
@@ -272,11 +264,6 @@ function moisSuivant(annee, mois) {
|
||||
return mois === 12 ? { annee: annee + 1, mois: 1 } : { annee, mois: mois + 1 };
|
||||
}
|
||||
|
||||
export function formaterDateLongue(date) {
|
||||
const [a, m, j] = date.split('-');
|
||||
return `${j} ${MOIS_COURTS[Number(m) - 1]} ${a}`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Colonne des libellés
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -392,66 +379,6 @@ function libelleProjet(projet, tagsActifs = []) {
|
||||
return ligne;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pastilles de tags d'un projet, dans sa ligne de libellé.
|
||||
*
|
||||
* Chaque pastille est un bouton : cliquer un tag l'ajoute au filtre, le
|
||||
* recliquer l'en retire. C'est le geste le plus court pour passer d'un projet
|
||||
* repéré à « montre-moi tous ceux qui lui ressemblent », sans remonter à la
|
||||
* barre de filtres.
|
||||
*
|
||||
* Au-delà de `PASTILLES_VISIBLES`, les tags se résument à un compteur. La
|
||||
* colonne est étroite et disputée : montrer quatre pastilles y revenait à les
|
||||
* rogner toutes jusqu'à l'initiale, ce qui coûtait la place d'un tag sans en
|
||||
* livrer le nom. Les tags masqués restent lisibles dans l'infobulle, et tous
|
||||
* sont cliquables depuis la barre de filtres.
|
||||
*/
|
||||
const PASTILLES_VISIBLES = 2;
|
||||
|
||||
function pastillesTags(tags, tagsActifs) {
|
||||
const conteneur = document.createElement('span');
|
||||
conteneur.className = 'tags';
|
||||
|
||||
// Deux projets peuvent écrire le même tag différemment (« Client », « client ») :
|
||||
// c'est la clé, pas la graphie, qui décide s'il est actif.
|
||||
const clesActives = new Set(tagsActifs.map(cleTag));
|
||||
|
||||
// Un tag présent dans le filtre courant passe devant : c'est celui qui
|
||||
// explique pourquoi le projet est à l'écran, il ne doit pas tomber dans le
|
||||
// compteur.
|
||||
const ordonnes = [...tags].sort(
|
||||
(a, b) => Number(clesActives.has(cleTag(b))) - Number(clesActives.has(cleTag(a)))
|
||||
);
|
||||
const montres = ordonnes.slice(0, PASTILLES_VISIBLES);
|
||||
const restants = ordonnes.slice(PASTILLES_VISIBLES);
|
||||
|
||||
for (const tag of montres) {
|
||||
const pastille = document.createElement('button');
|
||||
pastille.type = 'button';
|
||||
pastille.className = 'tag';
|
||||
pastille.style.setProperty('--teinte-tag', String(teinteTag(tag)));
|
||||
pastille.textContent = tag;
|
||||
pastille.dataset.action = 'tag';
|
||||
pastille.dataset.tag = tag;
|
||||
|
||||
const actif = clesActives.has(cleTag(tag));
|
||||
pastille.setAttribute('aria-pressed', String(actif));
|
||||
pastille.title = actif ? `Retirer « ${tag} » du filtre` : `Filtrer sur « ${tag} »`;
|
||||
|
||||
conteneur.append(pastille);
|
||||
}
|
||||
|
||||
if (restants.length) {
|
||||
const compteur = document.createElement('span');
|
||||
compteur.className = 'tag--reste';
|
||||
compteur.textContent = `+${restants.length}`;
|
||||
compteur.title = restants.join(', ');
|
||||
conteneur.append(compteur);
|
||||
}
|
||||
|
||||
return conteneur;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Couloirs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user