diff --git a/src/api/types.ts b/src/api/types.ts index 3de916a..28719a2 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -261,6 +261,43 @@ export interface CatalogueProduct { fssai_license?: string; highlights?: string[]; nutrients?: string[]; + /** + * The structured nutrition panel, where the catalogue carries one. + * + * `nutrients` above is the older shape — display lines like "Energy 350kcal", + * which are enough to print bullets and not enough for an app to lay out a + * table or say what the figures are measured against. This is what the agent + * team fills and what the customer app renders. + * + * Optional because most rows do not have it yet: the backend derives a panel + * from the lines when this is absent, so a reader should prefer this and fall + * back rather than treating its absence as "no nutrition". + */ + nutrition?: NutritionPanel; +} + +/** + * A nutrition panel as Fiesta sends it. + * + * Field names are the backend's and the customer app's — `servingsize`, not + * `servingSize`. An API is a promise to clients already written against it, and + * renaming on the way in would mean this file disagreeing with the wire. + */ +export interface NutritionPanel { + /** What the figures are measured against: "100g", "100ml", "1 serving". */ + per?: string; + /** What the pack calls one serving: "30g". A label routinely states both. */ + servingsize?: string; + /** Never empty when the panel is present — the backend omits an empty one. */ + items: NutritionItem[]; +} + +export interface NutritionItem { + name: string; + /** A float: saturated fat is 11.5g as often as it is 11g. */ + value: number; + /** "kcal", "g", "mg". Free text — the label is the authority. */ + unit?: string; } export interface CatalogueBrand { diff --git a/src/features/catalogue/CatalogueDetailDrawer.tsx b/src/features/catalogue/CatalogueDetailDrawer.tsx index 59d214b..883a57f 100644 --- a/src/features/catalogue/CatalogueDetailDrawer.tsx +++ b/src/features/catalogue/CatalogueDetailDrawer.tsx @@ -18,6 +18,7 @@ import { import { categoryForCatalogueProduct } from '@/features/store-admin/productCategory'; import { aisleForCategory } from '@/features/store-admin/appAisle'; import { HealthScorePanel } from '@/features/store-admin/HealthScorePanel'; +import { NutritionTable } from '@/features/catalogue/NutritionPanel'; /** * One global-catalogue product, in full. @@ -202,7 +203,7 @@ export function CatalogueDetailDrawer({ category={product.category} /> - {product.highlights?.length || product.nutrients?.length ? ( + {product.highlights?.length || product.nutrients?.length || product.nutrition?.items?.length ? (
{basis}
+ ) : null} +| {item.name} | ++ {/* A row with no figure is real label text — "Contains permitted + natural colour" — carried through rather than shown as 0, + which would state something false about the food. */} + {item.value || item.unit ? ( + <> + {item.value} + {item.unit ? ( + + {' '} + {item.unit} + + ) : null} + > + ) : null} + | +