Auto-geocode tenant/store address into latitude/longitude on onboarding

Both the tenant onboarding and store/branch forms only had free-text
address fields with no coordinates captured, even though the backend
already accepts and stores latitude/longitude on both tenants and
tenantlocations. Wires in the existing keyless AddressAutocomplete
(Nominatim) component used by UsersPanel so picking an address also
geocodes it, and threads latitude/longitude through both create payloads.
This commit is contained in:
2026-07-27 11:34:35 +05:30
parent 02b5258000
commit 6a6221f9f3
2 changed files with 49 additions and 35 deletions

View File

@@ -32,6 +32,7 @@ import {
} from '../services/fiestaQueries';
import { FIESTA_TENANT_ID, str as fstr, num as fnum } from '../services/fiestaApi';
import StoreQRView from './StoreQRView';
import AddressAutocomplete, { type AddressResult } from './AddressAutocomplete';
export default function AdminConsole({ activeTab: propActiveTab, showHeader = true, onBack, tenantId }: { activeTab?: 'tenant' | 'store' | 'rider', showHeader?: boolean, onBack?: () => void, tenantId?: number }) {
const [activeTab, setActiveTab] = useState<'tenant' | 'store' | 'rider'>(propActiveTab || 'tenant');
@@ -62,6 +63,10 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
city: 'Coimbatore',
state: 'Tamil Nadu',
postcode: '',
// Populated from AddressAutocomplete's geocoding lookup, not typed —
// keeps HQ address and coordinates from drifting apart.
latitude: '',
longitude: '',
// Primary outlet — created in the same call as the tenant, so a fresh
// tenant is never left without a location to actually operate from.
locationname: '',
@@ -84,6 +89,10 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
city: 'Coimbatore',
state: 'Tamil Nadu',
postcode: '',
// Populated from AddressAutocomplete's geocoding lookup, not typed —
// keeps the branch address and coordinates from drifting apart.
latitude: '',
longitude: '',
contactno: '',
email: '',
opentime: '06:00:00',
@@ -128,6 +137,34 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
setTimeout(() => setCopiedSql(false), 2000);
};
// Address autocomplete → discrete fields + geocoded lat/long (or clear them
// when the field is emptied), same pattern UsersPanel uses for team members.
const handleTenantAddressSelect = (r: AddressResult | null) => {
setTenantForm((f) => ({
...f,
address: r?.address ?? '',
suburb: r?.suburb ?? '',
city: r?.city ?? '',
state: r?.state ?? '',
postcode: r?.postcode ?? '',
latitude: r?.latitude ?? '',
longitude: r?.longitude ?? '',
}));
};
const handleStoreAddressSelect = (r: AddressResult | null) => {
setStoreForm((f) => ({
...f,
address: r?.address ?? '',
suburb: r?.suburb ?? '',
city: r?.city ?? '',
state: r?.state ?? '',
postcode: r?.postcode ?? '',
latitude: r?.latitude ?? '',
longitude: r?.longitude ?? '',
}));
};
// ----------------------------------------------------
// Submissions
// ----------------------------------------------------
@@ -154,6 +191,8 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
city: tenantForm.city,
state: tenantForm.state,
postcode: tenantForm.postcode,
latitude: tenantForm.latitude,
longitude: tenantForm.longitude,
applocationid: tenantForm.applocationid,
},
});
@@ -353,13 +392,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
<input
type="text"
placeholder="e.g. 12, Avinashi Road"
value={tenantForm.address}
onChange={(e) => setTenantForm({ ...tenantForm, address: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
<AddressAutocomplete value={tenantForm.address} onSelect={handleTenantAddressSelect} placeholder="e.g. 12, Avinashi Road" />
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
@@ -471,7 +504,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
)}
<button
type="button"
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', latitude: '', longitude: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg border-none cursor-pointer active:scale-95 transition-all shadow-sm"
>
Add Another Branch
@@ -561,18 +594,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
</h4>
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Store Branch Address</label>
<div className="relative rounded-xl">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<MapPin size={14} />
</div>
<input
type="text"
placeholder="e.g. 240, DB Road"
value={storeForm.address}
onChange={(e) => setStoreForm({ ...storeForm, address: e.target.value })}
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
/>
</div>
<AddressAutocomplete value={storeForm.address} onSelect={handleStoreAddressSelect} placeholder="e.g. 240, DB Road" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
@@ -1107,13 +1129,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
<input
type="text"
placeholder="e.g. 12, Avinashi Road"
value={tenantForm.address}
onChange={(e) => setTenantForm({ ...tenantForm, address: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
<AddressAutocomplete value={tenantForm.address} onSelect={handleTenantAddressSelect} placeholder="e.g. 12, Avinashi Road" />
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
@@ -1218,7 +1234,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
</button>
)}
<button
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', latitude: '', longitude: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg border-none cursor-pointer"
>
Add Another Branch
@@ -1281,13 +1297,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
<div className="space-y-1">
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Store Branch Address</label>
<input
type="text"
placeholder="e.g. 240, DB Road"
value={storeForm.address}
onChange={(e) => setStoreForm({ ...storeForm, address: e.target.value })}
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
/>
<AddressAutocomplete value={storeForm.address} onSelect={handleStoreAddressSelect} placeholder="e.g. 240, DB Road" />
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">

View File

@@ -1037,6 +1037,8 @@ export interface CreateTenantLocationPayload {
city?: string;
state?: string;
postcode?: string;
latitude?: string;
longitude?: string;
applocationid?: number;
}
@@ -1050,6 +1052,8 @@ export interface CreateTenantInput {
city?: string;
state?: string;
postcode?: string;
latitude?: string;
longitude?: string;
approved?: number;
status?: string;
applocationid?: number;