Files
baya-monorepo/client/src/components/PriceBreakdown/PriceBreakdown.tsx
T
2026-07-19 09:49:25 +03:30

66 lines
2.6 KiB
TypeScript

'use client';
import { FunctionComponent } from 'react';
import { Divider, Stack, Typography } from '@mui/material';
import Money from '@/components/common/Money';
import SurfaceCard from '@/components/common/SurfaceCard';
import { parseIrr } from '@/utils';
export interface PriceBreakdownRow {
/** Stable row key (e.g. `service_cost`) — also exposed as `data-row` for tests/automation. */
key: string;
/** Display label — already translated by the caller (labels are i18n keys, never derived from codes). */
label: string;
/** IRR digit-string, straight off the wire. */
amountIrr: string;
}
export interface PriceBreakdownProps {
rows: PriceBreakdownRow[];
totalLabel: string;
/** IRR digit-string. Must equal the integer sum of `rows` — see the reconciliation guard below. */
totalAmountIrr: string;
}
/**
* The reconciling money breakdown (C6 checkout, invoice; f10/f11 reuse it for refunds/BNPL). Rows and
* total are **served amounts** — this component only formats (BigInt-safe, Toman display via the money
* util) and never computes a figure. The one thing it enforces is the phase's hard rule: the displayed
* rows must sum to the displayed total. A mismatch is a data bug upstream, so it is surfaced loudly in
* dev (console.error) rather than silently rendered.
* @component PriceBreakdown
*/
const PriceBreakdown: FunctionComponent<PriceBreakdownProps> = ({ rows, totalLabel, totalAmountIrr }) => {
if (process.env.NODE_ENV !== 'production') {
const sum = rows.reduce((acc, row) => acc + parseIrr(row.amountIrr), BigInt(0));
if (sum !== parseIrr(totalAmountIrr)) {
console.error(
`PriceBreakdown: rows sum to ${sum} but total is ${totalAmountIrr} — a breakdown must reconcile to the rial.`,
);
}
}
return (
<SurfaceCard>
<Stack sx={{ gap: 1.25 }}>
{rows.map((row) => (
<Stack key={row.key} data-row={row.key} direction="row" sx={{ justifyContent: 'space-between', gap: 2 }}>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{row.label}
</Typography>
<Money amountIrr={row.amountIrr} size="sm" sx={{ fontWeight: 500 }} />
</Stack>
))}
<Divider />
<Stack data-row="total" direction="row" sx={{ justifyContent: 'space-between', gap: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 800 }}>
{totalLabel}
</Typography>
<Money amountIrr={totalAmountIrr} tone="emphasis" size="md" sx={{ fontWeight: 800 }} />
</Stack>
</Stack>
</SurfaceCard>
);
};
export default PriceBreakdown;