The grouped product rows.
The parent row plus one row per non-parent variant, per product.
flattenProductVariants([
{ title: "NaBH4", quantity: 50, uom: "g",
variants: [{ quantity: 50, uom: "g" }, { title: "100g", quantity: 100, uom: "g" }] },
] as Product[]);
// => [parent NaBH4 50g (with variants), variant "NaBH4 100g" (parentProduct set)]
export function flattenProductVariants(products: readonly Product[]): Product[] {
return products.flatMap((product) => {
const variants = product.variants ?? [];
if (variants.length === 0) return [product];
const parentProduct = { title: product.title, url: product.url, permalink: product.permalink };
const rows: Product[] = [product];
for (const variant of variants) {
if (isParentPurchasableUnit(product, variant)) continue;
rows.push(
omit(
{
...product,
...variant,
title: variantRowTitle(product, variant),
parentProduct,
priceSeriesKey: variantSeriesKey(product, variant),
},
'variants',
),
);
}
return rows;
});
}
Flattens each product into standalone Product rows for the results table's ungrouped display mode. Every product yields a parent row (the product itself, keeping its
variantsso the detail panel can still list them) plus one variant row per variant that isn't the parent's own purchasable unit. Each variant row inherits the product's shared fields (CAS, formula, images…) with the variant's own price/quantity/identity overlaid, carries a Product.parentProduct back-reference, carries its resolved Product.priceSeriesKey (so the results table can look up the variant's own price history), gets a disambiguated title (see variantRowTitle), and drops the nestedvariantsarray so it stays a leaf. Products without variants pass through unchanged. This is a presentational transform only — it never mutates the inputs or the stored product data.