Grille : colonnes par créneau (pas par heure), largeur adaptative

- `buildLattice` (frontend/src/lib/planning.ts) identifie une colonne par
  (date, heure de début, heure de fin) et non plus par la seule heure de
  début : deux créneaux qui démarrent en même temps mais durent
  différemment ont désormais chacun leur colonne, avec un en-tête « début
  → fin » au lieu du seul début.
- Largeur adaptative par jour (`columnWidthPx`) : peu de créneaux dans une
  journée ⇒ colonnes larges, beaucoup de créneaux ⇒ largeur compacte
  (défilement horizontal) — fini les colonnes étroites par défaut.
- `VolunteerViewPage` mis à jour pour rester synchronisé avec la nouvelle
  clé de colonne (fonction `buildLattice` partagée).
- CLAUDE.md à jour.

Corrige au passage `bg-hatch-mauve` (case en sureffectif) : la classe était
utilisée depuis le commit précédent mais l'utilitaire Tailwind
correspondant n'avait jamais été ajouté à `backgroundImage` — la case
retombait silencieusement sur un fond uni.

typecheck + lint + build + smoke OK.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
AdamThomas
2026-09-11 20:28:41 +02:00
co-authored by Claude Sonnet 5
parent 55f634e25b
commit 367dfe42eb
5 changed files with 56 additions and 14 deletions
+25 -6
View File
@@ -137,11 +137,15 @@ export function candidateChanceScore(params: {
return score;
}
/** Colonne de la grille : une (date, heure de début) distincte. */
/** Colonne de la grille : un créneau distinct (date, heure de début, heure de
* fin) — pas seulement une heure de début : deux créneaux qui démarrent à la
* même heure mais durent différemment ont chacun leur colonne. */
export interface LatticeColumn {
key: string; // `YYYY-MM-DD|HH:MM`
key: string; // `YYYY-MM-DD|HH:MM|HH:MM`
date: string;
time: string;
endTime: string;
label: string; // « 17:00 → 22:00 »
}
export interface LatticeDay {
@@ -150,7 +154,7 @@ export interface LatticeDay {
}
/** Construit le treillis de colonnes à partir de créneaux (grille alignée). */
export function buildLattice(starts: Array<{ starts_at: string }>): {
export function buildLattice(starts: Array<{ starts_at: string; ends_at: string }>): {
days: LatticeDay[];
columns: LatticeColumn[];
} {
@@ -158,8 +162,9 @@ export function buildLattice(starts: Array<{ starts_at: string }>): {
for (const s of starts) {
const date = datePart(s.starts_at);
const time = timePart(s.starts_at);
const key = `${date}|${time}`;
if (!seen.has(key)) seen.set(key, { key, date, time });
const endTime = timePart(s.ends_at);
const key = `${date}|${time}|${endTime}`;
if (!seen.has(key)) seen.set(key, { key, date, time, endTime, label: `${time}–${endTime}` });
}
const columns = [...seen.values()].sort((a, b) => a.key.localeCompare(b.key));
const days: LatticeDay[] = [];
@@ -175,7 +180,21 @@ export function buildLattice(starts: Array<{ starts_at: string }>): {
}
export function latticeFromMatrix(matrix: MatrixResponse) {
return buildLattice(matrix.positions.flatMap((p) => p.shifts.map((s) => ({ starts_at: s.starts_at }))));
return buildLattice(
matrix.positions.flatMap((p) => p.shifts.map((s) => ({ starts_at: s.starts_at, ends_at: s.ends_at }))),
);
}
/**
* Largeur (px) des colonnes d'une journée, en fonction du nombre de créneaux
* distincts qu'elle compte : peu de créneaux ⇒ colonnes larges plutôt que
* resserrées ; beaucoup de créneaux ⇒ largeur compacte (défilement horizontal).
*/
export function columnWidthPx(columnsInDay: number): number {
if (columnsInDay <= 2) return 160;
if (columnsInDay <= 4) return 120;
if (columnsInDay <= 6) return 92;
return 66;
}
/** Heures cumulées par bénévole. */