Files
planflow/src/components/planning/ShiftChip.tsx
T
Claude 93852c2602 Fix four defects the screenshots and tests exposed
Verifying the rendered pages rather than the build turned up real bugs:

- The WP-00 health page still sat at src/app/page.tsx and silently won
  the route over the new Aperçu screen, so the home page was a database
  status readout. Moved to /api/sante, where a probe belongs, and wired
  into the compose healthcheck.
- The unassigned row showed a +14 h delta against a contract of zero,
  reading as an overshoot when it is simply the volume left to staff.
  It now shows what there is to fill.
- Two sidebar entries lit at once: an anchor link matched its own page,
  and /equipe matched an employee record. Highlighting now resolves to
  the most specific match, and a test asserts exactly one entry lights
  per screen.
- Section tabs with no built screen pointed at the home page, which
  reads as a broken tab. They now lead to their first entry's
  placeholder.

Also gives truncated compliance alerts a title attribute, so a narrow
cell no longer says there is a problem without saying which.

Playwright can reuse a preinstalled browser through
PLAYWRIGHT_CHROMIUM_PATH when its revision differs from the bundled one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-07 22:26:38 +00:00

104 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { posteShort, posteTokens, type PosteCode } from '@/lib/design/postes';
import { cx } from '@/lib/cx';
/**
* États d'un créneau, repris du design system.
*
* `alert` ajoute un liseré danger de 1,5 px : la couleur du poste occupe déjà
* le fond, l'alerte doit donc passer par la forme, pas par une seconde teinte
* qui entrerait en concurrence avec la palette catégorielle.
*/
export type ShiftState =
| 'published'
| 'validated'
| 'draft'
| 'unpublished'
| 'alert'
| 'unassigned';
export interface ShiftChipProps {
poste: PosteCode;
/** « 09:00–17:00 ». */
time: string;
state?: ShiftState;
/** Écart affiché en pastille, ex. « +2 h 55 ». */
delta?: string;
/** Motif de l'alerte, ex. « Repos < 11 h ». */
alert?: string;
/** Durée de pause, en minutes. */
breakMinutes?: number;
className?: string;
}
export function ShiftChip({
poste,
time,
state = 'published',
delta,
alert,
breakMinutes,
className,
}: ShiftChipProps) {
const tokens = posteTokens(poste);
const ghost = state === 'unassigned';
return (
<div
className={cx(
'flex min-w-0 flex-col gap-px rounded-2 px-1.5 py-1 text-micro',
state === 'draft' && 'border-dashed',
className,
)}
style={{
background: ghost ? 'transparent' : tokens.bg,
color: ghost ? 'var(--color-ink-2)' : tokens.fg,
border:
state === 'alert'
? '1.5px solid var(--color-danger)'
: ghost
? `1px dashed ${tokens.edge}`
: state === 'draft'
? `1px dashed ${tokens.edge}`
: state === 'unpublished'
? `1px dotted ${tokens.edge}`
: '1px solid transparent',
}}
>
<span className="flex items-center gap-1">
{/* Le code du poste est le second canal : la couleur ne suffit pas. */}
<span className="font-semibold tracking-wide">{posteShort(poste)}</span>
{state === 'validated' ? (
<span className="text-ok" title="Validé" aria-label="Validé">
✓
</span>
) : null}
{delta ? (
<span className="tnum ml-auto rounded-full bg-warn-soft px-1 font-semibold text-warn-soft-ink">
{delta}
</span>
) : null}
</span>
<span className="tnum truncate">
{time}
{breakMinutes ? (
<span className="opacity-70"> ({breakMinutes} mn)</span>
) : null}
</span>
{alert ? (
// Le motif est tronqué dans une cellule étroite : le `title` garde le
// texte complet accessible, sinon l'alerte dit qu'il y a un problème
// sans jamais dire lequel.
<span
className="mt-px flex items-center gap-1 font-medium text-danger-soft-ink"
title={alert}
>
<span aria-hidden>▲</span>
<span className="truncate">{alert}</span>
</span>
) : null}
</div>
);
}