From 5a6eca1f077e21383dd3126001cbf52c360cae8d Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Tue, 8 Sep 2026 16:11:11 +0530 Subject: [PATCH] Add GrnViewSheet in GrnClient --- .../grn/_components/grn-client.tsx | 23 ++++++ .../procurement/grn/_components/grn-sheet.tsx | 34 +++++++- .../grn/_components/grn-view-sheet.tsx | 80 +++++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 distribution_fe/app/(dashboard)/procurement/grn/_components/grn-view-sheet.tsx diff --git a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-client.tsx b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-client.tsx index e5a69f4..f3a6a97 100644 --- a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-client.tsx +++ b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-client.tsx @@ -10,6 +10,7 @@ import { toast } from '@/components/ui/toast' import { grn as grnApi, getApiError, getPaginatedItems } from '@/lib/api/index' import type { GrnResponse } from '@/types/api' import { GrnSheet } from './grn-sheet' +import { GrnViewSheet } from './grn-view-sheet' const fmtDate = (v: string) => new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) @@ -23,6 +24,13 @@ export function GrnClient() { const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(20) const [sheetOpen, setSheetOpen] = useState(false) + const [viewing, setViewing] = useState(null) + const [viewOpen, setViewOpen] = useState(false) + + function openView(row: GrnResponse) { + setViewing(row) + setViewOpen(true) + } const fetchData = useCallback(async (p: number, ps: number) => { setLoading(true) @@ -94,6 +102,15 @@ export function GrnClient() { width: 140, }, ], + rowActions: { + actions: () => [ + { + action: 'view', + label: 'View receipt', + onClick: openView, + }, + ], + }, }), [pageSize]) return ( @@ -127,6 +144,12 @@ export function GrnClient() { onClose={() => setSheetOpen(false)} onSuccess={() => fetchData(page, pageSize)} /> + + setViewOpen(false)} + grn={viewing} + /> ) } diff --git a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-sheet.tsx b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-sheet.tsx index e0cc8c8..97d7c8f 100644 --- a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-sheet.tsx +++ b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-sheet.tsx @@ -28,10 +28,11 @@ import { grn as grnApi, purchaseOrders as purchaseOrdersApi, warehouses as warehousesApi, + users as usersApi, getApiError, getPaginatedItems, } from '@/lib/api/index' -import type { PurchaseOrderResponse, WarehouseResponse } from '@/types/api' +import type { PurchaseOrderResponse, WarehouseResponse, UserResponse } from '@/types/api' const grnItemSchema = z.object({ purchaseOrderItemId: z.string().min(1), @@ -43,6 +44,7 @@ const schema = z.object({ purchaseOrderId: z.string().min(1, 'Purchase order is required'), warehouseId: z.string().min(1, 'Warehouse is required'), receivedAt: z.string().min(1, 'Received date is required'), + receivedByUserId: z.string().min(1, 'Received by is required'), items: z.array(grnItemSchema).min(1, 'PO must have at least one line item'), }) @@ -61,6 +63,7 @@ function todayIso() { export function GrnSheet({ open, onClose, onSuccess }: GrnSheetProps) { const [eligiblePOs, setEligiblePOs] = useState([]) const [warehousesList, setWarehousesList] = useState([]) + const [usersList, setUsersList] = useState([]) const [selectedPO, setSelectedPO] = useState(null) const [loadingPO, setLoadingPO] = useState(false) @@ -70,6 +73,7 @@ export function GrnSheet({ open, onClose, onSuccess }: GrnSheetProps) { purchaseOrderId: '', warehouseId: '', receivedAt: todayIso(), + receivedByUserId: '', items: [], }, }) @@ -79,17 +83,19 @@ export function GrnSheet({ open, onClose, onSuccess }: GrnSheetProps) { // Load reference data when sheet opens useEffect(() => { if (!open) return - form.reset({ purchaseOrderId: '', warehouseId: '', receivedAt: todayIso(), items: [] }) + form.reset({ purchaseOrderId: '', warehouseId: '', receivedAt: todayIso(), receivedByUserId: '', items: [] }) setSelectedPO(null) Promise.all([ purchaseOrdersApi.list({ pageSize: 200 }), warehousesApi.list({ pageSize: 200 }), + usersApi.list({ pageSize: 200 }), ]) - .then(([pos, ws]) => { + .then(([pos, ws, usersRes]) => { // Only POs that can still receive goods const pos_ = getPaginatedItems(pos) as PurchaseOrderResponse[] setEligiblePOs(pos_.filter(po => po.status === 'Issued' || po.status === 'PartiallyReceived')) setWarehousesList(getPaginatedItems(ws)) + setUsersList(getPaginatedItems(usersRes)) }) .catch(err => toast.add({ title: 'Failed to load reference data', description: getApiError(err), type: 'error' }) @@ -131,6 +137,7 @@ export function GrnSheet({ open, onClose, onSuccess }: GrnSheetProps) { warehouseId: values.warehouseId, receivedAt: `${values.receivedAt}T00:00:00.000Z`, items: values.items, + receivedByUserId: values.receivedByUserId, }) toast.add({ title: 'Goods receipt recorded', type: 'success' }) onSuccess() @@ -214,6 +221,27 @@ export function GrnSheet({ open, onClose, onSuccess }: GrnSheetProps) { )} /> + + ( + + )} + /> {/* Line items — populated from the selected PO */} diff --git a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-view-sheet.tsx b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-view-sheet.tsx new file mode 100644 index 0000000..bb24183 --- /dev/null +++ b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-view-sheet.tsx @@ -0,0 +1,80 @@ +'use client' + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Separator } from '@/components/ui/separator' +import type { GrnResponse } from '@/types/api' + +const fmtDateTime = (v: string) => + new Date(v).toLocaleString('en-GB', { + day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', + }) + +interface GrnViewSheetProps { + open: boolean + onClose: () => void + grn: GrnResponse | null +} + +function Row({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value} +
+ ) +} + +export function GrnViewSheet({ open, onClose, grn }: GrnViewSheetProps) { + if (!grn) return null + + return ( + !v && onClose()}> + + + Goods Receipt Details + + +
+ {grn.purchaseOrderId}} /> + + + +
+ + + +
+

Line Items

+
+ + + + + + + + + + {grn.items.map((item) => ( + + + + + + ))} + +
ItemAcceptedRejected
+ {item.itemName} + ({item.sku}) + {item.acceptedQuantity}{item.rejectedQuantity}
+
+
+
+
+ ) +}