nutrition panel removed
This commit is contained in:
@@ -261,43 +261,6 @@ export interface CatalogueProduct {
|
|||||||
fssai_license?: string;
|
fssai_license?: string;
|
||||||
highlights?: string[];
|
highlights?: string[];
|
||||||
nutrients?: 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 {
|
export interface CatalogueBrand {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
import { categoryForCatalogueProduct } from '@/features/store-admin/productCategory';
|
import { categoryForCatalogueProduct } from '@/features/store-admin/productCategory';
|
||||||
import { aisleForCategory } from '@/features/store-admin/appAisle';
|
import { aisleForCategory } from '@/features/store-admin/appAisle';
|
||||||
import { HealthScorePanel } from '@/features/store-admin/HealthScorePanel';
|
import { HealthScorePanel } from '@/features/store-admin/HealthScorePanel';
|
||||||
import { NutritionTable } from '@/features/catalogue/NutritionPanel';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One global-catalogue product, in full.
|
* One global-catalogue product, in full.
|
||||||
@@ -203,7 +202,7 @@ export function CatalogueDetailDrawer({
|
|||||||
category={product.category}
|
category={product.category}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{product.highlights?.length || product.nutrients?.length || product.nutrition?.items?.length ? (
|
{product.highlights?.length || product.nutrients?.length ? (
|
||||||
<div className="drawer-bullets">
|
<div className="drawer-bullets">
|
||||||
{product.highlights?.length ? (
|
{product.highlights?.length ? (
|
||||||
<div className="drawer-section">
|
<div className="drawer-section">
|
||||||
@@ -211,13 +210,7 @@ export function CatalogueDetailDrawer({
|
|||||||
<Bullets items={product.highlights} />
|
<Bullets items={product.highlights} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{/* The structured panel wins where the catalogue has one: it lines the
|
{product.nutrients?.length ? (
|
||||||
figures up so two products can be compared down a column, which is
|
|
||||||
most of what a nutrition table is for. The lines are the fallback
|
|
||||||
for every row the agent team has not reached yet. */}
|
|
||||||
{product.nutrition?.items?.length ? (
|
|
||||||
<NutritionTable panel={product.nutrition} />
|
|
||||||
) : product.nutrients?.length ? (
|
|
||||||
<div className="drawer-section">
|
<div className="drawer-section">
|
||||||
<h4 className="drawer-section-title">Nutrition</h4>
|
<h4 className="drawer-section-title">Nutrition</h4>
|
||||||
<Bullets items={product.nutrients} />
|
<Bullets items={product.nutrients} />
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import type { NutritionPanel as Panel } from '@/api/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A nutrition panel, laid out the way a label is.
|
|
||||||
*
|
|
||||||
* ── Why this is not bullets ─────────────────────────────────────────────────
|
|
||||||
*
|
|
||||||
* Nutrition used to render through `Bullets`, because the catalogue only ever
|
|
||||||
* held display strings — "Energy 350kcal" — and a list was the honest shape for
|
|
||||||
* a list. The catalogue now carries a structured panel, and a structured panel
|
|
||||||
* read as bullets is worse than either: the figures no longer line up, so the
|
|
||||||
* eye cannot run down a column to compare two products, which is most of what a
|
|
||||||
* nutrition table is for.
|
|
||||||
*
|
|
||||||
* So: names left, figures right, aligned. The same information, in the shape it
|
|
||||||
* was printed in.
|
|
||||||
*
|
|
||||||
* ── Why `per` sits at the top and not beside each row ───────────────────────
|
|
||||||
*
|
|
||||||
* Because it applies to all of them. Repeating "per 100g" on seven rows is six
|
|
||||||
* repetitions of one fact, and a label does not do it either. Absent when the
|
|
||||||
* catalogue did not state it, rather than defaulted to "100g" — a wrong basis
|
|
||||||
* makes every figure beneath it a misstatement rather than an unknown.
|
|
||||||
*/
|
|
||||||
export function NutritionTable({ panel }: { panel: Panel }) {
|
|
||||||
if (!panel.items?.length) return null;
|
|
||||||
|
|
||||||
const basis = [
|
|
||||||
panel.per ? `per ${panel.per}` : null,
|
|
||||||
panel.servingsize ? `serving ${panel.servingsize}` : null,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' · ');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="drawer-section">
|
|
||||||
<h4 className="drawer-section-title">Nutrition</h4>
|
|
||||||
{basis ? (
|
|
||||||
<p style={{ margin: '0 0 8px', fontSize: 11.5, color: 'var(--color-ink-4)' }}>{basis}</p>
|
|
||||||
) : null}
|
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
|
|
||||||
<tbody>
|
|
||||||
{panel.items.map((item, index) => (
|
|
||||||
<tr
|
|
||||||
key={`${item.name}-${index}`}
|
|
||||||
style={{ borderTop: index === 0 ? 'none' : '1px solid var(--color-line)' }}
|
|
||||||
>
|
|
||||||
<td style={{ padding: '6px 0', color: 'var(--color-ink-2)' }}>{item.name}</td>
|
|
||||||
<td
|
|
||||||
style={{
|
|
||||||
padding: '6px 0',
|
|
||||||
textAlign: 'right',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
fontVariantNumeric: 'tabular-nums',
|
|
||||||
color: 'var(--color-ink-1)',
|
|
||||||
fontWeight: 500,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 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 ? (
|
|
||||||
<span style={{ color: 'var(--color-ink-4)', fontWeight: 400 }}>
|
|
||||||
{' '}
|
|
||||||
{item.unit}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -37,7 +37,6 @@ import {
|
|||||||
stateOf,
|
stateOf,
|
||||||
catalogueFactsOf,
|
catalogueFactsOf,
|
||||||
} from './productState';
|
} from './productState';
|
||||||
import { NutritionTable } from '@/features/catalogue/NutritionPanel';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One product, in full.
|
* One product, in full.
|
||||||
@@ -407,7 +406,7 @@ function CatalogueExtras({
|
|||||||
|
|
||||||
{/* Highlights and nutrition side by side — two short lists that each read
|
{/* Highlights and nutrition side by side — two short lists that each read
|
||||||
as a column, rather than one long stack that buries the second. */}
|
as a column, rather than one long stack that buries the second. */}
|
||||||
{data.highlights?.length || data.nutrients?.length || data.nutrition?.items?.length ? (
|
{data.highlights?.length || data.nutrients?.length ? (
|
||||||
<div className="drawer-bullets">
|
<div className="drawer-bullets">
|
||||||
{data.highlights?.length ? (
|
{data.highlights?.length ? (
|
||||||
<div className="drawer-section">
|
<div className="drawer-section">
|
||||||
@@ -415,13 +414,7 @@ function CatalogueExtras({
|
|||||||
<Bullets items={data.highlights} />
|
<Bullets items={data.highlights} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{/* The structured panel wins where the catalogue has one: it lines the
|
{data.nutrients?.length ? (
|
||||||
figures up so two products can be compared down a column, which is
|
|
||||||
most of what a nutrition table is for. The lines are the fallback
|
|
||||||
for every row the agent team has not reached yet. */}
|
|
||||||
{data.nutrition?.items?.length ? (
|
|
||||||
<NutritionTable panel={data.nutrition} />
|
|
||||||
) : data.nutrients?.length ? (
|
|
||||||
<div className="drawer-section">
|
<div className="drawer-section">
|
||||||
<h4 className="drawer-section-title">Nutrition</h4>
|
<h4 className="drawer-section-title">Nutrition</h4>
|
||||||
<Bullets items={data.nutrients} />
|
<Bullets items={data.nutrients} />
|
||||||
|
|||||||
Reference in New Issue
Block a user