import type { StepTechStepTemperatureView, StepTechStepView, SubmitTechStepCorrectionResult, } from "@batch-cooking/shared"; import { Fragment, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Tooltip } from "../../../components/ui/Tooltip"; import { splitDescriptionSegments } from "./highlight-tech-steps"; import { TechStepCorrectionPopover } from "./TechStepCorrectionPopover"; import { temperatureLabel } from "./temperature-label"; import { type TextSelectionRange, useTextSelection } from "./use-text-selection"; /** * Finds which of `techSteps`' occurrences a given temperature mention * belongs to — the read-only highlight flattens every occurrence's * `temperatures` together (see this file's own `temperatures` above), * losing that association, so a click handler that needs to reopen "the * technique this temperature is attached to" re-derives it by matching * span. `undefined` when nothing matches (shouldn't happen for a real * persisted temperature, but degrades to "not clickable" rather than a * crash — same posture as `use-text-selection.ts`'s `resolveOffset`). */ function findTechStepForTemperature( techSteps: StepTechStepView[], temperature: StepTechStepTemperatureView, ): StepTechStepView | undefined { return techSteps.find((techStep) => (techStep.temperatures ?? []).some( (candidate) => candidate.start === temperature.start && candidate.end === temperature.end, ), ); } /** * A recipe step's description, with every detected technique's exact * matched words highlighted and given a {@link Tooltip} naming the * technique (e.g. hovering/focusing "hacher" in "Hacher les oignons" shows * "Hacher") — `RecipeDetailPanel`'s replacement for a bare `
{description}
`. * * A match's wider `contextStart`/`contextEnd` clause (see * `StepTechStepView`) is deliberately *not* visualized here — only the * tight keyword span is highlighted. The backend still computes and * persists it (`tech-step-matcher.ts`/`StepTechStep`), and * `splitDescriptionByTechSteps` still splits the description around it * (`isKeyword: false` context segments), but this component now renders * those non-keyword segments as plain text, same as a segment with no * technique at all — the visual "wider clause, subtler highlight" * treatment (`.step-tech-step-context`) turned out to be more visual noise * than useful signal in practice and was turned back off. * * `techStep.key` resolves its tooltip label through `catalog.techSteps.
{segments.map((segment, index) => {
const start = offset;
offset += segment.text.length;
// Captured now, not read as `offset` later inside a click
// handler below — `offset` keeps mutating for every subsequent
// segment this same `.map()` pass renders, so a closure
// referencing it directly would see its *final* value (the end
// of the whole description) whenever it actually fires, long
// after render — found via a real correction submitted with
// `end` far past this segment's own text.
const end = offset;
// A segment's own text/techStep don't uniquely identify it (the
// same word can appear twice in one description) — index is the
// only thing that does, but this list is fully regenerated from
// `description`/`liveTechSteps` on every render (never reordered
// or spliced in place), so using it as part of the key is safe
// here.
const key = `${index}-${segment.text}`;
if (segment.temperature) {
// A detected temperature mention ("180°C", "feu doux") — its
// own subtle highlight + a Tooltip with the normalized value.
// In editable mode, clicking it reopens the correction popover
// for whichever technique occurrence it's attached to (found by
// span, see `findTechStepForTemperature` — temperature is a
// per-clause metadata like ingredients/utensils, corrected
// through that technique's own popover, not on its own) — same
// `data-offset` treatment either way, so a text selection
// spanning it still resolves correctly.
const label = temperatureLabel(segment.temperature, t);
const modifier = segment.temperature.qualitative ?? "value";
const owner = editable
? findTechStepForTemperature(liveTechSteps, segment.temperature)
: undefined;
const className = `step-temperature step-temperature--${modifier}`;
if (owner === undefined || owner.start === null || owner.end === null) {
// Read-only, or (for a persisted temperature this shouldn't
// happen, see `findTechStepForTemperature`'s own doc comment)
// no owning occurrence with a real span to reopen — degrades
// to the same non-interactive span as before rather than a
// dead click.
return (