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,8 +1,9 @@
import { Pencil, Plus, X } from 'lucide-react'
import type { InvoiceItemTemplate } from '../types/invoice'
import type { InvoiceAccountTemplate, InvoiceItemTemplate } from '../types/invoice'
import { formatIrtInput, formatIrtPrice } from '../utils/irtPrice'
import type { DraftAccount, DraftKeyPoint, DraftLineItem, DraftPaymentMethod } from '../utils/invoiceDraft'
import {
draftAccountFromAccountTemplate,
draftItemFromItemTemplate,
emptyDraftAccount,
emptyDraftItem,
@@ -26,32 +27,40 @@ 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
}
export function InvoiceDraftFields({
itemTemplates,
accountTemplates,
items,
keyPoints,
paymentMethods,
accounts,
selectedItemTemplateId,
onSelectedItemTemplateId,
selectedAccountTemplateId,
onSelectedAccountTemplateId,
onItemsChange,
onKeyPointsChange,
onPaymentMethodsChange,
onAccountsChange,
showItemTemplatePicker = true,
showAccountTemplatePicker = true,
}: Props) {
function updateItem(key: string, patch: Partial<DraftLineItem>) {
onItemsChange(items.map((item) => (item.key === key ? { ...item, ...patch } : item)))
@@ -74,6 +83,13 @@ export function InvoiceDraftFields({
onSelectedItemTemplateId('')
}
function addFromAccountTemplate() {
const template = accountTemplates.find((t) => t.id === selectedAccountTemplateId)
if (!template) return
onAccountsChange([...accounts, draftAccountFromAccountTemplate(template)])
onSelectedAccountTemplateId('')
}
return (
<>
<div className={styles.itemsStack}>
@@ -314,14 +330,44 @@ export function InvoiceDraftFields({
<div className={styles.blockSection}>
<div className={styles.blockHeader}>
<h4 className={styles.blockTitle}>Account numbers</h4>
<button
type="button"
className={`${tableStyles.btn} ${tableStyles.btnGhost}`}
onClick={() => onAccountsChange([...accounts, emptyDraftAccount()])}
>
<Plus size={16} />
Add account
</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="Add from account template"
>
<option value="">Add from account template…</option>
{accountTemplates.map((template) => (
<option key={template.id} value={template.id}>
{template.bankName}
{template.accountHolderName ? ` — ${template.accountHolderName}` : ''}
</option>
))}
</select>
<button
type="button"
className={`${tableStyles.btn} ${tableStyles.btnGhost} ${styles.compactBtn}`}
onClick={addFromAccountTemplate}
disabled={!selectedAccountTemplateId}
>
<Plus size={14} />
Add
</button>
</div>
) : null}
<button
type="button"
className={`${tableStyles.btn} ${tableStyles.btnGhost}`}
onClick={() => onAccountsChange([...accounts, emptyDraftAccount()])}
>
<Plus size={16} />
Add account
</button>
</div>
</div>
{accounts.length === 0 ? (
<p className={styles.templateHint}>No bank accounts yet.</p>