batchCooking/apps/web/src/features/profile/AllergySelect.tsx
Nicolas 1d9bb6d112 feat(web,api): zone dangereuse rouge, préférences élargies, onglet favoris par défaut, e2e recettes, catalogue en uid+i18n
- Zone dangereuse (compte) : le bouton "Supprimer mon compte" est rouge.
- Pages préférences/paramétrage : contenu centré et élargi (32rem -> 56rem)
  au lieu de coller à gauche sur un écran large.
- Page recettes : l'onglet "Favoris" est sélectionné par défaut.
- Ajout de apps/web/cypress/e2e/recipes.cy.ts (onglets, recherche, sélection
  master-detail, favori, suppression, lien nouvelle recette).
- Catalogue de référence (ingrédients/régimes/allergènes) : la colonne
  `name` (le libellé français, utilisé comme clé unique) devient `key`, un
  slug stable et opaque au sens produit (ex. "vegetarien", "boeuf_hache").
  Le libellé lui-même déménage entièrement côté client, dans
  apps/web/src/locales/fr/translation.json sous le namespace `catalog.*`,
  résolu via `t(\`catalog.ingredients.${key}\`)` etc. — même schéma que
  IngredientCategory/IngredientSubcategory. Migration Prisma
  (rename + backfill des ~456 lignes déjà seedées), seed/service/tests API
  et composants web mis à jour en conséquence.
  - apps/api/src/utils/slugify.ts + scripts/generate-catalog-i18n.ts
    (regénère le fichier de traduction depuis reference-seed-data.ts).
  - 102 tests Mocha + 32 scénarios Cucumber passent contre la base migrée.

Note : cypress run plante dans cet environnement (le processus GPU
Chromium/Electron crash même headless, indépendamment des flags) — les
recipes.cy.ts n'ont pas pu être exécutés ici ; vérifiés par lecture du code
source des composants visés et par un passage manuel dans le navigateur de
prévisualisation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 19:44:30 +02:00

57 lines
2.1 KiB
TypeScript

import type { AllergyView } from "@batch-cooking/shared";
import { useTranslation } from "react-i18next";
import { CheckboxOption } from "../../components/ui/Checkbox";
import "./profile-forms.scss";
interface AllergySelectProps {
legend: string;
allergies: AllergyView[];
value: number[];
onChange: (allergyIds: number[]) => void;
}
/**
* Multi-select (checkbox grid, not a native `<select multiple>` — far more
* discoverable/tappable, especially on the mobile viewport this app is
* eventually embedded into via Capacitor) for a group of allergens. Used
* both by the signup wizard's allergens step and the `/parametres/preferences`
* settings page, and rendered *twice* by each — once for allergies, once for
* intolerances (`AllergyView.kind` groups them; callers filter and pass
* two separate lists rather than this component knowing about the split).
* An empty `value` is a normal, valid state (no declared allergies, or
* this skippable step was skipped), not an incomplete one.
*
* `legend` (not a fixed internal label) — the same component serves both
* groups, only the heading differs. A `<fieldset>`/`<legend>` (not a bare
* `<label>`, which only associates with a single control) is the correct
* semantic label for a group of checkboxes.
*
* Receives `allergies` as a prop rather than fetching them itself — same
* rationale as `DietSelect`.
*/
export function AllergySelect({ legend, allergies, value, onChange }: AllergySelectProps) {
const { t } = useTranslation();
function toggle(id: number) {
onChange(value.includes(id) ? value.filter((existing) => existing !== id) : [...value, id]);
}
return (
<fieldset className="allergy-select">
<legend>{legend}</legend>
{allergies.map((allergy) => {
const checked = value.includes(allergy.id);
return (
<CheckboxOption
key={allergy.id}
checked={checked}
onChange={() => toggle(allergy.id)}
className="allergy-select__option"
>
{t(`catalog.allergens.${allergy.key}`)}
</CheckboxOption>
);
})}
</fieldset>
);
}