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:
@@ -32,6 +32,7 @@ import {
|
|||||||
} from '../services/fiestaQueries';
|
} from '../services/fiestaQueries';
|
||||||
import { FIESTA_TENANT_ID, str as fstr, num as fnum } from '../services/fiestaApi';
|
import { FIESTA_TENANT_ID, str as fstr, num as fnum } from '../services/fiestaApi';
|
||||||
import StoreQRView from './StoreQRView';
|
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 }) {
|
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');
|
const [activeTab, setActiveTab] = useState<'tenant' | 'store' | 'rider'>(propActiveTab || 'tenant');
|
||||||
@@ -62,6 +63,10 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
|
|||||||
city: 'Coimbatore',
|
city: 'Coimbatore',
|
||||||
state: 'Tamil Nadu',
|
state: 'Tamil Nadu',
|
||||||
postcode: '',
|
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
|
// Primary outlet — created in the same call as the tenant, so a fresh
|
||||||
// tenant is never left without a location to actually operate from.
|
// tenant is never left without a location to actually operate from.
|
||||||
locationname: '',
|
locationname: '',
|
||||||
@@ -84,6 +89,10 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
|
|||||||
city: 'Coimbatore',
|
city: 'Coimbatore',
|
||||||
state: 'Tamil Nadu',
|
state: 'Tamil Nadu',
|
||||||
postcode: '',
|
postcode: '',
|
||||||
|
// Populated from AddressAutocomplete's geocoding lookup, not typed —
|
||||||
|
// keeps the branch address and coordinates from drifting apart.
|
||||||
|
latitude: '',
|
||||||
|
longitude: '',
|
||||||
contactno: '',
|
contactno: '',
|
||||||
email: '',
|
email: '',
|
||||||
opentime: '06:00:00',
|
opentime: '06:00:00',
|
||||||
@@ -128,6 +137,34 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
|
|||||||
setTimeout(() => setCopiedSql(false), 2000);
|
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
|
// Submissions
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
@@ -154,6 +191,8 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
|
|||||||
city: tenantForm.city,
|
city: tenantForm.city,
|
||||||
state: tenantForm.state,
|
state: tenantForm.state,
|
||||||
postcode: tenantForm.postcode,
|
postcode: tenantForm.postcode,
|
||||||
|
latitude: tenantForm.latitude,
|
||||||
|
longitude: tenantForm.longitude,
|
||||||
applocationid: tenantForm.applocationid,
|
applocationid: tenantForm.applocationid,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -353,13 +392,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
|||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
|
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
|
||||||
<input
|
<AddressAutocomplete value={tenantForm.address} onSelect={handleTenantAddressSelect} placeholder="e.g. 12, Avinashi Road" />
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
@@ -471,7 +504,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
type="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"
|
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
|
Add Another Branch
|
||||||
@@ -561,18 +594,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
|||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Store Branch Address</label>
|
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Store Branch Address</label>
|
||||||
<div className="relative rounded-xl">
|
<AddressAutocomplete value={storeForm.address} onSelect={handleStoreAddressSelect} placeholder="e.g. 240, DB Road" />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
<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">
|
<div className="space-y-1">
|
||||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
|
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
|
||||||
<input
|
<AddressAutocomplete value={tenantForm.address} onSelect={handleTenantAddressSelect} placeholder="e.g. 12, Avinashi Road" />
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
@@ -1218,7 +1234,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<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"
|
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
|
Add Another Branch
|
||||||
@@ -1281,13 +1297,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
|||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Store Branch Address</label>
|
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Store Branch Address</label>
|
||||||
<input
|
<AddressAutocomplete value={storeForm.address} onSelect={handleStoreAddressSelect} placeholder="e.g. 240, DB Road" />
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
|
|||||||
@@ -1037,6 +1037,8 @@ export interface CreateTenantLocationPayload {
|
|||||||
city?: string;
|
city?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
postcode?: string;
|
postcode?: string;
|
||||||
|
latitude?: string;
|
||||||
|
longitude?: string;
|
||||||
applocationid?: number;
|
applocationid?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1050,6 +1052,8 @@ export interface CreateTenantInput {
|
|||||||
city?: string;
|
city?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
postcode?: string;
|
postcode?: string;
|
||||||
|
latitude?: string;
|
||||||
|
longitude?: string;
|
||||||
approved?: number;
|
approved?: number;
|
||||||
status?: string;
|
status?: string;
|
||||||
applocationid?: number;
|
applocationid?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user