Dev #44

Merged
DeepnaPooja merged 8 commits from dev into production 2026-09-24 17:30:43 +05:30
5 changed files with 93 additions and 79 deletions
Showing only changes of commit 41f992b824 - Show all commits
@@ -29,7 +29,8 @@ const LocationPicker = dynamic(
const schema = z.object({
storeName: z.string().min(1, 'Store name is required'),
ownerName: z.string().min(1, 'Owner name is required'),
ownerName: z.string().optional(),
contactNumber: z.string().optional(),
storeAddress: z.string().min(1, 'Store address is required'),
routeId: z.string().min(1, 'Route is required'),
latitude: z.number(),
@@ -55,7 +56,8 @@ function StoreEditForm({ store, routes, onSaved, onCancel }: StoreEditFormProps)
resolver: zodResolver(schema),
defaultValues: {
storeName: store.storeName,
ownerName: store.ownerName,
ownerName: store.ownerName ?? '',
contactNumber: store.contactNumber ?? '',
storeAddress: store.storeAddress,
routeId: store.routeId,
latitude: store.latitude,
@@ -74,6 +76,8 @@ function StoreEditForm({ store, routes, onSaved, onCancel }: StoreEditFormProps)
await storesApi.update(store.id, {
...values,
segment: values.segment || undefined,
ownerName: values.ownerName || null,
contactNumber: values.contactNumber || null,
})
toast.add({ title: 'Store updated', type: 'success' })
onSaved()
@@ -108,25 +112,6 @@ function StoreEditForm({ store, routes, onSaved, onCancel }: StoreEditFormProps)
)}
/>
<FormField
control={form.control}
name="ownerName"
label="Owner Name"
required
render={({ field, invalid }) => (
<Input
id="ownerName"
name={field.name}
ref={field.ref}
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="e.g. Kamal Perera"
aria-invalid={invalid}
/>
)}
/>
<FormField
control={form.control}
name="storeAddress"
@@ -167,6 +152,43 @@ function StoreEditForm({ store, routes, onSaved, onCancel }: StoreEditFormProps)
)}
/>
<FormField
control={form.control}
name="ownerName"
label="Owner Name"
render={({ field, invalid }) => (
<Input
id="ownerName"
name={field.name}
ref={field.ref}
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="e.g. Kamal Perera"
aria-invalid={invalid}
/>
)}
/>
<FormField
control={form.control}
name="contactNumber"
label="Contact Number"
render={({ field, invalid }) => (
<Input
id="contactNumber"
name={field.name}
ref={field.ref}
type="tel"
value={(field.value as string) ?? ""}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="e.g. 0771234567"
aria-invalid={invalid}
/>
)}
/>
<FormField
control={form.control}
name="creditLimit"
@@ -9,7 +9,7 @@ import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { toast } from '@/components/ui/toast'
import { stores as storesApi, routes as routesApi, getApiError, getPaginatedItems } from '@/lib/api/index'
import type { StoreResponse, KycStatus, RouteResponse } from '@/types/api'
import type { StoreResponse, RouteResponse } from '@/types/api'
const LocationPicker = dynamic(
() => import('../_components/location-picker').then((m) => m.LocationPicker),
@@ -22,12 +22,6 @@ const fmtDate = (v: string) =>
const fmtMoney = (v: number) =>
`Rs. ${v.toLocaleString('en-LK', { minimumFractionDigits: 2 })}`
function kycVariant(status: KycStatus): 'default' | 'secondary' | 'destructive' {
if (status === 'Verified') return 'default'
if (status === 'Rejected') return 'destructive'
return 'secondary'
}
function DetailRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="flex flex-col gap-0.5 py-3 border-b last:border-0">
@@ -95,7 +89,6 @@ export default function StoreViewPage() {
<Badge variant={store.isActive ? 'default' : 'secondary'}>
{store.isActive ? 'Active' : 'Inactive'}
</Badge>
<Badge variant={kycVariant(store.kycStatus)}>{store.kycStatus}</Badge>
</>
)}
</div>
@@ -136,7 +129,8 @@ export default function StoreViewPage() {
{/* Left — store details */}
<div className="lg:col-span-1">
<div className="rounded-2xl border px-4">
<DetailRow label="Owner">{store.ownerName}</DetailRow>
<DetailRow label="Owner">{store.ownerName ?? "—"}</DetailRow>
<DetailRow label="Contact">{store.contactNumber ?? "—"}</DetailRow>
<DetailRow label="Address">{store.storeAddress}</DetailRow>
<DetailRow label="Route">
{routeName ?? <span className="font-mono text-xs">{store.routeId}</span>}
@@ -145,9 +139,6 @@ export default function StoreViewPage() {
<DetailRow label="Segment">{store.segment}</DetailRow>
)}
<DetailRow label="Credit Limit">{fmtMoney(store.creditLimit)}</DetailRow>
<DetailRow label="KYC Status">
<Badge variant={kycVariant(store.kycStatus)}>{store.kycStatus}</Badge>
</DetailRow>
<DetailRow label="Registered">{fmtDate(store.createdAt)}</DetailRow>
</div>
</div>
@@ -35,7 +35,7 @@ const LocationPicker = dynamic(
const schema = z.object({
storeName: z.string().min(1, 'Store name is required'),
ownerName: z.string().min(1, 'Owner name is required'),
ownerName: z.string().optional(),
storeAddress: z.string().min(1, 'Store address is required'),
routeId: z.string().min(1, 'Route is required'),
latitude: z.number(),
@@ -83,7 +83,7 @@ export function StoreSheet({ open, onClose, store, onSuccess }: StoreSheetProps)
if (store) form.reset({
storeName: store.storeName,
ownerName: store.ownerName,
ownerName: store.ownerName ?? '',
storeAddress: store.storeAddress,
routeId: store.routeId,
latitude: store.latitude,
@@ -11,17 +11,11 @@ import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/toast'
import { stores as storesApi, routes as routesApi, getApiError, getPaginatedItems } from '@/lib/api/index'
import type { StoreResponse, KycStatus, RouteResponse } from '@/types/api'
import type { StoreResponse, RouteResponse } from '@/types/api'
import { StoreSheet } from './store-sheet'
import { StoreVisitHistorySheet } from './store-visit-history-sheet'
function kycBadgeVariant(status: KycStatus): 'default' | 'secondary' | 'destructive' {
if (status === 'Verified') return 'default'
if (status === 'Rejected') return 'destructive'
return 'secondary'
}
export function StoresClient() {
const router = useRouter()
const [data, setData] = useState<StoreResponse[]>([])
@@ -120,17 +114,6 @@ export function StoresClient() {
<span className="text-sm">{routeNameById.get(row.routeId) ?? row.routeId}</span>
),
},
{
key: 'kycStatus',
title: 'KYC',
fields: ['kycStatus'],
width: 130,
cellRender: (row) => (
<Badge variant={kycBadgeVariant(row.kycStatus)}>
{row.kycStatus}
</Badge>
),
},
{
key: 'status',
title: 'Status',
@@ -155,11 +138,6 @@ export function StoresClient() {
label: 'Edit store',
onClick: (store) => router.push(`/stores/${store.id}/edit`),
},
{
action: 'confirm',
label: 'View visit history',
onClick: openHistory,
},
{
action: 'deactivate',
label: 'Deactivate store',
@@ -182,7 +160,7 @@ export function StoresClient() {
<div>
<h1 className="text-2xl font-semibold text-foreground">Stores</h1>
<p className="text-sm text-muted-foreground mt-0.5">
Retail store accounts, credit limits, and KYC status
Retail store accounts, credit limits and balances
</p>
</div>
<Button onClick={() => router.push('/stores/new')}>
@@ -195,6 +173,7 @@ export function StoresClient() {
config={tableConfig}
data={data}
loading={loading}
onRowClick={openHistory}
totalCount={total}
onServerSideChange={({ page: p }) => {
setPage(p.page)
@@ -29,7 +29,8 @@ const LocationPicker = dynamic(
const schema = z.object({
storeName: z.string().min(1, 'Store name is required'),
ownerName: z.string().min(1, 'Owner name is required'),
ownerName: z.string().optional(),
contactNumber: z.string().optional(),
storeAddress: z.string().min(1, 'Store address is required'),
routeId: z.string().min(1, 'Route is required'),
latitude: z.number(),
@@ -50,6 +51,7 @@ export default function NewStorePage() {
defaultValues: {
storeName: '',
ownerName: '',
contactNumber: '',
storeAddress: '',
routeId: '',
latitude: 0,
@@ -76,6 +78,8 @@ export default function NewStorePage() {
await storesApi.create({
...values,
segment: values.segment || undefined,
ownerName: values.ownerName || null,
contactNumber: values.contactNumber || null,
})
toast.add({ title: 'Store created', type: 'success' })
router.push('/stores')
@@ -129,25 +133,6 @@ export default function NewStorePage() {
)}
/>
<FormField
control={form.control}
name="ownerName"
label="Owner Name"
required
render={({ field, invalid }) => (
<Input
id="ownerName"
name={field.name}
ref={field.ref}
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="e.g. Kamal Perera"
aria-invalid={invalid}
/>
)}
/>
<FormField
control={form.control}
name="storeAddress"
@@ -188,6 +173,43 @@ export default function NewStorePage() {
)}
/>
<FormField
control={form.control}
name="ownerName"
label="Owner Name"
render={({ field, invalid }) => (
<Input
id="ownerName"
name={field.name}
ref={field.ref}
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="e.g. Kamal Perera"
aria-invalid={invalid}
/>
)}
/>
<FormField
control={form.control}
name="contactNumber"
label="Contact Number"
render={({ field, invalid }) => (
<Input
id="contactNumber"
name={field.name}
ref={field.ref}
type="tel"
value={(field.value as string) ?? ""}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="e.g. 0771234567"
aria-invalid={invalid}
/>
)}
/>
<FormField
control={form.control}
name="creditLimit"