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>
54 lines
1.8 KiB
TypeScript
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>
|
|
);
|
|
}
|