From f2d5c9f50aee8f9f6fefa0145d58c2bf480845a9 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Wed, 21 Oct 2026 12:51:02 +0530 Subject: [PATCH] fix invoice action dialog and order view with editing and printing capabilities --- .../_components/invoice-action-dialog.tsx | 78 ++++++++++++++++++- .../orders/_components/order-view-sheet.tsx | 70 ++++++----------- .../orders/_components/orders-client.tsx | 20 ++++- 3 files changed, 117 insertions(+), 51 deletions(-) diff --git a/distribution_fe/app/(dashboard)/sales/orders/_components/invoice-action-dialog.tsx b/distribution_fe/app/(dashboard)/sales/orders/_components/invoice-action-dialog.tsx index 8ec09c3..8859339 100644 --- a/distribution_fe/app/(dashboard)/sales/orders/_components/invoice-action-dialog.tsx +++ b/distribution_fe/app/(dashboard)/sales/orders/_components/invoice-action-dialog.tsx @@ -21,6 +21,7 @@ import { } from '@/components/ui/dialog' import { FieldGroup } from '@/components/ui/field' import { toast } from '@/components/ui/toast' +import { Input } from '@/components/ui/input' import { invoices as invoicesApi, users as usersApi, getApiError, getPaginatedItems } from '@/lib/api/index' import type { InvoiceResponse, UserResponse } from '@/types/api' @@ -39,13 +40,17 @@ interface InvoiceActionDialogProps { } const COPY = { - print: { title: 'Mark Invoice as Printed', label: 'Printed By', action: 'Mark as Printed', success: 'Invoice marked as printed' }, + print: { title: 'Print Invoice', label: 'Printed By', action: 'Print Invoice', success: 'Invoice printed' }, finalize: { title: 'Finalize Invoice', label: 'Finalized By', action: 'Finalize Invoice', success: 'Invoice finalized' }, } export function InvoiceActionDialog({ open, onClose, onSuccess, invoice, mode }: InvoiceActionDialogProps) { const [usersList, setUsersList] = useState([]) const copy = COPY[mode] + const [qty, setQty] = useState>({}) + const [removed, setRemoved] = useState>({}) + // Rows from the list endpoint carry no line items, so the full invoice is loaded for the finalize table. + const [full, setFull] = useState(null) const form = useForm({ resolver: zodResolver(schema), @@ -55,6 +60,18 @@ export function InvoiceActionDialog({ open, onClose, onSuccess, invoice, mode }: useEffect(() => { if (!open) return form.reset({ userId: '' }) + setFull(null) + setQty({}) + setRemoved({}) + if (invoice) { + invoicesApi.get(invoice.id) + .then((inv) => { + setFull(inv) + setQty(Object.fromEntries(inv.items.map((i) => [i.id, String(i.quantity)]))) + setRemoved(Object.fromEntries(inv.items.map((i) => [i.id, i.isRemoved]))) + }) + .catch((err) => toast.add({ title: 'Failed to load invoice', description: getApiError(err), type: 'error' })) + } usersApi.list() .then((res) => setUsersList(res)) .catch((err) => toast.add({ title: 'Failed to load users', description: getApiError(err), type: 'error' })) @@ -62,21 +79,35 @@ export function InvoiceActionDialog({ open, onClose, onSuccess, invoice, mode }: if (!invoice) return null + const lines = full?.items ?? [] + async function onSubmit(values: FormValues) { + // Opened synchronously, while the click still counts as a user gesture, so the browser allows the new tab. + const printTab = mode === 'print' ? window.open('', '_blank') : null try { - if (mode === 'print') await invoicesApi.print(invoice!.id, values.userId) - else await invoicesApi.finalize(invoice!.id, values.userId) + if (mode === 'print') { + await invoicesApi.print(invoice!.id, values.userId) + if (printTab) printTab.location.href = `/invoices/${invoice!.id}/print` + } else { + if (!full) throw new Error('Invoice is still loading') + // Only lines the agent changed on the printed copy are sent as adjustments. + const adjustments = lines + .filter((i) => Number(qty[i.id]) !== i.quantity || !!removed[i.id] !== i.isRemoved) + .map((i) => ({ invoiceItemId: i.id, adjustedQuantity: Number(qty[i.id]), isRemoved: !!removed[i.id] })) + await invoicesApi.finalize(invoice!.id, values.userId, adjustments) + } toast.add({ title: copy.success, type: 'success' }) onSuccess() onClose() } catch (err) { + printTab?.close() toast.add({ title: `Failed to ${mode} invoice`, description: getApiError(err), type: 'error' }) } } return ( { if (!v) onClose() }}> - + {copy.title} {invoice.storeName} — Rs. {invoice.totalAmount.toLocaleString()} @@ -106,6 +137,45 @@ export function InvoiceActionDialog({ open, onClose, onSuccess, invoice, mode }: /> + {mode === 'finalize' && lines.length > 0 && ( +
+

Edits from the printed copy

+
+ + + + + + + + + + {lines.map((i) => ( + + + + + + + ))} + +
ItemPrintedFinal qty +
{i.itemName}{i.quantity} + setQty((q) => ({ ...q, [i.id]: e.target.value }))} + /> + + +
+
+
+ )} + + + )}
) 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 6515b85..6a98077 100644 --- a/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx +++ b/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx @@ -1,6 +1,7 @@ 'use client' import { useState, useEffect, useCallback, useMemo } from 'react' +import { useRouter } from 'next/navigation' import { DataTable } from '@/components/shared/data-table' import type { TableConfig } from '@/components/shared/data-table' @@ -35,6 +36,7 @@ const PAYMENT_STATUS_VARIANT: Record([]) const [loading, setLoading] = useState(true) const [total, setTotal] = useState(0) @@ -76,6 +78,10 @@ export function OrdersClient() { setViewOpen(true) } + function handleEdit(row: InvoiceResponse) { + router.push(`/sales/invoices/${row.id}/edit`) + } + function handlePrint(row: InvoiceResponse) { setSelectedInvoice(row) setActionMode('print') @@ -156,12 +162,24 @@ export function OrdersClient() { label: 'View invoice', onClick: handleView, }, + { + action: 'edit', + label: 'Edit draft items', + onClick: handleEdit, + visible: (target) => target.status === 'Draft', + }, { action: 'print', - label: 'Mark as printed', + label: 'Print invoice', onClick: handlePrint, visible: (target) => target.status === 'Draft', }, + { + action: 'print', + label: 'Reprint invoice', + onClick: (target) => window.open(`/invoices/${target.id}/print`, '_blank'), + visible: (target) => target.status !== 'Draft', + }, { action: 'finalize', label: 'Finalize invoice',