store page fixes
This commit is contained in:
@@ -29,7 +29,8 @@ const LocationPicker = dynamic(
|
|||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
storeName: z.string().min(1, 'Store name is required'),
|
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'),
|
storeAddress: z.string().min(1, 'Store address is required'),
|
||||||
routeId: z.string().min(1, 'Route is required'),
|
routeId: z.string().min(1, 'Route is required'),
|
||||||
latitude: z.number(),
|
latitude: z.number(),
|
||||||
@@ -55,7 +56,8 @@ function StoreEditForm({ store, routes, onSaved, onCancel }: StoreEditFormProps)
|
|||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
storeName: store.storeName,
|
storeName: store.storeName,
|
||||||
ownerName: store.ownerName,
|
ownerName: store.ownerName ?? '',
|
||||||
|
contactNumber: store.contactNumber ?? '',
|
||||||
storeAddress: store.storeAddress,
|
storeAddress: store.storeAddress,
|
||||||
routeId: store.routeId,
|
routeId: store.routeId,
|
||||||
latitude: store.latitude,
|
latitude: store.latitude,
|
||||||
@@ -74,6 +76,8 @@ function StoreEditForm({ store, routes, onSaved, onCancel }: StoreEditFormProps)
|
|||||||
await storesApi.update(store.id, {
|
await storesApi.update(store.id, {
|
||||||
...values,
|
...values,
|
||||||
segment: values.segment || undefined,
|
segment: values.segment || undefined,
|
||||||
|
ownerName: values.ownerName || null,
|
||||||
|
contactNumber: values.contactNumber || null,
|
||||||
})
|
})
|
||||||
toast.add({ title: 'Store updated', type: 'success' })
|
toast.add({ title: 'Store updated', type: 'success' })
|
||||||
onSaved()
|
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
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="storeAddress"
|
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
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="creditLimit"
|
name="creditLimit"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { toast } from '@/components/ui/toast'
|
import { toast } from '@/components/ui/toast'
|
||||||
import { stores as storesApi, routes as routesApi, getApiError, getPaginatedItems } from '@/lib/api/index'
|
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(
|
const LocationPicker = dynamic(
|
||||||
() => import('../_components/location-picker').then((m) => m.LocationPicker),
|
() => import('../_components/location-picker').then((m) => m.LocationPicker),
|
||||||
@@ -22,12 +22,6 @@ const fmtDate = (v: string) =>
|
|||||||
const fmtMoney = (v: number) =>
|
const fmtMoney = (v: number) =>
|
||||||
`Rs. ${v.toLocaleString('en-LK', { minimumFractionDigits: 2 })}`
|
`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 }) {
|
function DetailRow({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-0.5 py-3 border-b last:border-0">
|
<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'}>
|
<Badge variant={store.isActive ? 'default' : 'secondary'}>
|
||||||
{store.isActive ? 'Active' : 'Inactive'}
|
{store.isActive ? 'Active' : 'Inactive'}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant={kycVariant(store.kycStatus)}>{store.kycStatus}</Badge>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +129,8 @@ export default function StoreViewPage() {
|
|||||||
{/* Left — store details */}
|
{/* Left — store details */}
|
||||||
<div className="lg:col-span-1">
|
<div className="lg:col-span-1">
|
||||||
<div className="rounded-2xl border px-4">
|
<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="Address">{store.storeAddress}</DetailRow>
|
||||||
<DetailRow label="Route">
|
<DetailRow label="Route">
|
||||||
{routeName ?? <span className="font-mono text-xs">{store.routeId}</span>}
|
{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="Segment">{store.segment}</DetailRow>
|
||||||
)}
|
)}
|
||||||
<DetailRow label="Credit Limit">{fmtMoney(store.creditLimit)}</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>
|
<DetailRow label="Registered">{fmtDate(store.createdAt)}</DetailRow>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const LocationPicker = dynamic(
|
|||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
storeName: z.string().min(1, 'Store name is required'),
|
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'),
|
storeAddress: z.string().min(1, 'Store address is required'),
|
||||||
routeId: z.string().min(1, 'Route is required'),
|
routeId: z.string().min(1, 'Route is required'),
|
||||||
latitude: z.number(),
|
latitude: z.number(),
|
||||||
@@ -83,7 +83,7 @@ export function StoreSheet({ open, onClose, store, onSuccess }: StoreSheetProps)
|
|||||||
|
|
||||||
if (store) form.reset({
|
if (store) form.reset({
|
||||||
storeName: store.storeName,
|
storeName: store.storeName,
|
||||||
ownerName: store.ownerName,
|
ownerName: store.ownerName ?? '',
|
||||||
storeAddress: store.storeAddress,
|
storeAddress: store.storeAddress,
|
||||||
routeId: store.routeId,
|
routeId: store.routeId,
|
||||||
latitude: store.latitude,
|
latitude: store.latitude,
|
||||||
|
|||||||
@@ -11,17 +11,11 @@ import { Badge } from '@/components/ui/badge'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { toast } from '@/components/ui/toast'
|
import { toast } from '@/components/ui/toast'
|
||||||
import { stores as storesApi, routes as routesApi, getApiError, getPaginatedItems } from '@/lib/api/index'
|
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 { StoreSheet } from './store-sheet'
|
||||||
import { StoreVisitHistorySheet } from './store-visit-history-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() {
|
export function StoresClient() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [data, setData] = useState<StoreResponse[]>([])
|
const [data, setData] = useState<StoreResponse[]>([])
|
||||||
@@ -120,17 +114,6 @@ export function StoresClient() {
|
|||||||
<span className="text-sm">{routeNameById.get(row.routeId) ?? row.routeId}</span>
|
<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',
|
key: 'status',
|
||||||
title: 'Status',
|
title: 'Status',
|
||||||
@@ -155,11 +138,6 @@ export function StoresClient() {
|
|||||||
label: 'Edit store',
|
label: 'Edit store',
|
||||||
onClick: (store) => router.push(`/stores/${store.id}/edit`),
|
onClick: (store) => router.push(`/stores/${store.id}/edit`),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
action: 'confirm',
|
|
||||||
label: 'View visit history',
|
|
||||||
onClick: openHistory,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
action: 'deactivate',
|
action: 'deactivate',
|
||||||
label: 'Deactivate store',
|
label: 'Deactivate store',
|
||||||
@@ -182,7 +160,7 @@ export function StoresClient() {
|
|||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-semibold text-foreground">Stores</h1>
|
<h1 className="text-2xl font-semibold text-foreground">Stores</h1>
|
||||||
<p className="text-sm text-muted-foreground mt-0.5">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => router.push('/stores/new')}>
|
<Button onClick={() => router.push('/stores/new')}>
|
||||||
@@ -195,6 +173,7 @@ export function StoresClient() {
|
|||||||
config={tableConfig}
|
config={tableConfig}
|
||||||
data={data}
|
data={data}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
onRowClick={openHistory}
|
||||||
totalCount={total}
|
totalCount={total}
|
||||||
onServerSideChange={({ page: p }) => {
|
onServerSideChange={({ page: p }) => {
|
||||||
setPage(p.page)
|
setPage(p.page)
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ const LocationPicker = dynamic(
|
|||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
storeName: z.string().min(1, 'Store name is required'),
|
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'),
|
storeAddress: z.string().min(1, 'Store address is required'),
|
||||||
routeId: z.string().min(1, 'Route is required'),
|
routeId: z.string().min(1, 'Route is required'),
|
||||||
latitude: z.number(),
|
latitude: z.number(),
|
||||||
@@ -50,6 +51,7 @@ export default function NewStorePage() {
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
storeName: '',
|
storeName: '',
|
||||||
ownerName: '',
|
ownerName: '',
|
||||||
|
contactNumber: '',
|
||||||
storeAddress: '',
|
storeAddress: '',
|
||||||
routeId: '',
|
routeId: '',
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
@@ -76,6 +78,8 @@ export default function NewStorePage() {
|
|||||||
await storesApi.create({
|
await storesApi.create({
|
||||||
...values,
|
...values,
|
||||||
segment: values.segment || undefined,
|
segment: values.segment || undefined,
|
||||||
|
ownerName: values.ownerName || null,
|
||||||
|
contactNumber: values.contactNumber || null,
|
||||||
})
|
})
|
||||||
toast.add({ title: 'Store created', type: 'success' })
|
toast.add({ title: 'Store created', type: 'success' })
|
||||||
router.push('/stores')
|
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
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="storeAddress"
|
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
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="creditLimit"
|
name="creditLimit"
|
||||||
|
|||||||
Reference in New Issue
Block a user