store page fixes
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user