updates on the admin and zone things and minor changes

This commit is contained in:
2026-10-06 19:21:06 +05:30
parent 6e73b59ca9
commit 7aca6a6a48
14 changed files with 589 additions and 90 deletions

View File

@@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ArrowLeft, Building2, Copy, Eye, EyeOff, KeyRound, Pencil, ShieldCheck, Trash2, UserPlus } from 'lucide-react';
import { ArrowLeft, Building2, Copy, Eye, EyeOff, KeyRound, MapPin, Pencil, ShieldCheck, Trash2, UserPlus } from 'lucide-react';
import {
Alert, Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage,
BreadcrumbSeparator, Button, DataTable, EmptyState, Field, Input, PageHeader, Stack,
@@ -9,9 +9,11 @@ import {
import { inputVariants } from '@/components/ui/input';
import { useAuth } from '@/lib/AuthContext';
import { useOnboardClient, useOnboardedClients, useOnboardingCities } from '@/lib/doormileHooks';
import AddressAutocomplete from '@/components/doormile/AddressAutocomplete';
import { EditClientModal, RemoveClientModal } from './ClientLoginDialogs';
import {
canOnboardClients, generateClientPassword, onboardingUnavailableMessage, validateOnboarding,
addressFromPlace, addressPayload, canOnboardClients, generateClientPassword, hasMapLocation,
onboardingUnavailableMessage, validateOnboarding,
} from '@/lib/clientOnboarding';
/**
@@ -35,6 +37,14 @@ const EMPTY = {
password: '',
confirm: '',
requiredeliveryotp: false,
// Main address, saved as the client's primary location. Coordinates come
// only from picking a suggestion.
address: '',
city: '',
state: '',
pincode: '',
latitude: '',
longitude: '',
};
const SERVER_FIELD = [
@@ -44,6 +54,9 @@ const SERVER_FIELD = [
['mobile', 'phone'],
['password', 'password'],
['operating city', 'applocationid'],
['pincode', 'pincode'],
['address', 'address'],
['map location', 'address'],
];
/** Put a server message under the field it is about, when it names one. */
@@ -65,6 +78,11 @@ function OnboardedList() {
{ key: 'tenantname', header: 'Client', accessor: (r) => r.tenantname },
{ key: 'loginemail', header: 'Console login', accessor: (r) => r.loginemail },
{ key: 'primarycontact', header: 'Contact', accessor: (r) => r.primarycontact || '—' },
{
key: 'address',
header: 'Address',
accessor: (r) => (r.address ? [r.address, r.pincode].filter(Boolean).join(' · ') : '—'),
},
{ key: 'status', header: 'Status', cell: (r) => <StatusBadge status={r.status} size="sm" /> },
{
key: 'logincreatedat',
@@ -181,6 +199,7 @@ export default function ClientOnboarding() {
password,
applocationid: Number(form.applocationid),
requiredeliveryotp: form.requiredeliveryotp,
...addressPayload(form),
},
{
onSuccess: (res) => {
@@ -286,7 +305,9 @@ export default function ClientOnboarding() {
role="alert"
title="Client onboarding is not available right now"
action={
<Button size="sm" variant="outline" onClick={() => citiesQ.refetch()}>
// type="button": this sits inside the form, and a plain button
// would also submit it, flashing every field's error.
<Button type="button" size="sm" variant="outline" onClick={() => citiesQ.refetch()}>
Try again
</Button>
}
@@ -356,6 +377,47 @@ export default function ClientOnboarding() {
</select>
</Field>
<div className="sm:col-span-2">
<AddressAutocomplete
id="onboard-address"
label="Client address"
required
placeholder="Search the client's building, street or area…"
value={form.address}
onChange={(text) => set('address')(text)}
onPlaceSelected={(place) => {
setForm((f) => ({ ...f, ...addressFromPlace(place) }));
setErrors((e) => ({ ...e, address: undefined, pincode: undefined }));
}}
onPlaceCleared={() => setForm((f) => ({ ...f, latitude: '', longitude: '' }))}
/>
{errors.address ? (
<p className="mt-1 text-caption text-destructive" role="alert">
{errors.address}
</p>
) : (
<p className="mt-1 flex items-center gap-1 text-caption text-ink-3">
<MapPin className="h-3.5 w-3.5" />
{hasMapLocation(form)
? "Location pinned. This becomes the client's pickup point and zone."
: 'Pick a suggestion so the address gets a map location.'}
</p>
)}
</div>
<Field label="Pincode" required error={errors.pincode}>
<Input
value={form.pincode}
onChange={(e) => set('pincode')(e.target.value.replace(/\D/g, '').slice(0, 6))}
placeholder="641002"
inputMode="numeric"
/>
</Field>
<Field label="City">
<Input value={form.city} onChange={(e) => set('city')(e.target.value)} placeholder="Coimbatore" maxLength={80} />
</Field>
<Field label="Password" required error={errors.password} hint="At least 8 characters" htmlFor="onboard-password">
<Input
id="onboard-password"