Deuxième étape du chantier "onglet Sources" : l'UI de parcours, construite contre les endpoints backend de l'étape 1 (#45). L'onglet désactivé placeholder de RecipeTabs devient un vrai onglet fonctionnel. - RecipeTabs.tsx : nouveau type RecipesPageTab (RecipeTab | "sources") — gardé hors du type partagé RecipeTab puisque l'API n'a pas de tab=sources à valider. Un prop `tabs` optionnel restreint quels onglets s'affichent — RecipePickerDialog (choix d'une recette pour un planning) s'y restreint aux 4 onglets réels, parcourir des sources externes en plein milieu de ce dialogue n'a pas de sens sans le flux de revue/import. - Nouveau RecipeSourcesPanel.tsx : contenu de l'onglet "Sources" — autonome (son propre master-detail), ne partage pas le fetching RecipeTab de RecipesPage puisqu'il parcourt le catalogue *live* d'une source (GET /sources/:key/browse), pas la table Recipe sauvegardée. Sélecteur de source si le foyer en a activé plusieurs ; sélectionner un item déjà importé navigue directement vers la vraie recette (SourceItemTable + navigate), un item pas encore importé affiche un aperçu en lecture seule (SourceItemPreviewPanel, réutilise StepDescription — les tech steps sont donc déjà surlignés dans l'aperçu). - Bug trouvé et corrigé en écrivant le scénario Cypress : cliquer un item déjà importé changeait l'URL mais restait affiché sur l'onglet Sources (RecipesPage ne rend RecipeDetailPanel/RecipeTable qu'en dehors de l'onglet "sources"). RecipeSourcesPanel prend maintenant un callback `onViewImportedRecipe` pour repasser sur un onglet réel avant de naviguer. Tests : nouveau recipe-sources.feature (parcours utilisateur complet — onglet vide, parcours avec items importés/non importés, aperçu avec surlignage de technique) ; recipes.cy.ts corrigé (assertion obsolète sur l'ancien placeholder désactivé). Étape suivante (3/4) : écran de revue (corriger les ingrédients non résolus) + finalisation de l'import. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
import type { BrowsableSourceItemView } from "@batch-cooking/shared";
|
|
import { useTranslation } from "react-i18next";
|
|
import "./recipes.scss";
|
|
|
|
/**
|
|
* List of one source's browsable items (`RecipeSourcesPanel`) — same
|
|
* "photo + name, click/Enter to select" row shape as `RecipeTable`, plus
|
|
* an "already imported" badge in place of allergen/regime columns (a
|
|
* source item has neither, it's not resolved against our catalogs until
|
|
* previewed).
|
|
*/
|
|
export function SourceItemTable({
|
|
items,
|
|
selectedExternalId,
|
|
onSelect,
|
|
}: {
|
|
items: BrowsableSourceItemView[];
|
|
selectedExternalId: string | null;
|
|
onSelect: (item: BrowsableSourceItemView) => void;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<div className="recipe-table-wrap">
|
|
<table className="recipe-table">
|
|
<thead>
|
|
<tr>
|
|
<th />
|
|
<th>{t("recipes.table.name")}</th>
|
|
<th />
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{items.map((item) => (
|
|
<tr
|
|
key={item.externalId}
|
|
className={item.externalId === selectedExternalId ? "selected" : undefined}
|
|
onClick={() => onSelect(item)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" || e.key === " ") {
|
|
e.preventDefault();
|
|
onSelect(item);
|
|
}
|
|
}}
|
|
tabIndex={0}
|
|
aria-current={item.externalId === selectedExternalId ? "true" : undefined}
|
|
>
|
|
<td>
|
|
<span className="recipe-table__photo" aria-hidden="true">
|
|
{item.picture ? <img src={item.picture} alt="" /> : "🍽️"}
|
|
</span>
|
|
</td>
|
|
<td className="recipe-table__name">{item.title}</td>
|
|
<td>
|
|
{item.alreadyImported && (
|
|
<span className="source-item-table__imported-badge">
|
|
{t("recipes.sources.alreadyImported")}
|
|
</span>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|