feat: integrate payment functionality with new payment sheet and enhance supplier payments UI
This commit is contained in:
@@ -11,6 +11,7 @@ import type { InvoiceResponse, InvoiceStatus, PaymentStatus } from '@/types/api'
|
||||
|
||||
import { OrderViewSheet } from './order-view-sheet'
|
||||
import { InvoiceActionDialog } from './invoice-action-dialog'
|
||||
import { PaymentSheet } from '../../payments/_components/payment-sheet'
|
||||
|
||||
const fmtDate = (v: string) =>
|
||||
new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
|
||||
@@ -41,6 +42,8 @@ export function OrdersClient() {
|
||||
const [selectedInvoice, setSelectedInvoice] = useState<InvoiceResponse | null>(null)
|
||||
const [actionOpen, setActionOpen] = useState(false)
|
||||
const [actionMode, setActionMode] = useState<'print' | 'finalize'>('print')
|
||||
const [paymentOpen, setPaymentOpen] = useState(false)
|
||||
const [paymentInvoice, setPaymentInvoice] = useState<InvoiceResponse | null>(null)
|
||||
|
||||
const fetchData = useCallback(async (p: number, ps: number) => {
|
||||
setLoading(true)
|
||||
@@ -76,6 +79,11 @@ export function OrdersClient() {
|
||||
setActionOpen(true)
|
||||
}
|
||||
|
||||
function handleRecordPayment(row: InvoiceResponse) {
|
||||
setPaymentInvoice(row)
|
||||
setPaymentOpen(true)
|
||||
}
|
||||
|
||||
const tableConfig = useMemo<TableConfig<InvoiceResponse>>(() => ({
|
||||
rowKey: 'id',
|
||||
serverSide: true,
|
||||
@@ -151,6 +159,12 @@ export function OrdersClient() {
|
||||
onClick: handleFinalize,
|
||||
visible: (target) => target.status === 'Printed',
|
||||
},
|
||||
{
|
||||
action: 'edit',
|
||||
label: 'Record payment',
|
||||
onClick: handleRecordPayment,
|
||||
visible: (target) => target.paymentStatus !== 'Paid',
|
||||
},
|
||||
],
|
||||
},
|
||||
}), [pageSize])
|
||||
@@ -190,6 +204,14 @@ export function OrdersClient() {
|
||||
invoice={selectedInvoice}
|
||||
mode={actionMode}
|
||||
/>
|
||||
|
||||
<PaymentSheet
|
||||
open={paymentOpen}
|
||||
onClose={() => setPaymentOpen(false)}
|
||||
onSuccess={() => fetchData(page, pageSize)}
|
||||
invoices={paymentInvoice ? [paymentInvoice] : []}
|
||||
defaultInvoiceId={paymentInvoice?.id}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useForm, zodResolver, z } from '@/lib/form'
|
||||
import { FormField } from '@/components/shared/form-field'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -23,8 +23,9 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { FieldGroup } from '@/components/ui/field'
|
||||
import { toast } from '@/components/ui/toast'
|
||||
import { payments as paymentsApi, getApiError } from '@/lib/api/index'
|
||||
import { payments as paymentsApi, bankAccounts as bankAccountsApi, getApiError } from '@/lib/api/index'
|
||||
import type { InvoiceResponse } from '@/types/api'
|
||||
import type { BankAccountResponse } from '@/types/payments'
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
@@ -32,8 +33,15 @@ const schema = z
|
||||
amount: z.number().min(0.01, 'Amount must be greater than 0'),
|
||||
method: z.enum(['Cash', 'Cheque', 'Credit']),
|
||||
entryMode: z.enum(['RealTime', 'DayEndManual']),
|
||||
bankAccountId: z.string().optional(),
|
||||
chequeNumber: z.string().optional(),
|
||||
chequeDate: z.string().optional(),
|
||||
drawerBankName: z.string().optional(),
|
||||
accountHolderName: z.string().optional(),
|
||||
})
|
||||
.refine((data) => data.method !== 'Cash' || !!data.bankAccountId, {
|
||||
message: 'Bank/Cash account is required for cash payments',
|
||||
path: ['bankAccountId'],
|
||||
})
|
||||
.refine((data) => data.method !== 'Cheque' || !!data.chequeNumber, {
|
||||
message: 'Cheque number is required for cheque payments',
|
||||
@@ -51,21 +59,27 @@ interface PaymentSheetProps {
|
||||
onClose: () => void
|
||||
onSuccess: () => void
|
||||
invoices: InvoiceResponse[]
|
||||
defaultInvoiceId?: string
|
||||
}
|
||||
|
||||
const fmtAmount = (n: number) => `Rs. ${n.toLocaleString()}`
|
||||
const truncId = (id: string) => id.substring(0, 8)
|
||||
|
||||
export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentSheetProps) {
|
||||
export function PaymentSheet({ open, onClose, onSuccess, invoices, defaultInvoiceId }: PaymentSheetProps) {
|
||||
const [accounts, setAccounts] = useState<BankAccountResponse[]>([])
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
invoiceId: '',
|
||||
invoiceId: defaultInvoiceId ?? '',
|
||||
amount: 0,
|
||||
method: 'Cash',
|
||||
entryMode: 'RealTime',
|
||||
bankAccountId: '',
|
||||
chequeNumber: '',
|
||||
chequeDate: '',
|
||||
drawerBankName: '',
|
||||
accountHolderName: '',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -74,23 +88,32 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
form.reset({
|
||||
invoiceId: '',
|
||||
invoiceId: defaultInvoiceId ?? '',
|
||||
amount: 0,
|
||||
method: 'Cash',
|
||||
entryMode: 'RealTime',
|
||||
bankAccountId: '',
|
||||
chequeNumber: '',
|
||||
chequeDate: '',
|
||||
drawerBankName: '',
|
||||
accountHolderName: '',
|
||||
})
|
||||
}, [open])
|
||||
bankAccountsApi.list()
|
||||
.then(list => setAccounts(list.filter(a => a.isActive !== false)))
|
||||
.catch(() => {})
|
||||
}, [open, defaultInvoiceId])
|
||||
|
||||
async function onSubmit(values: FormValues) {
|
||||
const { invoiceId, method, chequeNumber, chequeDate, ...rest } = values
|
||||
const { invoiceId, method, bankAccountId, chequeNumber, chequeDate, drawerBankName, accountHolderName, ...rest } = values
|
||||
try {
|
||||
await paymentsApi.create(invoiceId, {
|
||||
...rest,
|
||||
method,
|
||||
bankAccountId: method === 'Cash' ? bankAccountId : undefined,
|
||||
chequeNumber: method === 'Cheque' ? chequeNumber : undefined,
|
||||
chequeDate: method === 'Cheque' ? chequeDate : undefined,
|
||||
drawerBankName: method === 'Cheque' ? drawerBankName || undefined : undefined,
|
||||
accountHolderName: method === 'Cheque' ? accountHolderName || undefined : undefined,
|
||||
})
|
||||
toast.add({ title: 'Payment recorded', type: 'success' })
|
||||
onSuccess()
|
||||
@@ -118,12 +141,11 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee
|
||||
label="Invoice"
|
||||
required
|
||||
render={({ field }) => (
|
||||
<Select value={field.value as string} onValueChange={field.onChange}>
|
||||
<Select value={field.value as string} onValueChange={field.onChange} disabled={!!defaultInvoiceId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select invoice..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="" disabled>Select invoice...</SelectItem>
|
||||
{pendingInvoices.map((inv) => (
|
||||
<SelectItem key={inv.id} value={inv.id}>
|
||||
{inv.storeName} — {fmtAmount(inv.totalAmount)} ({inv.paymentStatus}) [{truncId(inv.id)}]
|
||||
@@ -174,6 +196,29 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee
|
||||
)}
|
||||
/>
|
||||
|
||||
{method === 'Cash' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bankAccountId"
|
||||
label="Deposit Account"
|
||||
required
|
||||
render={({ field }) => (
|
||||
<Select value={(field.value as string) ?? ''} onValueChange={field.onChange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Choose where the money lands" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{accounts.map((a) => (
|
||||
<SelectItem key={a.accountId} value={a.accountId}>
|
||||
{a.accountName}{a.bankName ? ` (${a.bankName})` : ''}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{method === 'Cheque' && (
|
||||
<>
|
||||
<FormField
|
||||
@@ -213,6 +258,40 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="drawerBankName"
|
||||
label="Drawer Bank (optional)"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="drawerBankName"
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
value={(field.value as string) ?? ''}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
placeholder="e.g. Sampath Bank"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accountHolderName"
|
||||
label="Account Holder (optional)"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="accountHolderName"
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
value={(field.value as string) ?? ''}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
placeholder="Name on the cheque"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user