From a9a201cce101970b59c9716093de0dbf267279ed Mon Sep 17 00:00:00 2001
From: DeepnaPooja
Date: Sun, 20 Sep 2026 21:33:44 +0530
Subject: [PATCH] Update sales pages
---
.../_components/loading-sheets-client.tsx | 82 +++++-
.../app/(dashboard)/sales/[id]/page.tsx | 216 +++++++++++++++
.../sales/_components/sales-client.tsx | 166 ++++++++++--
.../_components/shop-po-create-sheet.tsx | 217 ----------------
.../sales/_components/shop-po-view-sheet.tsx | 106 --------
.../(dashboard)/sales/invoices/[id]/page.tsx | 245 ++++++++++++++++++
6 files changed, 681 insertions(+), 351 deletions(-)
create mode 100644 distribution_fe/app/(dashboard)/sales/[id]/page.tsx
delete mode 100644 distribution_fe/app/(dashboard)/sales/_components/shop-po-create-sheet.tsx
delete mode 100644 distribution_fe/app/(dashboard)/sales/_components/shop-po-view-sheet.tsx
create mode 100644 distribution_fe/app/(dashboard)/sales/invoices/[id]/page.tsx
diff --git a/distribution_fe/app/(dashboard)/operations/loading/_components/loading-sheets-client.tsx b/distribution_fe/app/(dashboard)/operations/loading/_components/loading-sheets-client.tsx
index 25acbc0..b033786 100644
--- a/distribution_fe/app/(dashboard)/operations/loading/_components/loading-sheets-client.tsx
+++ b/distribution_fe/app/(dashboard)/operations/loading/_components/loading-sheets-client.tsx
@@ -8,9 +8,18 @@ import { DataTable } from '@/components/shared/data-table'
import type { TableConfig } from '@/components/shared/data-table'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select'
import { toast } from '@/components/ui/toast'
-import { loadingSheets as loadingSheetsApi, getApiError, getPaginatedItems } from '@/lib/api/index'
-import type { LoadingSheetResponse, LoadingSheetStatus } from '@/types/api'
+import { loadingSheets as loadingSheetsApi, rides as ridesApi, getApiError, getPaginatedItems } from '@/lib/api/index'
+import type { LoadingSheetResponse, LoadingSheetStatus, RideResponse } from '@/types/api'
+
+const ALL = '__all__'
import { LoadingSheetViewSheet } from './loading-sheet-view-sheet'
import { LoadingSheetCreateDialog } from './loading-sheet-create-dialog'
@@ -36,10 +45,25 @@ export function LoadingSheetsClient() {
const [viewOpen, setViewOpen] = useState(false)
const [selectedSheet, setSelectedSheet] = useState(null)
+ const [ridesList, setRidesList] = useState([])
+ const [rideId, setRideId] = useState('')
+ const [statusFilter, setStatusFilter] = useState('')
+
+ useEffect(() => {
+ ridesApi.list({ pageSize: 200 })
+ .then((res) => setRidesList(getPaginatedItems(res)))
+ .catch((err) => toast.add({ title: 'Failed to load rides', description: getApiError(err), type: 'error' }))
+ }, [])
+
const fetchData = useCallback(async (p: number, ps: number) => {
setLoading(true)
try {
- const res = await loadingSheetsApi.list({ page: p, pageSize: ps })
+ const res = await loadingSheetsApi.list({
+ page: p,
+ pageSize: ps,
+ rideId: rideId || undefined,
+ status: statusFilter || undefined,
+ })
setData(getPaginatedItems(res))
setTotal(res.total)
} catch (err) {
@@ -47,12 +71,19 @@ export function LoadingSheetsClient() {
} finally {
setLoading(false)
}
- }, [])
+ }, [rideId, statusFilter])
useEffect(() => {
fetchData(page, pageSize)
}, [fetchData, page, pageSize])
+ const hasFilters = !!rideId || !!statusFilter
+
+ function clearFilters() {
+ setRideId('')
+ setStatusFilter('')
+ }
+
function handleView(row: LoadingSheetResponse) {
setSelectedSheet(row)
setViewOpen(true)
@@ -159,6 +190,49 @@ export function LoadingSheetsClient() {
+
+
+
+
+
+
+
+
+
+
+
+ {hasFilters && (
+
+ )}
+
+
= {
+ Pending: 'secondary',
+ PartiallyFulfilled: 'secondary',
+ Fulfilled: 'default',
+ Cancelled: 'destructive',
+}
+
+const fmtAmount = (n: number) => `Rs. ${n.toLocaleString()}`
+const truncId = (id: string) => id.substring(0, 8)
+
+const fmtDateTime = (v: string | null) =>
+ v
+ ? new Date(v).toLocaleString('en-GB', {
+ day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit',
+ })
+ : '—'
+
+function Stat({ label, value }: { label: string; value: React.ReactNode }) {
+ return (
+
+ {label}
+ {value}
+
+ )
+}
+
+export default function ShopPOViewPage() {
+ const router = useRouter()
+ const params = useParams<{ id: string }>()
+ const [po, setPo] = useState(null)
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ let cancelled = false
+ setLoading(true)
+ shopPosApi
+ .get(params.id)
+ .then((res) => {
+ if (!cancelled) setPo(res)
+ })
+ .catch((err) => {
+ toast.add({ title: 'Failed to load Shop PO', description: getApiError(err), type: 'error' })
+ router.push('/sales')
+ })
+ .finally(() => {
+ if (!cancelled) setLoading(false)
+ })
+ return () => {
+ cancelled = true
+ }
+ }, [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 {
+ const updated = await shopPosApi.cancel(po.id)
+ setPo(updated)
+ toast.add({ title: 'Shop PO cancelled', type: 'success' })
+ } catch (err) {
+ toast.add({ title: 'Failed to cancel Shop PO', description: getApiError(err), type: 'error' })
+ }
+ }
+
+ if (loading) {
+ return (
+
+
+
+
+
Loading Shop PO…
+
+ )
+ }
+
+ if (!po) return null
+
+ const activeItems = po.items.filter((i) => i.isActive)
+
+ return (
+
+
+
+
+
+
+
+ Shop PO {truncId(po.id)}
+
+ {po.status}
+
+
{po.storeName}
+
+
+
+
+ {!po.confirmedAt && po.status !== 'Cancelled' && (
+
+ )}
+ {po.status !== 'Cancelled' && po.status !== 'Fulfilled' && (
+
+ )}
+
+
+
+
+ router.push(`/sales/invoices/${po.invoiceId}`)}
+ >
+ {truncId(po.invoiceId)}
+
+ ) : (
+ 'Not raised yet'
+ )
+ }
+ />
+
+
+
+
+
+
+
+
Items
+
+ {activeItems.length} item{activeItems.length === 1 ? '' : 's'} on this PO.
+
+
+
+
+
+
+
+ | Item |
+ Quantity |
+ Fulfilled |
+ Unit Price |
+ Line Total |
+
+
+
+ {activeItems.length === 0 ? (
+
+ |
+ No items on this PO.
+ |
+
+ ) : (
+ activeItems.map((item) => (
+
+ | {item.itemName} |
+ {item.quantity} |
+ {item.quantityFulfilled} |
+ {fmtAmount(item.unitPrice)} |
+ {fmtAmount(item.quantity * item.unitPrice)} |
+
+ ))
+ )}
+
+ {activeItems.length > 0 && (
+
+
+ |
+ Total
+ |
+
+ {fmtAmount(po.totalAmount)}
+ |
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx b/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx
index bb02cd2..d5cc631 100644
--- a/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx
+++ b/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx
@@ -1,6 +1,7 @@
'use client'
import { useState, useEffect, useCallback, useMemo } from 'react'
+import { useRouter } from 'next/navigation'
import { HugeiconsIcon } from '@hugeicons/react'
import { Add01Icon } from '@hugeicons/core-free-icons'
@@ -8,12 +9,26 @@ import { DataTable } from '@/components/shared/data-table'
import type { TableConfig } from '@/components/shared/data-table'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select'
import { toast } from '@/components/ui/toast'
-import { shopPos as shopPosApi, getApiError, getPaginatedItems } from '@/lib/api/index'
-import type { ShopPOResponse, ShopPOStatus } from '@/types/api'
+import {
+ shopPos as shopPosApi,
+ users as usersApi,
+ routes as routesApi,
+ stores as storesApi,
+ getApiError,
+ getPaginatedItems,
+} from '@/lib/api/index'
+import type { ShopPOResponse, ShopPOStatus, UserResponse, RouteResponse, StoreResponse } from '@/types/api'
-import { ShopPOViewSheet } from './shop-po-view-sheet'
-import { ShopPOCreateSheet } from './shop-po-create-sheet'
+const ALL = '__all__'
+type ConfirmedFilter = 'all' | 'confirmed' | 'unconfirmed'
const fmtDate = (v: string) =>
new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
@@ -29,19 +44,47 @@ const PO_STATUS_VARIANT: Record([])
const [loading, setLoading] = useState(true)
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
- const [viewOpen, setViewOpen] = useState(false)
- const [createOpen, setCreateOpen] = useState(false)
- const [selectedPO, setSelectedPO] = useState(null)
+
+ const [agentsList, setAgentsList] = useState([])
+ const [routesList, setRoutesList] = useState([])
+ const [storesList, setStoresList] = useState([])
+
+ const [agentId, setAgentId] = useState('')
+ const [routeId, setRouteId] = useState('')
+ const [shopId, setShopId] = useState('')
+ const [confirmedFilter, setConfirmedFilter] = useState('all')
+
+ useEffect(() => {
+ Promise.all([
+ usersApi.list({ pageSize: 200 }),
+ routesApi.list({ pageSize: 200 }),
+ storesApi.list({ pageSize: 200 }),
+ ])
+ .then(([u, r, s]) => {
+ setAgentsList(getPaginatedItems(u))
+ setRoutesList(getPaginatedItems(r))
+ setStoresList(getPaginatedItems(s))
+ })
+ .catch((err) => toast.add({ title: 'Failed to load filter options', description: getApiError(err), type: 'error' }))
+ }, [])
const fetchData = useCallback(async (p: number, ps: number) => {
setLoading(true)
try {
- const res = await shopPosApi.list({ page: p, pageSize: ps })
+ const res = await shopPosApi.list({
+ page: p,
+ pageSize: ps,
+ agentId: agentId || undefined,
+ routeId: routeId || undefined,
+ shopId: shopId || undefined,
+ confirmed: confirmedFilter === 'all' ? undefined : confirmedFilter === 'confirmed',
+ })
setData(getPaginatedItems(res))
setTotal(res.total)
} catch (err) {
@@ -49,15 +92,23 @@ export function SalesClient() {
} finally {
setLoading(false)
}
- }, [])
+ }, [agentId, routeId, shopId, confirmedFilter])
useEffect(() => {
fetchData(page, pageSize)
}, [fetchData, page, pageSize])
+ const hasFilters = !!agentId || !!routeId || !!shopId || confirmedFilter !== 'all'
+
+ function clearFilters() {
+ setAgentId('')
+ setRouteId('')
+ setShopId('')
+ setConfirmedFilter('all')
+ }
+
function handleView(row: ShopPOResponse) {
- setSelectedPO(row)
- setViewOpen(true)
+ router.push(`/sales/${row.id}`)
}
async function handleConfirm(row: ShopPOResponse) {
@@ -170,12 +221,91 @@ export function SalesClient() {
Purchase orders raised by agents on behalf of stores
-