@fayz-ai/plugin-financial 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FinancialContext.d.ts +44 -0
- package/dist/FinancialContext.d.ts.map +1 -0
- package/dist/FinancialPage.d.ts +18 -0
- package/dist/FinancialPage.d.ts.map +1 -0
- package/dist/components/FinancialGeneralSettings.d.ts +3 -0
- package/dist/components/FinancialGeneralSettings.d.ts.map +1 -0
- package/dist/components/FinancialOnboarding.d.ts +5 -0
- package/dist/components/FinancialOnboarding.d.ts.map +1 -0
- package/dist/components/FinancialSettings.d.ts +8 -0
- package/dist/components/FinancialSettings.d.ts.map +1 -0
- package/dist/components/PaymentModal.d.ts +8 -0
- package/dist/components/PaymentModal.d.ts.map +1 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/mock.d.ts +9 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/types.d.ts +27 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/index.cjs +4937 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4930 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tenant.d.ts +3 -0
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/registries.d.ts +3 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/store.d.ts +38 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +302 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/CardsView.d.ts +3 -0
- package/dist/views/CardsView.d.ts.map +1 -0
- package/dist/views/CashRegistersView.d.ts +3 -0
- package/dist/views/CashRegistersView.d.ts.map +1 -0
- package/dist/views/CommissionsView.d.ts +3 -0
- package/dist/views/CommissionsView.d.ts.map +1 -0
- package/dist/views/InvoiceDetailView.d.ts +9 -0
- package/dist/views/InvoiceDetailView.d.ts.map +1 -0
- package/dist/views/InvoiceFormView.d.ts +13 -0
- package/dist/views/InvoiceFormView.d.ts.map +1 -0
- package/dist/views/InvoiceListView.d.ts +8 -0
- package/dist/views/InvoiceListView.d.ts.map +1 -0
- package/dist/views/PayablesView.d.ts +7 -0
- package/dist/views/PayablesView.d.ts.map +1 -0
- package/dist/views/ReceivablesView.d.ts +7 -0
- package/dist/views/ReceivablesView.d.ts.map +1 -0
- package/dist/views/StatementsView.d.ts +3 -0
- package/dist/views/StatementsView.d.ts.map +1 -0
- package/dist/views/SummaryView.d.ts +9 -0
- package/dist/views/SummaryView.d.ts.map +1 -0
- package/dist/views/dashboardWidgets.d.ts +11 -0
- package/dist/views/dashboardWidgets.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/FinancialContext.tsx +47 -0
- package/src/FinancialPage.tsx +261 -0
- package/src/README.md +155 -0
- package/src/components/FinancialGeneralSettings.tsx +26 -0
- package/src/components/FinancialOnboarding.tsx +87 -0
- package/src/components/FinancialSettings.tsx +31 -0
- package/src/components/PaymentModal.tsx +322 -0
- package/src/data/index.ts +2 -0
- package/src/data/mock.ts +520 -0
- package/src/data/supabase.ts +509 -0
- package/src/data/types.ts +53 -0
- package/src/index.ts +307 -0
- package/src/lib/tenant.ts +3 -0
- package/src/locales/en.ts +331 -0
- package/src/locales/index.ts +7 -0
- package/src/locales/pt-BR.ts +331 -0
- package/src/migrations/001_financial_base.sql +109 -0
- package/src/migrations/002_chart_of_accounts.sql +28 -0
- package/src/migrations/003_card_brands.sql +12 -0
- package/src/migrations/004_order_to_cash.sql +164 -0
- package/src/migrations/005_seed_defaults.sql +38 -0
- package/src/registries.ts +305 -0
- package/src/store.ts +221 -0
- package/src/types.ts +359 -0
- package/src/views/CardsView.tsx +74 -0
- package/src/views/CashRegistersView.tsx +597 -0
- package/src/views/CommissionsView.tsx +18 -0
- package/src/views/InvoiceDetailView.tsx +595 -0
- package/src/views/InvoiceFormView.tsx +900 -0
- package/src/views/InvoiceListView.tsx +177 -0
- package/src/views/PayablesView.tsx +37 -0
- package/src/views/ReceivablesView.tsx +37 -0
- package/src/views/StatementsView.tsx +123 -0
- package/src/views/SummaryView.tsx +12 -0
- package/src/views/dashboardWidgets.tsx +191 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react'
|
|
2
|
+
import { createPortal } from 'react-dom'
|
|
3
|
+
import { X, Check, Banknote, QrCode, CreditCard, Building2, ArrowRightLeft, FileCheck, Pencil } from 'lucide-react'
|
|
4
|
+
import { useFinancialConfig, useFinancialStore, formatCurrency } from '../FinancialContext'
|
|
5
|
+
import { useTranslation } from '@fayz-ai/core'
|
|
6
|
+
import { CurrencyInput } from '@fayz-ai/ui'
|
|
7
|
+
import { DatePicker } from '@fayz-ai/ui'
|
|
8
|
+
import { Button } from '@fayz-ai/ui'
|
|
9
|
+
import type { FinancialMovement, PaymentMethodType } from '../types'
|
|
10
|
+
|
|
11
|
+
const TYPE_ICONS: Record<string, React.ElementType> = {
|
|
12
|
+
cash: Banknote,
|
|
13
|
+
pix: QrCode,
|
|
14
|
+
credit_card: CreditCard,
|
|
15
|
+
debit_card: CreditCard,
|
|
16
|
+
bank_transfer: ArrowRightLeft,
|
|
17
|
+
check: FileCheck,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TYPE_COLORS: Record<string, string> = {
|
|
21
|
+
cash: 'border-success/30 bg-success/5 hover:bg-success/10 data-[active=true]:border-success data-[active=true]:bg-success/15',
|
|
22
|
+
pix: 'border-teal-500/30 bg-teal-500/5 hover:bg-teal-500/10 data-[active=true]:border-teal-500 data-[active=true]:bg-teal-500/15',
|
|
23
|
+
credit_card: 'border-info/30 bg-info/5 hover:bg-info/10 data-[active=true]:border-info data-[active=true]:bg-info/15',
|
|
24
|
+
debit_card: 'border-magic/30 bg-magic/5 hover:bg-magic/10 data-[active=true]:border-magic data-[active=true]:bg-magic/15',
|
|
25
|
+
bank_transfer: 'border-warning/30 bg-warning/5 hover:bg-warning/10 data-[active=true]:border-warning data-[active=true]:bg-warning/15',
|
|
26
|
+
check: 'border-border bg-muted/40 hover:bg-muted/60 data-[active=true]:border-foreground data-[active=true]:bg-muted',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ICON_COLORS: Record<string, string> = {
|
|
30
|
+
cash: 'text-success',
|
|
31
|
+
pix: 'text-teal-500',
|
|
32
|
+
credit_card: 'text-info',
|
|
33
|
+
debit_card: 'text-magic',
|
|
34
|
+
bank_transfer: 'text-warning',
|
|
35
|
+
check: 'text-muted-foreground',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function PaymentModal({ movement, onClose, onPaid }: {
|
|
39
|
+
movement: FinancialMovement
|
|
40
|
+
onClose: () => void
|
|
41
|
+
onPaid: () => void
|
|
42
|
+
}) {
|
|
43
|
+
const t = useTranslation()
|
|
44
|
+
const { currency } = useFinancialConfig()
|
|
45
|
+
const payMovement = useFinancialStore((s) => s.payMovement)
|
|
46
|
+
const bankAccounts = useFinancialStore((s) => s.bankAccounts)
|
|
47
|
+
const paymentMethods = useFinancialStore((s) => s.paymentMethods)
|
|
48
|
+
const paymentMethodTypes = useFinancialStore((s) => s.paymentMethodTypes)
|
|
49
|
+
const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts)
|
|
50
|
+
const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods)
|
|
51
|
+
|
|
52
|
+
const remaining = movement.amount - movement.paidAmount
|
|
53
|
+
const [amount, setAmount] = useState(remaining)
|
|
54
|
+
const [paymentDate, setPaymentDate] = useState(new Date().toISOString().slice(0, 10))
|
|
55
|
+
const [selectedTypeId, setSelectedTypeId] = useState('')
|
|
56
|
+
const [paymentMethodId, setPaymentMethodId] = useState('')
|
|
57
|
+
const [bankAccountId, setBankAccountId] = useState('')
|
|
58
|
+
const [cardBrand, setCardBrand] = useState('')
|
|
59
|
+
const [cardInstallments, setCardInstallments] = useState(1)
|
|
60
|
+
const [notes, setNotes] = useState('')
|
|
61
|
+
const [saving, setSaving] = useState(false)
|
|
62
|
+
const [visible, setVisible] = useState(false)
|
|
63
|
+
const [expandMethodPicker, setExpandMethodPicker] = useState(true)
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
fetchBankAccounts()
|
|
67
|
+
fetchPaymentMethods()
|
|
68
|
+
requestAnimationFrame(() => setVisible(true))
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
const selectedType = useMemo(() =>
|
|
72
|
+
paymentMethodTypes.find((tp) => tp.id === selectedTypeId),
|
|
73
|
+
[paymentMethodTypes, selectedTypeId]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const txType = selectedType?.transactionType ?? ''
|
|
77
|
+
|
|
78
|
+
const filteredMethods = useMemo(() =>
|
|
79
|
+
paymentMethods.filter((m) => m.paymentMethodTypeId === selectedTypeId),
|
|
80
|
+
[paymentMethods, selectedTypeId]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const needsBankAccount = txType === 'bank_transfer' || txType === 'pix' || txType === 'check'
|
|
84
|
+
const needsCard = txType === 'credit_card' || txType === 'debit_card'
|
|
85
|
+
const needsCardInstallments = txType === 'credit_card'
|
|
86
|
+
|
|
87
|
+
function handleSelectType(typeId: string) {
|
|
88
|
+
setSelectedTypeId(typeId)
|
|
89
|
+
setExpandMethodPicker(false)
|
|
90
|
+
setPaymentMethodId('')
|
|
91
|
+
setBankAccountId('')
|
|
92
|
+
setCardBrand('')
|
|
93
|
+
setCardInstallments(1)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function handleClose() {
|
|
97
|
+
setVisible(false)
|
|
98
|
+
setTimeout(onClose, 200)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function handlePay() {
|
|
102
|
+
if (amount <= 0 || !selectedTypeId) return
|
|
103
|
+
setSaving(true)
|
|
104
|
+
try {
|
|
105
|
+
await payMovement({
|
|
106
|
+
movementId: movement.id,
|
|
107
|
+
amount,
|
|
108
|
+
paymentDate,
|
|
109
|
+
paymentMethodId: paymentMethodId || undefined,
|
|
110
|
+
paymentMethodTypeId: selectedTypeId || undefined,
|
|
111
|
+
bankAccountId: bankAccountId || undefined,
|
|
112
|
+
cardBrand: needsCard ? cardBrand || undefined : undefined,
|
|
113
|
+
cardInstallments: needsCardInstallments ? cardInstallments : undefined,
|
|
114
|
+
})
|
|
115
|
+
setVisible(false)
|
|
116
|
+
setTimeout(onPaid, 200)
|
|
117
|
+
} catch {
|
|
118
|
+
// toast handled by store
|
|
119
|
+
} finally {
|
|
120
|
+
setSaving(false)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const SelectedIcon = selectedType ? (TYPE_ICONS[txType] ?? Banknote) : null
|
|
125
|
+
|
|
126
|
+
return createPortal(
|
|
127
|
+
<div
|
|
128
|
+
className="fixed inset-0 z-50 flex items-center justify-center transition-colors duration-200"
|
|
129
|
+
style={{ backgroundColor: visible ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)' }}
|
|
130
|
+
onClick={handleClose}
|
|
131
|
+
>
|
|
132
|
+
<div
|
|
133
|
+
className="w-full max-w-md rounded-modal border bg-card shadow-lg mx-4 transition-all duration-200 max-h-[90vh] overflow-y-auto"
|
|
134
|
+
style={{
|
|
135
|
+
opacity: visible ? 1 : 0,
|
|
136
|
+
transform: visible ? 'scale(1) translateY(0)' : 'scale(0.95) translateY(8px)',
|
|
137
|
+
}}
|
|
138
|
+
onClick={(e) => e.stopPropagation()}
|
|
139
|
+
>
|
|
140
|
+
{/* Header */}
|
|
141
|
+
<div className="flex items-center justify-between px-5 py-4 border-b sticky top-0 bg-card z-10">
|
|
142
|
+
<div>
|
|
143
|
+
<h3 className="text-base font-semibold">{t('financial.payment.title')}</h3>
|
|
144
|
+
<p className="text-xs text-muted-foreground">
|
|
145
|
+
{t('financial.payment.installment', { number: String(movement.installmentNumber ?? 1) })} · {t('financial.payment.remaining', { amount: formatCurrency(remaining, currency) })}
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
148
|
+
<button onClick={handleClose} className="p-1 text-muted-foreground hover:text-foreground transition-colors">
|
|
149
|
+
<X className="h-4 w-4" />
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div className="px-5 py-4 space-y-4">
|
|
154
|
+
{/* Payment method type selector */}
|
|
155
|
+
<div>
|
|
156
|
+
<label className="text-xs font-medium text-muted-foreground">{t('financial.payment.method')}</label>
|
|
157
|
+
|
|
158
|
+
{/* Collapsed: show selected as inline chip */}
|
|
159
|
+
<div
|
|
160
|
+
className="grid transition-all duration-200 ease-out"
|
|
161
|
+
style={{ gridTemplateRows: selectedTypeId && !expandMethodPicker ? '1fr' : '0fr' }}
|
|
162
|
+
>
|
|
163
|
+
<div className="overflow-hidden">
|
|
164
|
+
{selectedType && (
|
|
165
|
+
<button
|
|
166
|
+
onClick={() => setExpandMethodPicker(true)}
|
|
167
|
+
className={`flex items-center gap-2 mt-1.5 w-full rounded-lg border px-3 py-2.5 text-left transition-colors hover:bg-muted/30 ${TYPE_COLORS[txType] ?? ''}`}
|
|
168
|
+
data-active="true"
|
|
169
|
+
>
|
|
170
|
+
{SelectedIcon && <SelectedIcon className={`h-4 w-4 ${ICON_COLORS[txType] ?? ''}`} />}
|
|
171
|
+
<span className="text-sm font-medium flex-1">{selectedType?.name}</span>
|
|
172
|
+
<Pencil className="h-3 w-3 text-muted-foreground" />
|
|
173
|
+
</button>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{/* Expanded: full grid */}
|
|
179
|
+
<div
|
|
180
|
+
className="grid transition-all duration-200 ease-out"
|
|
181
|
+
style={{ gridTemplateRows: expandMethodPicker ? '1fr' : '0fr' }}
|
|
182
|
+
>
|
|
183
|
+
<div className="overflow-hidden">
|
|
184
|
+
{paymentMethodTypes.length === 0 ? (
|
|
185
|
+
<div className="rounded-lg border border-dashed border-input bg-muted/30 px-3 py-3 text-center mt-1.5">
|
|
186
|
+
<p className="text-xs text-muted-foreground">{t('financial.payment.noMethods')}</p>
|
|
187
|
+
<a href="#/financial/settings" className="mt-1.5 inline-block text-xs font-medium text-primary hover:underline">
|
|
188
|
+
{t('financial.payment.configureMethods')} →
|
|
189
|
+
</a>
|
|
190
|
+
</div>
|
|
191
|
+
) : (
|
|
192
|
+
<div className="grid grid-cols-3 gap-2 mt-1.5">
|
|
193
|
+
{paymentMethodTypes.map((type) => {
|
|
194
|
+
const tt = type.transactionType ?? ''
|
|
195
|
+
const Icon = TYPE_ICONS[tt] ?? Banknote
|
|
196
|
+
const isActive = selectedTypeId === type.id
|
|
197
|
+
return (
|
|
198
|
+
<button
|
|
199
|
+
key={type.id}
|
|
200
|
+
data-active={isActive}
|
|
201
|
+
onClick={() => handleSelectType(type.id)}
|
|
202
|
+
className={`flex flex-col items-center gap-1.5 rounded-lg border px-2 py-3 text-center transition-all ${TYPE_COLORS[tt] ?? TYPE_COLORS.cash}`}
|
|
203
|
+
>
|
|
204
|
+
<Icon className={`h-5 w-5 ${isActive ? ICON_COLORS[tt] ?? '' : 'text-muted-foreground'}`} />
|
|
205
|
+
<span className={`text-[11px] font-medium leading-tight ${isActive ? 'text-foreground' : 'text-muted-foreground'}`}>{type.name}</span>
|
|
206
|
+
</button>
|
|
207
|
+
)
|
|
208
|
+
})}
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* Form fields — only after method selected */}
|
|
216
|
+
{selectedTypeId && (
|
|
217
|
+
<div key={selectedTypeId} className="space-y-4">
|
|
218
|
+
{/* Amount + Date side by side */}
|
|
219
|
+
<div className="grid grid-cols-2 gap-3 stagger-field" style={{ animationDelay: '0ms' }}>
|
|
220
|
+
<CurrencyInput
|
|
221
|
+
label={t('financial.payment.amount')}
|
|
222
|
+
value={amount}
|
|
223
|
+
onChange={setAmount}
|
|
224
|
+
symbol={currency.symbol}
|
|
225
|
+
locale={currency.locale}
|
|
226
|
+
currencyCode={currency.code}
|
|
227
|
+
/>
|
|
228
|
+
<div>
|
|
229
|
+
<label className="text-xs font-medium text-muted-foreground">{t('financial.payment.date')}</label>
|
|
230
|
+
<DatePicker value={paymentDate} onChange={setPaymentDate} className="mt-1" />
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
{/* Specific payment method */}
|
|
235
|
+
{filteredMethods.length > 0 && (
|
|
236
|
+
<div className="stagger-field" style={{ animationDelay: '50ms' }}>
|
|
237
|
+
<label className="text-xs font-medium text-muted-foreground">
|
|
238
|
+
{needsCard ? t('financial.payment.card') : t('financial.payment.account')}
|
|
239
|
+
</label>
|
|
240
|
+
<select value={paymentMethodId} onChange={(e) => setPaymentMethodId(e.target.value)} className="w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm">
|
|
241
|
+
<option value="">{t('financial.payment.select')}</option>
|
|
242
|
+
{filteredMethods.map((m) => <option key={m.id} value={m.id}>{m.name}</option>)}
|
|
243
|
+
</select>
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
|
|
247
|
+
{/* Card fields side by side */}
|
|
248
|
+
{needsCard && (
|
|
249
|
+
<div className={`grid gap-3 stagger-field ${needsCardInstallments ? 'grid-cols-2' : 'grid-cols-1'}`} style={{ animationDelay: '100ms' }}>
|
|
250
|
+
<div>
|
|
251
|
+
<label className="text-xs font-medium text-muted-foreground">{t('financial.payment.cardBrand')}</label>
|
|
252
|
+
<select value={cardBrand} onChange={(e) => setCardBrand(e.target.value)} className="w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm">
|
|
253
|
+
<option value="">{t('financial.payment.selectBrand')}</option>
|
|
254
|
+
{['Visa', 'Mastercard', 'American Express', 'Elo', 'Hipercard', 'Diners Club'].map((b) => (
|
|
255
|
+
<option key={b} value={b}>{b}</option>
|
|
256
|
+
))}
|
|
257
|
+
</select>
|
|
258
|
+
</div>
|
|
259
|
+
{needsCardInstallments && (
|
|
260
|
+
<div>
|
|
261
|
+
<label className="text-xs font-medium text-muted-foreground">{t('financial.payment.cardInstallments')}</label>
|
|
262
|
+
<select value={cardInstallments} onChange={(e) => setCardInstallments(Number(e.target.value))} className="w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm">
|
|
263
|
+
{Array.from({ length: 12 }, (_, i) => i + 1).map((n) => (
|
|
264
|
+
<option key={n} value={n}>{n}x {n > 1 ? formatCurrency(amount / n, currency) : ''}</option>
|
|
265
|
+
))}
|
|
266
|
+
</select>
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{/* Bank account */}
|
|
273
|
+
{needsBankAccount && bankAccounts.length > 0 && (
|
|
274
|
+
<div className="stagger-field" style={{ animationDelay: '100ms' }}>
|
|
275
|
+
<label className="text-xs font-medium text-muted-foreground">{t('financial.payment.bankAccount')}</label>
|
|
276
|
+
<select value={bankAccountId} onChange={(e) => setBankAccountId(e.target.value)} className="w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm">
|
|
277
|
+
<option value="">{t('financial.payment.selectAccount')}</option>
|
|
278
|
+
{bankAccounts.map((a) => <option key={a.id} value={a.id}>{a.name}</option>)}
|
|
279
|
+
</select>
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
|
|
283
|
+
{/* Notes */}
|
|
284
|
+
<div className="stagger-field" style={{ animationDelay: '150ms' }}>
|
|
285
|
+
<label className="text-xs font-medium text-muted-foreground">{t('financial.payment.notes')}</label>
|
|
286
|
+
<input type="text" value={notes} onChange={(e) => setNotes(e.target.value)} placeholder={t('financial.payment.optionalNotes')} className="w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" />
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Keyframe for staggered field entrance */}
|
|
293
|
+
<style>{`
|
|
294
|
+
.stagger-field {
|
|
295
|
+
animation: field-slide-in 250ms ease-out both;
|
|
296
|
+
}
|
|
297
|
+
@keyframes field-slide-in {
|
|
298
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
299
|
+
to { opacity: 1; transform: translateY(0); }
|
|
300
|
+
}
|
|
301
|
+
`}</style>
|
|
302
|
+
|
|
303
|
+
{/* Actions */}
|
|
304
|
+
<div className="flex justify-end gap-2 px-5 py-4 border-t sticky bottom-0 bg-card">
|
|
305
|
+
<Button variant="outline" size="sm" onClick={handleClose}>
|
|
306
|
+
{t('financial.payment.cancel')}
|
|
307
|
+
</Button>
|
|
308
|
+
<Button
|
|
309
|
+
variant="default"
|
|
310
|
+
size="sm"
|
|
311
|
+
onClick={handlePay}
|
|
312
|
+
disabled={amount <= 0 || !selectedTypeId || saving}
|
|
313
|
+
>
|
|
314
|
+
<Check className="h-3.5 w-3.5" />
|
|
315
|
+
{saving ? t('financial.payment.processing') : t('financial.payment.payAmount', { amount: formatCurrency(amount, currency) })}
|
|
316
|
+
</Button>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>,
|
|
320
|
+
document.body
|
|
321
|
+
)
|
|
322
|
+
}
|