updates on the zone selector for the specific login
This commit is contained in:
197
src/lib/ZoneContext.jsx
Normal file
197
src/lib/ZoneContext.jsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useHubs, useTenantLocations } from '@/lib/doormileHooks';
|
||||
import { useAuth } from '@/lib/AuthContext';
|
||||
|
||||
const ZONE_STORAGE_KEY = 'doormile_active_zone_id';
|
||||
|
||||
const ALL_ZONE = Object.freeze({
|
||||
hubid: 'all',
|
||||
id: 'all',
|
||||
hubname: 'All Zones',
|
||||
city: 'All Cities',
|
||||
hubtype: 'all',
|
||||
status: 'Active',
|
||||
});
|
||||
|
||||
const haversineKm = (lat1, lon1, lat2, lon2) => {
|
||||
if (![lat1, lon1, lat2, lon2].every((v) => Number.isFinite(Number(v)))) return undefined;
|
||||
const R = 6371;
|
||||
const toRad = (deg) => (Number(deg) * Math.PI) / 180;
|
||||
const dLat = toRad(lat2 - lat1);
|
||||
const dLon = toRad(lon2 - lon1);
|
||||
const a =
|
||||
Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
|
||||
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
};
|
||||
|
||||
const ZoneContext = createContext(null);
|
||||
|
||||
export function ZoneProvider({ children }) {
|
||||
const { user } = useAuth();
|
||||
const tenantId = user?.tenantid || localStorage.getItem('tenantid') || '';
|
||||
const isTenantUser = Boolean(tenantId && tenantId !== '0');
|
||||
|
||||
// Staff loads global hubs; Tenant loads tenant locations / kitchen hubs
|
||||
const { data: rawHubs = [], isLoading: isHubsLoading, refetch: refetchHubs } = useHubs({
|
||||
enabled: !isTenantUser,
|
||||
});
|
||||
const { data: tenantLocations = [], isLoading: isLocationsLoading, refetch: refetchLocations } = useTenantLocations(
|
||||
isTenantUser ? tenantId : null,
|
||||
{ enabled: isTenantUser }
|
||||
);
|
||||
|
||||
const [selectedZoneId, setSelectedZoneIdState] = useState(() => {
|
||||
try {
|
||||
return localStorage.getItem(ZONE_STORAGE_KEY) || 'all';
|
||||
} catch {
|
||||
return 'all';
|
||||
}
|
||||
});
|
||||
|
||||
const activeHubs = useMemo(() => {
|
||||
if (isTenantUser) {
|
||||
return (tenantLocations || []).map((loc) => ({
|
||||
hubid: String(loc.tenantlocationid || loc.locationid || loc.id),
|
||||
id: String(loc.tenantlocationid || loc.locationid || loc.id),
|
||||
hubname: loc.locationname || loc.name || `Kitchen Hub #${loc.tenantlocationid || loc.locationid}`,
|
||||
city: loc.city || '',
|
||||
address: loc.address || '',
|
||||
latitude: loc.latitude,
|
||||
longitude: loc.longitude,
|
||||
pincode: loc.pincode || '',
|
||||
hubtype: 'kitchen_hub',
|
||||
isTenantLocation: true,
|
||||
status: 'Active',
|
||||
}));
|
||||
}
|
||||
|
||||
return (rawHubs || []).filter(
|
||||
(hub) => String(hub.status || 'Active').toLowerCase() === 'active'
|
||||
);
|
||||
}, [isTenantUser, tenantLocations, rawHubs]);
|
||||
|
||||
const zones = useMemo(() => {
|
||||
return [ALL_ZONE, ...activeHubs];
|
||||
}, [activeHubs]);
|
||||
|
||||
const selectedZone = useMemo(() => {
|
||||
if (selectedZoneId === 'all') return ALL_ZONE;
|
||||
const found = activeHubs.find((h) => String(h.hubid) === String(selectedZoneId));
|
||||
return found || ALL_ZONE;
|
||||
}, [selectedZoneId, activeHubs]);
|
||||
|
||||
const setSelectedZoneId = (id) => {
|
||||
const nextId = String(id || 'all');
|
||||
setSelectedZoneIdState(nextId);
|
||||
try {
|
||||
localStorage.setItem(ZONE_STORAGE_KEY, nextId);
|
||||
} catch {
|
||||
// ignore storage errors
|
||||
}
|
||||
};
|
||||
|
||||
const isAllZones = selectedZone.hubid === 'all';
|
||||
|
||||
/**
|
||||
* Matches an order, delivery, tripsheet or rider against the active zone.
|
||||
*/
|
||||
const matchesZone = (item) => {
|
||||
if (!item) return false;
|
||||
|
||||
// Tenant check: if item carries tenantid and current session is a tenant, match tenant first
|
||||
if (isTenantUser && item.tenantid != null && String(item.tenantid) !== String(tenantId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAllZones) return true;
|
||||
|
||||
const currentHub = selectedZone;
|
||||
const targetHubId = String(currentHub.hubid);
|
||||
|
||||
// 1. Direct hub/location id match
|
||||
const itemHubId =
|
||||
item.hubid ??
|
||||
item.sourcehubid ??
|
||||
item.applocationid ??
|
||||
item.tenantlocationid ??
|
||||
item.locationid;
|
||||
if (itemHubId != null && String(itemHubId) === targetHubId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. City or Hub name match
|
||||
const hubCity = String(currentHub.city || '').trim().toLowerCase();
|
||||
const hubName = String(currentHub.hubname || '').trim().toLowerCase();
|
||||
const hubPincode = String(currentHub.pincode || '').trim();
|
||||
|
||||
const addressText = `${item.pickupaddress || ''} ${item.deliveryaddress || ''} ${item.address || ''} ${item.pickuplocation || ''} ${item.deliverylocation || ''} ${item.suburb || ''} ${item.city || ''}`.toLowerCase();
|
||||
|
||||
if (hubPincode && addressText.includes(hubPincode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hubCity && addressText.includes(hubCity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hubName && addressText.includes(hubName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Coordinate distance match (within 35km radius of hub)
|
||||
const hubLat = Number(currentHub.latitude);
|
||||
const hubLon = Number(currentHub.longitude);
|
||||
if (Number.isFinite(hubLat) && Number.isFinite(hubLon)) {
|
||||
const pickLat = Number(item.pickuplatitude ?? item.latitude);
|
||||
const pickLon = Number(item.pickuplongitude ?? item.longitude);
|
||||
const dropLat = Number(item.deliverylatitude ?? item.droplat);
|
||||
const dropLon = Number(item.deliverylongitude ?? item.droplon);
|
||||
|
||||
const pickDist = haversineKm(hubLat, hubLon, pickLat, pickLon);
|
||||
const dropDist = haversineKm(hubLat, hubLon, dropLat, dropLon);
|
||||
|
||||
if (pickDist !== undefined && pickDist <= 35) return true;
|
||||
if (dropDist !== undefined && dropDist <= 35) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
selectedZone,
|
||||
selectedZoneId,
|
||||
setSelectedZoneId,
|
||||
zones,
|
||||
isAllZones,
|
||||
isTenantUser,
|
||||
tenantId,
|
||||
isHubsLoading: isTenantUser ? isLocationsLoading : isHubsLoading,
|
||||
refetchHubs: isTenantUser ? refetchLocations : refetchHubs,
|
||||
matchesZone,
|
||||
}),
|
||||
[
|
||||
selectedZone,
|
||||
selectedZoneId,
|
||||
zones,
|
||||
isAllZones,
|
||||
isTenantUser,
|
||||
tenantId,
|
||||
isLocationsLoading,
|
||||
isHubsLoading,
|
||||
refetchLocations,
|
||||
refetchHubs,
|
||||
matchesZone,
|
||||
]
|
||||
);
|
||||
|
||||
return <ZoneContext.Provider value={value}>{children}</ZoneContext.Provider>;
|
||||
}
|
||||
|
||||
export function useZone() {
|
||||
const context = useContext(ZoneContext);
|
||||
if (!context) {
|
||||
throw new Error('useZone must be used within a ZoneProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user