From eb0b042f0cd8127d82d056dc58f94b2611252983 Mon Sep 17 00:00:00 2001 From: "Kalana B. Thilakarathna" Date: Mon, 19 Oct 2026 22:02:15 +0530 Subject: [PATCH] feat: integrate payment functionality with new payment sheet and enhance supplier payments UI --- .../orders/_components/orders-client.tsx | 22 +++++ .../payments/_components/payment-sheet.tsx | 97 +++++++++++++++++-- .../_components/supplier-payments-client.tsx | 6 +- .../components/shared/top-loader.tsx | 44 +++++++++ distribution_fe/lib/api/sales.ts | 3 + 5 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 distribution_fe/components/shared/top-loader.tsx diff --git a/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx b/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx index 31d139f..595080d 100644 --- a/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx +++ b/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx @@ -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(null) const [actionOpen, setActionOpen] = useState(false) const [actionMode, setActionMode] = useState<'print' | 'finalize'>('print') + const [paymentOpen, setPaymentOpen] = useState(false) + const [paymentInvoice, setPaymentInvoice] = useState(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>(() => ({ 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} /> + + setPaymentOpen(false)} + onSuccess={() => fetchData(page, pageSize)} + invoices={paymentInvoice ? [paymentInvoice] : []} + defaultInvoiceId={paymentInvoice?.id} + /> ) } diff --git a/distribution_fe/app/(dashboard)/sales/payments/_components/payment-sheet.tsx b/distribution_fe/app/(dashboard)/sales/payments/_components/payment-sheet.tsx index 0878203..82a79dd 100644 --- a/distribution_fe/app/(dashboard)/sales/payments/_components/payment-sheet.tsx +++ b/distribution_fe/app/(dashboard)/sales/payments/_components/payment-sheet.tsx @@ -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([]) + const form = useForm({ 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 invoice... {pendingInvoices.map((inv) => ( {inv.storeName} — {fmtAmount(inv.totalAmount)} ({inv.paymentStatus}) [{truncId(inv.id)}] @@ -174,6 +196,29 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee )} /> + {method === 'Cash' && ( + ( + + )} + /> + )} + {method === 'Cheque' && ( <> )} /> + + ( + + )} + /> + + ( + + )} + /> )} diff --git a/distribution_fe/app/(dashboard)/suppliers/[id]/payments/_components/supplier-payments-client.tsx b/distribution_fe/app/(dashboard)/suppliers/[id]/payments/_components/supplier-payments-client.tsx index c4b3929..63bf17f 100644 --- a/distribution_fe/app/(dashboard)/suppliers/[id]/payments/_components/supplier-payments-client.tsx +++ b/distribution_fe/app/(dashboard)/suppliers/[id]/payments/_components/supplier-payments-client.tsx @@ -30,6 +30,7 @@ import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { toast } from '@/components/ui/toast' +import { TopLoader } from '@/components/shared/top-loader' function formatDate(value: string) { return new Date(value).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) @@ -70,7 +71,8 @@ export function SupplierPaymentsClient({ supplierId }: { supplierId: string }) { const pageCount = Math.max(1, Math.ceil(total / pageSize)) return ( - <> +
+

Supplier Payments

@@ -132,7 +134,7 @@ export function SupplierPaymentsClient({ supplierId }: { supplierId: string }) { onCreated={() => { setCreateOpen(false); loadPayments() }} /> )} - +
) } diff --git a/distribution_fe/components/shared/top-loader.tsx b/distribution_fe/components/shared/top-loader.tsx new file mode 100644 index 0000000..140f32d --- /dev/null +++ b/distribution_fe/components/shared/top-loader.tsx @@ -0,0 +1,44 @@ +'use client' + +export function TopLoader({ active }: { active: boolean }) { + if (!active) return null + return ( +
+
+ +
+ +
+ ) +} diff --git a/distribution_fe/lib/api/sales.ts b/distribution_fe/lib/api/sales.ts index c7fbcdb..a9bdf93 100644 --- a/distribution_fe/lib/api/sales.ts +++ b/distribution_fe/lib/api/sales.ts @@ -97,8 +97,11 @@ export interface PaymentBody { amount: number method: PaymentMethod entryMode?: EntryMode + bankAccountId?: string | null chequeNumber?: string | null chequeDate?: string | null + drawerBankName?: string | null + accountHolderName?: string | null collectedByUserId?: string | null } -- 2.52.0