From ee672da9f72dfda2f98fc579ed6a00ed735a237d Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Wed, 23 Sep 2026 12:47:53 +0530 Subject: [PATCH] Add user selection to Shop PO flow --- .../app/(dashboard)/sales/[id]/page.tsx | 25 ++-- .../_components/confirm-shop-po-dialog.tsx | 111 ++++++++++++++++++ .../sales/_components/sales-client.tsx | 23 ++-- .../app/(dashboard)/sales/new/page.tsx | 79 +++++++++---- distribution_fe/lib/api/operations.ts | 5 +- distribution_fe/lib/api/sales.ts | 9 +- 6 files changed, 202 insertions(+), 50 deletions(-) create mode 100644 distribution_fe/app/(dashboard)/sales/_components/confirm-shop-po-dialog.tsx diff --git a/distribution_fe/app/(dashboard)/sales/[id]/page.tsx b/distribution_fe/app/(dashboard)/sales/[id]/page.tsx index ba49bae..1476f8e 100644 --- a/distribution_fe/app/(dashboard)/sales/[id]/page.tsx +++ b/distribution_fe/app/(dashboard)/sales/[id]/page.tsx @@ -10,6 +10,8 @@ import { toast } from '@/components/ui/toast' import { shopPos as shopPosApi, getApiError } from '@/lib/api/index' import type { ShopPOResponse, ShopPOStatus } from '@/types/api' +import { ConfirmShopPODialog } from '../_components/confirm-shop-po-dialog' + const PO_STATUS_VARIANT: Record = { Pending: 'secondary', PartiallyFulfilled: 'secondary', @@ -41,6 +43,7 @@ export default function ShopPOViewPage() { const params = useParams<{ id: string }>() const [po, setPo] = useState(null) const [loading, setLoading] = useState(true) + const [confirmOpen, setConfirmOpen] = useState(false) useEffect(() => { let cancelled = false @@ -62,17 +65,6 @@ export default function ShopPOViewPage() { } }, [params.id, router]) - async function handleConfirm() { - if (!po) return - try { - const updated = await shopPosApi.confirm(po.id) - setPo(updated) - toast.add({ title: 'Shop PO confirmed', type: 'success' }) - } catch (err) { - toast.add({ title: 'Failed to confirm Shop PO', description: getApiError(err), type: 'error' }) - } - } - async function handleCancel() { if (!po) return try { @@ -123,7 +115,7 @@ export default function ShopPOViewPage() {
{!po.confirmedAt && po.status !== 'Cancelled' && ( - )} @@ -211,6 +203,15 @@ export default function ShopPOViewPage() {
+ + setConfirmOpen(false)} + onSuccess={() => { + shopPosApi.get(po.id).then(setPo) + }} + po={po} + /> ) } diff --git a/distribution_fe/app/(dashboard)/sales/_components/confirm-shop-po-dialog.tsx b/distribution_fe/app/(dashboard)/sales/_components/confirm-shop-po-dialog.tsx new file mode 100644 index 0000000..3a9face --- /dev/null +++ b/distribution_fe/app/(dashboard)/sales/_components/confirm-shop-po-dialog.tsx @@ -0,0 +1,111 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useForm, zodResolver, z } from '@/lib/form' +import { FormField } from '@/components/shared/form-field' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { FieldGroup } from '@/components/ui/field' +import { toast } from '@/components/ui/toast' +import { shopPos as shopPosApi, users as usersApi, getApiError, getPaginatedItems } from '@/lib/api/index' +import type { ShopPOResponse, UserResponse } from '@/types/api' + +const schema = z.object({ + confirmedByUserId: z.string().min(1, 'Confirmed by is required'), +}) + +type FormValues = z.infer + +interface ConfirmShopPODialogProps { + open: boolean + onClose: () => void + onSuccess: () => void + po: ShopPOResponse | null +} + +export function ConfirmShopPODialog({ open, onClose, onSuccess, po }: ConfirmShopPODialogProps) { + const [usersList, setUsersList] = useState([]) + + const form = useForm({ + resolver: zodResolver(schema), + defaultValues: { confirmedByUserId: '' }, + }) + + useEffect(() => { + if (!open) return + form.reset({ confirmedByUserId: '' }) + usersApi.list({ pageSize: 200 }) + .then((res) => setUsersList(getPaginatedItems(res))) + .catch((err) => toast.add({ title: 'Failed to load users', description: getApiError(err), type: 'error' })) + }, [open]) + + if (!po) return null + + async function onSubmit(values: FormValues) { + try { + await shopPosApi.confirm(po!.id, values.confirmedByUserId) + toast.add({ title: 'Shop PO confirmed', type: 'success' }) + onSuccess() + onClose() + } catch (err) { + toast.add({ title: 'Failed to confirm Shop PO', description: getApiError(err), type: 'error' }) + } + } + + return ( + { if (!v) onClose() }}> + + + Confirm Shop PO + {po.storeName} — Rs. {po.totalAmount.toLocaleString()} + + +
+ + ( + + )} + /> + + + + + + +
+
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx b/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx index c248a5c..c2f5e99 100644 --- a/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx +++ b/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx @@ -27,6 +27,8 @@ import { } from '@/lib/api/index' import type { ShopPOResponse, ShopPOStatus, UserResponse, RouteResponse, StoreResponse } from '@/types/api' +import { ConfirmShopPODialog } from './confirm-shop-po-dialog' + const ALL = '__all__' type ConfirmedFilter = 'all' | 'confirmed' | 'unconfirmed' @@ -60,6 +62,9 @@ export function SalesClient() { const [shopId, setShopId] = useState('') const [confirmedFilter, setConfirmedFilter] = useState('all') + const [confirmOpen, setConfirmOpen] = useState(false) + const [selectedPO, setSelectedPO] = useState(null) + useEffect(() => { Promise.all([ usersApi.list({ pageSize: 200 }), @@ -111,14 +116,9 @@ export function SalesClient() { router.push(`/sales/${row.id}`) } - async function handleConfirm(row: ShopPOResponse) { - try { - await shopPosApi.confirm(row.id) - toast.add({ title: 'Shop PO confirmed', type: 'success' }) - fetchData(page, pageSize) - } catch (err) { - toast.add({ title: 'Failed to confirm Shop PO', description: getApiError(err), type: 'error' }) - } + function handleConfirm(row: ShopPOResponse) { + setSelectedPO(row) + setConfirmOpen(true) } async function handleCancel(row: ShopPOResponse) { @@ -316,6 +316,13 @@ export function SalesClient() { setPageSize(p.pageSize) }} /> + + setConfirmOpen(false)} + onSuccess={() => fetchData(page, pageSize)} + po={selectedPO} + /> ) } diff --git a/distribution_fe/app/(dashboard)/sales/new/page.tsx b/distribution_fe/app/(dashboard)/sales/new/page.tsx index d384969..0e56632 100644 --- a/distribution_fe/app/(dashboard)/sales/new/page.tsx +++ b/distribution_fe/app/(dashboard)/sales/new/page.tsx @@ -24,10 +24,11 @@ import { stores as storesApi, items as itemsApi, priceLists as priceListsApi, + users as usersApi, getApiError, getPaginatedItems, } from '@/lib/api/index' -import type { ItemSummaryResponse, StoreResponse } from '@/types/api' +import type { ItemSummaryResponse, StoreResponse, UserResponse } from '@/types/api' const lineItemSchema = z.object({ itemId: z.string().min(1, 'Item is required'), @@ -37,6 +38,7 @@ const lineItemSchema = z.object({ const schema = z.object({ storeId: z.string().min(1, 'Store is required'), + raisedByUserId: z.string().min(1, 'Raised by is required'), items: z.array(lineItemSchema).min(1, 'At least one item is required'), }) @@ -52,12 +54,13 @@ export default function NewShopPOPage() { const router = useRouter() const [storesList, setStoresList] = useState([]) const [itemsList, setItemsList] = useState([]) + const [usersList, setUsersList] = useState([]) const [priceMinByIndex, setPriceMinByIndex] = useState>({}) const [priceRetailByIndex, setPriceRetailByIndex] = useState>({}) const form = useForm({ resolver: zodResolver(schema), - defaultValues: { storeId: '', items: [createDefaultItem()] }, + defaultValues: { storeId: '', raisedByUserId: '', items: [createDefaultItem()] }, }) const { fields, append, remove } = useFieldArray({ control: form.control, name: 'items' }) @@ -107,10 +110,12 @@ export default function NewShopPOPage() { Promise.all([ storesApi.list({ pageSize: 200 }), itemsApi.list({ pageSize: 200 }), + usersApi.list({ pageSize: 200 }), ]) - .then(([stores, items]) => { + .then(([stores, items, users]) => { setStoresList(getPaginatedItems(stores)) setItemsList(getPaginatedItems(items)) + setUsersList(getPaginatedItems(users)) }) .catch((err) => { toast.add({ title: 'Failed to load reference data', description: getApiError(err), type: 'error' }) @@ -121,6 +126,7 @@ export default function NewShopPOPage() { try { await shopPosApi.create({ storeId: values.storeId, + raisedByUserId: values.raisedByUserId, items: values.items, }) toast.add({ title: 'Shop PO created', type: 'success' }) @@ -147,27 +153,52 @@ export default function NewShopPOPage() {

Order Details

- ( - - )} - /> +
+ ( + + )} + /> + + ( + + )} + /> +
diff --git a/distribution_fe/lib/api/operations.ts b/distribution_fe/lib/api/operations.ts index a9efa70..9d7e672 100644 --- a/distribution_fe/lib/api/operations.ts +++ b/distribution_fe/lib/api/operations.ts @@ -54,6 +54,7 @@ export const rides = { export interface LoadingSheetCreateBody { fK_RideId: string + populatedByUserId?: string | null } export interface LoadingSheetItemInput { @@ -83,8 +84,8 @@ export const loadingSheets = { updateItems: (id: string, items: LoadingSheetItemInput[]) => api.put(`/loading-sheets/${id}/items`, { items }).then(r => r.data), - confirm: (id: string) => - api.post(`/loading-sheets/${id}/confirm`).then(r => r.data), + confirm: (id: string, confirmedByUserId?: string | null) => + api.post(`/loading-sheets/${id}/confirm`, { confirmedByUserId }).then(r => r.data), } // ─── Delivery Tasks ─────────────────────────────────────────────────────────── diff --git a/distribution_fe/lib/api/sales.ts b/distribution_fe/lib/api/sales.ts index e47d1c8..b635114 100644 --- a/distribution_fe/lib/api/sales.ts +++ b/distribution_fe/lib/api/sales.ts @@ -23,6 +23,7 @@ export interface ShopPOItemBody { export interface CreateShopPOBody { storeId: string items: ShopPOItemBody[] + raisedByUserId?: string | null } export interface UpdateShopPOItemsBody { @@ -51,11 +52,11 @@ export const shopPos = { updateItems: (id: string, body: UpdateShopPOItemsBody) => api.put(`/shop-pos/${id}/items`, body).then(r => r.data), - confirm: (id: string) => - api.post(`/shop-pos/${id}/confirm`).then(r => r.data), + confirm: (id: string, confirmedByUserId?: string | null) => + api.post(`/shop-pos/${id}/confirm`, { confirmedByUserId }).then(r => r.data), - confirmAndCreateInvoice: (id: string) => - api.post(`/shop-pos/${id}/confirm-and-create-invoice`).then(r => r.data), + confirmAndCreateInvoice: (id: string, confirmedByUserId?: string | null) => + api.post(`/shop-pos/${id}/confirm-and-create-invoice`, { confirmedByUserId }).then(r => r.data), cancel: (id: string) => api.post(`/shop-pos/${id}/cancel`).then(r => r.data),