Skip to content
BigBang

Popular

    ↑↓Navigate↵OpenEscClose
    TR

    Templates

    Receipt and statement

    One record's summary or a period's records on PrintShell; facts in a description list, figures in a table.

    This block builds two document types inside PrintShell: one record’s summary with its line items (variant="receipt") and a period’s record statement (variant="statement").

    The brand and mode switches in the page header restyle the preview too. “Print” opens the browser’s print window.

    Live example, StorybookOpen in Storybook
    Live example, StorybookOpen in Storybook
    Live example, StorybookOpen in Storybook
    Terminal window
    pnpm add @bigbang/ui
    Data Type Note
    variant 'receipt' | 'statement' Summary with line items or period statement.
    Line items { quantity, unit }[] Amounts are written with Intl.NumberFormat and two decimals.
    Reference string Only the last four characters show; masking happens on the server.
    import { Button, DescriptionList, Heading, Icon, Stack, StatusBadge, Table, Text } from '@bigbang/ui';
    import type { StatusKey } from '@bigbang/ui';
    import type { Lang } from '../../patterns/lang';
    import { PrintShell } from '../../templates/print-shell/PrintShell';
    const copy = {
    tr: {
    receipt: 'İşlem özeti',
    statement: 'Dönem dökümü',
    number: 'Belge no',
    date: 'Tarih',
    print: 'Yazdır',
    pdf: 'PDF olarak indir',
    details: 'Ayrıntılar',
    type: 'Kayıt türü',
    typeValue: 'Hizmet kaydı',
    status: 'Durum',
    owner: 'Oluşturan',
    ownerValue: 'Kullanıcı A',
    reference: 'Referans',
    lines: 'Kalemler',
    description: 'Açıklama',
    quantity: 'Adet',
    unit: 'Birim tutar',
    total: 'Toplam',
    grandTotal: 'Genel toplam',
    items: ['Hizmet A', 'Hizmet B', 'Hizmet C'],
    period: 'Dönem',
    periodValue: '1–31 Ekim 2026',
    records: 'Dönem kayıtları',
    record: 'Kayıt',
    footer: 'Bu belge elektronik olarak oluşturulmuştur. Doğrulama kodu: 7K2P-9QX4',
    },
    en: {
    receipt: 'Record summary',
    statement: 'Period statement',
    number: 'Document no.',
    date: 'Date',
    print: 'Print',
    pdf: 'Download as PDF',
    details: 'Details',
    type: 'Record type',
    typeValue: 'Service record',
    status: 'Status',
    owner: 'Created by',
    ownerValue: 'User A',
    reference: 'Reference',
    lines: 'Line items',
    description: 'Description',
    quantity: 'Quantity',
    unit: 'Unit amount',
    total: 'Total',
    grandTotal: 'Grand total',
    items: ['Service A', 'Service B', 'Service C'],
    period: 'Period',
    periodValue: '1–31 October 2026',
    records: 'Records in the period',
    record: 'Record',
    footer: 'This document was created electronically. Verification code: 7K2P-9QX4',
    },
    };
    const lineItems = [
    { quantity: 2, unit: 500 },
    { quantity: 1, unit: 1000 },
    { quantity: 4, unit: 250 },
    ];
    const statementRows: { day: number; status: StatusKey; amount: number }[] = [
    { day: 2, status: 'completed', amount: 1000 },
    { day: 6, status: 'completed', amount: 250 },
    { day: 11, status: 'cancelled', amount: 500 },
    { day: 17, status: 'completed', amount: 750 },
    { day: 23, status: 'pending', amount: 1200 },
    { day: 29, status: 'completed', amount: 300 },
    ];
    export type ReceiptBlockProps = {
    lang?: Lang;
    /** `receipt` for one record with line items, `statement` for the records of a period. */
    variant?: 'receipt' | 'statement';
    };
    /** A printable document on PrintShell: facts as a description list, figures in a table, a masked reference. */
    export function ReceiptBlock({ lang = 'tr', variant = 'receipt' }: ReceiptBlockProps) {
    const t = copy[lang];
    const locale = lang === 'tr' ? 'tr-TR' : 'en-GB';
    const formatAmount = (value: number) => new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
    const date = new Intl.DateTimeFormat(locale, { dateStyle: 'medium' });
    const isReceipt = variant === 'receipt';
    const total = isReceipt ? lineItems.reduce((sum, item) => sum + item.quantity * item.unit, 0) : statementRows.filter((row) => row.status !== 'cancelled').reduce((sum, row) => sum + row.amount, 0);
    return (
    <PrintShell
    lang={lang}
    title={isReceipt ? t.receipt : t.statement}
    meta={`${t.number} BB-2026-000123 · ${t.date} ${date.format(new Date(2026, 9, 28))}`}
    footer={t.footer}
    actions={
    <>
    <Button variant="secondary" size="sm" startIcon={<Icon name="file" />} onClick={() => window.print()}>
    {t.print}
    </Button>
    <Button variant="tertiary" size="sm" startIcon={<Icon name="download" />}>
    {t.pdf}
    </Button>
    </>
    }
    >
    <Stack gap="2xl">
    <section aria-labelledby={`receipt-details-${lang}`}>
    <Stack gap="md">
    <Heading level={2} size="sm" id={`receipt-details-${lang}`}>
    {t.details}
    </Heading>
    <DescriptionList
    layout="grid"
    columns={2}
    items={
    isReceipt
    ? [
    { term: t.type, details: t.typeValue },
    { term: t.status, details: <StatusBadge status="completed" size="sm" /> },
    { term: t.owner, details: t.ownerValue },
    { term: t.reference, details: <Text numeric>•••• 1234</Text> },
    ]
    : [
    { term: t.period, details: t.periodValue },
    { term: t.owner, details: t.ownerValue },
    { term: t.reference, details: <Text numeric>•••• 1234</Text> },
    { term: t.records, details: statementRows.length },
    ]
    }
    />
    </Stack>
    </section>
    {/* The table's scroll region already carries this name, so the section stays unnamed to keep landmarks unique. */}
    <section>
    <Stack gap="md">
    <Heading level={2} size="sm" id={`receipt-lines-${lang}`}>
    {isReceipt ? t.lines : t.records}
    </Heading>
    {isReceipt ? (
    <Table caption={t.lines} captionHidden density="compact">
    <Table.Header>
    <Table.Row>
    <Table.HeaderCell>{t.description}</Table.HeaderCell>
    <Table.HeaderCell numeric>{t.quantity}</Table.HeaderCell>
    <Table.HeaderCell numeric>{t.unit}</Table.HeaderCell>
    <Table.HeaderCell numeric>{t.total}</Table.HeaderCell>
    </Table.Row>
    </Table.Header>
    <Table.Body>
    {lineItems.map((item, index) => (
    <Table.Row key={t.items[index]}>
    <Table.RowHeader>{t.items[index]}</Table.RowHeader>
    <Table.Cell numeric>{item.quantity}</Table.Cell>
    <Table.Cell numeric>{formatAmount(item.unit)}</Table.Cell>
    <Table.Cell numeric>{formatAmount(item.quantity * item.unit)}</Table.Cell>
    </Table.Row>
    ))}
    </Table.Body>
    <Table.Footer>
    <Table.Row>
    <Table.RowHeader colSpan={3}>{t.grandTotal}</Table.RowHeader>
    <Table.Cell numeric>{formatAmount(total)}</Table.Cell>
    </Table.Row>
    </Table.Footer>
    </Table>
    ) : (
    <Table caption={t.records} captionHidden density="compact">
    <Table.Header>
    <Table.Row>
    <Table.HeaderCell>{t.date}</Table.HeaderCell>
    <Table.HeaderCell>{t.description}</Table.HeaderCell>
    <Table.HeaderCell>{t.status}</Table.HeaderCell>
    <Table.HeaderCell numeric>{t.total}</Table.HeaderCell>
    </Table.Row>
    </Table.Header>
    <Table.Body>
    {statementRows.map((row, index) => (
    <Table.Row key={row.day}>
    <Table.Cell>{date.format(new Date(2026, 9, row.day))}</Table.Cell>
    <Table.RowHeader>{`${t.record} ${index + 1}`}</Table.RowHeader>
    <Table.Cell>
    <StatusBadge status={row.status} size="sm" />
    </Table.Cell>
    <Table.Cell numeric>{formatAmount(row.amount)}</Table.Cell>
    </Table.Row>
    ))}
    </Table.Body>
    <Table.Footer>
    <Table.Row>
    <Table.RowHeader colSpan={3}>{t.grandTotal}</Table.RowHeader>
    <Table.Cell numeric>{formatAmount(total)}</Table.Cell>
    </Table.Row>
    </Table.Footer>
    </Table>
    )}
    </Stack>
    </section>
    </Stack>
    </PrintShell>
    );
    }
    1. A “Document actions” toolbar: Print and Download as PDF (hidden on paper).
    2. Header: brand, h1 title, document number and date.
    3. “Details” section: a DescriptionList in grid layout; status as a StatusBadge.
    4. “Line items” or “Records in the period” section: a Table with right-aligned numeric columns and the total in tfoot.
    5. Footer: electronic document note and verification code.
    • Each section is a section named by its h2.
    • Tables are named by a hidden caption; description cells are row headers (th scope="row").
    • Numeric cells line up with tabular-nums; amounts never count up.
    • A cancelled record stays out of the total and its status is read as text.

    DescriptionList, Table, StatusBadge, Button, Heading, Text. Patterns: Data export.