batchCooking/apps/web/src/pages/cooking-session/cooking-session-page.scss
Nicolas 85268729e6
All checks were successful
CI / lint (push) Successful in 1m57s
CI / build (push) Successful in 3m2s
CI / e2e (push) Successful in 7m59s
CI / intent-service-test (push) Successful in 18m44s
CI / test (push) Successful in 28m2s
feat(cooking-session): regroupement d'étapes sensible aux métadonnées d'action
L'optimiseur ne mutualisait que le pré-découpage. Il regroupe maintenant
aussi les actions SETUP (préchauffage, ébullition) entre recettes, mais
seulement quand elles sont compatibles : deux préchauffages ne fusionnent
que s'ils visent la même température détectée. Deux préchauffages à des
températures différentes restent séparés, ordonnés du plus froid au plus
chaud, et la mise en place porte une note « Four requis à N températures —
enchaîner … ». Défaut optimiste : deux préchauffages sans température
détectée fusionnent quand même (supposés compatibles).

- cooking-optimizer.ts : temperatureKey (comparaison exacte, aucune
  conversion inter-représentations), isPureSetupStep / setupSignature /
  extractMergedSetup en miroir du chemin merged-prep ; comparableTemp pour
  l'ordre seul ; detectApplianceTemperatureConflicts → CookingPhaseView.notes
- cooking-session.service.ts : `temperatures` dans l'include + mapping
- packages/shared : CookingTemperatureView, kind « merged-setup »,
  CookingTaskView.temperatures, CookingPhaseNoteView, CookingPhaseView.notes
- web : temperature-label.ts extrait de StepDescription et réutilisé ;
  taskHeadline gère merged-setup ; phaseNoteText ; bandeau .cooking-phase__note
- i18n : cookingSession.task.mergedSetupLabel, cookingSession.note.*
- tests : 7 cas Mocha « setup pooling » + test DB cooking-session,
  cas Cypress mode layout + scénario Gherkin

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:05:44 +02:00

421 lines
11 KiB
SCSS

// =============================================================================
// Styles specific to CookingSessionPage — colocated next to
// CookingSessionPage.tsx since nothing else uses these classes.
//
// Layout: a fixed page shell (header + subtitle + tab bar), then one of two
// tab panels. The "Recettes" panel is a plain scrolling list; the "Plan"
// panel (`.cooking-plan`) is itself a flex column — a pinned stepper and
// meta line on top, a pinned prev/next bar at the bottom, and the current
// phase scrolling in between so the nav is always reachable.
//
// Every value goes through a `var(--token)` from _theme.scss; light/dark is
// handled entirely by those tokens being redefined, nothing here branches
// on the theme.
// =============================================================================
.cooking-session-page {
height: 100%;
display: flex;
flex-direction: column;
&__header {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: var(--space-md);
margin-bottom: var(--space-sm);
}
&__subtitle {
flex-shrink: 0;
margin: 0 0 var(--space-md);
color: var(--color-text-muted);
font-size: var(--font-size-md);
}
&__status {
color: var(--color-text-muted);
font-size: var(--font-size-md);
}
&__status--error {
color: var(--color-error);
}
// --- Tab bar ---------------------------------------------------------------
// Same underline-tab language as the recipe catalog's `.recipe-tabs`
// (features/recipes/recipes.scss): scrolls within itself rather than
// widening the page (global.scss forbids a horizontal body scroll).
&__tabs {
flex-shrink: 0;
display: flex;
gap: var(--space-xs);
overflow-x: auto;
border-bottom: 1px solid var(--color-border);
margin-bottom: var(--space-md);
}
&__tab {
appearance: none;
flex-shrink: 0;
padding: var(--space-sm) var(--space-md);
font-family: var(--font-display);
font-size: var(--font-size-sm);
font-weight: 700;
letter-spacing: 0.02em;
color: var(--color-text-muted);
background: none;
border: none;
border-bottom: 2.5px solid transparent;
cursor: pointer;
transition:
color 0.15s ease,
border-color 0.15s ease;
&:hover {
color: var(--color-text);
}
&.active {
color: var(--color-primary);
border-color: var(--color-primary);
}
}
}
// --- "Recettes" tab ---------------------------------------------------------
// The week's planned recipes as a flat list — name, its own portion count,
// and a jump to the recipe's page. Deliberately thin: `plan.recipes` only
// carries `{recipeId, name, portions}`.
.cooking-recipes {
flex: 1;
min-height: 0;
overflow-y: auto;
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-xs);
&__item {
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md);
background: var(--color-surface);
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
}
&__name {
font-weight: 600;
color: var(--color-text);
}
&__portions {
padding: 0.1rem var(--space-sm);
border-radius: var(--radius-pill);
background: var(--color-surface-alt);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
font-variant-numeric: tabular-nums;
}
// Pushed to the trailing edge — a quiet secondary action, not the point
// of the row.
&__link {
margin-left: auto;
flex-shrink: 0;
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--color-primary);
}
}
// --- "Plan" tab -----------------------------------------------------------
.cooking-plan {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: var(--space-md);
// Meta line under the stepper: "Étape 2 / 4 · Cuisson" on the left, the
// "{done} / {total} tâches" counter on the right.
&__meta {
flex-shrink: 0;
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: var(--space-sm);
}
&__position {
font-family: var(--font-display);
font-weight: 700;
font-size: var(--font-size-md);
color: var(--color-text);
}
&__count {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
font-variant-numeric: tabular-nums;
}
// The current phase scrolls; everything around it stays put.
.cooking-phase {
flex: 1;
min-height: 0;
overflow-y: auto;
}
&__nav {
flex-shrink: 0;
display: flex;
justify-content: space-between;
gap: var(--space-sm);
}
&__nav-btn {
appearance: none;
padding: var(--space-sm) var(--space-lg);
border: 1.5px solid var(--color-primary);
border-radius: var(--radius-base);
background: var(--color-surface);
color: var(--color-primary);
font-family: var(--font-display);
font-weight: 700;
font-size: var(--font-size-sm);
cursor: pointer;
transition:
background-color 0.15s ease,
opacity 0.15s ease;
&:hover:not(:disabled) {
background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
}
&:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
// --- Stepper --------------------------------------------------------------
// One clickable node per phase, in a row that scrolls sideways within
// itself when the phases don't all fit. A thin connector sits between
// consecutive nodes.
.cooking-stepper {
flex-shrink: 0;
list-style: none;
margin: 0;
padding: 0 0 var(--space-xs);
display: flex;
gap: var(--space-xs);
overflow-x: auto;
&__step {
flex: 0 0 auto;
display: flex;
align-items: center;
&:not(:last-child)::after {
content: "";
width: var(--space-md);
height: 2px;
background: var(--color-border);
margin: 0 var(--space-xs);
}
}
&__node {
appearance: none;
display: flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-sm);
border: 1.5px solid var(--color-border);
border-radius: var(--radius-pill);
background: var(--color-surface);
color: var(--color-text-muted);
font-family: var(--font-display);
font-size: var(--font-size-sm);
font-weight: 700;
white-space: nowrap;
cursor: pointer;
transition:
color 0.15s ease,
border-color 0.15s ease,
background-color 0.15s ease;
&:hover {
color: var(--color-text);
border-color: var(--color-primary);
}
// The phase currently on screen.
&--current {
color: var(--color-primary);
border-color: var(--color-primary);
background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
}
// Every task in the phase is ticked.
&--done {
color: var(--color-success);
border-color: var(--color-success);
}
}
// The leading circle — a step number, or a check once the phase is done.
&__marker {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.4rem;
height: 1.4rem;
border-radius: var(--radius-pill);
background: var(--color-surface-alt);
font-size: var(--font-size-xs);
font-variant-numeric: tabular-nums;
}
&__node--current &__marker {
background: var(--color-primary);
color: var(--color-surface);
}
&__node--done &__marker {
background: var(--color-success);
color: var(--color-surface);
}
}
// --- One phase ----------------------------------------------------------------
.cooking-phase {
display: flex;
flex-direction: column;
gap: var(--space-sm);
// A phase-level caveat — today only the "four requis à 2 températures"
// conflict. A tinted callout in the accent colour so it reads as a
// heads-up, not a plain paragraph.
&__note {
margin: 0;
padding: var(--space-sm) var(--space-md);
border-left: 3px solid var(--color-accent);
border-radius: 0 var(--radius-md) var(--radius-md) 0;
background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
color: var(--color-text);
font-size: var(--font-size-sm);
font-weight: 600;
}
// "Pendant ce temps" — passive cooks still running from earlier phases.
&__background {
border-left: 3px solid var(--color-border);
padding: var(--space-xs) var(--space-md);
color: var(--color-text-muted);
font-size: var(--font-size-sm);
&-title {
display: block;
font-weight: 600;
margin-bottom: 0.15rem;
}
ul {
margin: 0;
padding-left: var(--space-md);
}
}
&__tasks {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-sm);
> li {
display: flex;
}
// The task card *is* a shared "selectable card" checkbox label (see
// global.scss's `label:has(> input[type="checkbox"])`), so its frame,
// hover, focus ring and hidden native input all come from there. The
// rules below only set what's task-specific — a top-aligned multi-line
// body, the merged-prep accent, and a faded/struck "done" look — and
// are kept at `.cooking-phase__tasks` depth on purpose so they outrank
// that shared rule's own `&.is-selected` treatment.
.cooking-task {
flex: 1;
align-items: flex-start;
}
.cooking-task--merged-prep,
.cooking-task--merged-setup {
border-left: 3px solid var(--color-accent);
}
// Ticked = done: cancel the shared rule's "selected" green tint/border
// (which would clash with the stepper's own green "current" node) and
// just fade the card and strike its headline.
.cooking-task--done {
opacity: 0.7;
background: var(--color-surface);
border-color: var(--color-border);
.cooking-task__headline {
text-decoration: line-through;
}
}
}
}
// --- One task card --------------------------------------------------------
// (frame comes from the shared checkbox-label rule — see above)
.cooking-task {
&__body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.2rem;
}
&__headline {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-xs);
font-weight: 600;
color: var(--color-text);
}
// A pooled prep task is the headline feature of this page — flag it.
&__badge {
padding: 0.05rem var(--space-xs);
border-radius: var(--radius-pill);
background: var(--color-accent);
color: var(--color-surface);
font-size: var(--font-size-xs);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
&__recipes,
&__utensils {
display: block;
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
}