From 5ab983213138dfc524f9fdd2788b93d71b112260 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 27 Aug 2026 08:13:30 +0200 Subject: [PATCH 1/2] fix(recipes): pre-selectionne la technique deja detectee dans le correcteur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ouvrir le popover de correction depuis un highlight existant tombait sur la liste de choix (avec l'option "Aucune technique ici"), pas sur la vue metadonnees — pour voir/editer les ingredients/ustensiles deja rattaches a une technique correctement detectee, il fallait recliquer cette meme technique dans la liste, sans aucun indice que c'est ce qu'il fallait faire (rien ne la distingue des autres dans cette liste). Resultat cote utilisateur : la fonctionnalite de correction/edition de metadonnees etait techniquement presente mais invisible en pratique. TechStepCorrectionPopover demarre desormais selectionne sur `previousTechStepId` quand il est defini (un clic sur un highlight existant) — droit dans la vue Ingredients/Ustensiles, deja pre-remplie. "Changer" reste disponible pour rejoindre la liste complete (relabelliser ou supprimer la correspondance). Tests Cypress (component + e2e) mis a jour pour ce nouveau point d'entree par defaut. Co-Authored-By: Claude Sonnet 5 --- .../component/TechStepCorrectionPopover.cy.tsx | 11 +++++++++-- apps/web/cypress/e2e/recipes.ts | 7 ++++++- .../recipes/steps/TechStepCorrectionPopover.tsx | 12 +++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/web/cypress/component/TechStepCorrectionPopover.cy.tsx b/apps/web/cypress/component/TechStepCorrectionPopover.cy.tsx index 6c0cf07..08a4da2 100644 --- a/apps/web/cypress/component/TechStepCorrectionPopover.cy.tsx +++ b/apps/web/cypress/component/TechStepCorrectionPopover.cy.tsx @@ -103,8 +103,13 @@ describe("TechStepCorrectionPopover", () => { cy.wait("@getTechSteps"); cy.get(".tech-step-correction-popover__remove").should("not.exist"); + // An existing match starts pre-selected straight into the metadata view + // (see TechStepCorrectionPopover's own doc comment on why) — "Changer" + // reaches the pick list, where the remove option lives. cy.mount(); cy.wait("@getTechSteps"); + cy.get(".tech-step-correction-popover__remove").should("not.exist"); + cy.contains("button", "Changer").click(); cy.get(".tech-step-correction-popover__remove").should("exist"); }); @@ -199,9 +204,11 @@ describe("TechStepCorrectionPopover", () => { existingUtensils={[{ utensil: pan, start: 14, end: 23, source: "auto" }]} />, ); + // An existing match starts pre-selected on itself (see + // TechStepCorrectionPopover's own doc comment) — the metadata sections, + // pre-seeded from `existingIngredients`/`existingUtensils`, are visible + // immediately, no need to re-pick "Cuire" from a list first. cy.wait("@getTechSteps"); - - cy.contains(".tech-step-correction-popover__list button", "Mijoter").click(); cy.wait(["@getIngredients", "@getUnits", "@getUtensils"]); cy.contains(".tech-step-correction-popover__chip", "50 g Beurre").find("button").click(); diff --git a/apps/web/cypress/e2e/recipes.ts b/apps/web/cypress/e2e/recipes.ts index af0f2a9..3abd38b 100644 --- a/apps/web/cypress/e2e/recipes.ts +++ b/apps/web/cypress/e2e/recipes.ts @@ -114,8 +114,13 @@ Then("I should see the technique correction options", () => { // "Valider" click to actually submit (room was made for attaching // ingredient/utensil metadata first, see `TechStepCorrectionPopover.tsx`'s // own doc comment) — folded into this one step since nothing in this -// scenario cares about that intermediate state on its own. +// scenario cares about that intermediate state on its own. This step is +// only ever reached after clicking an *existing* highlight (see the +// previous step above), which now opens straight into that same +// technique's metadata view, not the pick list — "Changer" reaches the +// list this step actually needs to pick a different one from. When("I choose {string} as the correct technique", (label: string) => { + cy.contains("button", "Changer").click(); cy.contains(".tech-step-correction-popover__list button", label).click(); cy.contains(".tech-step-correction-popover__confirm-button", "Valider").click(); }); diff --git a/apps/web/src/features/recipes/steps/TechStepCorrectionPopover.tsx b/apps/web/src/features/recipes/steps/TechStepCorrectionPopover.tsx index 7493b46..ce24cf0 100644 --- a/apps/web/src/features/recipes/steps/TechStepCorrectionPopover.tsx +++ b/apps/web/src/features/recipes/steps/TechStepCorrectionPopover.tsx @@ -118,7 +118,17 @@ export function TechStepCorrectionPopover({ const { t } = useTranslation(); const popoverRef = useRef(null); const [techSteps, setTechSteps] = useState(null); - const [selectedTechStepId, setSelectedTechStepId] = useState(null); + // Opening this popover on an *already-detected* match (`previousTechStepId + // !== null`, i.e. the user clicked an existing highlight rather than + // selecting fresh text) starts pre-selected on that same technique — + // straight into the Ingrédients/Ustensiles view below, not the pick-a- + // technique list. Without this, editing an already-correct technique's + // metadata required re-picking that exact same technique from the list + // first, with nothing in the list marking it as the current one — the + // metadata sections were technically reachable but effectively + // undiscoverable. "Changer de technique" (below) still reaches the full + // list — to relabel, or to remove via the option only shown there. + const [selectedTechStepId, setSelectedTechStepId] = useState(previousTechStepId); const [catalogs, setCatalogs] = useState<{ ingredients: IngredientView[]; units: UnitView[]; -- 2.45.2 From 478914787fc3a1d1d2f362b4c3ce0d057706788b Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 27 Aug 2026 08:30:59 +0200 Subject: [PATCH 2/2] feat(recipes): fusionne l'edition de la technique et des metadonnees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deux retours utilisateur distincts, meme cause : le popover de correction presentait la selection de technique et l'edition des ingredients/ ustensiles comme deux etapes successives (liste plate -> "Valider" pour la selection, puis un ecran separe pour les metadonnees), et cette liste de techniques elle-meme n'etait qu'un flot de ~74 boutons sans recherche ni tri, illisible en pratique. TechStepCorrectionPopover.tsx : les deux fonctions fusionnent en un seul ecran — la technique se choisit desormais via le meme CatalogSearchPicker (recherche + liste filtrée) deja utilise pour les ingredients/ustensiles, avec le choix courant marque visuellement (nouveau prop `selectedId`), et les sections Ingredients/Ustensiles restent affichees en permanence a cote plutot que masquees tant qu'aucune technique n'est choisie. "Valider" reste desactive tant qu'aucune technique n'est selectionnee. CatalogSearchPicker.tsx : nouveau prop optionnel `selectedId` pour marquer visuellement l'item courant dans la liste (utilise par le picker de technique, pas par les sous-flux ingredient/ustensile qui n'ont pas de notion de "choix courant"). Tests Cypress (component + e2e) et traductions mis a jour pour ce nouvel ecran unique. Co-Authored-By: Claude Sonnet 5 --- .../TechStepCorrectionPopover.cy.tsx | 95 +++++++++----- apps/web/cypress/e2e/recipes.ts | 24 ++-- apps/web/src/features/recipes/recipes.scss | 106 ++++++++-------- .../recipes/steps/CatalogSearchPicker.tsx | 31 +++-- .../steps/TechStepCorrectionPopover.tsx | 117 +++++++++--------- apps/web/src/locales/fr/translation.json | 6 +- 6 files changed, 215 insertions(+), 164 deletions(-) diff --git a/apps/web/cypress/component/TechStepCorrectionPopover.cy.tsx b/apps/web/cypress/component/TechStepCorrectionPopover.cy.tsx index 08a4da2..2eaf71c 100644 --- a/apps/web/cypress/component/TechStepCorrectionPopover.cy.tsx +++ b/apps/web/cypress/component/TechStepCorrectionPopover.cy.tsx @@ -90,41 +90,65 @@ describe("TechStepCorrectionPopover", () => { ); }); - it("shows the selected text and every technique option once loaded", () => { + it("shows the selected text, the technique catalog (searchable) and the metadata sections all together", () => { cy.mount(); cy.wait("@getTechSteps"); cy.contains(".tech-step-correction-popover__selection", "Cuire").should("be.visible"); - cy.get(".tech-step-correction-popover__list button").should("have.length", 2); - }); - - it("offers a 'no technique here' option only when correcting an existing match", () => { - cy.mount(); - cy.wait("@getTechSteps"); - cy.get(".tech-step-correction-popover__remove").should("not.exist"); - - // An existing match starts pre-selected straight into the metadata view - // (see TechStepCorrectionPopover's own doc comment on why) — "Changer" - // reaches the pick list, where the remove option lives. - cy.mount(); - cy.wait("@getTechSteps"); - cy.get(".tech-step-correction-popover__remove").should("not.exist"); - cy.contains("button", "Changer").click(); - cy.get(".tech-step-correction-popover__remove").should("exist"); - }); - - it("selecting a technique reveals the Ingrédients/Ustensiles sections instead of submitting immediately", () => { - cy.mount(); - cy.wait("@getTechSteps"); - - cy.contains(".tech-step-correction-popover__list button", "Mijoter").click(); - - cy.get(".tech-step-correction-popover__list").should("not.exist"); + // Merged editor (see TechStepCorrectionPopover's own doc comment) — no + // separate "pick, then metadata reveals itself" step, both render at + // once, and the technique catalog goes through the same searchable + // `CatalogSearchPicker` as the ingredient/utensil sub-flows (a plain + // unfiltered list of the real ~74-entry catalog isn't browsable). + cy.get( + ".tech-step-correction-popover__technique-section .catalog-search-picker__list button", + ).should("have.length", 2); cy.contains("h4", "Ingrédients").should("be.visible"); cy.contains("h4", "Ustensiles").should("be.visible"); cy.contains("button", "Valider").should("be.visible"); }); + it("offers a 'no technique here' option, and marks the current pick, only when correcting an existing match", () => { + cy.mount(); + cy.wait("@getTechSteps"); + cy.get(".tech-step-correction-popover__remove").should("not.exist"); + cy.contains(".tech-step-correction-popover__chosen-technique", "Aucune technique sélectionnée"); + + cy.mount(); + cy.wait("@getTechSteps"); + cy.get(".tech-step-correction-popover__remove").should("exist"); + cy.contains(".tech-step-correction-popover__chosen-technique", "Cuire"); + cy.contains(".catalog-search-picker__list button", "Cuire").should( + "have.class", + "catalog-search-picker__item--selected", + ); + }); + + it("picking a technique from the catalog selects it without submitting immediately", () => { + cy.mount(); + cy.wait("@getTechSteps"); + + cy.contains( + ".tech-step-correction-popover__technique-section .catalog-search-picker__list button", + "Mijoter", + ).click(); + + cy.contains(".tech-step-correction-popover__chosen-technique", "Mijoter"); + cy.contains("button", "Valider").should("be.visible"); + }); + + it("Valider stays disabled until a technique is actually picked", () => { + cy.mount(); + cy.wait("@getTechSteps"); + + cy.contains("button", "Valider").should("be.disabled"); + cy.contains( + ".tech-step-correction-popover__technique-section .catalog-search-picker__list button", + "Mijoter", + ).click(); + cy.contains("button", "Valider").should("not.be.disabled"); + }); + it("submits the selected technique (no metadata touched) with ingredients/utensils omitted from the request", () => { cy.intercept("POST", "**/recipes/2/steps/2/corrections", { statusCode: 201, @@ -141,7 +165,10 @@ describe("TechStepCorrectionPopover", () => { cy.mount(); cy.wait("@getTechSteps"); - cy.contains(".tech-step-correction-popover__list button", "Mijoter").click(); + cy.contains( + ".tech-step-correction-popover__technique-section .catalog-search-picker__list button", + "Mijoter", + ).click(); cy.contains("button", "Valider").click(); cy.wait("@submitCorrection").its("request.body").should("deep.equal", { @@ -167,11 +194,14 @@ describe("TechStepCorrectionPopover", () => { }).as("submitCorrection"); cy.mount(); cy.wait("@getTechSteps"); - - cy.contains(".tech-step-correction-popover__list button", "Mijoter").click(); - cy.contains("button", "+ Ajouter un ingrédient").click(); cy.wait(["@getIngredients", "@getUnits", "@getUtensils"]); + cy.contains( + ".tech-step-correction-popover__technique-section .catalog-search-picker__list button", + "Mijoter", + ).click(); + cy.contains("button", "+ Ajouter un ingrédient").click(); + cy.contains(".catalog-search-picker button", "Beurre").click(); cy.get('input[type="number"]').type("50"); cy.get("select").select(String(gram.id)); @@ -224,7 +254,10 @@ describe("TechStepCorrectionPopover", () => { cy.mount(); cy.wait("@getTechSteps"); - cy.contains(".tech-step-correction-popover__list button", "Cuire").click(); + cy.contains( + ".tech-step-correction-popover__technique-section .catalog-search-picker__list button", + "Cuire", + ).click(); cy.contains("button", "Valider").click(); cy.wait("@submitCorrection"); diff --git a/apps/web/cypress/e2e/recipes.ts b/apps/web/cypress/e2e/recipes.ts index 3abd38b..206e4fb 100644 --- a/apps/web/cypress/e2e/recipes.ts +++ b/apps/web/cypress/e2e/recipes.ts @@ -110,18 +110,20 @@ Then("I should see the technique correction options", () => { cy.get(".tech-step-correction-popover").should("be.visible"); }); -// Picking a technique only *selects* it now — it takes a separate -// "Valider" click to actually submit (room was made for attaching -// ingredient/utensil metadata first, see `TechStepCorrectionPopover.tsx`'s -// own doc comment) — folded into this one step since nothing in this -// scenario cares about that intermediate state on its own. This step is -// only ever reached after clicking an *existing* highlight (see the -// previous step above), which now opens straight into that same -// technique's metadata view, not the pick list — "Changer" reaches the -// list this step actually needs to pick a different one from. +// Picking a technique only *selects* it — it takes a separate "Valider" +// click to actually submit (room was made for attaching ingredient/utensil +// metadata alongside it, see `TechStepCorrectionPopover.tsx`'s own doc +// comment on its merged editor) — folded into this one step since nothing +// in this scenario cares about that intermediate state on its own. The +// technique catalog is picked via the same searchable `CatalogSearchPicker` +// the ingredient/utensil sub-flows use, scoped to +// `__technique-section` since that same search-and-pick component is +// reused inside this popover for more than just techniques. When("I choose {string} as the correct technique", (label: string) => { - cy.contains("button", "Changer").click(); - cy.contains(".tech-step-correction-popover__list button", label).click(); + cy.contains( + ".tech-step-correction-popover__technique-section .catalog-search-picker__list button", + label, + ).click(); cy.contains(".tech-step-correction-popover__confirm-button", "Valider").click(); }); diff --git a/apps/web/src/features/recipes/recipes.scss b/apps/web/src/features/recipes/recipes.scss index cf0a77c..fe06ec9 100644 --- a/apps/web/src/features/recipes/recipes.scss +++ b/apps/web/src/features/recipes/recipes.scss @@ -729,40 +729,38 @@ margin: 0 0 var(--space-sm); } - &__list { - display: flex; - flex-wrap: wrap; - gap: var(--space-xs); - list-style: none; - margin: 0 0 var(--space-sm); - padding: 0; - - button { - padding: 0.3rem 0.6rem; + // Technique picker + Ingrédients/Ustensiles render together as one + // screen now (see `TechStepCorrectionPopover.tsx`'s own doc comment) — + // this section just needs its own small header row, the actual picker + // is `.catalog-search-picker` (below), reused as-is from the ingredient/ + // utensil sub-flows. + &__technique-section { + h4 { + margin: 0 0 var(--space-xs); font-size: var(--font-size-sm); - color: var(--color-text); - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius-pill); - cursor: pointer; - - &:hover:not(:disabled) { - background: color-mix(in srgb, var(--color-primary) 14%, transparent); - border-color: var(--color-primary); - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - } + color: var(--color-text-muted); } + } - // Nested (rather than a sibling `&__remove` block) so its border-color - // wins over the plain `button` rule above by class-count specificity, - // no `!important` needed. - .tech-step-correction-popover__remove { - color: var(--color-error); - border-color: var(--color-error); + &__technique-header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--space-sm); + } + + &__remove { + padding: 0.2rem 0.5rem; + font-size: var(--font-size-sm); + color: var(--color-error); + background: none; + border: 1px solid var(--color-error); + border-radius: var(--radius-pill); + cursor: pointer; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; } } @@ -808,36 +806,27 @@ gap: var(--space-sm); } + // "Aucune technique sélectionnée."/"Technique retenue : X" — no button + // here anymore (re-picking happens directly through the search picker + // right below, see `TechStepCorrectionPopover.tsx`'s doc comment on the + // merged editor), just a small status line. &__chosen-technique { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-sm); - margin: 0; + margin: 0 0 var(--space-xs); font-weight: 600; } - &__change { - background: none; - border: none; - color: var(--color-primary); - text-decoration: underline; - cursor: pointer; - padding: 0; - font-size: var(--font-size-sm); - font-weight: 400; - } - &__metadata-section { + margin-top: var(--space-sm); + h4 { margin: 0 0 var(--space-xs); font-size: var(--font-size-sm); color: var(--color-text-muted); } - // "+ Ajouter…" button — deliberately the same plain-text-link styling - // as `&__change` above, not another pill button (`&__list button`) — - // this is a secondary action inside an already-open popover, not a + // "+ Ajouter…" button — deliberately a plain text-link style, not + // another pill button (`.catalog-search-picker__list button`) — this + // is a secondary action inside an already-open popover, not a // top-level choice competing with the chips above it. > button { background: none; @@ -925,7 +914,10 @@ list-style: none; margin: 0; padding: 0; - max-height: 8rem; + // Raised from the original 8rem — this component is now also the + // technique picker (~74 entries, see this file's own doc comment), + // where 8rem left only a couple of rows visible before scrolling. + max-height: 14rem; overflow-y: auto; button { @@ -941,6 +933,18 @@ background: color-mix(in srgb, var(--color-primary) 14%, transparent); border-color: var(--color-primary); } + + // The technique picker's current pick (`selectedId` prop) — stays + // visually marked even while filtered/scrolled past, so re-opening + // this popover's picker doesn't read as "nothing chosen yet" when + // something already is. Unused by the ingredient/utensil sub-flows + // (they never pass `selectedId` — each pick there just appends a + // fresh mention, nothing to mark as "current"). + &.catalog-search-picker__item--selected { + background: color-mix(in srgb, var(--color-primary) 20%, transparent); + border-color: var(--color-primary); + font-weight: 600; + } } } } diff --git a/apps/web/src/features/recipes/steps/CatalogSearchPicker.tsx b/apps/web/src/features/recipes/steps/CatalogSearchPicker.tsx index 0cccd2f..47ca431 100644 --- a/apps/web/src/features/recipes/steps/CatalogSearchPicker.tsx +++ b/apps/web/src/features/recipes/steps/CatalogSearchPicker.tsx @@ -3,25 +3,30 @@ import { useState } from "react"; /** * Small search-and-pick list — a lighter alternative to `IngredientPicker.tsx` * (category/subcategory grid + allergen/diet toggles) for a context that - * doesn't have room for that: `TechStepCorrectionPopover.tsx`'s "attach an - * ingredient/utensil to this correction" flow, embedded in a small popover - * rather than a full recipe form. Reused for both — an ingredient and a - * utensil are both "search a short reference list by translated label, pick - * one" from this component's point of view, the only difference is which - * `items`/labels the caller passes in. + * doesn't have room for that: `TechStepCorrectionPopover.tsx`'s ingredient/ + * utensil/**technique** pickers, all embedded in a small popover rather than + * a full recipe form. Reused for all three — an ingredient, a utensil, and a + * technique are all "search a reference list by translated label, pick one" + * from this component's point of view, the only difference is which + * `items`/labels the caller passes in. The technique catalog in particular + * (~74 entries) is exactly the case a plain unfiltered list stops being + * readable at — the original motivation for adding search here at all. * * Deliberately just `{ id, label }` in, `id` out — no `IngredientView`/ - * `UtensilView` dependency here, so this stays reusable for any future - * "search this small reference catalog" need without growing a new prop - * per catalog shape. + * `UtensilView`/`TechStepView` dependency here, so this stays reusable for + * any future "search this small reference catalog" need without growing a + * new prop per catalog shape. */ export function CatalogSearchPicker({ items, + selectedId, onSelect, placeholder, emptyLabel, }: { items: { id: number; label: string }[]; + /** The currently-picked item, if any — marked with a distinct modifier class so it stays visible at a glance while browsing/filtering a longer list (e.g. `TechStepCorrectionPopover`'s ~74-entry technique catalog), not just implied by whatever's selected elsewhere on screen. Omit for a picker with no notion of a "current" pick (the ingredient/utensil span sub-flows — each `onSelect` there just appends a brand-new mention, nothing to mark as already chosen). */ + selectedId?: number; onSelect: (id: number) => void; placeholder: string; emptyLabel: string; @@ -48,7 +53,13 @@ export function CatalogSearchPicker({
    {visible.map((item) => (
  • -
  • diff --git a/apps/web/src/features/recipes/steps/TechStepCorrectionPopover.tsx b/apps/web/src/features/recipes/steps/TechStepCorrectionPopover.tsx index ce24cf0..c20e3b8 100644 --- a/apps/web/src/features/recipes/steps/TechStepCorrectionPopover.tsx +++ b/apps/web/src/features/recipes/steps/TechStepCorrectionPopover.tsx @@ -61,12 +61,17 @@ function toPendingUtensil(view: StepTechStepUtensilView): PendingUtensil { * first — the description stays fully visible and selectable the whole * time, nothing overlays it. * - * **Removing** a match (`submit(null)`) stays a single immediate action — - * nothing to attach when removing. **Picking/relabeling** a technique used - * to submit immediately too; it no longer does, so there's room to attach - * metadata before committing — clicking a technique now only *selects* it - * (`selectedTechStepId`), revealing the Ingrédients/Ustensiles sections and - * a final "Valider" button that submits everything together. + * **One merged editor, not a wizard**: picking a technique + * (`CatalogSearchPicker`, searchable — the reference catalog is ~74 + * entries, an unfiltered flat list wasn't browsable) and editing its + * Ingrédients/Ustensiles metadata render together on the same screen, + * always — there's no separate "pick, then a metadata step reveals + * itself" sequence to go through, and no dead end where metadata is + * technically attachable but not visible until some other action happens + * first. A single "Valider" submits everything at once; disabled until a + * technique is actually selected (there's nothing to attach metadata to + * otherwise). **Removing** a match (`submit(null)`) stays its own + * immediate action next to the picker — nothing to attach when removing. * * The two metadata sections are pre-seeded from `existingIngredients`/ * `existingUtensils` (whatever's already attached to this occurrence, auto- @@ -120,14 +125,11 @@ export function TechStepCorrectionPopover({ const [techSteps, setTechSteps] = useState(null); // Opening this popover on an *already-detected* match (`previousTechStepId // !== null`, i.e. the user clicked an existing highlight rather than - // selecting fresh text) starts pre-selected on that same technique — - // straight into the Ingrédients/Ustensiles view below, not the pick-a- - // technique list. Without this, editing an already-correct technique's - // metadata required re-picking that exact same technique from the list - // first, with nothing in the list marking it as the current one — the - // metadata sections were technically reachable but effectively - // undiscoverable. "Changer de technique" (below) still reaches the full - // list — to relabel, or to remove via the option only shown there. + // selecting fresh text) starts pre-selected on that same technique, its + // name shown next to the picker right away — since the picker and the + // metadata sections render together regardless (see this component's own + // doc comment), this just saves re-picking the technique that's already + // correct before its metadata becomes editable. const [selectedTechStepId, setSelectedTechStepId] = useState(previousTechStepId); const [catalogs, setCatalogs] = useState<{ ingredients: IngredientView[]; @@ -183,12 +185,12 @@ export function TechStepCorrectionPopover({ }; }, []); - // Only fetched once a technique is actually selected — the Ingrédients/ - // Ustensiles sections (the only things that need these) don't render - // before then, so a popover only ever used to relabel/remove a technique - // never pays for these three extra requests. + // Fetched unconditionally on mount — the Ingrédients/Ustensiles sections + // render alongside the technique picker from the start (see this + // component's own doc comment on the merged editor), so there's no later + // point to defer this to anymore. useEffect(() => { - if (selectedTechStepId === null || catalogs !== null) return; + if (catalogs !== null) return; let cancelled = false; Promise.all([apiClient.getIngredients(), apiClient.getUnits(), apiClient.getUtensils()]) .then(([ingredients, units, utensils]) => { @@ -200,7 +202,7 @@ export function TechStepCorrectionPopover({ return () => { cancelled = true; }; - }, [selectedTechStepId, catalogs]); + }, [catalogs]); // Consumes a span `StepDescription` just resolved on this popover's // behalf (see `resolvedMetadataSpan`'s own doc comment above) — opens the @@ -401,47 +403,42 @@ export function TechStepCorrectionPopover({ ) : techSteps === null ? (

    {t("recipes.loading")}

    - ) : selectedTechStepId === null ? ( -
      - {previousTechStepId !== null && ( -
    • - -
    • - )} - {techSteps.map((techStep) => ( -
    • - -
    • - ))} -
    ) : (
    -

    - {t( - `catalog.techSteps.${techSteps.find((ts) => ts.id === selectedTechStepId)?.key ?? ""}`, - )} - -

    +
    +
    +

    {t("recipes.techStepCorrection.techniqueSection")}

    + {previousTechStepId !== null && ( + + )} +
    +

    + {selectedTechStepId !== null + ? t("recipes.techStepCorrection.currentTechnique", { + technique: t( + `catalog.techSteps.${techSteps.find((ts) => ts.id === selectedTechStepId)?.key ?? ""}`, + ), + }) + : t("recipes.techStepCorrection.noTechniqueSelected")} +

    + ({ + id: techStep.id, + label: t(`catalog.techSteps.${techStep.key}`), + }))} + selectedId={selectedTechStepId ?? undefined} + onSelect={setSelectedTechStepId} + placeholder={t("recipes.techStepCorrection.searchTechniquePlaceholder")} + emptyLabel={t("recipes.techStepCorrection.noTechniqueFound")} + /> +

    {t("recipes.techStepCorrection.ingredientsSection")}

    @@ -504,7 +501,7 @@ export function TechStepCorrectionPopover({ type="button" className="tech-step-correction-popover__confirm-button" onClick={confirm} - disabled={isSubmitting} + disabled={isSubmitting || selectedTechStepId === null} > {t("recipes.techStepCorrection.confirm")} diff --git a/apps/web/src/locales/fr/translation.json b/apps/web/src/locales/fr/translation.json index 681a8dd..f19bbbf 100644 --- a/apps/web/src/locales/fr/translation.json +++ b/apps/web/src/locales/fr/translation.json @@ -170,8 +170,12 @@ "cancel": "Annuler", "manualTooltip": "{{technique}} (correction manuelle)", "discoverabilityHint": "💡 Sélectionnez du texte, ou cliquez sur une technique surlignée, pour la corriger.", - "changeTechnique": "Changer", "confirm": "Valider", + "techniqueSection": "Technique", + "currentTechnique": "Technique retenue : {{technique}}", + "noTechniqueSelected": "Aucune technique sélectionnée.", + "searchTechniquePlaceholder": "Rechercher une technique…", + "noTechniqueFound": "Aucune technique trouvée.", "ingredientsSection": "Ingrédients", "utensilsSection": "Ustensiles", "addIngredient": "+ Ajouter un ingrédient", -- 2.45.2