Add bank account templates and default invoice payment text.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-21 23:31:58 +03:30
co-authored by Cursor
parent 8465a29ce1
commit ff419603bf
19 changed files with 1316 additions and 107 deletions
@@ -1,10 +1,11 @@
import { Pencil, Plus, X } from 'lucide-react'
import type { ReactNode } from 'react'
import { useT } from '../i18n/useT'
import type { InvoiceItemTemplate } from '../types/invoice'
import type { InvoiceAccountTemplate, InvoiceItemTemplate } from '../types/invoice'
import { formatIrtInput } from '../utils/irtPrice'
import type { DraftAccount, DraftKeyPoint, DraftLineItem, DraftPaymentMethod } from '../utils/invoiceDraft'
import {
draftAccountFromAccountTemplate,
draftItemFromItemTemplate,
emptyDraftAccount,
emptyDraftItem,
@@ -28,34 +29,42 @@ function focusPaymentMethodInput(index: number) {
type Props = {
itemTemplates: InvoiceItemTemplate[]
accountTemplates: InvoiceAccountTemplate[]
items: DraftLineItem[]
keyPoints: DraftKeyPoint[]
paymentMethods: DraftPaymentMethod[]
accounts: DraftAccount[]
selectedItemTemplateId: string
onSelectedItemTemplateId: (id: string) => void
selectedAccountTemplateId: string
onSelectedAccountTemplateId: (id: string) => void
onItemsChange: (items: DraftLineItem[]) => void
onKeyPointsChange: (points: DraftKeyPoint[]) => void
onPaymentMethodsChange: (methods: DraftPaymentMethod[]) => void
onAccountsChange: (accounts: DraftAccount[]) => void
showItemTemplatePicker?: boolean
showAccountTemplatePicker?: boolean
/** Rendered after line items (before key points / accounts), e.g. totals row. */
afterItems?: ReactNode
}
export function InvoiceDraftFields({
itemTemplates,
accountTemplates,
items,
keyPoints,
paymentMethods,
accounts,
selectedItemTemplateId,
onSelectedItemTemplateId,
selectedAccountTemplateId,
onSelectedAccountTemplateId,
onItemsChange,
onKeyPointsChange,
onPaymentMethodsChange,
onAccountsChange,
showItemTemplatePicker = true,
showAccountTemplatePicker = true,
afterItems,
}: Props) {
const t = useT()
@@ -81,6 +90,13 @@ export function InvoiceDraftFields({
onSelectedItemTemplateId('')
}
function addFromAccountTemplate() {
const template = accountTemplates.find((entry) => entry.id === selectedAccountTemplateId)
if (!template) return
onAccountsChange([...accounts, draftAccountFromAccountTemplate(template)])
onSelectedAccountTemplateId('')
}
return (
<>
<div className={styles.itemsStack}>
@@ -323,14 +339,44 @@ export function InvoiceDraftFields({
<div className={styles.blockSection}>
<div className={styles.blockHeader}>
<h4 className={styles.blockTitle}>{t('invoiceDraft.accounts')}</h4>
<button
type="button"
className={`${formStyles.btn} ${formStyles.btnGhost}`}
onClick={() => onAccountsChange([...accounts, emptyDraftAccount()])}
>
<Plus size={16} />
{t('invoiceDraft.addAccount')}
</button>
<div className={styles.blockHeaderActions}>
{showAccountTemplatePicker ? (
<div className={styles.itemActionsLeft}>
<select
id="account-template-pick"
className={styles.compactSelect}
value={selectedAccountTemplateId}
onChange={(e) => onSelectedAccountTemplateId(e.target.value)}
aria-label={t('invoiceDraft.addFromAccountTemplateAria')}
>
<option value="">{t('invoiceDraft.addFromAccountTemplate')}</option>
{accountTemplates.map((template) => (
<option key={template.id} value={template.id}>
{template.bankName}
{template.accountHolderName ? ` — ${template.accountHolderName}` : ''}
</option>
))}
</select>
<button
type="button"
className={`${formStyles.btn} ${formStyles.btnGhost} ${styles.compactBtn}`}
onClick={addFromAccountTemplate}
disabled={!selectedAccountTemplateId}
>
<Plus size={14} />
{t('invoiceDraft.add')}
</button>
</div>
) : null}
<button
type="button"
className={`${formStyles.btn} ${formStyles.btnGhost}`}
onClick={() => onAccountsChange([...accounts, emptyDraftAccount()])}
>
<Plus size={16} />
{t('invoiceDraft.addAccount')}
</button>
</div>
</div>
{accounts.length === 0 ? (
<p className={styles.templateHint}>{t('invoiceDraft.noAccounts')}</p>