From 5e48873422b24978d0cf1a8ee986c071f5a991e0 Mon Sep 17 00:00:00 2001
From: DeepnaPooja
Date: Thu, 3 Sep 2026 16:08:34 +0530
Subject: [PATCH] Update sales UI
---
.../sales/_components/sale-sheet.tsx | 354 ---------------
.../sales/_components/sale-view-sheet.tsx | 111 +++++
.../sales/_components/sales-client.tsx | 47 +-
.../app/(dashboard)/sales/new/page.tsx | 412 ++++++++++++++++++
distribution_fe/lib/api/sales.ts | 3 +
5 files changed, 566 insertions(+), 361 deletions(-)
delete mode 100644 distribution_fe/app/(dashboard)/sales/_components/sale-sheet.tsx
create mode 100644 distribution_fe/app/(dashboard)/sales/_components/sale-view-sheet.tsx
create mode 100644 distribution_fe/app/(dashboard)/sales/new/page.tsx
diff --git a/distribution_fe/app/(dashboard)/sales/_components/sale-sheet.tsx b/distribution_fe/app/(dashboard)/sales/_components/sale-sheet.tsx
deleted file mode 100644
index c696c20..0000000
--- a/distribution_fe/app/(dashboard)/sales/_components/sale-sheet.tsx
+++ /dev/null
@@ -1,354 +0,0 @@
-'use client'
-
-import { useEffect, useMemo, useState } from 'react'
-import { useFieldArray } from 'react-hook-form'
-import { useForm, zodResolver, z } from '@/lib/form'
-import { FormField } from '@/components/shared/form-field'
-import { Input } from '@/components/ui/input'
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select'
-import { Checkbox } from '@/components/ui/checkbox'
-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 {
- sales as salesApi,
- stores as storesApi,
- rides as ridesApi,
- items as itemsApi,
- getApiError,
- getPaginatedItems,
-} from '@/lib/api/index'
-import type { ItemSummaryResponse, RideResponse, StoreResponse } from '@/types/api'
-
-const saleItemSchema = z.object({
- itemId: z.string().min(1, 'Item is required'),
- quantity: z.number().min(1, 'Quantity must be at least 1'),
- unitPrice: z.number().min(0.01, 'Unit price must be greater than 0'),
-})
-
-const schema = z.object({
- storeId: z.string().min(1, 'Store is required'),
- rideId: z.string(),
- fulfillImmediately: z.boolean(),
- items: z.array(saleItemSchema).min(1, 'At least one item is required'),
-})
-
-type FormValues = z.infer
-
-interface SaleSheetProps {
- open: boolean
- onClose: () => void
- onSuccess: () => void
-}
-
-
-function createDefaultItem() {
- return {
- itemId: '',
- quantity: 1,
- unitPrice: 0,
- }
-}
-
-export function SaleSheet({ open, onClose, onSuccess }: SaleSheetProps) {
- const [storesList, setStoresList] = useState([])
- const [ridesList, setRidesList] = useState([])
- const [itemsList, setItemsList] = useState([])
-
- const form = useForm({
- resolver: zodResolver(schema),
- defaultValues: {
- storeId: '',
- rideId: '',
- fulfillImmediately: false,
- items: [createDefaultItem()],
- },
- })
-
- const { fields, append, remove } = useFieldArray({
- control: form.control,
- name: 'items',
- })
-
- const watchedItems = form.watch('items')
- const itemsById = useMemo(
- () => new Map(itemsList.map((item) => [item.id, item])),
- [itemsList]
- )
- const totalAmount = useMemo(
- () => watchedItems.reduce((sum, item) => sum + (Number(item.quantity) || 0) * (Number(item.unitPrice) || 0), 0),
- [watchedItems]
- )
-
- useEffect(() => {
- if (!open) return
-
- form.reset({
- storeId: '',
- rideId: '',
- fulfillImmediately: false,
- items: [createDefaultItem()],
- })
-
- Promise.all([
- storesApi.list({ pageSize: 200 }),
- ridesApi.list({ pageSize: 200, rideType: 'Sale' }),
- itemsApi.list({ pageSize: 200 }),
- ])
- .then(([stores, rides, items]) => {
- setStoresList(getPaginatedItems(stores))
- setRidesList(getPaginatedItems(rides))
- setItemsList(getPaginatedItems(items))
- })
- .catch((err) => {
- toast.add({ title: 'Failed to load reference data', description: getApiError(err), type: 'error' })
- })
- }, [open])
-
- async function onSubmit(values: FormValues) {
- try {
- await salesApi.create({
- storeId: values.storeId,
- rideId: values.rideId || null,
- fulfillImmediately: values.fulfillImmediately,
- items: values.items,
- })
- toast.add({ title: 'Sale created', type: 'success' })
- onSuccess()
- onClose()
- } catch (err) {
- toast.add({ title: 'Failed to create sale', description: getApiError(err), type: 'error' })
- }
- }
-
- return (
-
- )
-}
diff --git a/distribution_fe/app/(dashboard)/sales/_components/sale-view-sheet.tsx b/distribution_fe/app/(dashboard)/sales/_components/sale-view-sheet.tsx
new file mode 100644
index 0000000..4f028ad
--- /dev/null
+++ b/distribution_fe/app/(dashboard)/sales/_components/sale-view-sheet.tsx
@@ -0,0 +1,111 @@
+'use client'
+
+import { Badge } from '@/components/ui/badge'
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Separator } from '@/components/ui/separator'
+import type { SaleResponse, SaleStatus, PaymentStatus } from '@/types/api'
+
+const SALE_STATUS_VARIANT: Record = {
+ Pending: 'secondary',
+ Completed: 'default',
+ Cancelled: 'destructive',
+}
+
+const PAYMENT_STATUS_VARIANT: Record = {
+ Pending: 'secondary',
+ PartiallyPaid: 'secondary',
+ Paid: 'default',
+}
+
+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',
+ })
+ : '—'
+
+interface SaleViewSheetProps {
+ open: boolean
+ onClose: () => void
+ sale: SaleResponse | null
+}
+
+function Row({ label, value }: { label: string; value: React.ReactNode }) {
+ return (
+
+ {label}
+ {value}
+
+ )
+}
+
+export function SaleViewSheet({ open, onClose, sale }: SaleViewSheetProps) {
+ if (!sale) return null
+
+ return (
+
+ )
+}
diff --git a/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx b/distribution_fe/app/(dashboard)/sales/_components/sales-client.tsx
index 370cf08..4f9c997 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'
@@ -12,7 +13,7 @@ import { toast } from '@/components/ui/toast'
import { sales as salesApi, getApiError, getPaginatedItems } from '@/lib/api/index'
import type { SaleResponse, SaleStatus, PaymentStatus } from '@/types/api'
-import { SaleSheet } from './sale-sheet'
+import { SaleViewSheet } from './sale-view-sheet'
const fmtDate = (v: string) =>
new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
@@ -33,12 +34,14 @@ const PAYMENT_STATUS_VARIANT: Record([])
const [loading, setLoading] = useState(true)
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
- const [sheetOpen, setSheetOpen] = useState(false)
+ const [viewOpen, setViewOpen] = useState(false)
+ const [selectedSale, setSelectedSale] = useState(null)
const fetchData = useCallback(async (p: number, ps: number) => {
setLoading(true)
@@ -57,6 +60,21 @@ export function SalesClient() {
fetchData(page, pageSize)
}, [fetchData, page, pageSize])
+ function handleView(row: SaleResponse) {
+ setSelectedSale(row)
+ setViewOpen(true)
+ }
+
+ async function handleCancel(row: SaleResponse) {
+ try {
+ await salesApi.cancel(row.id)
+ toast.add({ title: 'Sale cancelled', type: 'success' })
+ fetchData(page, pageSize)
+ } catch (err) {
+ toast.add({ title: 'Failed to cancel sale', description: getApiError(err), type: 'error' })
+ }
+ }
+
const tableConfig = useMemo>(() => ({
rowKey: 'id',
serverSide: true,
@@ -122,6 +140,21 @@ export function SalesClient() {
width: 140,
},
],
+ rowActions: {
+ actions: () => [
+ {
+ action: 'view',
+ label: 'View sale',
+ onClick: handleView,
+ },
+ {
+ action: 'cancel',
+ label: 'Cancel sale',
+ onClick: handleCancel,
+ visible: (target) => target.status !== 'Cancelled',
+ },
+ ],
+ },
}), [pageSize])
return (
@@ -133,7 +166,7 @@ export function SalesClient() {
Sales transactions recorded against stores
-