Şablonlar
Dekont ve döküm
PrintShell üzerinde tek kaydın özeti veya bir dönemin kayıtları; bilgiler tanım listesinde, rakamlar tabloda.
Bu blok PrintShell içinde iki belge türü kurar: tek kaydın kalemleriyle özeti (variant="receipt") ve bir dönemin kayıt dökümü (variant="statement").
Önizleme
Bölüm başlığı “Önizleme”Sayfa başlığındaki marka ve mod seçimi önizlemeyi de değiştirir. “Yazdır” düğmesi tarayıcının yazdırma penceresini açar.
pnpm add @bigbang/ui| Veri | Tür | Not |
|---|---|---|
variant |
'receipt' | 'statement' |
Kalemli özet veya dönem dökümü. |
| Kalemler | { quantity, unit }[] |
Tutarlar Intl.NumberFormat ile, iki ondalıkla yazılır. |
| Referans | string |
Yalnız son dört karakter görünür; maskeleme sunucuda yapılır. |
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> );}- “Belge işlemleri” araç çubuğu: Yazdır ve PDF olarak indir (kâğıtta gizli).
- Üst bilgi: marka,
h1başlık, belge no ve tarih. - “Ayrıntılar” bölümü: DescriptionList ızgara düzeninde; durum StatusBadge ile.
- “Kalemler” veya “Dönem kayıtları” bölümü: Table, sayısal sütunlar sağa hizalı, toplam
tfootiçinde. - Alt bilgi: elektronik belge notu ve doğrulama kodu.
Erişilebilirlik
Bölüm başlığı “Erişilebilirlik”- Her bölüm
h2başlığıyla adlandırılmış birsection’dır. - Tablolar gizli başlıkla (
caption) adlandırılır; açıklama hücreleri satır başlığıdır (th scope="row"). - Sayısal hücreler
tabular-numsile hizalanır; tutarlar sayarak değişmez. - İptal edilen kayıt toplamın dışında kalır ve durumu metinle okunur.
Kullanılan bileşenler
Bölüm başlığı “Kullanılan bileşenler”DescriptionList, Table, StatusBadge, Button, Heading, Text. Kalıplar: Veri dışa aktarma.