Unify category tree pickers and prefer free-text technical AI fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-21 20:02:05 +03:30
co-authored by Cursor
parent 838e84056e
commit 8465a29ce1
40 changed files with 1192 additions and 586 deletions
@@ -1,12 +1,13 @@
import { Pencil, Plus, X } from 'lucide-react'
import type { InvoiceItemTemplate } from '../types/invoice'
import { formatIrtInput, formatIrtPrice } from '../utils/irtPrice'
import type { DraftAccount, DraftKeyPoint, DraftLineItem } from '../utils/invoiceDraft'
import type { DraftAccount, DraftKeyPoint, DraftLineItem, DraftPaymentMethod } from '../utils/invoiceDraft'
import {
draftItemFromItemTemplate,
emptyDraftAccount,
emptyDraftItem,
emptyDraftKeyPoint,
emptyDraftPaymentMethod,
} from '../utils/invoiceDraft'
import tableStyles from '../pages/BusinessesPage.module.css'
import styles from '../pages/BusinessInvoicesPage.module.css'
@@ -17,15 +18,23 @@ function focusKeyPointInput(index: number) {
el?.select()
}
function focusPaymentMethodInput(index: number) {
const el = document.querySelector<HTMLInputElement>(`input[data-payment-method-index="${index}"]`)
el?.focus()
el?.select()
}
type Props = {
itemTemplates: InvoiceItemTemplate[]
items: DraftLineItem[]
keyPoints: DraftKeyPoint[]
paymentMethods: DraftPaymentMethod[]
accounts: DraftAccount[]
selectedItemTemplateId: string
onSelectedItemTemplateId: (id: string) => void
onItemsChange: (items: DraftLineItem[]) => void
onKeyPointsChange: (points: DraftKeyPoint[]) => void
onPaymentMethodsChange: (methods: DraftPaymentMethod[]) => void
onAccountsChange: (accounts: DraftAccount[]) => void
showItemTemplatePicker?: boolean
}
@@ -34,11 +43,13 @@ export function InvoiceDraftFields({
itemTemplates,
items,
keyPoints,
paymentMethods,
accounts,
selectedItemTemplateId,
onSelectedItemTemplateId,
onItemsChange,
onKeyPointsChange,
onPaymentMethodsChange,
onAccountsChange,
showItemTemplatePicker = true,
}: Props) {
@@ -239,6 +250,67 @@ export function InvoiceDraftFields({
)}
</div>
<div className={styles.blockSection}>
<div className={styles.blockHeader}>
<h4 className={styles.blockTitle}>Payment methods</h4>
<button
type="button"
className={`${tableStyles.btn} ${tableStyles.btnGhost}`}
onClick={() => onPaymentMethodsChange([...paymentMethods, emptyDraftPaymentMethod()])}
>
<Plus size={16} />
Add payment method
</button>
</div>
{paymentMethods.length === 0 ? (
<p className={styles.templateHint}>No payment methods yet.</p>
) : (
<div className={styles.repeatStack}>
{paymentMethods.map((method, index) => (
<div key={method.key} className={styles.repeatRow}>
<div className={`${tableStyles.field} ${styles.flexGrow} ${styles.fieldNoLabel}`}>
<input
data-payment-method-index={index}
value={method.text}
onChange={(e) =>
onPaymentMethodsChange(
paymentMethods.map((m) =>
m.key === method.key ? { ...m, text: e.target.value } : m,
),
)
}
onKeyDown={(e) => {
if (e.key !== 'Enter') return
e.preventDefault()
const next = index + 1
if (next < paymentMethods.length) {
focusPaymentMethodInput(next)
return
}
onPaymentMethodsChange([...paymentMethods, emptyDraftPaymentMethod()])
window.setTimeout(() => focusPaymentMethodInput(next), 0)
}}
placeholder="e.g. Cash, card transfer, or installments"
aria-label={`Payment method ${index + 1}`}
/>
</div>
<button
type="button"
className={styles.removeFieldBtn}
onClick={() =>
onPaymentMethodsChange(paymentMethods.filter((m) => m.key !== method.key))
}
aria-label="Remove payment method"
title="Remove"
>
<X size={16} />
</button>
</div>
))}
</div>
)}
</div>
<div className={styles.blockSection}>
<div className={styles.blockHeader}>
<h4 className={styles.blockTitle}>Account numbers</h4>