batchCooking/apps/web/src/features/recipes/RecipeTabs.tsx
Nicolas acab18ac4a feat(recipes): catalogue v2 - visibilité, favoris, régimes et catalogue d'ingrédients exhaustif
Recipe catalog v2:
- Recipe gagne visibility (PERSONAL/HOUSE/PUBLIC), authorId, authorHouseId
- Favoris par utilisateur (RecipeFavorite), régimes associés (RecipeDiet)
- Aliments "pas aimés" par utilisateur (UserProfileDislikedIngredient),
  distinct des allergies médicales
- API: GET /recipes?tab=favoris|perso|foyer|publique avec contrôle d'accès
  complet, POST/DELETE /recipes/:id/favorite, édition/suppression réservées
  à l'auteur (403 NOT_RECIPE_AUTHOR), GET/PATCH /profile/disliked-ingredients
- Frontend: vue maître-détail (onglets + tableau + panneau détail),
  formulaire enrichi (visibilité, régimes), section préférences pour les
  aliments pas aimés

Catalogue d'ingrédients de référence:
- Extension du seed de 39 à ~430 ingrédients (viandes, poissons/fruits de
  mer, légumes, fruits, féculents, condiments/sauces, épices/herbes, pains
  à sandwich, cuisines italienne/asiatique/mexicaine/maghrébine, liquides
  et boissons de cuisine, bouillons/fonds)
- Chaque ingrédient lié à ses allergènes UE (IngredientAllergy) — les 14
  allergènes réglementaires restent tous couverts
- Seeding optimisé en requêtes groupées (createMany/diff ciblé) plutôt
  qu'un upsert par ligne, pour garder resetDatabase() rapide en test

Tests: 102 tests Mocha + 32 scénarios BDD, tous verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 09:29:11 +02:00

54 lines
1.8 KiB
TypeScript

import type { RecipeTab } from "@batch-cooking/shared";
import { useTranslation } from "react-i18next";
import { AccountIcon, FavoriteIcon, HouseholdIcon, PublicIcon } from "../../layouts/nav-icons";
import "./recipes.scss";
/** Every functional tab, in display order, with its icon — reuses `AccountIcon`/`HouseholdIcon` from the sidebar's own icon set (see nav-icons.tsx) rather than a second "person"/"house" glyph. */
const TABS: Array<{ value: RecipeTab; Icon: () => JSX.Element }> = [
{ value: "favoris", Icon: FavoriteIcon },
{ value: "perso", Icon: AccountIcon },
{ value: "foyer", Icon: HouseholdIcon },
{ value: "publique", Icon: PublicIcon },
];
/**
* Catalog tab bar — Favoris / Perso / Foyer / Publique, plus a disabled
* placeholder for external sources (not built yet, see the plan's "hors
* scope" note) so the eventual nav slot is visible without being
* functional. No "toutes" tab: every recipe a viewer can see falls under
* exactly one of perso/foyer/publique (its own visibility) — see
* `recipe.service.ts`'s `listRecipes`.
*/
export function RecipeTabs({
active,
onChange,
}: {
active: RecipeTab;
onChange: (tab: RecipeTab) => void;
}) {
const { t } = useTranslation();
return (
<div className="recipe-tabs">
{TABS.map(({ value, Icon }) => (
<button
key={value}
type="button"
className={`recipe-tabs__tab${value === active ? " active" : ""}`}
onClick={() => onChange(value)}
>
<Icon />
{t(`recipes.tabs.${value}`)}
</button>
))}
<button
type="button"
className="recipe-tabs__tab placeholder"
disabled
title={t("recipes.tabs.sourcesSoonHint")}
>
{t("recipes.tabs.sourcesSoon")}
</button>
</div>
);
}