Unify category tree pickers and prefer free-text technical AI fields.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
838e84056e
commit
8465a29ce1
@@ -3,12 +3,13 @@ import type { ReactNode } from 'react'
|
||||
import { useT } from '../i18n/useT'
|
||||
import type { InvoiceItemTemplate } from '../types/invoice'
|
||||
import { formatIrtInput } 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 formStyles from './InvoiceForm.module.css'
|
||||
import styles from './InvoiceDraftFields.module.css'
|
||||
@@ -19,15 +20,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
|
||||
/** Rendered after line items (before key points / accounts), e.g. totals row. */
|
||||
@@ -38,11 +47,13 @@ export function InvoiceDraftFields({
|
||||
itemTemplates,
|
||||
items,
|
||||
keyPoints,
|
||||
paymentMethods,
|
||||
accounts,
|
||||
selectedItemTemplateId,
|
||||
onSelectedItemTemplateId,
|
||||
onItemsChange,
|
||||
onKeyPointsChange,
|
||||
onPaymentMethodsChange,
|
||||
onAccountsChange,
|
||||
showItemTemplatePicker = true,
|
||||
afterItems,
|
||||
@@ -248,6 +259,67 @@ export function InvoiceDraftFields({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.blockSection}>
|
||||
<div className={styles.blockHeader}>
|
||||
<h4 className={styles.blockTitle}>{t('invoiceDraft.paymentMethods')}</h4>
|
||||
<button
|
||||
type="button"
|
||||
className={`${formStyles.btn} ${formStyles.btnGhost}`}
|
||||
onClick={() => onPaymentMethodsChange([...paymentMethods, emptyDraftPaymentMethod()])}
|
||||
>
|
||||
<Plus size={16} />
|
||||
{t('invoiceDraft.addPaymentMethod')}
|
||||
</button>
|
||||
</div>
|
||||
{paymentMethods.length === 0 ? (
|
||||
<p className={styles.templateHint}>{t('invoiceDraft.noPaymentMethods')}</p>
|
||||
) : (
|
||||
<div className={styles.repeatStack}>
|
||||
{paymentMethods.map((method, index) => (
|
||||
<div key={method.key} className={styles.repeatRow}>
|
||||
<div className={`${formStyles.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={t('invoiceDraft.paymentMethodPlaceholder')}
|
||||
aria-label={t('invoiceDraft.paymentMethodAria', { index: index + 1 })}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.removeFieldBtn}
|
||||
onClick={() =>
|
||||
onPaymentMethodsChange(paymentMethods.filter((m) => m.key !== method.key))
|
||||
}
|
||||
aria-label={t('invoiceDraft.removePaymentMethod')}
|
||||
title={t('invoiceDraft.removePaymentMethod')}
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.blockSection}>
|
||||
<div className={styles.blockHeader}>
|
||||
<h4 className={styles.blockTitle}>{t('invoiceDraft.accounts')}</h4>
|
||||
|
||||
Reference in New Issue
Block a user