66 lines
2.6 KiB
TypeScript
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;
|