From 78e0f895038b9711e648d3b3cee7c61742beff22 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 18 Aug 2026 12:30:25 +0200 Subject: [PATCH] =?UTF-8?q?feat(recipes):=20remplace=20les=20emoji=20par?= =?UTF-8?q?=20des=20ic=C3=B4nes=20SVG=20=C3=A9pur=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deux usages d'emoji supprimés, remplacés par un vrai jeu d'icônes SVG (style ligne, cohérent avec layouts/nav-icons.tsx) : - Catégories/sous-catégories du picker (auparavant préfixées d'un emoji dans les libellés) : nouvelles icônes CategoryIcon/ SubcategoryIcon, une par catégorie/sous-catégorie. - Icône par ingrédient (auparavant un emoji différent pour chacun des 437 ingrédients) : remplacé par un petit vocabulaire de ~22 pictogrammes génériques ("un légume", "une bouteille", "un fromage"...) réutilisés selon la nature de l'ingrédient plutôt qu'un dessin par ingrédient (irréaliste à la main pour 437 items). - Schéma : nouvel enum IngredientIcon (22 valeurs), colonne Ingredient.icon passe de String? (texte libre) à IngredientIcon (non nullable, toujours une valeur générique pertinente désormais). - reference-seed-data.ts : chaque groupe porte un defaultIcon (calqué sur sa sous-catégorie), avec override par ingrédient pour les exceptions (ex. les fromages dans "produits laitiers", les jus/cafés dans "assaisonnements"). - apps/web/src/features/recipes/ingredient-icons.tsx (nouveau) : 22 icônes SVG + CategoryIcon/SubcategoryIcon (réutilisent le même vocabulaire pour représenter chaque catégorie/sous-catégorie). - packages/shared : IngredientView.icon devient IngredientIcon (union de 22 valeurs) au lieu de string | null. Co-Authored-By: Claude Sonnet 5 --- .../migration.sql | 6 + apps/api/prisma/schema.prisma | 40 +- apps/api/src/db/reference-seed-data.ts | 885 +++++++++--------- .../profile/DislikedIngredientsField.tsx | 7 +- .../src/features/recipes/IngredientPicker.tsx | 5 +- .../src/features/recipes/IngredientRow.tsx | 3 +- .../src/features/recipes/ingredient-icons.tsx | 360 +++++++ apps/web/src/features/recipes/recipes.scss | 45 +- apps/web/src/locales/fr/translation.json | 14 +- packages/shared/src/types/reference.ts | 39 +- 10 files changed, 964 insertions(+), 440 deletions(-) create mode 100644 apps/api/prisma/migrations/20260818121549_ingredient_icon_type/migration.sql create mode 100644 apps/web/src/features/recipes/ingredient-icons.tsx diff --git a/apps/api/prisma/migrations/20260818121549_ingredient_icon_type/migration.sql b/apps/api/prisma/migrations/20260818121549_ingredient_icon_type/migration.sql new file mode 100644 index 0000000..8ba6cb1 --- /dev/null +++ b/apps/api/prisma/migrations/20260818121549_ingredient_icon_type/migration.sql @@ -0,0 +1,6 @@ +-- CreateEnum +CREATE TYPE "IngredientIcon" AS ENUM ('VEGETABLE', 'FRUIT', 'HERB', 'MEAT', 'POULTRY', 'FISH', 'SHELLFISH', 'GRAIN', 'LEGUME', 'NUT_SEED', 'BREAD', 'DOUGH', 'MILK', 'CHEESE', 'EGG', 'SPROUT', 'SPICE', 'JAR', 'BOTTLE', 'DRINK', 'STOCK_POT', 'SUGAR'); + +-- AlterTable +ALTER TABLE "ingredients" DROP COLUMN "icon", +ADD COLUMN "icon" "IngredientIcon" NOT NULL DEFAULT 'JAR'; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index c43981f..c680e77 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -384,10 +384,48 @@ enum IngredientSubcategory { SUCRES } +/// Generic pictogram *type* for an ingredient — not in the original spec +/// doc. Started as a free-text emoji column (one character per ingredient, +/// 437 different ones), which the product decision recorded in chat +/// rejected as unprofessional/inconsistent. Rather than 437 hand-drawn SVG +/// icons (unrealistic), ingredients share a small vocabulary of ~20 +/// generic shapes grouped by *what kind of thing* they are — a vegetable, +/// a bottle of oil, a wedge of cheese — regardless of which specific +/// ingredient. `apps/web`'s `features/recipes/ingredient-icons.tsx` maps +/// each value to its actual SVG (matching the app's hand-drawn line-icon +/// style, never emoji — see that file for the full reasoning and the +/// exact `reference-seed-data.ts` assignment per ingredient). +/// `@default(JAR)` — same NOT-NULL-migration-safety-net reasoning as +/// `IngredientCategory`'s default, never the intended value for a real row. +enum IngredientIcon { + VEGETABLE + FRUIT + HERB + MEAT + POULTRY + FISH + SHELLFISH + GRAIN + LEGUME + NUT_SEED + BREAD + DOUGH + MILK + CHEESE + EGG + SPROUT + SPICE + JAR + BOTTLE + DRINK + STOCK_POT + SUGAR +} + model Ingredient { id Int @id @default(autoincrement()) name String @unique - icon String? + icon IngredientIcon @default(JAR) category IngredientCategory @default(EPICERIE_SECHE) subcategory IngredientSubcategory @default(AUTRES) alternateRecipeId Int? @map("alternate_recipe") diff --git a/apps/api/src/db/reference-seed-data.ts b/apps/api/src/db/reference-seed-data.ts index 8d2f83d..d9de8d5 100644 --- a/apps/api/src/db/reference-seed-data.ts +++ b/apps/api/src/db/reference-seed-data.ts @@ -1,6 +1,7 @@ import type { AllergenKind, IngredientCategory, + IngredientIcon, IngredientSubcategory, PrismaClient, } from "@prisma/client"; @@ -35,7 +36,17 @@ const ALLERGENS: Array<{ name: string; kind: AllergenKind }> = [ interface IngredientSeed { name: string; - icon?: string; + /** + * Generic pictogram type, overriding its group's `defaultIcon` below — + * only needed for the exceptions within a subcategory (a wedge of cheese + * inside an otherwise-milk "produits laitiers" group, a stockpot inside + * an otherwise-flour "bases" group…). See `IngredientIcon` in + * schema.prisma and `apps/web`'s `features/recipes/ingredient-icons.tsx` + * for the actual pictograms — this was a free-text emoji field until the + * product decision recorded in chat replaced it with this small, shared + * vocabulary. + */ + icon?: IngredientIcon; allergenNames: string[]; /** * Diet regimes this ingredient is compatible with, overriding its group's @@ -59,10 +70,7 @@ interface IngredientSeed { // meant to already be comprehensive at first deploy rather than grown // piecemeal as recipes need more of it. `allergenNames` reference // `ALLERGENS` above by name — every one of the 14 EU-regulated allergens is -// covered by at least one ingredient here. `icon` is left unset (rather than -// forcing a misleading emoji) for the handful of items with no good match in -// the standard emoji set (e.g. `Radis`, `Asperge`, or a raw pastry dough — -// there's no "puff pastry" emoji). +// covered by at least one ingredient here. // // Grouped by (`category`, `subcategory`) — mirrors `IngredientCategory`/ // `IngredientSubcategory` in schema.prisma, a supermarket-aisle taxonomy @@ -77,101 +85,105 @@ interface IngredientSeed { // truth for that mapping; `INGREDIENTS` below just flattens it back to one // array for the seeding loop. // -// `defaultDiets` is the regime compatibility every item in the group shares -// unless it sets its own `dietNames` — most groups are diet-homogeneous (a -// vegetable is always vegan, a cut of meat never is), so this avoids -// repeating the same diet names on hundreds of items; only a group's -// exceptions (a fish-based stock inside "bases", a butter-based dough -// inside "pâtes à cuire"…) need a per-item override. +// `defaultDiets`/`defaultIcon` are what every item in the group shares +// unless it sets its own `dietNames`/`icon` — most groups are homogeneous +// on both counts (a vegetable is always vegan and always looks like a +// vegetable; a cut of meat never is and never does), so this avoids +// repeating the same values on hundreds of items; only a group's +// exceptions (a fish-based stock inside "bases", a cheese inside "produits +// laitiers"…) need a per-item override. const INGREDIENT_GROUPS: Array<{ category: IngredientCategory; subcategory: IngredientSubcategory; defaultDiets: string[]; + defaultIcon: IngredientIcon; items: IngredientSeed[]; }> = [ // ========================================================================= - // 🥦 Produits frais + // Produits frais // ========================================================================= { category: "PRODUITS_FRAIS", subcategory: "LEGUMES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "VEGETABLE", items: [ - { name: "Tomate", icon: "🍅", allergenNames: [] }, - { name: "Oignon", icon: "🧅", allergenNames: [] }, - { name: "Échalote", icon: "🧅", allergenNames: [] }, - { name: "Ail", icon: "🧄", allergenNames: [] }, - { name: "Carotte", icon: "🥕", allergenNames: [] }, - { name: "Courgette", icon: "🥒", allergenNames: [] }, - { name: "Concombre", icon: "🥒", allergenNames: [] }, - { name: "Cornichons", icon: "🥒", allergenNames: [] }, - { name: "Poivron", icon: "🫑", allergenNames: [] }, - { name: "Champignon", icon: "🍄", allergenNames: [] }, - { name: "Cèpes", icon: "🍄", allergenNames: [] }, - { name: "Aubergine", icon: "🍆", allergenNames: [] }, - { name: "Brocoli", icon: "🥦", allergenNames: [] }, - { name: "Chou-fleur", icon: "🥦", allergenNames: [] }, - { name: "Chou blanc", icon: "🥬", allergenNames: [] }, - { name: "Chou rouge", icon: "🥬", allergenNames: [] }, - { name: "Chou de Bruxelles", icon: "🥬", allergenNames: [] }, - { name: "Épinard", icon: "🥬", allergenNames: [] }, - { name: "Blette", icon: "🥬", allergenNames: [] }, - { name: "Salade", icon: "🥬", allergenNames: [] }, - { name: "Roquette", icon: "🥬", allergenNames: [] }, - { name: "Cresson", icon: "🥬", allergenNames: [] }, - { name: "Poireau", icon: "🥬", allergenNames: [] }, - { name: "Céleri", icon: "🥬", allergenNames: ["Céleri"] }, + { name: "Tomate", allergenNames: [] }, + { name: "Oignon", allergenNames: [] }, + { name: "Échalote", allergenNames: [] }, + { name: "Ail", allergenNames: [] }, + { name: "Carotte", allergenNames: [] }, + { name: "Courgette", allergenNames: [] }, + { name: "Concombre", allergenNames: [] }, + { name: "Cornichons", allergenNames: [] }, + { name: "Poivron", allergenNames: [] }, + { name: "Champignon", allergenNames: [] }, + { name: "Cèpes", allergenNames: [] }, + { name: "Aubergine", allergenNames: [] }, + { name: "Brocoli", allergenNames: [] }, + { name: "Chou-fleur", allergenNames: [] }, + { name: "Chou blanc", allergenNames: [] }, + { name: "Chou rouge", allergenNames: [] }, + { name: "Chou de Bruxelles", allergenNames: [] }, + { name: "Épinard", allergenNames: [] }, + { name: "Blette", allergenNames: [] }, + { name: "Salade", allergenNames: [] }, + { name: "Roquette", allergenNames: [] }, + { name: "Cresson", allergenNames: [] }, + { name: "Poireau", allergenNames: [] }, + { name: "Céleri", allergenNames: ["Céleri"] }, { name: "Radis", allergenNames: [] }, { name: "Betterave", allergenNames: [] }, { name: "Navet", allergenNames: [] }, { name: "Panais", allergenNames: [] }, - { name: "Haricot vert", icon: "🫛", allergenNames: [] }, - { name: "Petit pois", icon: "🫛", allergenNames: [] }, - { name: "Maïs", icon: "🌽", allergenNames: [] }, + { name: "Haricot vert", allergenNames: [] }, + { name: "Petit pois", allergenNames: [] }, + { name: "Maïs", allergenNames: [] }, { name: "Artichaut", allergenNames: [] }, { name: "Fenouil", allergenNames: [] }, { name: "Endive", allergenNames: [] }, - { name: "Potiron", icon: "🎃", allergenNames: [] }, - { name: "Butternut", icon: "🎃", allergenNames: [] }, + { name: "Potiron", allergenNames: [] }, + { name: "Butternut", allergenNames: [] }, { name: "Asperge", allergenNames: [] }, - { name: "Avocat", icon: "🥑", allergenNames: [] }, - { name: "Pomme de terre", icon: "🥔", allergenNames: [] }, - { name: "Patate douce", icon: "🍠", allergenNames: [] }, - { name: "Tomates cerises", icon: "🍅", allergenNames: [] }, - { name: "Pak-choï", icon: "🥬", allergenNames: [] }, - { name: "Germes de soja", icon: "🌱", allergenNames: ["Soja"] }, - { name: "Shiitake", icon: "🍄", allergenNames: [] }, + { name: "Avocat", allergenNames: [] }, + { name: "Pomme de terre", allergenNames: [] }, + { name: "Patate douce", allergenNames: [] }, + { name: "Tomates cerises", allergenNames: [] }, + { name: "Pak-choï", allergenNames: [] }, + { name: "Germes de soja", allergenNames: ["Soja"] }, + { name: "Shiitake", allergenNames: [] }, { name: "Daikon", allergenNames: [] }, - { name: "Piment vert frais", icon: "🌶️", allergenNames: [] }, + { name: "Piment vert frais", allergenNames: [] }, ], }, { category: "PRODUITS_FRAIS", subcategory: "FRUITS", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "FRUIT", items: [ - { name: "Citron", icon: "🍋", allergenNames: [] }, - { name: "Citron vert", icon: "🍋", allergenNames: [] }, - { name: "Pomme", icon: "🍎", allergenNames: [] }, - { name: "Poire", icon: "🍐", allergenNames: [] }, - { name: "Banane", icon: "🍌", allergenNames: [] }, - { name: "Orange", icon: "🍊", allergenNames: [] }, - { name: "Clémentine", icon: "🍊", allergenNames: [] }, - { name: "Pamplemousse", icon: "🍊", allergenNames: [] }, - { name: "Fraise", icon: "🍓", allergenNames: [] }, - { name: "Framboise", icon: "🍓", allergenNames: [] }, - { name: "Myrtille", icon: "🫐", allergenNames: [] }, - { name: "Mûre", icon: "🫐", allergenNames: [] }, - { name: "Cerise", icon: "🍒", allergenNames: [] }, - { name: "Abricot", icon: "🍑", allergenNames: [] }, - { name: "Pêche", icon: "🍑", allergenNames: [] }, + { name: "Citron", allergenNames: [] }, + { name: "Citron vert", allergenNames: [] }, + { name: "Pomme", allergenNames: [] }, + { name: "Poire", allergenNames: [] }, + { name: "Banane", allergenNames: [] }, + { name: "Orange", allergenNames: [] }, + { name: "Clémentine", allergenNames: [] }, + { name: "Pamplemousse", allergenNames: [] }, + { name: "Fraise", allergenNames: [] }, + { name: "Framboise", allergenNames: [] }, + { name: "Myrtille", allergenNames: [] }, + { name: "Mûre", allergenNames: [] }, + { name: "Cerise", allergenNames: [] }, + { name: "Abricot", allergenNames: [] }, + { name: "Pêche", allergenNames: [] }, { name: "Prune", allergenNames: [] }, - { name: "Raisin", icon: "🍇", allergenNames: [] }, - { name: "Melon", icon: "🍈", allergenNames: [] }, - { name: "Pastèque", icon: "🍉", allergenNames: [] }, - { name: "Ananas", icon: "🍍", allergenNames: [] }, - { name: "Mangue", icon: "🥭", allergenNames: [] }, - { name: "Kiwi", icon: "🥝", allergenNames: [] }, + { name: "Raisin", allergenNames: [] }, + { name: "Melon", allergenNames: [] }, + { name: "Pastèque", allergenNames: [] }, + { name: "Ananas", allergenNames: [] }, + { name: "Mangue", allergenNames: [] }, + { name: "Kiwi", allergenNames: [] }, { name: "Figue", allergenNames: [] }, { name: "Datte", allergenNames: [] }, { name: "Litchi", allergenNames: [] }, @@ -184,267 +196,275 @@ const INGREDIENT_GROUPS: Array<{ category: "PRODUITS_FRAIS", subcategory: "HERBES_FRAICHES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "HERB", items: [ - { name: "Basilic", icon: "🌿", allergenNames: [] }, - { name: "Persil", icon: "🌿", allergenNames: [] }, - { name: "Thym", icon: "🌿", allergenNames: [] }, - { name: "Romarin", icon: "🌿", allergenNames: [] }, - { name: "Laurier", icon: "🌿", allergenNames: [] }, - { name: "Ciboulette", icon: "🌿", allergenNames: [] }, - { name: "Coriandre fraîche", icon: "🌿", allergenNames: [] }, - { name: "Menthe", icon: "🌿", allergenNames: [] }, - { name: "Origan", icon: "🌿", allergenNames: [] }, - { name: "Aneth", icon: "🌿", allergenNames: [] }, - { name: "Estragon", icon: "🌿", allergenNames: [] }, - { name: "Sarriette", icon: "🌿", allergenNames: [] }, - { name: "Marjolaine", icon: "🌿", allergenNames: [] }, - { name: "Sauge", icon: "🌿", allergenNames: [] }, - { name: "Cerfeuil", icon: "🌿", allergenNames: [] }, - { name: "Gingembre", icon: "🫚", allergenNames: [] }, - { name: "Citronnelle", icon: "🌿", allergenNames: [] }, - { name: "Combava", icon: "🌿", allergenNames: [] }, + { name: "Basilic", allergenNames: [] }, + { name: "Persil", allergenNames: [] }, + { name: "Thym", allergenNames: [] }, + { name: "Romarin", allergenNames: [] }, + { name: "Laurier", allergenNames: [] }, + { name: "Ciboulette", allergenNames: [] }, + { name: "Coriandre fraîche", allergenNames: [] }, + { name: "Menthe", allergenNames: [] }, + { name: "Origan", allergenNames: [] }, + { name: "Aneth", allergenNames: [] }, + { name: "Estragon", allergenNames: [] }, + { name: "Sarriette", allergenNames: [] }, + { name: "Marjolaine", allergenNames: [] }, + { name: "Sauge", allergenNames: [] }, + { name: "Cerfeuil", allergenNames: [] }, + { name: "Gingembre", allergenNames: [] }, + { name: "Citronnelle", allergenNames: [] }, + { name: "Combava", allergenNames: [] }, ], }, // ========================================================================= - // 🥩 Boucherie & poissonnerie + // Boucherie & poissonnerie // ========================================================================= { category: "BOUCHERIE_POISSONNERIE", subcategory: "VIANDES", defaultDiets: [], + defaultIcon: "MEAT", items: [ - { name: "Lapin", icon: "🐇", allergenNames: [] }, - { name: "Bœuf haché", icon: "🥩", allergenNames: [] }, - { name: "Steak de bœuf", icon: "🥩", allergenNames: [] }, - { name: "Rôti de bœuf", icon: "🥩", allergenNames: [] }, - { name: "Escalope de veau", icon: "🥩", allergenNames: [] }, - { name: "Filet mignon de porc", icon: "🥩", allergenNames: [] }, - { name: "Côte de porc", icon: "🥩", allergenNames: [] }, - { name: "Agneau", icon: "🍖", allergenNames: [] }, - { name: "Gigot d'agneau", icon: "🍖", allergenNames: [] }, - { name: "Lardons", icon: "🥓", allergenNames: [] }, - { name: "Bacon", icon: "🥓", allergenNames: [] }, - { name: "Jambon blanc", icon: "🍖", allergenNames: [] }, - { name: "Jambon cru", icon: "🍖", allergenNames: [] }, - { name: "Saucisse", icon: "🌭", allergenNames: [] }, - { name: "Chorizo", icon: "🌭", allergenNames: [] }, - { name: "Merguez", icon: "🌭", allergenNames: [] }, - { name: "Prosciutto", icon: "🍖", allergenNames: [] }, - { name: "Pancetta", icon: "🥓", allergenNames: [] }, - { name: "Mortadelle", icon: "🍖", allergenNames: [] }, - { name: "Salami", icon: "🍖", allergenNames: [] }, + { name: "Lapin", allergenNames: [] }, + { name: "Bœuf haché", allergenNames: [] }, + { name: "Steak de bœuf", allergenNames: [] }, + { name: "Rôti de bœuf", allergenNames: [] }, + { name: "Escalope de veau", allergenNames: [] }, + { name: "Filet mignon de porc", allergenNames: [] }, + { name: "Côte de porc", allergenNames: [] }, + { name: "Agneau", allergenNames: [] }, + { name: "Gigot d'agneau", allergenNames: [] }, + { name: "Lardons", allergenNames: [] }, + { name: "Bacon", allergenNames: [] }, + { name: "Jambon blanc", allergenNames: [] }, + { name: "Jambon cru", allergenNames: [] }, + { name: "Saucisse", allergenNames: [] }, + { name: "Chorizo", allergenNames: [] }, + { name: "Merguez", allergenNames: [] }, + { name: "Prosciutto", allergenNames: [] }, + { name: "Pancetta", allergenNames: [] }, + { name: "Mortadelle", allergenNames: [] }, + { name: "Salami", allergenNames: [] }, ], }, { category: "BOUCHERIE_POISSONNERIE", subcategory: "VOLAILLES", defaultDiets: [], + defaultIcon: "POULTRY", items: [ - { name: "Poulet", icon: "🍗", allergenNames: [] }, - { name: "Dinde", icon: "🍗", allergenNames: [] }, - { name: "Canard", icon: "🦆", allergenNames: [] }, - { name: "Magret de canard", icon: "🦆", allergenNames: [] }, + { name: "Poulet", allergenNames: [] }, + { name: "Dinde", allergenNames: [] }, + { name: "Canard", allergenNames: [] }, + { name: "Magret de canard", allergenNames: [] }, ], }, { category: "BOUCHERIE_POISSONNERIE", subcategory: "POISSONS", defaultDiets: ["Pescétarien"], + defaultIcon: "FISH", items: [ - { name: "Saumon", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Thon", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Cabillaud", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Truite", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Sardine", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Anchois", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Merlan", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Surimi", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Bar (loup de mer)", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Dorade", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Sole", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Turbot", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Merlu", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Colin", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Lieu noir", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Églefin", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Maquereau", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Hareng", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Rouget", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Raie", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Lotte", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Flétan", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Espadon", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Carpe", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Brochet", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Perche", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Tilapia", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Panga", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Saumon fumé", icon: "🐟", allergenNames: ["Poissons"] }, - { name: "Poisson séché", icon: "🐟", allergenNames: ["Poissons"] }, + { name: "Saumon", allergenNames: ["Poissons"] }, + { name: "Thon", allergenNames: ["Poissons"] }, + { name: "Cabillaud", allergenNames: ["Poissons"] }, + { name: "Truite", allergenNames: ["Poissons"] }, + { name: "Sardine", allergenNames: ["Poissons"] }, + { name: "Anchois", allergenNames: ["Poissons"] }, + { name: "Merlan", allergenNames: ["Poissons"] }, + { name: "Surimi", allergenNames: ["Poissons"] }, + { name: "Bar (loup de mer)", allergenNames: ["Poissons"] }, + { name: "Dorade", allergenNames: ["Poissons"] }, + { name: "Sole", allergenNames: ["Poissons"] }, + { name: "Turbot", allergenNames: ["Poissons"] }, + { name: "Merlu", allergenNames: ["Poissons"] }, + { name: "Colin", allergenNames: ["Poissons"] }, + { name: "Lieu noir", allergenNames: ["Poissons"] }, + { name: "Églefin", allergenNames: ["Poissons"] }, + { name: "Maquereau", allergenNames: ["Poissons"] }, + { name: "Hareng", allergenNames: ["Poissons"] }, + { name: "Rouget", allergenNames: ["Poissons"] }, + { name: "Raie", allergenNames: ["Poissons"] }, + { name: "Lotte", allergenNames: ["Poissons"] }, + { name: "Flétan", allergenNames: ["Poissons"] }, + { name: "Espadon", allergenNames: ["Poissons"] }, + { name: "Carpe", allergenNames: ["Poissons"] }, + { name: "Brochet", allergenNames: ["Poissons"] }, + { name: "Perche", allergenNames: ["Poissons"] }, + { name: "Tilapia", allergenNames: ["Poissons"] }, + { name: "Panga", allergenNames: ["Poissons"] }, + { name: "Saumon fumé", allergenNames: ["Poissons"] }, + { name: "Poisson séché", allergenNames: ["Poissons"] }, ], }, { category: "BOUCHERIE_POISSONNERIE", subcategory: "CRUSTACES_FRUITS_DE_MER", defaultDiets: ["Pescétarien"], + defaultIcon: "SHELLFISH", items: [ - { name: "Crevettes", icon: "🍤", allergenNames: ["Crustacés"] }, - { name: "Langoustines", icon: "🍤", allergenNames: ["Crustacés"] }, - { name: "Homard", icon: "🦞", allergenNames: ["Crustacés"] }, - { name: "Crabe", icon: "🦀", allergenNames: ["Crustacés"] }, - { name: "Langouste", icon: "🦞", allergenNames: ["Crustacés"] }, - { name: "Moules", icon: "🦪", allergenNames: ["Mollusques"] }, - { name: "Huîtres", icon: "🦪", allergenNames: ["Mollusques"] }, - { name: "Saint-Jacques", icon: "🦪", allergenNames: ["Mollusques"] }, - { name: "Calamar", icon: "🦑", allergenNames: ["Mollusques"] }, - { name: "Poulpe", icon: "🐙", allergenNames: ["Mollusques"] }, - { name: "Palourdes", icon: "🦪", allergenNames: ["Mollusques"] }, - { name: "Bulots", icon: "🐚", allergenNames: ["Mollusques"] }, + { name: "Crevettes", allergenNames: ["Crustacés"] }, + { name: "Langoustines", allergenNames: ["Crustacés"] }, + { name: "Homard", allergenNames: ["Crustacés"] }, + { name: "Crabe", allergenNames: ["Crustacés"] }, + { name: "Langouste", allergenNames: ["Crustacés"] }, + { name: "Moules", allergenNames: ["Mollusques"] }, + { name: "Huîtres", allergenNames: ["Mollusques"] }, + { name: "Saint-Jacques", allergenNames: ["Mollusques"] }, + { name: "Calamar", allergenNames: ["Mollusques"] }, + { name: "Poulpe", allergenNames: ["Mollusques"] }, + { name: "Palourdes", allergenNames: ["Mollusques"] }, + { name: "Bulots", allergenNames: ["Mollusques"] }, ], }, // ========================================================================= - // 🥫 Épicerie sèche + // Épicerie sèche // ========================================================================= { category: "EPICERIE_SECHE", subcategory: "FECULENTS", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "GRAIN", items: [ - { name: "Semoule", icon: "🌾", allergenNames: ["Gluten"] }, - { name: "Couscous", icon: "🌾", allergenNames: ["Gluten"] }, - { name: "Boulgour", icon: "🌾", allergenNames: ["Gluten"] }, - { name: "Polenta", icon: "🌽", allergenNames: [] }, - { name: "Quinoa", icon: "🌾", allergenNames: [] }, - { name: "Pâtes", icon: "🍝", allergenNames: ["Gluten"] }, - { name: "Pâtes complètes", icon: "🍝", allergenNames: ["Gluten"] }, - { name: "Riz", icon: "🍚", allergenNames: [] }, - { name: "Riz basmati", icon: "🍚", allergenNames: [] }, - { name: "Riz complet", icon: "🍚", allergenNames: [] }, - { name: "Flocons d'avoine", icon: "🌾", allergenNames: ["Gluten"] }, - { name: "Spaghetti", icon: "🍝", allergenNames: ["Gluten"] }, - { name: "Penne", icon: "🍝", allergenNames: ["Gluten"] }, - { name: "Tagliatelles", icon: "🍝", allergenNames: ["Gluten"] }, - { name: "Lasagnes (feuilles)", icon: "🍝", allergenNames: ["Gluten"] }, - { name: "Gnocchi", icon: "🍝", allergenNames: ["Gluten"] }, - { name: "Riz arborio", icon: "🍚", allergenNames: [] }, - { name: "Nouilles de riz", icon: "🍜", allergenNames: [] }, - { name: "Nouilles udon", icon: "🍜", allergenNames: ["Gluten"] }, - { name: "Nouilles soba", icon: "🍜", allergenNames: ["Gluten"] }, - { name: "Nouilles chinoises", icon: "🍜", allergenNames: ["Gluten"] }, - { name: "Vermicelles de riz", icon: "🍜", allergenNames: [] }, - { name: "Vermicelles de soja", icon: "🍜", allergenNames: [] }, - { name: "Riz gluant", icon: "🍚", allergenNames: [] }, - { name: "Riz à sushi", icon: "🍚", allergenNames: [] }, - { name: "Riz jasmin", icon: "🍚", allergenNames: [] }, + { name: "Semoule", allergenNames: ["Gluten"] }, + { name: "Couscous", allergenNames: ["Gluten"] }, + { name: "Boulgour", allergenNames: ["Gluten"] }, + { name: "Polenta", allergenNames: [] }, + { name: "Quinoa", allergenNames: [] }, + { name: "Pâtes", allergenNames: ["Gluten"] }, + { name: "Pâtes complètes", allergenNames: ["Gluten"] }, + { name: "Riz", allergenNames: [] }, + { name: "Riz basmati", allergenNames: [] }, + { name: "Riz complet", allergenNames: [] }, + { name: "Flocons d'avoine", allergenNames: ["Gluten"] }, + { name: "Spaghetti", allergenNames: ["Gluten"] }, + { name: "Penne", allergenNames: ["Gluten"] }, + { name: "Tagliatelles", allergenNames: ["Gluten"] }, + { name: "Lasagnes (feuilles)", allergenNames: ["Gluten"] }, + { name: "Gnocchi", allergenNames: ["Gluten"] }, + { name: "Riz arborio", allergenNames: [] }, + { name: "Nouilles de riz", allergenNames: [] }, + { name: "Nouilles udon", allergenNames: ["Gluten"] }, + { name: "Nouilles soba", allergenNames: ["Gluten"] }, + { name: "Nouilles chinoises", allergenNames: ["Gluten"] }, + { name: "Vermicelles de riz", allergenNames: [] }, + { name: "Vermicelles de soja", allergenNames: [] }, + { name: "Riz gluant", allergenNames: [] }, + { name: "Riz à sushi", allergenNames: [] }, + { name: "Riz jasmin", allergenNames: [] }, ], }, { category: "EPICERIE_SECHE", subcategory: "LEGUMINEUSES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "LEGUME", items: [ - { name: "Lentilles vertes", icon: "🫘", allergenNames: [] }, - { name: "Lentilles corail", icon: "🫘", allergenNames: [] }, - { name: "Pois chiches", icon: "🫘", allergenNames: [] }, - { name: "Haricots blancs", icon: "🫘", allergenNames: [] }, - { name: "Haricots rouges", icon: "🫘", allergenNames: [] }, - { name: "Haricots noirs", icon: "🫘", allergenNames: [] }, - { name: "Pois cassés", icon: "🫘", allergenNames: [] }, - { name: "Fèves", icon: "🫘", allergenNames: [] }, - { name: "Edamame", icon: "🫛", allergenNames: ["Soja"] }, - { name: "Haricots pinto", icon: "🫘", allergenNames: [] }, + { name: "Lentilles vertes", allergenNames: [] }, + { name: "Lentilles corail", allergenNames: [] }, + { name: "Pois chiches", allergenNames: [] }, + { name: "Haricots blancs", allergenNames: [] }, + { name: "Haricots rouges", allergenNames: [] }, + { name: "Haricots noirs", allergenNames: [] }, + { name: "Pois cassés", allergenNames: [] }, + { name: "Fèves", allergenNames: [] }, + { name: "Edamame", allergenNames: ["Soja"] }, + { name: "Haricots pinto", allergenNames: [] }, ], }, { category: "EPICERIE_SECHE", subcategory: "GRAINES_FRUITS_SECS", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "NUT_SEED", items: [ - { name: "Cacahuètes", icon: "🥜", allergenNames: ["Arachides"] }, - { name: "Amandes", icon: "🌰", allergenNames: ["Fruits à coque"] }, - { name: "Noix", icon: "🌰", allergenNames: ["Fruits à coque"] }, - { name: "Noisettes", icon: "🌰", allergenNames: ["Fruits à coque"] }, - { name: "Noix de cajou", icon: "🌰", allergenNames: ["Fruits à coque"] }, - { name: "Pistaches", icon: "🌰", allergenNames: ["Fruits à coque"] }, - { name: "Noix de pécan", icon: "🌰", allergenNames: ["Fruits à coque"] }, - { name: "Poudre d'amande", icon: "🌰", allergenNames: ["Fruits à coque"] }, - { name: "Pignons de pin", icon: "🌰", allergenNames: [] }, - { name: "Graines de tournesol", icon: "🌻", allergenNames: [] }, + { name: "Cacahuètes", allergenNames: ["Arachides"] }, + { name: "Amandes", allergenNames: ["Fruits à coque"] }, + { name: "Noix", allergenNames: ["Fruits à coque"] }, + { name: "Noisettes", allergenNames: ["Fruits à coque"] }, + { name: "Noix de cajou", allergenNames: ["Fruits à coque"] }, + { name: "Pistaches", allergenNames: ["Fruits à coque"] }, + { name: "Noix de pécan", allergenNames: ["Fruits à coque"] }, + { name: "Poudre d'amande", allergenNames: ["Fruits à coque"] }, + { name: "Pignons de pin", allergenNames: [] }, + { name: "Graines de tournesol", allergenNames: [] }, { name: "Graines de courge", allergenNames: [] }, - { name: "Noix de coco râpée", icon: "🥥", allergenNames: [] }, - { name: "Raisins secs", icon: "🍇", allergenNames: ["Sulfites"] }, + { name: "Noix de coco râpée", allergenNames: [] }, + { name: "Raisins secs", allergenNames: ["Sulfites"] }, { name: "Pruneaux", allergenNames: ["Sulfites"] }, - { name: "Abricots secs", icon: "🍑", allergenNames: ["Sulfites"] }, - { name: "Graines de sésame", icon: "🌱", allergenNames: ["Graines de sésame"] }, + { name: "Abricots secs", allergenNames: ["Sulfites"] }, + { name: "Graines de sésame", allergenNames: ["Graines de sésame"] }, ], }, { category: "EPICERIE_SECHE", subcategory: "AUTRES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "JAR", items: [ - { name: "Champignons noirs", icon: "🍄", allergenNames: [] }, - { name: "Algue nori", icon: "🌿", allergenNames: [] }, - { name: "Algue wakamé", icon: "🌿", allergenNames: [] }, - { name: "Algue kombu", icon: "🌿", allergenNames: [] }, + { name: "Champignons noirs", allergenNames: [] }, + { name: "Algue nori", allergenNames: [] }, + { name: "Algue wakamé", allergenNames: [] }, + { name: "Algue kombu", allergenNames: [] }, { name: "Pousses de bambou", allergenNames: [] }, { name: "Châtaignes d'eau", allergenNames: [] }, ], }, // ========================================================================= - // 🍞 Boulangerie + // Boulangerie // ========================================================================= { category: "BOULANGERIE", subcategory: "PAINS", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "BREAD", items: [ - { name: "Pain", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Pain de mie", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Pain complet", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Baguette", icon: "🥖", allergenNames: ["Gluten"] }, - { name: "Pain de seigle", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Chapelure", icon: "🍞", allergenNames: ["Gluten"] }, + { name: "Pain", allergenNames: ["Gluten"] }, + { name: "Pain de mie", allergenNames: ["Gluten"] }, + { name: "Pain complet", allergenNames: ["Gluten"] }, + { name: "Baguette", allergenNames: ["Gluten"] }, + { name: "Pain de seigle", allergenNames: ["Gluten"] }, + { name: "Chapelure", allergenNames: ["Gluten"] }, { name: "Pain à burger", - icon: "🍔", allergenNames: ["Gluten", "Lait", "Œufs"], dietNames: ["Végétarien", "Pescétarien"], }, { name: "Pain brioché", - icon: "🍞", allergenNames: ["Gluten", "Lait", "Œufs"], dietNames: ["Végétarien", "Pescétarien"], }, - { name: "Pain à hot-dog", icon: "🌭", allergenNames: ["Gluten"] }, - { name: "Pain pita", icon: "🫓", allergenNames: ["Gluten"] }, - { name: "Pain bagel", icon: "🥯", allergenNames: ["Gluten"] }, - { name: "Naan", icon: "🫓", allergenNames: ["Gluten"] }, - { name: "Pain wrap", icon: "🫓", allergenNames: ["Gluten"] }, + { name: "Pain à hot-dog", allergenNames: ["Gluten"] }, + { name: "Pain pita", allergenNames: ["Gluten"] }, + { name: "Pain bagel", allergenNames: ["Gluten"] }, + { name: "Naan", allergenNames: ["Gluten"] }, + { name: "Pain wrap", allergenNames: ["Gluten"] }, { name: "Pain viennois", - icon: "🍞", allergenNames: ["Gluten", "Lait"], dietNames: ["Végétarien", "Pescétarien"], }, - { name: "Pain de campagne", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Pain aux céréales", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Petit pain", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Pain suédois", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Pain sans gluten", icon: "🍞", allergenNames: [] }, - { name: "Biscotte", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Croûtons", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Focaccia", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Ciabatta", icon: "🍞", allergenNames: ["Gluten"] }, - { name: "Tortilla de maïs", icon: "🌮", allergenNames: [] }, - { name: "Tortilla de blé", icon: "🌮", allergenNames: ["Gluten"] }, + { name: "Pain de campagne", allergenNames: ["Gluten"] }, + { name: "Pain aux céréales", allergenNames: ["Gluten"] }, + { name: "Petit pain", allergenNames: ["Gluten"] }, + { name: "Pain suédois", allergenNames: ["Gluten"] }, + { name: "Pain sans gluten", allergenNames: [] }, + { name: "Biscotte", allergenNames: ["Gluten"] }, + { name: "Croûtons", allergenNames: ["Gluten"] }, + { name: "Focaccia", allergenNames: ["Gluten"] }, + { name: "Ciabatta", allergenNames: ["Gluten"] }, + { name: "Tortilla de maïs", allergenNames: [] }, + { name: "Tortilla de blé", allergenNames: ["Gluten"] }, ], }, { category: "BOULANGERIE", subcategory: "PATES_A_CUIRE", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "DOUGH", items: [ { name: "Pâte feuilletée", @@ -465,312 +485,325 @@ const INGREDIENT_GROUPS: Array<{ ], }, // ========================================================================= - // 🧈 Crémerie & fromage + // Crémerie & fromage // ========================================================================= { category: "CREMERIE_FROMAGE", subcategory: "PRODUITS_LAITIERS", defaultDiets: ["Végétarien", "Pescétarien"], + defaultIcon: "MILK", items: [ - { name: "Lait", icon: "🥛", allergenNames: ["Lait"] }, - { name: "Beurre", icon: "🧈", allergenNames: ["Lait"] }, - { name: "Crème fraîche", icon: "🥛", allergenNames: ["Lait"] }, - { name: "Crème liquide", icon: "🥛", allergenNames: ["Lait"] }, - { name: "Fromage", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Emmental", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Gruyère", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Parmesan", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Mozzarella", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Chèvre (fromage)", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Feta", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Comté", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Fromage blanc", icon: "🥣", allergenNames: ["Lait"] }, - { name: "Mascarpone", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Yaourt", icon: "🥣", allergenNames: ["Lait"] }, - { name: "Burrata", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Ricotta", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Pecorino", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Gorgonzola", icon: "🧀", allergenNames: ["Lait"] }, - { name: "Cheddar", icon: "🧀", allergenNames: ["Lait"] }, + { name: "Lait", allergenNames: ["Lait"] }, + { name: "Beurre", allergenNames: ["Lait"] }, + { name: "Crème fraîche", allergenNames: ["Lait"] }, + { name: "Crème liquide", allergenNames: ["Lait"] }, + { name: "Fromage", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Emmental", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Gruyère", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Parmesan", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Mozzarella", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Chèvre (fromage)", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Feta", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Comté", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Fromage blanc", allergenNames: ["Lait"] }, + { name: "Mascarpone", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Yaourt", allergenNames: ["Lait"] }, + { name: "Burrata", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Ricotta", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Pecorino", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Gorgonzola", icon: "CHEESE", allergenNames: ["Lait"] }, + { name: "Cheddar", icon: "CHEESE", allergenNames: ["Lait"] }, ], }, { category: "CREMERIE_FROMAGE", subcategory: "OEUFS", defaultDiets: ["Végétarien", "Pescétarien"], - items: [{ name: "Œuf", icon: "🥚", allergenNames: ["Œufs"] }], + defaultIcon: "EGG", + items: [{ name: "Œuf", allergenNames: ["Œufs"] }], }, { category: "CREMERIE_FROMAGE", subcategory: "ALTERNATIVES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "SPROUT", items: [ - { name: "Lait de coco", icon: "🥥", allergenNames: [] }, - { name: "Crème de coco", icon: "🥥", allergenNames: [] }, - { name: "Lait d'amande", icon: "🥛", allergenNames: ["Fruits à coque"] }, - { name: "Lait d'avoine", icon: "🥛", allergenNames: ["Gluten"] }, - { name: "Tofu", icon: "🧊", allergenNames: ["Soja"] }, - { name: "Tofu soyeux", icon: "🧊", allergenNames: ["Soja"] }, + { name: "Lait de coco", icon: "MILK", allergenNames: [] }, + { name: "Crème de coco", icon: "MILK", allergenNames: [] }, + { name: "Lait d'amande", icon: "MILK", allergenNames: ["Fruits à coque"] }, + { name: "Lait d'avoine", icon: "MILK", allergenNames: ["Gluten"] }, + { name: "Tofu", allergenNames: ["Soja"] }, + { name: "Tofu soyeux", allergenNames: ["Soja"] }, ], }, // ========================================================================= - // 🧂 Condiments & épices + // Condiments & épices // ========================================================================= { category: "CONDIMENTS_EPICES", subcategory: "EPICES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "SPICE", items: [ - { name: "Herbes de Provence", icon: "🌿", allergenNames: [] }, + { name: "Herbes de Provence", allergenNames: [] }, { name: "Poivre noir", allergenNames: [] }, - { name: "Paprika", icon: "🌶️", allergenNames: [] }, - { name: "Piment d'Espelette", icon: "🌶️", allergenNames: [] }, - { name: "Piment de Cayenne", icon: "🌶️", allergenNames: [] }, - { name: "Cumin", icon: "🌿", allergenNames: [] }, - { name: "Curry (poudre)", icon: "🌿", allergenNames: [] }, - { name: "Curcuma", icon: "🌿", allergenNames: [] }, - { name: "Cannelle", icon: "🌿", allergenNames: [] }, - { name: "Muscade", icon: "🌿", allergenNames: [] }, - { name: "Safran", icon: "🌿", allergenNames: [] }, - { name: "Clou de girofle", icon: "🌿", allergenNames: [] }, - { name: "Vanille (gousse)", icon: "🌿", allergenNames: [] }, + { name: "Paprika", allergenNames: [] }, + { name: "Piment d'Espelette", allergenNames: [] }, + { name: "Piment de Cayenne", allergenNames: [] }, + { name: "Cumin", allergenNames: [] }, + { name: "Curry (poudre)", allergenNames: [] }, + { name: "Curcuma", allergenNames: [] }, + { name: "Cannelle", allergenNames: [] }, + { name: "Muscade", allergenNames: [] }, + { name: "Safran", allergenNames: [] }, + { name: "Clou de girofle", allergenNames: [] }, + { name: "Vanille (gousse)", allergenNames: [] }, { name: "Poivre blanc", allergenNames: [] }, { name: "Poivre rose", allergenNames: [] }, { name: "Poivre du Sichuan", allergenNames: [] }, - { name: "Paprika fumé", icon: "🌶️", allergenNames: [] }, - { name: "Piment oiseau", icon: "🌶️", allergenNames: [] }, + { name: "Paprika fumé", allergenNames: [] }, + { name: "Piment oiseau", allergenNames: [] }, { name: "Baies de genièvre", allergenNames: [] }, - { name: "Anis étoilé (badiane)", icon: "🌿", allergenNames: [] }, - { name: "Anis vert", icon: "🌿", allergenNames: [] }, - { name: "Graines de fenouil", icon: "🌿", allergenNames: [] }, + { name: "Anis étoilé (badiane)", allergenNames: [] }, + { name: "Anis vert", allergenNames: [] }, + { name: "Graines de fenouil", allergenNames: [] }, { name: "Sumac", allergenNames: [] }, { name: "Nigelle", allergenNames: [] }, - { name: "Quatre épices", icon: "🌿", allergenNames: [] }, - { name: "Colombo (poudre)", icon: "🌿", allergenNames: [] }, - { name: "Baharat", icon: "🌿", allergenNames: [] }, + { name: "Quatre épices", allergenNames: [] }, + { name: "Colombo (poudre)", allergenNames: [] }, + { name: "Baharat", allergenNames: [] }, { name: "Raifort", allergenNames: [] }, - { name: "Sel aux herbes", icon: "🧂", allergenNames: [] }, - { name: "Sel de céleri", icon: "🧂", allergenNames: ["Céleri"] }, - { name: "Fleur de sel", icon: "🧂", allergenNames: [] }, - { name: "Sel", icon: "🧂", allergenNames: [] }, - { name: "Cinq épices", icon: "🌿", allergenNames: [] }, - { name: "Garam masala", icon: "🌿", allergenNames: [] }, - { name: "Graines de coriandre", icon: "🌿", allergenNames: [] }, - { name: "Cardamome", icon: "🌿", allergenNames: [] }, - { name: "Fenugrec", icon: "🌿", allergenNames: [] }, - { name: "Piment jalapeño", icon: "🌶️", allergenNames: [] }, - { name: "Piment chipotle", icon: "🌶️", allergenNames: [] }, - { name: "Piment poblano", icon: "🌶️", allergenNames: [] }, - { name: "Piment habanero", icon: "🌶️", allergenNames: [] }, - { name: "Ras el hanout", icon: "🌿", allergenNames: [] }, - { name: "Za'atar", icon: "🌿", allergenNames: ["Graines de sésame"] }, + { name: "Sel aux herbes", allergenNames: [] }, + { name: "Sel de céleri", allergenNames: ["Céleri"] }, + { name: "Fleur de sel", allergenNames: [] }, + { name: "Sel", allergenNames: [] }, + { name: "Cinq épices", allergenNames: [] }, + { name: "Garam masala", allergenNames: [] }, + { name: "Graines de coriandre", allergenNames: [] }, + { name: "Cardamome", allergenNames: [] }, + { name: "Fenugrec", allergenNames: [] }, + { name: "Piment jalapeño", allergenNames: [] }, + { name: "Piment chipotle", allergenNames: [] }, + { name: "Piment poblano", allergenNames: [] }, + { name: "Piment habanero", allergenNames: [] }, + { name: "Ras el hanout", allergenNames: [] }, + { name: "Za'atar", allergenNames: ["Graines de sésame"] }, ], }, { category: "CONDIMENTS_EPICES", subcategory: "SAUCES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "JAR", items: [ - { name: "Sauce soja", icon: "🍶", allergenNames: ["Soja"] }, - { name: "Moutarde", icon: "🟡", allergenNames: ["Moutarde"] }, + { name: "Sauce soja", allergenNames: ["Soja"] }, + { name: "Moutarde", allergenNames: ["Moutarde"] }, { name: "Mayonnaise", - icon: "🫙", allergenNames: ["Œufs"], dietNames: ["Végétarien", "Pescétarien"], }, - { name: "Ketchup", icon: "🫙", allergenNames: [] }, - { name: "Tabasco", icon: "🌶️", allergenNames: [] }, + { name: "Ketchup", allergenNames: [] }, + { name: "Tabasco", allergenNames: [] }, { name: "Sauce Worcestershire", - icon: "🫙", allergenNames: ["Poissons"], dietNames: ["Pescétarien"], }, { name: "Sauce nuoc-mâm", - icon: "🫙", allergenNames: ["Poissons"], dietNames: ["Pescétarien"], }, { name: "Wasabi", allergenNames: [] }, - { name: "Harissa", icon: "🌶️", allergenNames: [] }, - { name: "Pâte de curry", icon: "🍛", allergenNames: [] }, - { name: "Beurre de cacahuète", icon: "🥜", allergenNames: ["Arachides"] }, - { name: "Moutarde de Dijon", icon: "🟡", allergenNames: ["Moutarde"] }, - { name: "Moutarde à l'ancienne", icon: "🟡", allergenNames: ["Moutarde"] }, - { name: "Sauce barbecue", icon: "🫙", allergenNames: [] }, + { name: "Harissa", allergenNames: [] }, + { name: "Pâte de curry", allergenNames: [] }, + { name: "Beurre de cacahuète", allergenNames: ["Arachides"] }, + { name: "Moutarde de Dijon", allergenNames: ["Moutarde"] }, + { name: "Moutarde à l'ancienne", allergenNames: ["Moutarde"] }, + { name: "Sauce barbecue", allergenNames: [] }, { name: "Sauce tartare", - icon: "🫙", allergenNames: ["Œufs"], dietNames: ["Végétarien", "Pescétarien"], }, { name: "Sauce cocktail", - icon: "🫙", allergenNames: ["Œufs"], dietNames: ["Végétarien", "Pescétarien"], }, { name: "Sauce béarnaise", - icon: "🫙", allergenNames: ["Œufs", "Lait"], dietNames: ["Végétarien", "Pescétarien"], }, { name: "Sauce hollandaise", - icon: "🫙", allergenNames: ["Œufs", "Lait"], dietNames: ["Végétarien", "Pescétarien"], }, { name: "Sauce béchamel", - icon: "🫙", allergenNames: ["Lait", "Gluten"], dietNames: ["Végétarien", "Pescétarien"], }, - { name: "Sauce teriyaki", icon: "🫙", allergenNames: ["Soja"] }, + { name: "Sauce teriyaki", allergenNames: ["Soja"] }, { name: "Sauce ponzu", - icon: "🫙", allergenNames: ["Soja", "Poissons"], dietNames: ["Pescétarien"], }, - { name: "Chimichurri", icon: "🌿", allergenNames: [] }, + { name: "Chimichurri", allergenNames: [] }, { name: "Pesto rouge (tomates séchées)", - icon: "🫙", allergenNames: ["Lait", "Fruits à coque"], dietNames: ["Végétarien", "Pescétarien"], }, { name: "Pesto", - icon: "🫙", allergenNames: ["Lait", "Fruits à coque"], dietNames: ["Végétarien", "Pescétarien"], }, { name: "Sauce huître", - icon: "🫙", allergenNames: ["Mollusques"], dietNames: ["Pescétarien"], }, - { name: "Sauce hoisin", icon: "🫙", allergenNames: ["Soja"] }, - { name: "Sauce sriracha", icon: "🫙", allergenNames: [] }, - { name: "Sauce sweet chili", icon: "🫙", allergenNames: [] }, - { name: "Miso", icon: "🫙", allergenNames: ["Soja"] }, + { name: "Sauce hoisin", allergenNames: ["Soja"] }, + { name: "Sauce sriracha", allergenNames: [] }, + { name: "Sauce sweet chili", allergenNames: [] }, + { name: "Miso", allergenNames: ["Soja"] }, { name: "Pâte de crevettes", - icon: "🫙", allergenNames: ["Crustacés"], dietNames: ["Pescétarien"], }, - { name: "Pâte de curry rouge (thaï)", icon: "🍛", allergenNames: [] }, - { name: "Pâte de curry vert (thaï)", icon: "🍛", allergenNames: [] }, - { name: "Tahini", icon: "🫙", allergenNames: ["Graines de sésame"] }, + { name: "Pâte de curry rouge (thaï)", allergenNames: [] }, + { name: "Pâte de curry vert (thaï)", allergenNames: [] }, + { name: "Tahini", allergenNames: ["Graines de sésame"] }, ], }, { category: "CONDIMENTS_EPICES", subcategory: "ASSAISONNEMENTS", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "BOTTLE", items: [ - { name: "Huile d'olive", icon: "🫒", allergenNames: [] }, - { name: "Huile de tournesol", icon: "🧴", allergenNames: [] }, - { name: "Huile de colza", icon: "🧴", allergenNames: [] }, - { name: "Huile de coco", icon: "🥥", allergenNames: [] }, - { name: "Huile de sésame", icon: "🧴", allergenNames: ["Graines de sésame"] }, - { name: "Vinaigre de cidre", icon: "🧴", allergenNames: [] }, - { name: "Vinaigre blanc", icon: "🧴", allergenNames: [] }, - { name: "Vinaigre balsamique", icon: "🧴", allergenNames: ["Sulfites"] }, - { name: "Câpres", allergenNames: [] }, - { name: "Olives", icon: "🫒", allergenNames: [] }, - { name: "Vin blanc (cuisine)", icon: "🍷", allergenNames: ["Sulfites"] }, - { name: "Vin rouge (cuisine)", icon: "🍷", allergenNames: ["Sulfites"] }, - { name: "Vinaigre de vin rouge", icon: "🧴", allergenNames: ["Sulfites"] }, - { name: "Vinaigre de vin blanc", icon: "🧴", allergenNames: ["Sulfites"] }, - { name: "Vinaigre de xérès", icon: "🧴", allergenNames: ["Sulfites"] }, - { name: "Huile de noix", icon: "🧴", allergenNames: ["Fruits à coque"] }, - { name: "Huile de noisette", icon: "🧴", allergenNames: ["Fruits à coque"] }, - { name: "Huile d'arachide", icon: "🧴", allergenNames: ["Arachides"] }, - { name: "Huile pimentée", icon: "🌶️", allergenNames: [] }, - { name: "Vinaigre de riz", icon: "🧴", allergenNames: [] }, - { name: "Mirin", icon: "🍶", allergenNames: [] }, - { name: "Saké (cuisine)", icon: "🍶", allergenNames: [] }, - { name: "Jus de citron", icon: "🧃", allergenNames: [] }, - { name: "Jus de citron vert", icon: "🧃", allergenNames: [] }, - { name: "Jus d'orange", icon: "🧃", allergenNames: [] }, - { name: "Jus de pomme", icon: "🧃", allergenNames: [] }, - { name: "Jus de raisin", icon: "🧃", allergenNames: [] }, - { name: "Jus de tomate", icon: "🧃", allergenNames: [] }, - { name: "Jus de cranberry", icon: "🧃", allergenNames: [] }, - { name: "Café", icon: "☕", allergenNames: [] }, - { name: "Thé", icon: "🍵", allergenNames: [] }, - { name: "Bière (cuisine)", icon: "🍺", allergenNames: ["Gluten"] }, - { name: "Cidre (cuisine)", allergenNames: ["Sulfites"] }, - { name: "Champagne / vin pétillant (cuisine)", icon: "🥂", allergenNames: ["Sulfites"] }, - { name: "Porto (cuisine)", icon: "🍷", allergenNames: ["Sulfites"] }, - { name: "Vin jaune (cuisine)", icon: "🍷", allergenNames: ["Sulfites"] }, - { name: "Cognac", icon: "🥃", allergenNames: [] }, - { name: "Rhum", icon: "🥃", allergenNames: [] }, - { name: "Whisky", icon: "🥃", allergenNames: [] }, - { name: "Vodka", icon: "🥃", allergenNames: [] }, + { name: "Huile d'olive", allergenNames: [] }, + { name: "Huile de tournesol", allergenNames: [] }, + { name: "Huile de colza", allergenNames: [] }, + { name: "Huile de coco", allergenNames: [] }, + { name: "Huile de sésame", allergenNames: ["Graines de sésame"] }, + { name: "Vinaigre de cidre", allergenNames: [] }, + { name: "Vinaigre blanc", allergenNames: [] }, + { name: "Vinaigre balsamique", allergenNames: ["Sulfites"] }, + { name: "Câpres", icon: "JAR", allergenNames: [] }, + { name: "Olives", icon: "JAR", allergenNames: [] }, + { name: "Vin blanc (cuisine)", icon: "DRINK", allergenNames: ["Sulfites"] }, + { name: "Vin rouge (cuisine)", icon: "DRINK", allergenNames: ["Sulfites"] }, + { name: "Vinaigre de vin rouge", allergenNames: ["Sulfites"] }, + { name: "Vinaigre de vin blanc", allergenNames: ["Sulfites"] }, + { name: "Vinaigre de xérès", allergenNames: ["Sulfites"] }, + { name: "Huile de noix", allergenNames: ["Fruits à coque"] }, + { name: "Huile de noisette", allergenNames: ["Fruits à coque"] }, + { name: "Huile d'arachide", allergenNames: ["Arachides"] }, + { name: "Huile pimentée", allergenNames: [] }, + { name: "Vinaigre de riz", allergenNames: [] }, + { name: "Mirin", icon: "DRINK", allergenNames: [] }, + { name: "Saké (cuisine)", icon: "DRINK", allergenNames: [] }, + { name: "Jus de citron", icon: "DRINK", allergenNames: [] }, + { name: "Jus de citron vert", icon: "DRINK", allergenNames: [] }, + { name: "Jus d'orange", icon: "DRINK", allergenNames: [] }, + { name: "Jus de pomme", icon: "DRINK", allergenNames: [] }, + { name: "Jus de raisin", icon: "DRINK", allergenNames: [] }, + { name: "Jus de tomate", icon: "DRINK", allergenNames: [] }, + { name: "Jus de cranberry", icon: "DRINK", allergenNames: [] }, + { name: "Café", icon: "DRINK", allergenNames: [] }, + { name: "Thé", icon: "DRINK", allergenNames: [] }, + { name: "Bière (cuisine)", icon: "DRINK", allergenNames: ["Gluten"] }, + { name: "Cidre (cuisine)", icon: "DRINK", allergenNames: ["Sulfites"] }, + { + name: "Champagne / vin pétillant (cuisine)", + icon: "DRINK", + allergenNames: ["Sulfites"], + }, + { name: "Porto (cuisine)", icon: "DRINK", allergenNames: ["Sulfites"] }, + { name: "Vin jaune (cuisine)", icon: "DRINK", allergenNames: ["Sulfites"] }, + { name: "Cognac", icon: "DRINK", allergenNames: [] }, + { name: "Rhum", icon: "DRINK", allergenNames: [] }, + { name: "Whisky", icon: "DRINK", allergenNames: [] }, + { name: "Vodka", icon: "DRINK", allergenNames: [] }, ], }, // ========================================================================= - // 🍳 Aides culinaires + // Aides culinaires // ========================================================================= { category: "AIDES_CULINAIRES", subcategory: "BASES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "GRAIN", items: [ - { name: "Farine de blé", icon: "🌾", allergenNames: ["Gluten"] }, - { name: "Farine complète", icon: "🌾", allergenNames: ["Gluten"] }, - { name: "Farine de maïs", icon: "🌽", allergenNames: [] }, - { name: "Farine de sarrasin", icon: "🌾", allergenNames: [] }, - { name: "Farine de riz", icon: "🍚", allergenNames: [] }, - { name: "Bouillon cube légumes", icon: "🫙", allergenNames: ["Céleri"] }, - { name: "Bouillon cube volaille", icon: "🫙", allergenNames: ["Céleri"], dietNames: [] }, - { name: "Concentré de tomate", icon: "🍅", allergenNames: [] }, - { name: "Coulis de tomate", icon: "🍅", allergenNames: [] }, - { name: "Tomates pelées (conserve)", icon: "🍅", allergenNames: [] }, - { name: "Tomates séchées", icon: "🍅", allergenNames: [] }, - { name: "Fond de veau", icon: "🫙", allergenNames: [], dietNames: [] }, - { name: "Fond de volaille", icon: "🫙", allergenNames: [], dietNames: [] }, - { name: "Bouillon cube bœuf", icon: "🫙", allergenNames: ["Céleri"], dietNames: [] }, + { name: "Farine de blé", allergenNames: ["Gluten"] }, + { name: "Farine complète", allergenNames: ["Gluten"] }, + { name: "Farine de maïs", allergenNames: [] }, + { name: "Farine de sarrasin", allergenNames: [] }, + { name: "Farine de riz", allergenNames: [] }, + { name: "Bouillon cube légumes", icon: "STOCK_POT", allergenNames: ["Céleri"] }, + { + name: "Bouillon cube volaille", + icon: "STOCK_POT", + allergenNames: ["Céleri"], + dietNames: [], + }, + { name: "Concentré de tomate", icon: "JAR", allergenNames: [] }, + { name: "Coulis de tomate", icon: "JAR", allergenNames: [] }, + { name: "Tomates pelées (conserve)", icon: "JAR", allergenNames: [] }, + { name: "Tomates séchées", icon: "JAR", allergenNames: [] }, + { name: "Fond de veau", icon: "STOCK_POT", allergenNames: [], dietNames: [] }, + { name: "Fond de volaille", icon: "STOCK_POT", allergenNames: [], dietNames: [] }, + { + name: "Bouillon cube bœuf", + icon: "STOCK_POT", + allergenNames: ["Céleri"], + dietNames: [], + }, { name: "Bouillon cube poisson", - icon: "🫙", + icon: "STOCK_POT", allergenNames: ["Poissons", "Céleri"], dietNames: ["Pescétarien"], }, - { name: "Bouillon de légumes", icon: "🫙", allergenNames: ["Céleri"] }, - { name: "Bouillon de volaille", icon: "🫙", allergenNames: ["Céleri"], dietNames: [] }, - { name: "Bouillon de bœuf", icon: "🫙", allergenNames: ["Céleri"], dietNames: [] }, - { name: "Court-bouillon", icon: "🫙", allergenNames: [] }, + { name: "Bouillon de légumes", icon: "STOCK_POT", allergenNames: ["Céleri"] }, + { + name: "Bouillon de volaille", + icon: "STOCK_POT", + allergenNames: ["Céleri"], + dietNames: [], + }, + { name: "Bouillon de bœuf", icon: "STOCK_POT", allergenNames: ["Céleri"], dietNames: [] }, + { name: "Court-bouillon", icon: "STOCK_POT", allergenNames: [] }, { name: "Dashi (bouillon japonais)", - icon: "🫙", + icon: "STOCK_POT", allergenNames: ["Poissons"], dietNames: ["Pescétarien"], }, { name: "Bisque de crustacés", - icon: "🫙", + icon: "STOCK_POT", allergenNames: ["Crustacés"], dietNames: ["Pescétarien"], }, - { name: "Farine de tapioca", icon: "🌾", allergenNames: [] }, - { name: "Masa harina", icon: "🌽", allergenNames: [] }, - { name: "Eau", icon: "💧", allergenNames: [] }, - { name: "Eau gazeuse", icon: "💧", allergenNames: [] }, - { name: "Eau de fleur d'oranger", icon: "💧", allergenNames: [] }, - { name: "Eau de rose", icon: "💧", allergenNames: [] }, + { name: "Farine de tapioca", allergenNames: [] }, + { name: "Masa harina", allergenNames: [] }, + { name: "Eau", icon: "DRINK", allergenNames: [] }, + { name: "Eau gazeuse", icon: "DRINK", allergenNames: [] }, + { name: "Eau de fleur d'oranger", icon: "DRINK", allergenNames: [] }, + { name: "Eau de rose", icon: "DRINK", allergenNames: [] }, { name: "Fumet de poisson", - icon: "🫙", + icon: "STOCK_POT", allergenNames: ["Poissons"], dietNames: ["Pescétarien"], }, @@ -780,63 +813,65 @@ const INGREDIENT_GROUPS: Array<{ category: "AIDES_CULINAIRES", subcategory: "EPAISSISSANTS", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "JAR", items: [ - { name: "Levure boulangère", icon: "🫙", allergenNames: [] }, - { name: "Levure chimique", icon: "🫙", allergenNames: [] }, - { name: "Maïzena", icon: "🫙", allergenNames: [] }, - { name: "Farine de lupin", icon: "🌱", allergenNames: ["Lupin"] }, + { name: "Levure boulangère", allergenNames: [] }, + { name: "Levure chimique", allergenNames: [] }, + { name: "Maïzena", allergenNames: [] }, + { name: "Farine de lupin", allergenNames: ["Lupin"] }, // Animal collagen (bones/skin, usually pork or beef) — not // vegetarian/vegan, and not reliably fish-derived either, so no // pescetarian flag. - { name: "Gélatine", icon: "🫙", allergenNames: [], dietNames: [] }, - { name: "Bicarbonate de soude", icon: "🫙", allergenNames: [] }, - { name: "Fécule de pomme de terre", icon: "🫙", allergenNames: [] }, + { name: "Gélatine", allergenNames: [], dietNames: [] }, + { name: "Bicarbonate de soude", allergenNames: [] }, + { name: "Fécule de pomme de terre", allergenNames: [] }, ], }, { category: "AIDES_CULINAIRES", subcategory: "SUCRES", defaultDiets: ["Végétarien", "Végan", "Pescétarien"], + defaultIcon: "SUGAR", items: [ - { name: "Sucre", icon: "🍬", allergenNames: [] }, - { name: "Miel", icon: "🍯", allergenNames: [], dietNames: ["Végétarien", "Pescétarien"] }, - { name: "Sirop d'érable", icon: "🍁", allergenNames: [] }, - { name: "Sucre roux", icon: "🍬", allergenNames: [] }, - { name: "Sucre glace", icon: "🍬", allergenNames: [] }, - { name: "Cassonade", icon: "🍬", allergenNames: [] }, - { name: "Chocolat noir", icon: "🍫", allergenNames: [] }, + { name: "Sucre", allergenNames: [] }, + { name: "Miel", allergenNames: [], dietNames: ["Végétarien", "Pescétarien"] }, + { name: "Sirop d'érable", allergenNames: [] }, + { name: "Sucre roux", allergenNames: [] }, + { name: "Sucre glace", allergenNames: [] }, + { name: "Cassonade", allergenNames: [] }, + { name: "Chocolat noir", allergenNames: [] }, { name: "Chocolat au lait", - icon: "🍫", allergenNames: ["Lait"], dietNames: ["Végétarien", "Pescétarien"], }, { name: "Chocolat blanc", - icon: "🍫", allergenNames: ["Lait"], dietNames: ["Végétarien", "Pescétarien"], }, - { name: "Pépites de chocolat", icon: "🍫", allergenNames: [] }, - { name: "Cacao en poudre", icon: "🍫", allergenNames: [] }, - { name: "Extrait de vanille", icon: "🌿", allergenNames: [] }, - { name: "Sucre de palme", icon: "🍬", allergenNames: [] }, - { name: "Sirop de sucre de canne", icon: "🫙", allergenNames: [] }, + { name: "Pépites de chocolat", allergenNames: [] }, + { name: "Cacao en poudre", allergenNames: [] }, + { name: "Extrait de vanille", allergenNames: [] }, + { name: "Sucre de palme", allergenNames: [] }, + { name: "Sirop de sucre de canne", allergenNames: [] }, ], }, ]; const INGREDIENTS: Array< - IngredientSeed & { + Omit & { category: IngredientCategory; subcategory: IngredientSubcategory; + icon: IngredientIcon; dietNames: string[]; } -> = INGREDIENT_GROUPS.flatMap(({ category, subcategory, defaultDiets, items }) => +> = INGREDIENT_GROUPS.flatMap(({ category, subcategory, defaultDiets, defaultIcon, items }) => items.map((item) => ({ ...item, category, subcategory, + icon: item.icon ?? defaultIcon, dietNames: item.dietNames ?? defaultDiets, })), ); @@ -903,7 +938,7 @@ export async function seedReferenceData(prisma: PrismaClient): Promise { const existing = existingByName.get(i.name); return ( existing && - (existing.icon !== (i.icon ?? null) || + (existing.icon !== i.icon || existing.category !== i.category || existing.subcategory !== i.subcategory) ); diff --git a/apps/web/src/features/profile/DislikedIngredientsField.tsx b/apps/web/src/features/profile/DislikedIngredientsField.tsx index 9299065..6c9556f 100644 --- a/apps/web/src/features/profile/DislikedIngredientsField.tsx +++ b/apps/web/src/features/profile/DislikedIngredientsField.tsx @@ -1,5 +1,6 @@ import type { IngredientView } from "@batch-cooking/shared"; import { useTranslation } from "react-i18next"; +import { IngredientPicker } from "../recipes/IngredientPicker"; // Reuses `IngredientPicker` verbatim (built for the recipe form's // ingredient picker, features/recipes/) rather than a second search+browse // field — same reference ingredient list, same category/search browsing, @@ -9,7 +10,7 @@ import { useTranslation } from "react-i18next"; // recipes.scss — colocated there since it's the same "reference ingredient // picker" visual family, even though this field lives in the profile // feature). -import { IngredientPicker } from "../recipes/IngredientPicker"; +import { IngredientTypeIcon } from "../recipes/ingredient-icons"; import "../recipes/recipes.scss"; import "./profile-forms.scss"; @@ -50,7 +51,9 @@ export function DislikedIngredientsField({
    {selected.map((ingredient) => (
  • - + {ingredient.name} ))} @@ -147,6 +149,7 @@ export function IngredientPicker({ className={`ingredient-picker__subcategory${subcategory === s ? " active" : ""}`} onClick={() => setSubcategory(s)} > + {t(`recipes.form.subcategory.${s}`)} ))} @@ -165,7 +168,7 @@ export function IngredientPicker({ onClick={() => handleSelect(ingredient)} > {ingredient.name} {showAllergens && } diff --git a/apps/web/src/features/recipes/IngredientRow.tsx b/apps/web/src/features/recipes/IngredientRow.tsx index 58fd0dc..9982658 100644 --- a/apps/web/src/features/recipes/IngredientRow.tsx +++ b/apps/web/src/features/recipes/IngredientRow.tsx @@ -2,6 +2,7 @@ import type { IngredientView } from "@batch-cooking/shared"; import { useTranslation } from "react-i18next"; import { AllergenBadges } from "./AllergenBadges"; import { DietBadges } from "./DietBadges"; +import { IngredientTypeIcon } from "./ingredient-icons"; import "./recipes.scss"; /** One selected ingredient line in the recipe form — the ingredient itself (picked via `IngredientPicker`) plus its quantity/unit for this recipe. Quantity/unit are kept as raw strings while editing (not parsed to a number until submit) so an in-progress/invalid value doesn't fight the input. */ @@ -25,7 +26,7 @@ export function IngredientRow({ return (
  • {ingredient.name} + {children} + + ); +} + +/** Carrot — root vegetables, leafy greens, and produce generally (`PRODUITS_FRAIS`/`LEGUMES`). */ +export function VegetableIcon() { + return ( + + + + + ); +} + +/** Apple — `PRODUITS_FRAIS`/`FRUITS`. */ +export function FruitIcon() { + return ( + + + + + ); +} + +/** A leaf sprig — `PRODUITS_FRAIS`/`HERBES_FRAICHES`. */ +export function HerbIcon() { + return ( + + + + + + + ); +} + +/** A cut of meat — `BOUCHERIE_POISSONNERIE`/`VIANDES`. */ +export function MeatIcon() { + return ( + + + + + ); +} + +/** Drumstick — `BOUCHERIE_POISSONNERIE`/`VOLAILLES`. */ +export function PoultryIcon() { + return ( + + + + + + ); +} + +/** Fish — `BOUCHERIE_POISSONNERIE`/`POISSONS`. */ +export function FishIcon() { + return ( + + + + + + ); +} + +/** Shrimp — `BOUCHERIE_POISSONNERIE`/`CRUSTACES_FRUITS_DE_MER`. */ +export function ShellfishIcon() { + return ( + + + + + ); +} + +/** Wheat ear — grains, pasta, rice, flour (`EPICERIE_SECHE`/`FECULENTS`, and flours under `AIDES_CULINAIRES`/`BASES`). */ +export function GrainIcon() { + return ( + + + + + + + + ); +} + +/** Bean pod — `EPICERIE_SECHE`/`LEGUMINEUSES`. */ +export function LegumeIcon() { + return ( + + + + + + ); +} + +/** Acorn — nuts, seeds, dried fruit (`EPICERIE_SECHE`/`GRAINES_FRUITS_SECS`). */ +export function NutSeedIcon() { + return ( + + + + + ); +} + +/** A loaf, scored on top — `BOULANGERIE`/`PAINS`. */ +export function BreadIcon() { + return ( + + + + + ); +} + +/** Rolling pin — raw, uncooked pastry (`BOULANGERIE`/`PATES_A_CUIRE`). */ +export function DoughIcon() { + return ( + + + + + + ); +} + +/** A glass of milk — `CREMERIE_FROMAGE`/`PRODUITS_LAITIERS` (non-cheese items). */ +export function MilkIcon() { + return ( + + + + + ); +} + +/** A wedge of cheese with holes — `CREMERIE_FROMAGE`/`PRODUITS_LAITIERS` (cheese items). */ +export function CheeseIcon() { + return ( + + + + + + + ); +} + +/** An egg — `CREMERIE_FROMAGE`/`OEUFS`. */ +export function EggIcon() { + return ( + + + + ); +} + +/** A seedling — plant-based dairy/meat alternatives (`CREMERIE_FROMAGE`/`ALTERNATIVES`). */ +export function SproutIcon() { + return ( + + + + + + ); +} + +/** A shaker — dried spices/herbs (`CONDIMENTS_EPICES`/`EPICES`). */ +export function SpiceIcon() { + return ( + + + + + + ); +} + +/** A condiment jar — sauces, pickles, tinned/preserved goods (`CONDIMENTS_EPICES`/`SAUCES` and the `EPICERIE_SECHE`/`AUTRES` catch-all). */ +export function JarIcon() { + return ( + + + + + + ); +} + +/** A bottle — oils, vinegars (`CONDIMENTS_EPICES`/`ASSAISONNEMENTS`, the pourable subset). */ +export function BottleIcon() { + return ( + + + + + ); +} + +/** A glass — juices, coffee/tea, cooking alcohols, water (`CONDIMENTS_EPICES`/`ASSAISONNEMENTS`'s drinkable subset). */ +export function DrinkIcon() { + return ( + + + + + ); +} + +/** A stockpot — broths, stocks, water bases (`AIDES_CULINAIRES`/`BASES`'s liquid-base subset). */ +export function StockPotIcon() { + return ( + + + + + + ); +} + +/** A sugar cube — `AIDES_CULINAIRES`/`SUCRES`. */ +export function SugarIcon() { + return ( + + + + + + ); +} + +/** Every icon component, keyed by {@link IngredientIconType} — the single lookup `IngredientPicker`/`IngredientRow`/category chips render through. */ +export const INGREDIENT_ICON_COMPONENTS: Record JSX.Element> = { + VEGETABLE: VegetableIcon, + FRUIT: FruitIcon, + HERB: HerbIcon, + MEAT: MeatIcon, + POULTRY: PoultryIcon, + FISH: FishIcon, + SHELLFISH: ShellfishIcon, + GRAIN: GrainIcon, + LEGUME: LegumeIcon, + NUT_SEED: NutSeedIcon, + BREAD: BreadIcon, + DOUGH: DoughIcon, + MILK: MilkIcon, + CHEESE: CheeseIcon, + EGG: EggIcon, + SPROUT: SproutIcon, + SPICE: SpiceIcon, + JAR: JarIcon, + BOTTLE: BottleIcon, + DRINK: DrinkIcon, + STOCK_POT: StockPotIcon, + SUGAR: SugarIcon, +}; + +/** Renders the generic pictogram for one {@link IngredientIconType} — the actual component `IngredientPicker`'s cards and `IngredientRow` use, rather than importing {@link INGREDIENT_ICON_COMPONENTS} directly everywhere. */ +export function IngredientTypeIcon({ icon }: { icon: IngredientIconType }) { + const Component = INGREDIENT_ICON_COMPONENTS[icon]; + return ; +} + +/** + * One representative {@link IngredientIconType} per {@link IngredientCategory} + * aisle, for `IngredientPicker`'s top-level category chips — a category + * mixes several icon types (e.g. "Aides culinaires" has flour, stock, and + * sugar items), so this is a deliberate, illustrative pick rather than a + * derived fact. + */ +export const CATEGORY_ICON: Record = { + PRODUITS_FRAIS: "VEGETABLE", + BOUCHERIE_POISSONNERIE: "MEAT", + EPICERIE_SECHE: "GRAIN", + BOULANGERIE: "BREAD", + CREMERIE_FROMAGE: "CHEESE", + CONDIMENTS_EPICES: "SPICE", + AIDES_CULINAIRES: "STOCK_POT", +}; + +/** Renders {@link CATEGORY_ICON}'s pictogram for one category — used by the category chip row. */ +export function CategoryIcon({ category }: { category: IngredientCategory }) { + return ; +} + +/** + * One representative {@link IngredientIconType} per {@link IngredientSubcategory} + * rack, for `IngredientPicker`'s second-tier subcategory chips. Most map + * 1:1 onto their subcategory's dominant shape; a couple of heterogeneous + * subcategories (`ASSAISONNEMENTS` mixes oils with juices and coffee, + * `BASES` mixes flour with stock and canned tomato) get one illustrative + * pick rather than a derived fact, same reasoning as {@link CATEGORY_ICON}. + */ +export const SUBCATEGORY_ICON: Record = { + LEGUMES: "VEGETABLE", + FRUITS: "FRUIT", + HERBES_FRAICHES: "HERB", + VIANDES: "MEAT", + VOLAILLES: "POULTRY", + POISSONS: "FISH", + CRUSTACES_FRUITS_DE_MER: "SHELLFISH", + FECULENTS: "GRAIN", + LEGUMINEUSES: "LEGUME", + GRAINES_FRUITS_SECS: "NUT_SEED", + AUTRES: "JAR", + PAINS: "BREAD", + PATES_A_CUIRE: "DOUGH", + PRODUITS_LAITIERS: "MILK", + OEUFS: "EGG", + ALTERNATIVES: "SPROUT", + EPICES: "SPICE", + SAUCES: "JAR", + ASSAISONNEMENTS: "BOTTLE", + BASES: "STOCK_POT", + EPAISSISSANTS: "JAR", + SUCRES: "SUGAR", +}; + +/** Renders {@link SUBCATEGORY_ICON}'s pictogram for one subcategory — used by the subcategory chip row. */ +export function SubcategoryIcon({ subcategory }: { subcategory: IngredientSubcategory }) { + return ; +} diff --git a/apps/web/src/features/recipes/recipes.scss b/apps/web/src/features/recipes/recipes.scss index 89c131f..d0bfe92 100644 --- a/apps/web/src/features/recipes/recipes.scss +++ b/apps/web/src/features/recipes/recipes.scss @@ -637,6 +637,17 @@ background: var(--color-surface); border-radius: var(--radius-base); + &__icon { + display: flex; + flex-shrink: 0; + color: var(--color-text-muted); + + svg { + width: 1.25rem; + height: 1.25rem; + } + } + &__name { font-weight: 600; margin-right: auto; @@ -751,6 +762,9 @@ } &__category { + display: flex; + align-items: center; + gap: var(--space-xs); flex-shrink: 0; appearance: none; padding: var(--space-xs) var(--space-sm); @@ -763,6 +777,12 @@ background: var(--color-surface-alt); color: var(--color-text-muted); + svg { + flex-shrink: 0; + width: 1rem; + height: 1rem; + } + &:hover { border-color: var(--color-primary); color: var(--color-text); @@ -788,6 +808,9 @@ } &__subcategory { + display: flex; + align-items: center; + gap: 4px; flex-shrink: 0; appearance: none; padding: 2px var(--space-sm); @@ -800,6 +823,12 @@ background: transparent; color: var(--color-text-muted); + svg { + flex-shrink: 0; + width: 0.85rem; + height: 0.85rem; + } + &:hover { border-color: var(--color-tag); color: var(--color-text); @@ -841,8 +870,14 @@ } &__card-icon { - font-size: 1.5rem; - line-height: 1; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 1.75rem; + height: 1.75rem; + } } &__card-name { @@ -1007,6 +1042,12 @@ background: var(--color-surface-alt); border-radius: var(--radius-pill); + svg { + width: 1rem; + height: 1rem; + flex-shrink: 0; + } + button { display: flex; align-items: center; diff --git a/apps/web/src/locales/fr/translation.json b/apps/web/src/locales/fr/translation.json index 16b07c5..3598b17 100644 --- a/apps/web/src/locales/fr/translation.json +++ b/apps/web/src/locales/fr/translation.json @@ -172,13 +172,13 @@ "allSubcategories": "Tout", "noIngredientFound": "Aucun ingrédient trouvé.", "category": { - "PRODUITS_FRAIS": "🥦 Produits frais", - "BOUCHERIE_POISSONNERIE": "🥩 Boucherie & poissonnerie", - "EPICERIE_SECHE": "🥫 Épicerie sèche", - "BOULANGERIE": "🍞 Boulangerie", - "CREMERIE_FROMAGE": "🧈 Crémerie & fromage", - "CONDIMENTS_EPICES": "🧂 Condiments & épices", - "AIDES_CULINAIRES": "🍳 Aides culinaires" + "PRODUITS_FRAIS": "Produits frais", + "BOUCHERIE_POISSONNERIE": "Boucherie & poissonnerie", + "EPICERIE_SECHE": "Épicerie sèche", + "BOULANGERIE": "Boulangerie", + "CREMERIE_FROMAGE": "Crémerie & fromage", + "CONDIMENTS_EPICES": "Condiments & épices", + "AIDES_CULINAIRES": "Aides culinaires" }, "subcategory": { "LEGUMES": "Légumes", diff --git a/packages/shared/src/types/reference.ts b/packages/shared/src/types/reference.ts index b41f768..49c86f8 100644 --- a/packages/shared/src/types/reference.ts +++ b/packages/shared/src/types/reference.ts @@ -109,6 +109,43 @@ export const INGREDIENT_CATEGORY_SUBCATEGORIES: Record< AIDES_CULINAIRES: ["BASES", "EPAISSISSANTS", "SUCRES"], }; +/** + * Generic pictogram *type* for an ingredient — mirrors `IngredientIcon` in + * schema.prisma, declared by hand for the same reason as + * {@link AllergenKind}. Was a free-text emoji per ingredient (437 distinct + * characters); replaced with this small, shared vocabulary of ~20 generic + * shapes ("a vegetable", "a bottle", "a wedge of cheese"…) so + * `apps/web`'s `features/recipes/ingredient-icons.tsx` can render a real + * SVG line icon per ingredient instead — see that file for the actual + * pictograms and the full reasoning. + */ +export const INGREDIENT_ICONS = [ + "VEGETABLE", + "FRUIT", + "HERB", + "MEAT", + "POULTRY", + "FISH", + "SHELLFISH", + "GRAIN", + "LEGUME", + "NUT_SEED", + "BREAD", + "DOUGH", + "MILK", + "CHEESE", + "EGG", + "SPROUT", + "SPICE", + "JAR", + "BOTTLE", + "DRINK", + "STOCK_POT", + "SUGAR", +] as const; +/** Inferred TS type for one {@link INGREDIENT_ICONS} member. */ +export type IngredientIcon = (typeof INGREDIENT_ICONS)[number]; + /** * A selectable ingredient, as returned by `GET /reference/ingredients` — * reference data (`Ingredient`, seeded via `apps/api/src/db/ @@ -131,7 +168,7 @@ export const INGREDIENT_CATEGORY_SUBCATEGORIES: Record< export interface IngredientView { id: number; name: string; - icon: string | null; + icon: IngredientIcon; category: IngredientCategory; subcategory: IngredientSubcategory; allergens: AllergyView[];