upates on the google map removal
This commit is contained in:
@@ -363,6 +363,62 @@ export const updateDeliveryAPI = async (orderData) => {
|
||||
return axios.put(`${process.env.REACT_APP_URL}/deliveries/updatedelivery`, orderData);
|
||||
};
|
||||
|
||||
// ==============================|| getalltenants (tenants) ||============================== //
|
||||
|
||||
export const getalltenants = async ({ queryKey }) => {
|
||||
const [, appId, debouncedSearch, status, page, rowsPerPage] = queryKey;
|
||||
try {
|
||||
let url = `${process.env.REACT_APP_URL
|
||||
}/tenants/getalltenants/?status=${status}&applocationid=${appId}&keyword=${debouncedSearch}&pageno=${page + 1
|
||||
}&pagesize=${rowsPerPage}&moduleid=6`;
|
||||
const response = await axios.get(url);
|
||||
return response.data.details; // return only data, keep it clean
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
OpenToast(message);
|
||||
return null; // return null for failure
|
||||
}
|
||||
};
|
||||
// ==============================|| gettenantsummary (tenants) ||============================== //
|
||||
|
||||
export const gettenantsummary = async ({ queryKey }) => {
|
||||
const [, appId] = queryKey;
|
||||
try {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantsummary/?moduleid=6&applocationid=${appId}`);
|
||||
return response.data.summary; // return only data, keep it clean
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
OpenToast(message);
|
||||
return null; // return null for failure
|
||||
}
|
||||
};
|
||||
// ==============================|| getpricinglist (tenants) ||============================== //
|
||||
|
||||
export const getpricinglist = async ({ queryKey }) => {
|
||||
const [, appId] = queryKey;
|
||||
try {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/tenants/getpricinglist/?moduleid=6&applocationid=${appId}`);
|
||||
return response.data.summary; // return only data, keep it clean
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
OpenToast(message);
|
||||
return null; // return null for failure
|
||||
}
|
||||
};
|
||||
// ==============================|| getallpricing (clientPricing) ||============================== //
|
||||
|
||||
export const getallpricing = async ({ queryKey }) => {
|
||||
const [, appId] = queryKey;
|
||||
try {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/utils/getallpricing/?applocationid=${appId}`);
|
||||
return response.data.details || [];
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
OpenToast(message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| fetchAllRiders (riders) ||============================== //
|
||||
export const fetchAllRiders = async ({ pageParam = 1, queryKey }) => {
|
||||
try {
|
||||
|
||||
602
src/pages/nearle/clientPricing/clientPricing.js
Normal file
602
src/pages/nearle/clientPricing/clientPricing.js
Normal file
@@ -0,0 +1,602 @@
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Grid,
|
||||
Paper,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import {
|
||||
MdLocalOffer,
|
||||
MdMyLocation,
|
||||
MdAttachMoney,
|
||||
MdGroups,
|
||||
MdPlace,
|
||||
MdSpeed,
|
||||
MdPriceCheck,
|
||||
MdStraighten,
|
||||
MdReceiptLong,
|
||||
MdOutlineLocalOffer,
|
||||
MdOutlineGroups,
|
||||
MdOutlineAttachMoney,
|
||||
MdOutlinePlace
|
||||
} from 'react-icons/md';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import Loader from 'components/Loader';
|
||||
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
|
||||
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
|
||||
import PageHeader from 'components/nearle_components/PageHeader';
|
||||
import StatCard from 'components/nearle_components/StatCard';
|
||||
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
|
||||
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
|
||||
import { getallpricing } from 'pages/api/api';
|
||||
|
||||
// ============================================================================
|
||||
// Design tokens — shared with the deliveries / tenants / customers pages so every
|
||||
// surface (header, KPI tiles, table, badges) speaks the same visual language.
|
||||
// Keep this block in sync with customers.js / deliveries.js.
|
||||
// ============================================================================
|
||||
const DT = {
|
||||
radiusPill: 999,
|
||||
radiusCard: 14,
|
||||
radiusField: 10,
|
||||
shadowSoft: '0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.05)',
|
||||
shadowMd: '0 1px 3px rgba(15, 23, 42, 0.06)',
|
||||
shadowPop: '0 12px 32px rgba(15, 23, 42, 0.12)',
|
||||
textPrimary: '#0f172a',
|
||||
textSecondary: '#64748b',
|
||||
textMuted: '#94a3b8',
|
||||
borderSubtle: '#e2e8f0',
|
||||
borderHover: '#cbd5e1',
|
||||
divider: '#f1f5f9',
|
||||
surface: '#ffffff',
|
||||
surfaceAlt: '#f8fafc',
|
||||
brand: '#662582'
|
||||
};
|
||||
const a = (c, suffix) => `${c}${suffix}`;
|
||||
const tint = (c) => a(c, '08');
|
||||
const soft = (c) => a(c, '18');
|
||||
const ring = (c) => a(c, '26');
|
||||
const edge = (c) => a(c, '55');
|
||||
|
||||
const BRAND = '#662582';
|
||||
|
||||
const SoftPaper = (props) => (
|
||||
<Paper
|
||||
{...props}
|
||||
sx={{
|
||||
mt: 0.75,
|
||||
borderRadius: 2,
|
||||
boxShadow: DT.shadowPop,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const AccentAvatar = ({ color, selected, size = 24, children }) => (
|
||||
<Avatar
|
||||
sx={{
|
||||
width: size,
|
||||
height: size,
|
||||
bgcolor: selected ? color : soft(color),
|
||||
color: selected ? '#fff' : color,
|
||||
transition: 'background-color 0.15s, color 0.15s'
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Avatar>
|
||||
);
|
||||
|
||||
const formatRupees = (value) =>
|
||||
new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: 'INR',
|
||||
minimumFractionDigits: 2
|
||||
}).format(Number(value) || 0);
|
||||
|
||||
const formatDecimal = (value) =>
|
||||
new Intl.NumberFormat('en-IN', { minimumFractionDigits: 2 }).format(Number(value) || 0);
|
||||
|
||||
// Numeric table value — plain, strong, right-readable text. The old version
|
||||
// wrapped every cell in a coloured bordered pill, which made the table read
|
||||
// like a rainbow; corporate data tables keep figures as quiet typography and
|
||||
// let the column header carry the meaning.
|
||||
const MetricPill = ({ label }) => (
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ fontSize: 13.5, fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' }}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
// Subtle neutral category chip (zone / slab) — one quiet style, muted icon.
|
||||
const CategoryChip = ({ icon, label }) => (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
px: 1,
|
||||
py: 0.375,
|
||||
borderRadius: 8,
|
||||
bgcolor: DT.surfaceAlt,
|
||||
border: `1px solid ${DT.borderSubtle}`,
|
||||
color: DT.textPrimary,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
<Box component="span" sx={{ display: 'inline-flex', color: DT.textMuted }}>
|
||||
{icon}
|
||||
</Box>
|
||||
{label}
|
||||
</Box>
|
||||
);
|
||||
|
||||
// ==============================|| Pricing page ||============================== //
|
||||
|
||||
const ClientsPricing = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const containerRef = useRef();
|
||||
const [appId, setAppId] = useState(0);
|
||||
const [locaName, setLocoName] = useState('All');
|
||||
const [searchword, setSearchword] = useState('');
|
||||
const [debouncedSearch, setDebouncedSearch] = useState('');
|
||||
|
||||
const {
|
||||
data: pricing = [],
|
||||
isLoading
|
||||
} = useQuery({
|
||||
queryKey: ['getallpricing', appId],
|
||||
queryFn: getallpricing,
|
||||
keepPreviousData: true
|
||||
});
|
||||
|
||||
const rows = useMemo(() => {
|
||||
if (!debouncedSearch) return pricing;
|
||||
const q = debouncedSearch.toLowerCase().trim();
|
||||
return pricing.filter((row) =>
|
||||
[row.applocation, row.appname, row.slab, String(row.pricingid)]
|
||||
.filter(Boolean)
|
||||
.some((field) => String(field).toLowerCase().includes(q))
|
||||
);
|
||||
}, [pricing, debouncedSearch]);
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const total = pricing.length;
|
||||
const tenants = new Set(pricing.map((r) => r.appname).filter(Boolean)).size;
|
||||
const avgBase = total
|
||||
? pricing.reduce((sum, r) => sum + (Number(r.baseprice) || 0), 0) / total
|
||||
: 0;
|
||||
return { total, tenants, avgBase };
|
||||
}, [pricing]);
|
||||
|
||||
const KPI_META = [
|
||||
{ key: 'total', label: 'Total Pricing Slabs', color: BRAND, icon: MdOutlineLocalOffer, value: stats.total },
|
||||
{ key: 'tenants', label: 'Tenants Priced', color: '#0ea5e9', icon: MdOutlineGroups, value: stats.tenants },
|
||||
{ key: 'avg', label: 'Avg Base Price', color: '#f59e0b', icon: MdOutlineAttachMoney, value: formatRupees(stats.avgBase) },
|
||||
{ key: 'zone', label: 'Active Zone', color: '#10b981', icon: MdOutlinePlace, value: locaName || 'All Zones' }
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading && <Loader />}
|
||||
|
||||
{/* ============================================= || Header || ============================================= */}
|
||||
<PageHeader
|
||||
title="Pricing"
|
||||
subtitle={`Live · ${locaName || 'All Zones'}`}
|
||||
live
|
||||
action={
|
||||
<LocationAutocomplete
|
||||
locaName={locaName}
|
||||
setAppId={setAppId}
|
||||
setLocoName={setLocoName}
|
||||
pill
|
||||
accentColor={BRAND}
|
||||
icon={<MdMyLocation size={14} />}
|
||||
placeholder="Select Zone"
|
||||
paperComponent={SoftPaper}
|
||||
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* ============================================= || KPI Cards || ============================================= */}
|
||||
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
|
||||
{KPI_META.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Grid item key={item.key} xs={6} sm={6} md={3}>
|
||||
<StatCard title={item.label} value={item.value} icon={<Icon size={20} />} color={item.color} />
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
|
||||
{/* ============================================= || Search Header || ============================================= */}
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
mt: { xs: 1.5, md: 2 },
|
||||
p: { xs: 1, md: 1.5 },
|
||||
borderTopLeftRadius: DT.radiusCard / 8,
|
||||
borderTopRightRadius: DT.radiusCard / 8,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
border: '1px solid',
|
||||
borderColor: DT.borderSubtle,
|
||||
borderBottom: 0,
|
||||
background: '#fff'
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction={{ xs: 'column', sm: 'row' }}
|
||||
alignItems={{ xs: 'stretch', sm: 'center' }}
|
||||
justifyContent="space-between"
|
||||
spacing={1.25}
|
||||
>
|
||||
<Stack direction="row" alignItems="center" spacing={1.25}>
|
||||
<AccentAvatar color={BRAND} size={32}>
|
||||
<MdLocalOffer size={18} />
|
||||
</AccentAvatar>
|
||||
<Stack>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ fontWeight: 800, color: DT.textSecondary, letterSpacing: 0.6, textTransform: 'uppercase' }}
|
||||
>
|
||||
Pricing Catalog
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: DT.textPrimary, fontWeight: 700 }}>
|
||||
{pricing.length} total · {rows.length} shown
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Box sx={{ width: { xs: '100%', sm: 280, lg: 340 }, flex: { xs: '1 1 100%', sm: '0 0 auto' } }}>
|
||||
<DebounceSearchBar
|
||||
value={searchword}
|
||||
onChange={setSearchword}
|
||||
onDebouncedChange={setDebouncedSearch}
|
||||
placeholder={`Search pricing (ctrl+k)`}
|
||||
sx={{
|
||||
m: 0,
|
||||
width: '100%',
|
||||
borderRadius: DT.radiusField + 'px',
|
||||
bgcolor: DT.surface,
|
||||
'& fieldset': { borderColor: DT.borderSubtle, borderWidth: 1 },
|
||||
'&:hover fieldset': { borderColor: DT.borderHover },
|
||||
'&.Mui-focused fieldset': { borderColor: BRAND, borderWidth: 1.5 },
|
||||
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* ============================================= || Table || ============================================= */}
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomLeftRadius: DT.radiusCard / 8,
|
||||
borderBottomRightRadius: DT.radiusCard / 8,
|
||||
border: '1px solid',
|
||||
borderColor: DT.borderSubtle,
|
||||
overflow: 'hidden',
|
||||
background: '#fff'
|
||||
}}
|
||||
>
|
||||
{isMobile ? (
|
||||
rows.length === 0 && !isLoading ? (
|
||||
<Stack alignItems="center" spacing={1.5} sx={{ py: 6, px: 2 }}>
|
||||
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
|
||||
<MdLocalOffer size={28} />
|
||||
</Avatar>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
|
||||
No pricing to show
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary, textAlign: 'center' }}>
|
||||
{searchword ? 'Try a different keyword.' : 'Pick a zone above to load the catalog.'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
) : (
|
||||
<MobileCardList scroll>
|
||||
{rows.map((row, index) => (
|
||||
<MobileCard
|
||||
key={row.pricingid || `${row.appname}-${index}`}
|
||||
accent={BRAND}
|
||||
header={
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
|
||||
<Stack direction="row" alignItems="center" spacing={1} sx={{ minWidth: 0 }}>
|
||||
<AccentAvatar color={BRAND} size={36}>
|
||||
<MdGroups size={18} />
|
||||
</AccentAvatar>
|
||||
<Stack sx={{ minWidth: 0 }}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{ fontWeight: 700, color: DT.textPrimary }}
|
||||
noWrap
|
||||
>
|
||||
{row.appname || '—'}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||||
ID #{row.pricingid}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: DT.textMuted, flexShrink: 0 }}>
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Stack direction="row" spacing={0.75} sx={{ mt: 1, flexWrap: 'wrap', gap: 0.75 }}>
|
||||
{row.applocation ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
px: 1,
|
||||
py: 0.375,
|
||||
borderRadius: 999,
|
||||
bgcolor: tint('#10b981'),
|
||||
border: `1px solid ${edge('#10b981')}`,
|
||||
color: '#10b981',
|
||||
fontSize: 11,
|
||||
fontWeight: 800
|
||||
}}
|
||||
>
|
||||
<MdPlace size={12} /> {row.applocation}
|
||||
</Box>
|
||||
) : null}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
px: 1,
|
||||
py: 0.375,
|
||||
borderRadius: 999,
|
||||
bgcolor: tint('#0ea5e9'),
|
||||
border: `1px solid ${edge('#0ea5e9')}`,
|
||||
color: '#0ea5e9',
|
||||
fontSize: 11,
|
||||
fontWeight: 800
|
||||
}}
|
||||
>
|
||||
<MdSpeed size={12} /> {row.slab || '—'}
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<MobileFieldGrid>
|
||||
<MobileField label="Base Price">
|
||||
<MetricPill
|
||||
color={BRAND}
|
||||
icon={<MdPriceCheck size={12} />}
|
||||
label={formatRupees(row.baseprice)}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label="Price / KM">
|
||||
<MetricPill
|
||||
color="#10b981"
|
||||
icon={<MdAttachMoney size={12} />}
|
||||
label={formatRupees(row.priceperkm)}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label="Min KM">
|
||||
<MetricPill
|
||||
color="#f59e0b"
|
||||
icon={<MdStraighten size={12} />}
|
||||
label={`${formatDecimal(row.minkm)} km`}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label="Max KM">
|
||||
<MetricPill
|
||||
color="#ef4444"
|
||||
icon={<MdStraighten size={12} />}
|
||||
label={`${formatDecimal(row.maxkm)} km`}
|
||||
/>
|
||||
</MobileField>
|
||||
<MobileField label="Min Orders">
|
||||
<Stack direction="row" alignItems="center" spacing={0.5}>
|
||||
<MdReceiptLong size={14} color={DT.textMuted} />
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }}>
|
||||
{row.minorder ?? '—'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</MobileField>
|
||||
</MobileFieldGrid>
|
||||
</MobileCard>
|
||||
))}
|
||||
</MobileCardList>
|
||||
)
|
||||
) : (
|
||||
<TableContainer
|
||||
ref={containerRef}
|
||||
sx={{
|
||||
maxHeight: { xs: 'calc(100vh - 220px)', md: 'calc(100vh - 190px)' },
|
||||
'&::-webkit-scrollbar': { width: 10, height: 10 },
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: edge(BRAND),
|
||||
borderRadius: 8,
|
||||
'&:hover': { backgroundColor: BRAND }
|
||||
},
|
||||
'&::-webkit-scrollbar-track': { backgroundColor: DT.surfaceAlt }
|
||||
}}
|
||||
>
|
||||
<Table stickyHeader sx={{ minWidth: { xs: 860, md: 1080 } }}>
|
||||
<TableHead>
|
||||
<TableRow
|
||||
sx={{
|
||||
'& th': {
|
||||
backgroundColor: DT.surfaceAlt,
|
||||
color: DT.textSecondary,
|
||||
fontSize: { xs: 10, md: 11 },
|
||||
fontWeight: 800,
|
||||
letterSpacing: 0.6,
|
||||
textTransform: 'uppercase',
|
||||
whiteSpace: 'nowrap',
|
||||
borderBottom: `1px solid ${DT.borderSubtle}`,
|
||||
py: { xs: 1, md: 1.25 },
|
||||
px: { xs: 1, md: 2 }
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TableCell>#</TableCell>
|
||||
<TableCell>Tenant</TableCell>
|
||||
<TableCell>Zone</TableCell>
|
||||
<TableCell>Slab</TableCell>
|
||||
<TableCell align="center">Base Price</TableCell>
|
||||
<TableCell align="center">Min KM</TableCell>
|
||||
<TableCell align="center">Price / KM</TableCell>
|
||||
<TableCell align="center">Max KM</TableCell>
|
||||
<TableCell align="center">Min Orders</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{isLoading && <OrdersTableSkeleton col={5} />}
|
||||
{rows.length === 0 && !isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={9} sx={{ py: 6 }}>
|
||||
<Stack alignItems="center" spacing={1.5}>
|
||||
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
|
||||
<MdLocalOffer size={28} />
|
||||
</Avatar>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
|
||||
No pricing to show
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||||
{searchword ? 'Try a different keyword.' : 'Pick a zone above to load the catalog.'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
rows.map((row, index) => (
|
||||
<TableRow
|
||||
key={row.pricingid || `${row.appname}-${index}`}
|
||||
sx={{
|
||||
transition: 'background-color 0.15s',
|
||||
'& td': {
|
||||
borderBottom: `1px solid ${DT.divider}`,
|
||||
py: { xs: 1, md: 1.5 },
|
||||
px: { xs: 1, md: 2 }
|
||||
},
|
||||
'&:hover': { backgroundColor: DT.surfaceAlt }
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: DT.textMuted }}>
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<AccentAvatar color={BRAND} size={36}>
|
||||
<MdGroups size={18} />
|
||||
</AccentAvatar>
|
||||
<Stack>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{row.appname || '—'}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||||
ID #{row.pricingid}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{row.applocation ? (
|
||||
<CategoryChip icon={<MdPlace size={12} />} label={row.applocation} />
|
||||
) : (
|
||||
<Typography variant="caption" sx={{ color: DT.textMuted }}>—</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<CategoryChip icon={<MdSpeed size={12} />} label={row.slab || '—'} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<MetricPill
|
||||
color={BRAND}
|
||||
icon={<MdPriceCheck size={12} />}
|
||||
label={formatRupees(row.baseprice)}
|
||||
width={110}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<MetricPill
|
||||
color="#f59e0b"
|
||||
icon={<MdStraighten size={12} />}
|
||||
label={`${formatDecimal(row.minkm)} km`}
|
||||
width={90}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<MetricPill
|
||||
color="#10b981"
|
||||
icon={<MdAttachMoney size={12} />}
|
||||
label={formatRupees(row.priceperkm)}
|
||||
width={110}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<MetricPill
|
||||
color="#ef4444"
|
||||
icon={<MdStraighten size={12} />}
|
||||
label={`${formatDecimal(row.maxkm)} km`}
|
||||
width={90}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Stack direction="row" alignItems="center" justifyContent="center" spacing={0.5}>
|
||||
<MdReceiptLong size={14} color={DT.textMuted} />
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{row.minorder ?? '—'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClientsPricing;
|
||||
2100
src/pages/nearle/clients/Tenants.js
Normal file
2100
src/pages/nearle/clients/Tenants.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,13 +11,11 @@ import { MdPersonAddAlt1 } from 'react-icons/md';
|
||||
// project import
|
||||
import MainCard from 'components/MainCard';
|
||||
import axios from 'axios';
|
||||
import { usePlacesWidget } from 'react-google-autocomplete';
|
||||
import AddressAutocomplete, { geocodeAddress } from 'components/nearle_components/AddressAutocomplete';
|
||||
import Loader from 'components/Loader';
|
||||
import Geocode from 'react-geocode';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { DT, tint } from 'themes/dt/tokens';
|
||||
// import { setLocationType } from 'react-geocode';
|
||||
|
||||
// const avatarImage = require.context('assets/images/users', true);
|
||||
|
||||
@@ -51,9 +49,6 @@ const Createclient = () => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
|
||||
// Geocode.setApiKey('AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8');
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -65,25 +60,15 @@ const Createclient = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
Geocode.fromAddress(address).then(
|
||||
(response) => {
|
||||
if (response.status == 'OK') {
|
||||
const { lat, lng } = response.results[0].geometry.location;
|
||||
setLatlong({
|
||||
lat,
|
||||
lng
|
||||
});
|
||||
console.log(response);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
let active = true;
|
||||
geocodeAddress(address).then((place) => {
|
||||
if (active && place) {
|
||||
setLatlong({ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() });
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [address]);
|
||||
|
||||
const opentoast = (message) => {
|
||||
@@ -147,45 +132,33 @@ const Createclient = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const { ref: materialRef } = usePlacesWidget({
|
||||
apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
|
||||
onPlaceSelected: (place) => {
|
||||
console.log(place);
|
||||
|
||||
setAddress(place.formatted_address);
|
||||
let city1, zipcode1, state1, suburb1;
|
||||
for (let i = 0; i < place.address_components.length; i++) {
|
||||
for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
switch (place.address_components[i].types[j]) {
|
||||
case 'locality':
|
||||
city1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
state1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
zipcode1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'sublocality':
|
||||
suburb1 = place.address_components[i].long_name;
|
||||
break;
|
||||
}
|
||||
const handleAddressPlaceSelected = (place) => {
|
||||
setAddress(place.formatted_address);
|
||||
let city1, zipcode1, state1, suburb1;
|
||||
for (let i = 0; i < place.address_components.length; i++) {
|
||||
for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
switch (place.address_components[i].types[j]) {
|
||||
case 'locality':
|
||||
city1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
state1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
zipcode1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'sublocality':
|
||||
case 'sublocality_level_1':
|
||||
suburb1 = place.address_components[i].long_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setCity(city1 || '');
|
||||
setState(state1 || '');
|
||||
setZipcode(zipcode1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
|
||||
// setAddress(place.formatted_address)
|
||||
},
|
||||
// inputAutocompleteValue: "country",
|
||||
options: {
|
||||
// componentRestrictions: 'us',
|
||||
// types: ["establishment"]
|
||||
types: ['address' || 'geocode']
|
||||
}
|
||||
});
|
||||
setCity(city1 || '');
|
||||
setState(state1 || '');
|
||||
setZipcode(zipcode1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
};
|
||||
|
||||
const createprofile = async () => {
|
||||
console.log('res', businessname, businessno, mobilenumber, emailaddress, address, city, zipcode);
|
||||
@@ -499,14 +472,13 @@ const Createclient = () => {
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-address">Address</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
|
||||
<AddressAutocomplete
|
||||
id="personal-address"
|
||||
fullWidth
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
inputRef={materialRef}
|
||||
onChange={setAddress}
|
||||
onPlaceSelected={handleAddressPlaceSelected}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
@@ -47,7 +47,7 @@ import { TbMapPinCode } from 'react-icons/tb';
|
||||
import { FaLocationDot } from 'react-icons/fa6';
|
||||
import axios from 'axios';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Geocode from 'react-geocode';
|
||||
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
|
||||
import Loader from 'components/Loader';
|
||||
import * as geolib from 'geolib';
|
||||
import MainCard from 'components/MainCard';
|
||||
@@ -177,30 +177,14 @@ const SoftPaper = (props) => (
|
||||
/>
|
||||
);
|
||||
|
||||
function loadScript(src, position, id) {
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('async', '');
|
||||
script.setAttribute('id', id);
|
||||
script.src = src;
|
||||
position.appendChild(script);
|
||||
}
|
||||
|
||||
const Createorder1 = () => {
|
||||
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
|
||||
// ================================================= || GoogleMaps (Drawer) || =================================================
|
||||
const [value, setValue] = React.useState(null);
|
||||
const [value1, setValue1] = React.useState(null);
|
||||
const [inputValue, setInputValue] = React.useState('');
|
||||
const [inputValue1, setInputValue1] = React.useState('');
|
||||
const [inputValue2, setInputValue2] = React.useState('');
|
||||
const [inputValue3, setInputValue3] = React.useState('');
|
||||
const [options, setOptions] = React.useState([]);
|
||||
const [options1, setOptions1] = React.useState([]);
|
||||
const loaded = React.useRef(false);
|
||||
const loaded1 = React.useRef(false);
|
||||
const [mobilenumber, setMobilenumber] = useState('');
|
||||
const [emailaddress, setEmailaddress] = useState('');
|
||||
@@ -218,8 +202,6 @@ const Createorder1 = () => {
|
||||
const [dropDoorno, setDropDoorno] = useState('');
|
||||
const [pickLandmark, setPickLandmark] = useState('');
|
||||
const [dropLandmark, setDropLandmark] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
const [address1, setAddress1] = useState('');
|
||||
const [latlong, setLatlong] = useState({});
|
||||
const [latlong1, setLatlong1] = useState({});
|
||||
const autocompleteService = useRef(null);
|
||||
@@ -252,225 +234,6 @@ const Createorder1 = () => {
|
||||
{ label: '12 Angry Men', year: 1957 }
|
||||
];
|
||||
|
||||
// // // ====================================================== || address (pick)|| ======================================================
|
||||
// useEffect(() => {
|
||||
// if (address) {
|
||||
// try {
|
||||
// Geocode.fromAddress(address).then(
|
||||
// (response) => {
|
||||
// if (response.status == 'OK') {
|
||||
// const { lat, lng } = response.results[0].geometry.location;
|
||||
// console.log({ lat, lng });
|
||||
// setLatlong({
|
||||
// lat,
|
||||
// lng
|
||||
// });
|
||||
// console.log(response);
|
||||
// if (response.results[0].address_components) {
|
||||
// let place = response.results[0];
|
||||
// let cityA, zipcodeA, stateA, suburbA;
|
||||
// for (let i = 0; i < place.address_components.length; i++) {
|
||||
// for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
// switch (place.address_components[i].types[j]) {
|
||||
// case 'locality':
|
||||
// cityA = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'administrative_area_level_1':
|
||||
// stateA = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'postal_code':
|
||||
// zipcodeA = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'sublocality':
|
||||
// suburbA = place.address_components[i].long_name;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// setCity(cityA || '');
|
||||
// setState(stateA || '');
|
||||
// setZipcode(zipcodeA || '');
|
||||
// setSuburb(suburbA || '');
|
||||
// console.log({ lat, lng, cityA, stateA, zipcodeA, suburbA });
|
||||
// setPickCust({
|
||||
// ...pickCust
|
||||
// // city: cityA,
|
||||
// // state: stateA,
|
||||
// // postcode: zipcodeA,
|
||||
// // suburb: suburbA
|
||||
// // latitude: lat,
|
||||
// // longitude: lng
|
||||
// });
|
||||
// // setStartPoint({ latitude: lat, longitude: lng });
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// (error) => {
|
||||
// console.log(error);
|
||||
// }
|
||||
// );
|
||||
// } catch (err) {
|
||||
// console.log(err);
|
||||
// }
|
||||
// }
|
||||
// }, [address]);
|
||||
// // // ====================================================== || address 1 (drop)|| ======================================================
|
||||
// useEffect(() => {
|
||||
// if (address) {
|
||||
// try {
|
||||
// Geocode.fromAddress(address1).then(
|
||||
// (response) => {
|
||||
// if (response.status == 'OK') {
|
||||
// const { lat, lng } = response.results[0].geometry.location;
|
||||
|
||||
// setLatlong1({
|
||||
// lat,
|
||||
// lng
|
||||
// });
|
||||
// console.log(response);
|
||||
// if (response.results[0].address_components) {
|
||||
// let place = response.results[0];
|
||||
// let cityB, zipcodeB, stateB, suburbB;
|
||||
// for (let i = 0; i < place.address_components.length; i++) {
|
||||
// for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
// switch (place.address_components[i].types[j]) {
|
||||
// case 'locality':
|
||||
// cityB = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'administrative_area_level_1':
|
||||
// stateB = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'postal_code':
|
||||
// zipcodeB = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'sublocality':
|
||||
// suburbB = place.address_components[i].long_name;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// setCity(cityB || '');
|
||||
// setState(stateB || '');
|
||||
// setZipcode(zipcodeB || '');
|
||||
// setSuburb(suburbB || '');
|
||||
// console.log({ lat, lng, cityB, stateB, zipcodeB, suburbB });
|
||||
// setDropCust({
|
||||
// ...dropCust
|
||||
// // city: cityB,
|
||||
// // state: stateB,
|
||||
// // postcode: zipcodeB,
|
||||
// // suburb: suburbB
|
||||
// // latitude: lat,
|
||||
// // longitude: lng
|
||||
// });
|
||||
// // setEndPoint({ latitude: lat, longitude: lng });
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// (error) => {
|
||||
// console.log(error);
|
||||
// }
|
||||
// );
|
||||
// } catch (err) {
|
||||
// console.log(err);
|
||||
// }
|
||||
// }
|
||||
// }, [address1]);
|
||||
|
||||
if (typeof window !== 'undefined' && !loaded.current) {
|
||||
if (!document.querySelector('#google-maps')) {
|
||||
loadScript(
|
||||
`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&libraries=places&location=10.3656,77.9690&radius=50000&components=country:IN&strictbounds=true`,
|
||||
document.querySelector('head'),
|
||||
'google-maps'
|
||||
);
|
||||
}
|
||||
loaded.current = true;
|
||||
}
|
||||
|
||||
// const fetch = React.useMemo(
|
||||
// () =>
|
||||
// debounce((request, callback) => {
|
||||
// autocompleteService.current.getPlacePredictions(request, callback);
|
||||
// }, 400),
|
||||
// []
|
||||
// );
|
||||
// const fetch1 = React.useMemo(
|
||||
// () =>
|
||||
// debounce((request, callback) => {
|
||||
// autocompleteService.current.getPlacePredictions(request, callback);
|
||||
// }, 400),
|
||||
// []
|
||||
// );
|
||||
|
||||
// ====================================================== || options (pick)|| ======================================================
|
||||
|
||||
// React.useEffect(() => {
|
||||
// let active = true;
|
||||
// if (!autocompleteService.current && window.google) {
|
||||
// autocompleteService.current = new window.google.maps.places.AutocompleteService();
|
||||
// }
|
||||
// if (!autocompleteService.current) {
|
||||
// return undefined;
|
||||
// }
|
||||
// if (inputValue === '') {
|
||||
// setOptions(value ? [value] : []);
|
||||
// return undefined;
|
||||
// }
|
||||
// fetch({ input: inputValue }, (results) => {
|
||||
// if (active) {
|
||||
// let newOptions = [];
|
||||
|
||||
// if (value) {
|
||||
// newOptions = [value];
|
||||
// }
|
||||
|
||||
// if (results) {
|
||||
// newOptions = [...newOptions, ...results];
|
||||
// }
|
||||
|
||||
// setOptions(newOptions);
|
||||
// }
|
||||
// });
|
||||
|
||||
// return () => {
|
||||
// active = false;
|
||||
// };
|
||||
// }, [value, inputValue, fetch]);
|
||||
|
||||
// // ====================================================== || options1 (drop)|| ======================================================
|
||||
// React.useEffect(() => {
|
||||
// let active = true;
|
||||
// if (!autocompleteService.current && window.google) {
|
||||
// autocompleteService.current = new window.google.maps.places.AutocompleteService();
|
||||
// }
|
||||
// if (!autocompleteService.current) {
|
||||
// return undefined;
|
||||
// }
|
||||
// if (inputValue1 === '') {
|
||||
// setOptions1(value1 ? [value1] : []);
|
||||
// return undefined;
|
||||
// }
|
||||
// fetch1({ input: inputValue1 }, (results) => {
|
||||
// if (active) {
|
||||
// let newOptions = [];
|
||||
|
||||
// if (value1) {
|
||||
// newOptions = [value1];
|
||||
// }
|
||||
|
||||
// if (results) {
|
||||
// newOptions = [...newOptions, ...results];
|
||||
// }
|
||||
|
||||
// setOptions1(newOptions);
|
||||
// }
|
||||
// });
|
||||
|
||||
// return () => {
|
||||
// active = false;
|
||||
// };
|
||||
// }, [value1, inputValue1, fetch1]);
|
||||
|
||||
const appId = localStorage.getItem('applocationid');
|
||||
const navigate = useNavigate();
|
||||
@@ -687,41 +450,6 @@ const Createorder1 = () => {
|
||||
}
|
||||
}, [searchword]);
|
||||
|
||||
// const { ref: materialRef } = usePlacesWidget({
|
||||
// apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
|
||||
// onPlaceSelected: (place) => {
|
||||
// console.log(place);
|
||||
|
||||
// // setAddress(place.formatted_address)
|
||||
// let city1, zipcode1, state1, suburb1;
|
||||
// for (let i = 0; i < place.address_components.length; i++) {
|
||||
// for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
// switch (place.address_components[i].types[j]) {
|
||||
// case 'locality':
|
||||
// city1 = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'administrative_area_level_1':
|
||||
// state1 = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'postal_code':
|
||||
// zipcode1 = place.address_components[i].long_name;
|
||||
// break;
|
||||
// case 'sublocality':
|
||||
// suburb1 = place.address_components[i].long_name;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // setCity(city1 || '')
|
||||
// // setState(state1 || '');
|
||||
// // setZipcode(zipcode1 || '');
|
||||
// // setSuburb(suburb1 || '')
|
||||
// },
|
||||
|
||||
// options: {
|
||||
// types: ['address' || 'geocode']
|
||||
// }
|
||||
// });
|
||||
|
||||
// ==================================================== || fetchtenantinfo || ====================================================
|
||||
const fetchtenantinfo = async () => {
|
||||
@@ -1161,176 +889,128 @@ const Createorder1 = () => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
// ============================================= || Google Maps Autocomplete(pick) || =============================================
|
||||
useEffect(() => {
|
||||
// Initialize Google Maps Autocomplete
|
||||
if (inputValue2) {
|
||||
const autocompleteInput = document.getElementById('addressAuto1');
|
||||
const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, {
|
||||
// types: ['(cities)'], // You can adjust the types parameter based on your requirements
|
||||
strictBounds: true,
|
||||
bounds: new window.google.maps.Circle({
|
||||
// center: new window.google.maps.LatLng(11.0050707, 76.9509083),
|
||||
// radius: 100000
|
||||
center: new window.google.maps.LatLng(appLocaLat, appLocaLng),
|
||||
radius: appLocaRadius * 1000
|
||||
}).getBounds()
|
||||
// ============================================= || Address Autocomplete (pick) || =============================================
|
||||
const handlePickPlaceSelected = (place) => {
|
||||
setInputValue2(`${place.name}, ${place.formatted_address}`);
|
||||
// to trigger getDistance
|
||||
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
|
||||
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
|
||||
const address = {
|
||||
address: `${place.name} ${place.formatted_address}`,
|
||||
street_number: '',
|
||||
route: '',
|
||||
locality: '',
|
||||
sublocality_level_1: '',
|
||||
administrative_area_level_3: '',
|
||||
administrative_area_level_1: '',
|
||||
country: '',
|
||||
postal_code: ''
|
||||
};
|
||||
place.address_components.forEach((component) => {
|
||||
component.types.forEach((type) => {
|
||||
switch (type) {
|
||||
case 'street_number':
|
||||
address.street_number = component.long_name;
|
||||
break;
|
||||
case 'route':
|
||||
address.route = component.long_name;
|
||||
break;
|
||||
case 'locality':
|
||||
address.locality = component.long_name;
|
||||
break;
|
||||
case 'sublocality_level_1':
|
||||
address.sublocality_level_1 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_3':
|
||||
address.administrative_area_level_3 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
address.administrative_area_level_1 = component.long_name;
|
||||
break;
|
||||
case 'country':
|
||||
address.country = component.long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
address.postal_code = component.long_name;
|
||||
break;
|
||||
// Add more cases as needed for other types
|
||||
}
|
||||
});
|
||||
let arr = [];
|
||||
// Event listener for autocomplete place changed
|
||||
autocomplete.addListener('place_changed', () => {
|
||||
const place = autocomplete.getPlace();
|
||||
setInputValue2(`${place.name}, ${place.formatted_address}`);
|
||||
console.log('new place', place); // Do something with the selected place
|
||||
console.log(' pick (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place
|
||||
// to trigger getDistance
|
||||
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
|
||||
setValue(place);
|
||||
setAddress(`${place.name} ${place.formatted_address}`);
|
||||
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
|
||||
const address = {
|
||||
address: `${place.name} ${place.formatted_address}`,
|
||||
street_number: '',
|
||||
route: '',
|
||||
locality: '',
|
||||
sublocality_level_1: '',
|
||||
administrative_area_level_3: '',
|
||||
administrative_area_level_1: '',
|
||||
country: '',
|
||||
postal_code: ''
|
||||
};
|
||||
place.address_components.forEach((component) => {
|
||||
component.types.forEach((type) => {
|
||||
switch (type) {
|
||||
case 'street_number':
|
||||
address.street_number = component.long_name;
|
||||
break;
|
||||
case 'route':
|
||||
address.route = component.long_name;
|
||||
break;
|
||||
case 'locality':
|
||||
address.locality = component.long_name;
|
||||
break;
|
||||
case 'sublocality_level_1':
|
||||
address.sublocality_level_1 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_3':
|
||||
address.administrative_area_level_3 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
address.administrative_area_level_1 = component.long_name;
|
||||
break;
|
||||
case 'country':
|
||||
address.country = component.long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
address.postal_code = component.long_name;
|
||||
break;
|
||||
// Add more cases as needed for other types
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Use address object as per your requirements
|
||||
setPickCust({
|
||||
...pickCust,
|
||||
address: address.address,
|
||||
doorno: `${address.street_number} ${address.route}`,
|
||||
suburb: address.sublocality_level_1,
|
||||
city: address.locality,
|
||||
postcode: address.postal_code,
|
||||
latitude: place.geometry.location.lat(),
|
||||
longitude: place.geometry.location.lng()
|
||||
});
|
||||
console.log('Pick Address:', address);
|
||||
});
|
||||
}
|
||||
}, [inputValue2]);
|
||||
// ============================================= || Google Maps Autocomplete(Drop) || =============================================
|
||||
// Use address object as per your requirements
|
||||
setPickCust({
|
||||
...pickCust,
|
||||
address: address.address,
|
||||
doorno: `${address.street_number} ${address.route}`,
|
||||
suburb: address.sublocality_level_1,
|
||||
city: address.locality,
|
||||
postcode: address.postal_code,
|
||||
latitude: place.geometry.location.lat(),
|
||||
longitude: place.geometry.location.lng()
|
||||
});
|
||||
};
|
||||
// ============================================= || Address Autocomplete (Drop) || =============================================
|
||||
|
||||
useEffect(() => {
|
||||
if (inputValue3) {
|
||||
// Initialize Google Maps Autocomplete
|
||||
const autocompleteInput = document.getElementById('addressAuto2');
|
||||
const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, {
|
||||
// types: ['(cities)'], // You can adjust the types parameter based on your requirements
|
||||
strictBounds: true,
|
||||
bounds: new window.google.maps.Circle({
|
||||
// center: new window.google.maps.LatLng(11.0050707, 76.9509083),
|
||||
center: new window.google.maps.LatLng(appLocaLat, appLocaLng),
|
||||
radius: appLocaRadius * 1000 //km to m
|
||||
// radius: 100000 //km to m
|
||||
}).getBounds()
|
||||
const handleDropPlaceSelected = (place) => {
|
||||
setInputValue3(`${place.name}, ${place.formatted_address}`);
|
||||
setEndPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
|
||||
setDropCust({ ...dropCust, address: `${place.name} ${place.formatted_address}` });
|
||||
const address = {
|
||||
address: `${place.name} ${place.formatted_address}`,
|
||||
street_number: '',
|
||||
route: '',
|
||||
locality: '',
|
||||
sublocality_level_1: '',
|
||||
administrative_area_level_3: '',
|
||||
administrative_area_level_1: '',
|
||||
country: '',
|
||||
postal_code: ''
|
||||
};
|
||||
place.address_components.forEach((component) => {
|
||||
component.types.forEach((type) => {
|
||||
switch (type) {
|
||||
case 'street_number':
|
||||
address.street_number = component.long_name;
|
||||
break;
|
||||
case 'route':
|
||||
address.route = component.long_name;
|
||||
break;
|
||||
case 'locality':
|
||||
address.locality = component.long_name;
|
||||
break;
|
||||
case 'sublocality_level_1':
|
||||
address.sublocality_level_1 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_3':
|
||||
address.administrative_area_level_3 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
address.administrative_area_level_1 = component.long_name;
|
||||
break;
|
||||
case 'country':
|
||||
address.country = component.long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
address.postal_code = component.long_name;
|
||||
break;
|
||||
// Add more cases as needed for other types
|
||||
}
|
||||
});
|
||||
let arr = [];
|
||||
// Event listener for autocomplete place changed
|
||||
autocomplete.addListener('place_changed', () => {
|
||||
const place = autocomplete.getPlace();
|
||||
setInputValue3(`${place.name}, ${place.formatted_address}`);
|
||||
console.log('new place', place); // Do something with the selected place
|
||||
console.log('drop (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place
|
||||
setEndPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
|
||||
setValue1(place);
|
||||
setAddress1(`${place.name} ${place.formatted_address}`);
|
||||
setDropCust({ ...dropCust, address: `${place.name} ${place.formatted_address}` });
|
||||
const address = {
|
||||
address: `${place.name} ${place.formatted_address}`,
|
||||
street_number: '',
|
||||
route: '',
|
||||
locality: '',
|
||||
sublocality_level_1: '',
|
||||
administrative_area_level_3: '',
|
||||
administrative_area_level_1: '',
|
||||
country: '',
|
||||
postal_code: ''
|
||||
};
|
||||
place.address_components.forEach((component) => {
|
||||
component.types.forEach((type) => {
|
||||
switch (type) {
|
||||
case 'street_number':
|
||||
address.street_number = component.long_name;
|
||||
break;
|
||||
case 'route':
|
||||
address.route = component.long_name;
|
||||
break;
|
||||
case 'locality':
|
||||
address.locality = component.long_name;
|
||||
break;
|
||||
case 'sublocality_level_1':
|
||||
address.sublocality_level_1 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_3':
|
||||
address.administrative_area_level_3 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
address.administrative_area_level_1 = component.long_name;
|
||||
break;
|
||||
case 'country':
|
||||
address.country = component.long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
address.postal_code = component.long_name;
|
||||
break;
|
||||
// Add more cases as needed for other types
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Use address object as per your requirements
|
||||
setDropCust({
|
||||
...dropCust,
|
||||
address: address.address,
|
||||
doorno: `${address.street_number} ${address.route}`,
|
||||
suburb: address.sublocality_level_1,
|
||||
city: address.locality,
|
||||
postcode: address.postal_code,
|
||||
latitude: place.geometry.location.lat(),
|
||||
longitude: place.geometry.location.lng()
|
||||
});
|
||||
console.log('Drop Address:', address);
|
||||
});
|
||||
}
|
||||
}, [inputValue3]);
|
||||
// Use address object as per your requirements
|
||||
setDropCust({
|
||||
...dropCust,
|
||||
address: address.address,
|
||||
doorno: `${address.street_number} ${address.route}`,
|
||||
suburb: address.sublocality_level_1,
|
||||
city: address.locality,
|
||||
postcode: address.postal_code,
|
||||
latitude: place.geometry.location.lat(),
|
||||
longitude: place.geometry.location.lng()
|
||||
});
|
||||
};
|
||||
|
||||
// ============================================= || gettenantlocations (branches) || =============================================
|
||||
const gettenantlocations = async () => {
|
||||
@@ -1609,35 +1289,39 @@ const Createorder1 = () => {
|
||||
<Stack spacing={1.25} sx={{ mt: 0 }}>
|
||||
{addId1 == 0 ? (
|
||||
<div>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
<AddressAutocomplete
|
||||
label="Address"
|
||||
disabled={!isLocation}
|
||||
id="addressAuto1"
|
||||
fullWidth
|
||||
value={inputValue2}
|
||||
onChange={(e) => setInputValue2(e.target.value)}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setInputValue2('');
|
||||
setPickCust({
|
||||
...pickCust,
|
||||
doorno: '',
|
||||
suburb: '',
|
||||
city: '',
|
||||
postcode: '',
|
||||
landmark: ''
|
||||
});
|
||||
setShowDistance(false);
|
||||
setStartPoint({ latitude: 0, longitude: 0 });
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)
|
||||
onChange={setInputValue2}
|
||||
onPlaceSelected={handlePickPlaceSelected}
|
||||
bias={{ lat: appLocaLat, lng: appLocaLng }}
|
||||
TextFieldProps={{
|
||||
variant: 'outlined',
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setInputValue2('');
|
||||
setPickCust({
|
||||
...pickCust,
|
||||
doorno: '',
|
||||
suburb: '',
|
||||
city: '',
|
||||
postcode: '',
|
||||
landmark: ''
|
||||
});
|
||||
setShowDistance(false);
|
||||
setStartPoint({ latitude: 0, longitude: 0 });
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -2021,34 +1705,38 @@ const Createorder1 = () => {
|
||||
<Stack spacing={1.25} sx={{ mt: 0 }}>
|
||||
{addId2 == 0 ? (
|
||||
<div>
|
||||
<TextField
|
||||
<AddressAutocomplete
|
||||
id="addressAuto2"
|
||||
disabled={!isLocation}
|
||||
label="Address"
|
||||
fullWidth
|
||||
value={inputValue3}
|
||||
onChange={(e) => setInputValue3(e.target.value)}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setInputValue3('');
|
||||
setDropCust({
|
||||
...dropCust,
|
||||
doorno: '',
|
||||
suburb: '',
|
||||
city: '',
|
||||
postcode: '',
|
||||
landmark: ''
|
||||
});
|
||||
setShowDistance(false);
|
||||
setEndPoint({ latitude: 0, longitude: 0 });
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)
|
||||
onChange={setInputValue3}
|
||||
onPlaceSelected={handleDropPlaceSelected}
|
||||
bias={{ lat: appLocaLat, lng: appLocaLng }}
|
||||
TextFieldProps={{
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setInputValue3('');
|
||||
setDropCust({
|
||||
...dropCust,
|
||||
doorno: '',
|
||||
suburb: '',
|
||||
city: '',
|
||||
postcode: '',
|
||||
landmark: ''
|
||||
});
|
||||
setShowDistance(false);
|
||||
setEndPoint({ latitude: 0, longitude: 0 });
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import LocationOnIcon from '@mui/icons-material/LocationOn';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import parse from 'autosuggest-highlight/parse';
|
||||
import { debounce } from '@mui/material/utils';
|
||||
|
||||
// This key was created specifically for the demo in mui.com.
|
||||
// You need to create a new one for your application.
|
||||
const GOOGLE_MAPS_API_KEY ='AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8';
|
||||
|
||||
function loadScript(src, position, id) {
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('async', '');
|
||||
script.setAttribute('id', id);
|
||||
script.src = src;
|
||||
position.appendChild(script);
|
||||
}
|
||||
|
||||
const autocompleteService = { current: null };
|
||||
|
||||
export default function GoogleMaps() {
|
||||
const [value, setValue] = React.useState(null);
|
||||
const [inputValue, setInputValue] = React.useState('');
|
||||
const [options, setOptions] = React.useState([]);
|
||||
const loaded = React.useRef(false);
|
||||
|
||||
if (typeof window !== 'undefined' && !loaded.current) {
|
||||
if (!document.querySelector('#google-maps')) {
|
||||
loadScript(
|
||||
`https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=places`,
|
||||
document.querySelector('head'),
|
||||
'google-maps',
|
||||
);
|
||||
}
|
||||
|
||||
loaded.current = true;
|
||||
}
|
||||
|
||||
const fetch = React.useMemo(
|
||||
() =>
|
||||
debounce((request, callback) => {
|
||||
autocompleteService.current.getPlacePredictions(request, callback);
|
||||
}, 400),
|
||||
[],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
if (!autocompleteService.current && window.google) {
|
||||
autocompleteService.current =
|
||||
new window.google.maps.places.AutocompleteService();
|
||||
}
|
||||
if (!autocompleteService.current) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (inputValue === '') {
|
||||
setOptions(value ? [value] : []);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
fetch({ input: inputValue }, (results) => {
|
||||
if (active) {
|
||||
let newOptions = [];
|
||||
|
||||
if (value) {
|
||||
newOptions = [value];
|
||||
}
|
||||
|
||||
if (results) {
|
||||
newOptions = [...newOptions, ...results];
|
||||
}
|
||||
|
||||
setOptions(newOptions);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [value, inputValue, fetch]);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
id="google-map-demo"
|
||||
// sx={{ width: 300 }}
|
||||
fullWidth
|
||||
getOptionLabel={(option) =>
|
||||
typeof option === 'string' ? option : option.description
|
||||
}
|
||||
filterOptions={(x) => x}
|
||||
options={options}
|
||||
autoComplete
|
||||
includeInputInList
|
||||
filterSelectedOptions
|
||||
value={value}
|
||||
noOptionsText="No locations"
|
||||
onChange={(event, newValue) => {
|
||||
setOptions(newValue ? [newValue, ...options] : options);
|
||||
setValue(newValue);
|
||||
}}
|
||||
onInputChange={(event, newInputValue) => {
|
||||
setInputValue(newInputValue);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params}
|
||||
// label="Add a location"
|
||||
placeholder='Address'
|
||||
|
||||
fullWidth />
|
||||
)}
|
||||
renderOption={(props, option) => {
|
||||
const matches =
|
||||
option.structured_formatting.main_text_matched_substrings || [];
|
||||
|
||||
const parts = parse(
|
||||
option.structured_formatting.main_text,
|
||||
matches.map((match) => [match.offset, match.offset + match.length]),
|
||||
);
|
||||
|
||||
return (
|
||||
<li {...props}>
|
||||
<Grid container alignItems="center">
|
||||
<Grid item sx={{ display: 'flex', width: 44 }}>
|
||||
<LocationOnIcon sx={{ color: 'text.secondary' }} />
|
||||
</Grid>
|
||||
<Grid item sx={{ width: 'calc(100% - 44px)', wordWrap: 'break-word' }}>
|
||||
{parts.map((part, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
component="span"
|
||||
sx={{ fontWeight: part.highlight ? 'bold' : 'regular' }}
|
||||
>
|
||||
{part.text}
|
||||
</Box>
|
||||
))}
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{option.structured_formatting.secondary_text}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,846 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react';
|
||||
import Loader from 'components/Loader';
|
||||
import { useEffect, useState, Fragment } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import MainCard from 'components/MainCard';
|
||||
import axios from 'axios';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import { SearchOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { Empty } from 'antd';
|
||||
import MyLocationIcon from '@mui/icons-material/MyLocation';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import dayjs from 'dayjs';
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
dayjs.extend(utc);
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { calculateDrivingDistance, calculateTotalCharge } from '../../../utils/distance';
|
||||
import { GoogleMap, LoadScript, Marker } from '@react-google-maps/api';
|
||||
|
||||
import {
|
||||
FormControl,
|
||||
InputAdornment,
|
||||
Grid,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
TextField,
|
||||
Autocomplete,
|
||||
Divider,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
Checkbox,
|
||||
DialogActions,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
OutlinedInput,
|
||||
FormGroup,
|
||||
FormControlLabel,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableCell,
|
||||
TableBody,
|
||||
TableRow,
|
||||
Paper,
|
||||
TableHead,
|
||||
Box
|
||||
} from '@mui/material';
|
||||
import CircularLoader from 'components/nearle_components/CircularLoader';
|
||||
// import RidersPinPointOSM from './RidersPinPointOSM';
|
||||
import RidersPinPoint from './ridersPinPoint';
|
||||
|
||||
const MultipleOrders = () => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [btnLoading, setBtnLoading] = useState(false);
|
||||
const [appId, setAppId] = useState(0);
|
||||
|
||||
const [tenantLocations, setTenantlocations] = useState([]);
|
||||
const userid = localStorage.getItem('userid');
|
||||
const tenId = localStorage.getItem('tenantid');
|
||||
const [tid, setTid] = useState(0);
|
||||
const [isLocation, setIsLocation] = useState(false);
|
||||
const [basePrice, setBasePrice] = useState(0);
|
||||
const [pricePerKm, setPricePerKm] = useState(0);
|
||||
const [minKm, setMinKm] = useState(0);
|
||||
const [pickCust, setPickCust] = useState(null);
|
||||
const [dropCust, setDropCust] = useState([]);
|
||||
const [isCustomerOpen, setIsCustomerOpen] = useState(false);
|
||||
const [searchCustList, setSearchCustList] = useState('');
|
||||
const [customerlist, setCustomerlist] = useState([]);
|
||||
const [startdate, setStartdate] = useState(dayjs().format('MM-DD-YYYY'));
|
||||
const [timeslotarr, setTimeslotarr] = useState([]);
|
||||
const [starttime, setStatrttime] = useState();
|
||||
const [endtime, setEndtime] = useState();
|
||||
const [alertmessage, setAlertmessage] = useState('');
|
||||
const [otherinstructions, setOtherinstructions] = useState('');
|
||||
const [admintoken, setAdmintoken] = useState();
|
||||
const [totaldist, settotaldist] = useState(0);
|
||||
const [totalAmt, settotalAmt] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [showMap, setShowMap] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dropCust && console.log('dropCust', dropCust);
|
||||
}, [dropCust]);
|
||||
|
||||
// =============================================== || opentoast || ===============================================
|
||||
const opentoast = (message, variant, time) => {
|
||||
enqueueSnackbar(message, {
|
||||
variant: variant,
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: time ? time : 1500
|
||||
});
|
||||
console.log(alertmessage);
|
||||
};
|
||||
// ==============================|| fetchAppLocations ||============================== //
|
||||
const fetchAppLocations = async () => {
|
||||
try {
|
||||
const locationRes = await axios.get(`${process.env.REACT_APP_URL}/partners/getlocations/?userid=${userid}`);
|
||||
console.log('fetchAppLocations', locationRes.data.details);
|
||||
} catch (err) {
|
||||
console.log('locationRes', err);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchAppLocations();
|
||||
}, []);
|
||||
|
||||
// ============================================= || fetchTenantPricing || =============================================
|
||||
|
||||
const fetchTenantPricing = async (id) => {
|
||||
try {
|
||||
const pricingResponse = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantpricing/?tenantid=${tenId}`);
|
||||
console.log('pricingResponse', pricingResponse.data.details);
|
||||
setBasePrice(pricingResponse.data.details.baseprice);
|
||||
setPricePerKm(pricingResponse.data.details.priceperkm);
|
||||
setMinKm(pricingResponse.data.details.minkm);
|
||||
} catch (error) {
|
||||
console.log('fetchTenantPricing error', error);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchTenantPricing();
|
||||
}, []);
|
||||
// ============================================= || gettenantlocations (branches) || =============================================
|
||||
const gettenantlocations = async (id) => {
|
||||
try {
|
||||
const res = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantlocations/?tenantid=${id}`);
|
||||
console.log('gettenantlocations', res.data.details);
|
||||
if (res.data.details.length == 1) {
|
||||
setIsLocation(true);
|
||||
setTenantlocations(res.data.details);
|
||||
setPickCust(res.data.details[0]);
|
||||
} else {
|
||||
setTenantlocations(res.data.details);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('gettenantlocations', err);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
gettenantlocations(tenId);
|
||||
}, []);
|
||||
// ========================================================= || clientdetails || =========================================================
|
||||
const clientdetails = async () => {
|
||||
try {
|
||||
let url =
|
||||
searchCustList == ''
|
||||
? `${process.env.REACT_APP_URL}/customers/gettenantcustomers/?tenantid=${tenId}&pageno=1&pagesize=10`
|
||||
: `${process.env.REACT_APP_URL}/customers/search/?tenantid=${tenId}&keyword=${searchCustList}`;
|
||||
await axios
|
||||
.get(url)
|
||||
.then((res) => {
|
||||
if (res.data.status) {
|
||||
console.log('clientdetails', res.data.details);
|
||||
|
||||
setCustomerlist(res.data.details);
|
||||
let arr = [];
|
||||
res.data.details.map((val) => {
|
||||
arr.push({
|
||||
label: `${val.firstname} | ${val.contactno}`,
|
||||
...val
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
opentoast('server error', 'warning');
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (tenId) {
|
||||
clientdetails();
|
||||
}
|
||||
}, [searchCustList.length > 3, searchCustList == '', tenId]);
|
||||
|
||||
// ========================================================= || calculateTotal(dist , charge) || =========================================================
|
||||
const calculateTotal = () => {
|
||||
let a1 = 0;
|
||||
let a2 = 0;
|
||||
dropCust?.map((customer) => {
|
||||
a1 += customer.distance;
|
||||
a2 += customer.totalcharge;
|
||||
});
|
||||
settotaldist(a1);
|
||||
settotalAmt(a2);
|
||||
};
|
||||
useEffect(() => {
|
||||
dropCust && calculateTotal();
|
||||
}, [dropCust]);
|
||||
|
||||
// ========================================================= || handleCheckboxChange || =========================================================
|
||||
const handleCheckboxChange = async (event, customer) => {
|
||||
setIsLoading(true);
|
||||
console.log('event', event.target.checked);
|
||||
console.log('customer', customer);
|
||||
if (event.target.checked) {
|
||||
// If the checkbox is checked, calculate the distance and add the customer
|
||||
try {
|
||||
const obj = await calculateDistance(customer);
|
||||
console.log('return of calculateDistance', obj);
|
||||
|
||||
const { roundedDistance, totalcharge } = obj;
|
||||
// Create a new customer object with the distance property
|
||||
const updatedCustomer = {
|
||||
...customer,
|
||||
distance: roundedDistance,
|
||||
totalcharge: totalcharge
|
||||
};
|
||||
|
||||
// Add the updated customer object to dropCust
|
||||
setDropCust((prevDropCust) => [...prevDropCust, updatedCustomer]);
|
||||
|
||||
// Log the rounded distance
|
||||
console.log(`Rounded Distance: ${roundedDistance} km`);
|
||||
} catch (error) {
|
||||
console.error('Failed to calculate distance:', error);
|
||||
}
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
// If the checkbox is unchecked, remove the customer from dropCust
|
||||
setDropCust((prevDropCust) => {
|
||||
return prevDropCust.filter((cust) => cust.customerid !== customer.customerid);
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================================= || calculateDistance || =========================================================
|
||||
const calculateDistance = async (customer) => {
|
||||
console.log('Distance calculation starts');
|
||||
try {
|
||||
const roundedDistance = await calculateDrivingDistance(pickCust, customer);
|
||||
const totalcharge = calculateTotalCharge(roundedDistance, basePrice, pricePerKm, minKm);
|
||||
return { roundedDistance, totalcharge };
|
||||
} catch (error) {
|
||||
console.error('Error calculating distance:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// ==================================================== || fetchTiming || ====================================================
|
||||
const fetchTiming = async () => {
|
||||
setLoading(true);
|
||||
await axios
|
||||
.get(`${process.env.REACT_APP_URL}/utils/getapplocations/?applocationid=${appId}`)
|
||||
.then((res) => {
|
||||
console.log('fetchTiming', res);
|
||||
const { opentime, closetime, latitude, longitude, radius } = res.data.details[0];
|
||||
if (res.data.status) {
|
||||
setStatrttime(`${dayjs().format('MM-DD-YYYY')} ${opentime}`);
|
||||
setEndtime(`${dayjs().format('MM-DD-YYYY')} ${closetime}`);
|
||||
console.log('starttime', `${dayjs().format('MM-DD-YYYY')} ${opentime}`);
|
||||
console.log('endtime', `${dayjs().format('MM-DD-YYYY')} ${closetime} `);
|
||||
let arr = [];
|
||||
for (
|
||||
let i = `${dayjs().format('MM-DD-YYYY')} ${opentime}`, j = 0;
|
||||
dayjs(`${dayjs().format('MM-DD-YYYY')} ${closetime} `).diff(i, 'm') >= 0;
|
||||
j++, i = dayjs(i).add(30, 'm')
|
||||
) {
|
||||
arr.push(i);
|
||||
}
|
||||
console.log('setTimeslotarr', arr);
|
||||
setTimeslotarr(arr);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (appId) {
|
||||
fetchTiming();
|
||||
}
|
||||
}, [starttime, endtime, appId]);
|
||||
|
||||
const fetchAppAdminTokens = async () => {
|
||||
setLoading(true);
|
||||
await axios
|
||||
.get(`${process.env.REACT_APP_URL}/utils/getapplocationconfig/?applocationid=${appId}`)
|
||||
.then((res) => {
|
||||
const userfcmtokemArray = res.data.details.applocationadmins.map((admin) => admin.userfcmtokem); // fcm => firebase cloud messaging
|
||||
console.log('fetchAppAdminTokens', res);
|
||||
console.log('userfcmtokemArray', userfcmtokemArray);
|
||||
if (res.data.status) {
|
||||
setAdmintoken(userfcmtokemArray);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (starttime && endtime) {
|
||||
fetchAppAdminTokens();
|
||||
}
|
||||
}, [starttime, endtime]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('pickCust', pickCust);
|
||||
}, [pickCust]);
|
||||
|
||||
// ==================================================== || fetchtenantinfo || ====================================================
|
||||
const fetchtenantinfo = async () => {
|
||||
setLoading(true);
|
||||
console.log('tid', tid);
|
||||
|
||||
await axios
|
||||
.get(`${process.env.REACT_APP_URL}/tenants/gettenantinfo/?tenantid=${tid}`)
|
||||
.then((res) => {
|
||||
console.log('fetchtenantinfo', res);
|
||||
if (res.data.status) {
|
||||
fetchAppAdminTokens();
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (tid) {
|
||||
fetchtenantinfo();
|
||||
}
|
||||
}, [tid]);
|
||||
// ================================================== || sendnotifications || ==================================================
|
||||
const sendnotifications = async () => {
|
||||
setLoading(true);
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_URL}/utils/sendnotifications`, {
|
||||
priority: 'high',
|
||||
registration_ids: admintoken,
|
||||
data: {
|
||||
accessid: process.env.REACT_APP_RIDER_ACCESS_ID
|
||||
},
|
||||
notification: {
|
||||
title: 'Nearle Merchant',
|
||||
body: 'An Order has been placed successfully,kindly process the same',
|
||||
sound: 'ring'
|
||||
}
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
if (res.data.message == 'Success') {
|
||||
enqueueSnackbar('Notification sent Successfully', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 1000
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
enqueueSnackbar(err.message, {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 1000
|
||||
});
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
// =============================================== || creategrouporders || ===============================================
|
||||
const creategrouporders = async () => {
|
||||
const arr = dropCust?.map((customer) => ({
|
||||
applocationid: pickCust.applocationid,
|
||||
cancellled: '',
|
||||
// categoryid: +tenant.categoryid,
|
||||
configid: 9,
|
||||
customerid: customer.customerid,
|
||||
deliveryaddress: customer.address || '',
|
||||
deliverycharge: +customer.totalcharge || 0,
|
||||
deliverycity: customer.city || '',
|
||||
deliverycontactno: customer.contactno || '',
|
||||
deliverycustomer: customer.firstname || '',
|
||||
deliveryid: +customer.customerid,
|
||||
deliverylandmark: customer.landmark || '',
|
||||
deliverylat: customer.latitude,
|
||||
deliverylocation: customer.suburb || '',
|
||||
deliverylocationid: customer.deliverylocationid || 0,
|
||||
deliverylong: customer.longitude,
|
||||
// deliverytime: `${dayjs(startdate).format('YYYY-MM-DD HH:mm:ss')} `,
|
||||
deliverytime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
deliverytype: 'B',
|
||||
delivered: '',
|
||||
itemcount: 1,
|
||||
kms: customer.distance.toString() || 0,
|
||||
locationid: +pickCust.locationid,
|
||||
moduleid: +pickCust.moduleid,
|
||||
orderamount: +customer.totalcharge || 0,
|
||||
ordercharges: 0.0,
|
||||
orderdate: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
orderheaderid: 0,
|
||||
orderid: '', //
|
||||
ordernotes: otherinstructions,
|
||||
orderstatus: 'created',
|
||||
ordervalue: +customer.totalcharge || 0,
|
||||
partnerid: pickCust.partnerid,
|
||||
partneruserid: +userid,
|
||||
paymentstatus: 1,
|
||||
paymenttype: 42,
|
||||
pending: '',
|
||||
pickupaddress: pickCust.address || '',
|
||||
pickupcity: pickCust.locationcity || '',
|
||||
pickupcontactno: pickCust.contactno || '',
|
||||
pickupcustomer: pickCust.locationname || '',
|
||||
pickuplandmark: pickCust.landmark || '',
|
||||
pickuplat: pickCust.latitude,
|
||||
pickuplocation: pickCust.suburb || '',
|
||||
pickuplocationid: pickCust.locationid || 0,
|
||||
pickuplong: pickCust.longitude,
|
||||
processing: '',
|
||||
ready: '',
|
||||
remarks: '',
|
||||
taxamount: 0.0,
|
||||
tenantid: pickCust.tenantid,
|
||||
tenantuserid: 0
|
||||
}));
|
||||
console.log('arr', arr);
|
||||
|
||||
if (!tenId) {
|
||||
opentoast('Choose Client ', 'warning');
|
||||
} else {
|
||||
setLoading(true);
|
||||
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_URL}/orders/createorders`, arr)
|
||||
.then((res) => {
|
||||
if (res.data.status) {
|
||||
enqueueSnackbar('Order Created Successfully', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 1000
|
||||
});
|
||||
if (admintoken) {
|
||||
// notifyadmin(admintoken);
|
||||
sendnotifications();
|
||||
}
|
||||
navigate('/nearle/orders');
|
||||
} else {
|
||||
opentoast(res.data.message, 'warning');
|
||||
}
|
||||
setLoading(false);
|
||||
console.log(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
// opentoast(err.data.message, 'warning');
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
console.log(arr);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <Loader />}
|
||||
{/* <RidersPinPointOSM /> */}
|
||||
<Grid container sx={{ mb: 2 }}>
|
||||
<Grid item xs={12} sm={3} md={6}>
|
||||
<Stack>
|
||||
<Typography variant="h3" whiteSpace="nowrap">
|
||||
Multiple Orders
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={9} md={6}>
|
||||
<Stack
|
||||
sx={{}}
|
||||
width={'100%'}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={2}
|
||||
justifyContent={'flex-end'}
|
||||
flexWrap={{ xs: 'wrap', custom550: 'nowrap' }}
|
||||
gap={2}
|
||||
>
|
||||
{/* Business Location */}
|
||||
<Stack sx={{ width: '100%' }}>
|
||||
{tenantLocations?.length === 1 ? (
|
||||
<TextField
|
||||
label="Business Location"
|
||||
fullWidth
|
||||
focused
|
||||
value={tenantLocations[0]?.locationname}
|
||||
InputProps={{
|
||||
style: { color: theme.palette.primary.main },
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<MyLocationIcon color="primary" />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Autocomplete
|
||||
fullWidth
|
||||
options={tenantLocations || []}
|
||||
getOptionLabel={(option) => `${option.locationname} (${option.suburb})`}
|
||||
onChange={(event, value, reason) => {
|
||||
if (value) {
|
||||
setTid(value.tenantid);
|
||||
setIsLocation(true);
|
||||
setPickCust(value);
|
||||
}
|
||||
if (reason === 'clear') setIsLocation(false);
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} label="Select Business Location" color="primary" fullWidth />}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{/* Date Picker */}
|
||||
<Stack sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<DatePicker
|
||||
format="DD-MM-YYYY"
|
||||
disablePast
|
||||
value={dayjs(startdate)}
|
||||
sx={{ width: 150 }}
|
||||
onChange={(e) => {
|
||||
let diff = dayjs().diff(dayjs(dayjs(e).format('YYYY-MM-DD')), 'd');
|
||||
|
||||
if (diff <= 0) {
|
||||
setStartdate(e);
|
||||
|
||||
let arr = [];
|
||||
timeslotarr.forEach((val) => {
|
||||
if (dayjs().diff(dayjs(`${dayjs(e).format('MM-DD-YYYY')} ${dayjs(val).format('HH:mm:ss')}`), 'm') <= 0) {
|
||||
arr.push(val);
|
||||
}
|
||||
});
|
||||
|
||||
if (arr[0]) {
|
||||
setOrderarr([
|
||||
{
|
||||
sno: 1,
|
||||
address: '',
|
||||
customerid: '',
|
||||
deliverytime: dayjs(arr[0]),
|
||||
deliverylocationid: '',
|
||||
clientname: '',
|
||||
contactno: '',
|
||||
latitude: '',
|
||||
longitude: ''
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
setOrderarr([]);
|
||||
}
|
||||
} else {
|
||||
opentoast('choose Upcoming Date', 'warning');
|
||||
setStartdate(NaN);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* ===================================================== || Pickup || ===================================================== */}
|
||||
{pickCust && (
|
||||
<TableContainer component={Paper} sx={{ mb: 2 }}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Pickup Location</TableCell>
|
||||
<TableCell>Address</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>{pickCust?.locationname}</TableCell>
|
||||
<TableCell>{pickCust?.address}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{/* ===================================================== || Drop || ===================================================== */}
|
||||
|
||||
<MainCard
|
||||
sx={{ height: '100%' }}
|
||||
title={`Drop (${dropCust?.length || 0})`}
|
||||
secondary={
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{
|
||||
'&:hover': {
|
||||
bgcolor: theme.palette.primary.main,
|
||||
color: 'white'
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!isLocation) {
|
||||
opentoast('Select Business Location', 'warning');
|
||||
} else {
|
||||
setIsCustomerOpen(true);
|
||||
setSearchCustList('');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Select Customers
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>S.No</TableCell>
|
||||
<TableCell>Customer</TableCell>
|
||||
<TableCell>Address</TableCell>
|
||||
<TableCell>Kms</TableCell>
|
||||
<TableCell align="right">Charge</TableCell>
|
||||
<TableCell>Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{!dropCust && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6}>
|
||||
<Empty description={' Drop Customers Not Selected'} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{dropCust?.map((customer, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
<TableCell>{customer.firstname}</TableCell>
|
||||
<TableCell>{customer.address}</TableCell>
|
||||
<TableCell>{customer.distance}</TableCell>
|
||||
<TableCell align="right">{`₹${customer.totalcharge}.00`}</TableCell>
|
||||
<TableCell align="center">
|
||||
{
|
||||
<CloseOutlined
|
||||
style={{ cursor: 'pointer', color: 'red' }}
|
||||
onClick={(event) => handleCheckboxChange(event, customer)}
|
||||
/>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{dropCust?.length != 0 && (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Typography variant="h5">Total</Typography>
|
||||
</TableCell>
|
||||
<TableCell></TableCell>
|
||||
<TableCell></TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="h5">{`${totaldist} `}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography variant="h5"> {`₹${totalAmt}.00`}</Typography>
|
||||
</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</MainCard>
|
||||
|
||||
{/* ================================================= || Riders Map || ================================================= */}
|
||||
|
||||
{/* {showMap && dropCust.length >= 1 && <RidersPinPoint pickCust={pickCust} dropCust={dropCust} />} */}
|
||||
|
||||
{/* ================================================= || Notes || ================================================= */}
|
||||
{dropCust && (
|
||||
<MainCard sx={{ mt: 2 }} title={'Notes'}>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
focused
|
||||
id="outlined-multiline-static"
|
||||
sx={{ width: '100%', height: '100%', mb: 2 }}
|
||||
multiline
|
||||
rows={1}
|
||||
placeholder="Notes"
|
||||
value={otherinstructions}
|
||||
onChange={(e) => setOtherinstructions(e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Stack direction="row" justifyContent={'end'} sx={{ mt: 2, width: '100%' }}>
|
||||
<Button
|
||||
disabled={dropCust?.length == 0}
|
||||
size="medium"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
setBtnLoading(true);
|
||||
creategrouporders();
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
setBtnLoading(false);
|
||||
}, 2000);
|
||||
}}
|
||||
sx={{
|
||||
'&:hover': {
|
||||
transform: 'scale(1.05)',
|
||||
transition: 'transform 0.3s ease'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{btnLoading ? <CircularProgress color="primary" size={20} thickness={10} /> : 'Create'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
)}
|
||||
|
||||
{/* ============================================= || saved address Dialog || ============================================= */}
|
||||
<Dialog
|
||||
open={isCustomerOpen}
|
||||
onClose={() => {
|
||||
setIsCustomerOpen(false);
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ minWidth: 'lg' }}
|
||||
>
|
||||
{isLoading && <CircularLoader />}
|
||||
<DialogTitle sx={{ bgcolor: theme.palette.primary.main, color: 'white' }}>
|
||||
<Stack>
|
||||
<Typography variant="h4"> {`Select Drop Customers (${dropCust?.length || 0})`}</Typography>
|
||||
<FormControl
|
||||
sx={{
|
||||
width: '100%',
|
||||
mt: 1
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2} sx={{ py: 0.2 }}>
|
||||
<OutlinedInput
|
||||
fullWidth
|
||||
id="input-search-header"
|
||||
placeholder="Search"
|
||||
value={searchCustList}
|
||||
onChange={(e) => setSearchCustList(e.target.value)}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-input': {
|
||||
p: '10.5px 0px 12px'
|
||||
},
|
||||
bgcolor: 'white'
|
||||
}}
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
<SearchOutlined style={{ fontSize: 'small' }} />
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={
|
||||
<IconButton
|
||||
sx={{ visibility: searchCustList ? 'visible' : 'hidden' }}
|
||||
onClick={() => {
|
||||
setSearchCustList('');
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</DialogTitle>
|
||||
<Divider />
|
||||
<DialogContent sx={{ p: 2.5 }}>
|
||||
{customerlist.length == 0 ? (
|
||||
<Stack spacing={2} direction={'row'} alignItems={'center'} justifyContent={'center'} sx={{ minHeight: 600, maxHeight: 600 }}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack spacing={2} sx={{ minHeight: 600, maxHeight: 600 }}>
|
||||
{customerlist &&
|
||||
customerlist.map((customer, index) => (
|
||||
<FormGroup key={index}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={dropCust?.some((cust) => cust.customerid === customer.customerid)} // Set the checked state of the checkbox based on whether the customer is in `dropCust`
|
||||
onChange={(event) => handleCheckboxChange(event, customer)}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<div style={{ width: '100%' }}>
|
||||
<Typography variant="subtitle1" sx={{ textAlign: 'left' }}>
|
||||
{`${customer.firstname} (${customer.contactno})`}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="secondary" sx={{ textAlign: 'left' }}>
|
||||
{customer.address}
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContent>
|
||||
<Divider />
|
||||
<DialogActions sx={{ p: 2.5 }}>
|
||||
<Button
|
||||
color={dropCust?.length !== 0 ? 'primary' : 'error'}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
'&:hover': {
|
||||
bgcolor: dropCust?.length !== 0 ? theme.palette.primary.main : theme.palette.error.main,
|
||||
color: 'white'
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsCustomerOpen(false);
|
||||
{
|
||||
dropCust?.length !== 0 && setShowMap(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{dropCust?.length !== 0 ? 'Continue' : 'Close'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultipleOrders;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { LoadScriptNext, GoogleMap, Marker } from '@react-google-maps/api';
|
||||
|
||||
// distance function
|
||||
function distance(lat1, lng1, lat2, lng2) {
|
||||
const R = 6371;
|
||||
const dLat = (lat2 - lat1) * (Math.PI / 180);
|
||||
const dLng = (lng2 - lng1) * (Math.PI / 180);
|
||||
|
||||
const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) * Math.sin(dLng / 2) ** 2;
|
||||
|
||||
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
}
|
||||
|
||||
const containerStyle = {
|
||||
width: '100%',
|
||||
height: '300px'
|
||||
};
|
||||
|
||||
export default function RidersPinPoint({ pickCust, dropCust }) {
|
||||
// Ensure valid lat/lng
|
||||
const center = pickCust?.latitude && pickCust?.longitude ? { lat: Number(pickCust.latitude), lng: Number(pickCust.longitude) } : null;
|
||||
|
||||
// If center missing, don't render map
|
||||
if (!center) return null;
|
||||
|
||||
const sortedRiders = dropCust
|
||||
?.map((r) => ({
|
||||
...r,
|
||||
distance: distance(center.lat, center.lng, Number(r.latitude), Number(r.longitude))
|
||||
}))
|
||||
.sort((a, b) => a.distance - b.distance);
|
||||
|
||||
return (
|
||||
<LoadScriptNext googleMapsApiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
|
||||
<GoogleMap mapContainerStyle={containerStyle} zoom={11} center={center}>
|
||||
<Marker position={center} icon={{ url: 'http://maps.google.com/mapfiles/ms/icons/purple-dot.png' }} />
|
||||
|
||||
{sortedRiders?.map((r, index) => (
|
||||
<Marker
|
||||
key={index}
|
||||
position={{ lat: Number(r.latitude), lng: Number(r.longitude) }}
|
||||
label={{
|
||||
text: (index + 1).toString(),
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</GoogleMap>
|
||||
</LoadScriptNext>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,94 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { LoadScriptNext, GoogleMap } from '@react-google-maps/api';
|
||||
import { DT } from 'themes/dt/tokens';
|
||||
|
||||
const containerStyle = {
|
||||
width: '100%',
|
||||
height: '90vh'
|
||||
};
|
||||
|
||||
const MapWithRouteGoogle = ({ coordinates, additionalProps, setMapOpen }) => {
|
||||
const mapRef = useRef(null);
|
||||
|
||||
/** Convert coordinates to numbers */
|
||||
const numericCoordinates = coordinates
|
||||
.map((c) => {
|
||||
const lat = Number(c.lat);
|
||||
const lng = Number(c.lng);
|
||||
return isNaN(lat) || isNaN(lng) ? null : { lat, lng };
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (numericCoordinates.length < 2) {
|
||||
return <div>No route data available</div>;
|
||||
}
|
||||
|
||||
const start = numericCoordinates[0];
|
||||
const end = numericCoordinates[numericCoordinates.length - 1];
|
||||
|
||||
/** Map loaded callback */
|
||||
const onMapLoad = (map) => {
|
||||
// draw markers
|
||||
new window.google.maps.Marker({
|
||||
position: start,
|
||||
map,
|
||||
label: 'S',
|
||||
title: `Start: ${additionalProps?.riderStart}`
|
||||
});
|
||||
|
||||
new window.google.maps.Marker({
|
||||
position: end,
|
||||
map,
|
||||
label: 'E',
|
||||
title: `End: ${additionalProps?.riderEnd}`
|
||||
});
|
||||
|
||||
// draw rider route (point-to-point)
|
||||
const route = new window.google.maps.Polyline({
|
||||
path: numericCoordinates,
|
||||
geodesic: false,
|
||||
strokeColor: DT.brand,
|
||||
strokeOpacity: 1.0,
|
||||
strokeWeight: 4
|
||||
});
|
||||
|
||||
route.setMap(map);
|
||||
|
||||
// auto fit
|
||||
const bounds = new window.google.maps.LatLngBounds();
|
||||
numericCoordinates.forEach((p) => bounds.extend(p));
|
||||
map.fitBounds(bounds);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setMapOpen(false)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
zIndex: 999,
|
||||
padding: '6px 12px',
|
||||
background: DT.brand,
|
||||
color: 'white',
|
||||
borderRadius: DT.radiusInner,
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
fontWeight: 600,
|
||||
boxShadow: DT.shadowMd
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
||||
<LoadScriptNext googleMapsApiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
|
||||
<GoogleMap mapContainerStyle={containerStyle} center={start} zoom={14} onLoad={onMapLoad}>
|
||||
{/* Polyline and markers added via onLoad */}
|
||||
</GoogleMap>
|
||||
</LoadScriptNext>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapWithRouteGoogle;
|
||||
@@ -1,10 +1,46 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { GoogleMap, Polyline, Marker, InfoWindow, useJsApiLoader } from '@react-google-maps/api';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { MapContainer, TileLayer, Polyline, Marker, Popup, useMap } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { Box, IconButton, Stack, Typography, CircularProgress } from '@mui/material';
|
||||
import { MdClose, MdRoute } from 'react-icons/md';
|
||||
|
||||
const containerStyle = { width: '100%', height: '100%' };
|
||||
|
||||
// Numbered step icon — brand red to match the planned-route polyline below.
|
||||
// Drawn fresh per render as a data URL so the step number can be baked into
|
||||
// the SVG without juggling external marker assets.
|
||||
const stepIcon = (n, isFocused) => {
|
||||
const size = isFocused ? 38 : 32;
|
||||
const color = isFocused ? '#910E1D' : '#C01227';
|
||||
const svg = encodeURIComponent(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="${size}" height="${size}">` +
|
||||
`<circle cx="16" cy="16" r="14" fill="${color}" stroke="white" stroke-width="3"/>` +
|
||||
`<text x="16" y="21" text-anchor="middle" font-family="Arial,sans-serif" font-size="14" font-weight="700" fill="white">${n}</text>` +
|
||||
`</svg>`
|
||||
);
|
||||
return new L.Icon({
|
||||
iconUrl: `data:image/svg+xml;charset=UTF-8,${svg}`,
|
||||
iconSize: [size, size],
|
||||
iconAnchor: [size / 2, size / 2]
|
||||
});
|
||||
};
|
||||
|
||||
// Fits the map to the planned path once both the map and data are ready.
|
||||
// Re-runs whenever the route changes (different rider / date).
|
||||
const FitBoundsController = ({ dropPath }) => {
|
||||
const map = useMap();
|
||||
useEffect(() => {
|
||||
if (!dropPath.length) return;
|
||||
if (dropPath.length === 1) {
|
||||
map.setView(dropPath[0], 14);
|
||||
} else {
|
||||
map.fitBounds(dropPath, { padding: [48, 48] });
|
||||
}
|
||||
}, [dropPath, map]);
|
||||
return null;
|
||||
};
|
||||
|
||||
// Renders a single rider's PLANNED route for the date range chosen on the
|
||||
// Riders Summary page. `details` is an ordered array of waypoints (sorted by
|
||||
// the planning step number) shaped as:
|
||||
@@ -13,84 +49,38 @@ const containerStyle = { width: '100%', height: '100%' };
|
||||
// `dropLat/dropLng` are required; pickup coords are optional and rendered as
|
||||
// faded pre-stops if present.
|
||||
export default function RidersRoutes({ details, loading, riderName, dateRange, onClose }) {
|
||||
const mapRef = useRef(null);
|
||||
const [focusedStep, setFocusedStep] = useState(null);
|
||||
const [routePath, setRoutePath] = useState([]);
|
||||
const [routeLoading, setRouteLoading] = useState(false);
|
||||
|
||||
const { isLoaded } = useJsApiLoader({
|
||||
googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_KEY
|
||||
});
|
||||
|
||||
// Step-pin coordinates in planning order — what the polyline connects.
|
||||
const dropPath = useMemo(
|
||||
() => (details || []).map((d) => ({ lat: d.dropLat, lng: d.dropLng })),
|
||||
[details]
|
||||
);
|
||||
|
||||
// Auto-fit map bounds to the full planned path once the map and data are
|
||||
// both ready. Re-runs whenever the route changes (different rider / date).
|
||||
useEffect(() => {
|
||||
if (!isLoaded || !mapRef.current || dropPath.length === 0) return;
|
||||
const bounds = new window.google.maps.LatLngBounds();
|
||||
dropPath.forEach((p) => bounds.extend(p));
|
||||
mapRef.current.fitBounds(bounds, 48);
|
||||
}, [isLoaded, dropPath]);
|
||||
const dropPath = useMemo(() => (details || []).map((d) => [d.dropLat, d.dropLng]), [details]);
|
||||
|
||||
// Resolve the rider's planned waypoints into an actual road-following path
|
||||
// via the Directions API. Without this, the polyline would cut across
|
||||
// buildings / aerial lines — operators have no way to read the real route.
|
||||
// Directions has a 25-waypoint limit per request, so we chunk and stitch.
|
||||
// via OSRM. Without this, the polyline would cut across buildings / aerial
|
||||
// lines — operators have no way to read the real route.
|
||||
useEffect(() => {
|
||||
if (!isLoaded || dropPath.length < 2) {
|
||||
if (dropPath.length < 2) {
|
||||
setRoutePath([]);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
const ds = new window.google.maps.DirectionsService();
|
||||
const MAX_WPS = 23; // origin + 23 waypoints + destination = 25 stops/chunk
|
||||
|
||||
const fetchSegment = (origin, destination, waypoints) =>
|
||||
new Promise((resolve, reject) => {
|
||||
ds.route(
|
||||
{
|
||||
origin,
|
||||
destination,
|
||||
waypoints: waypoints.map((p) => ({ location: p, stopover: true })),
|
||||
travelMode: window.google.maps.TravelMode.DRIVING
|
||||
},
|
||||
(result, status) => {
|
||||
if (status === 'OK') resolve(result);
|
||||
else reject(new Error(status));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
(async () => {
|
||||
setRouteLoading(true);
|
||||
try {
|
||||
const points = dropPath;
|
||||
const all = [];
|
||||
let i = 0;
|
||||
while (i < points.length - 1) {
|
||||
const remaining = points.length - 1 - i;
|
||||
const take = Math.min(remaining, MAX_WPS + 1);
|
||||
const origin = points[i];
|
||||
const destination = points[i + take];
|
||||
const waypoints = points.slice(i + 1, i + take);
|
||||
const res = await fetchSegment(origin, destination, waypoints);
|
||||
const seg = res.routes[0].overview_path.map((ll) => ({
|
||||
lat: ll.lat(),
|
||||
lng: ll.lng()
|
||||
}));
|
||||
// Avoid duplicating the join point between adjacent chunks.
|
||||
if (all.length > 0 && seg.length > 0) seg.shift();
|
||||
all.push(...seg);
|
||||
i += take;
|
||||
const coords = dropPath.map(([lat, lng]) => `${lng},${lat}`).join(';');
|
||||
const url = `https://router.project-osrm.org/route/v1/driving/${coords}?overview=full&geometries=geojson`;
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
if (!cancelled && data.routes?.length) {
|
||||
const points = data.routes[0].geometry.coordinates.map(([lng, lat]) => [lat, lng]);
|
||||
setRoutePath(points);
|
||||
} else if (!cancelled) {
|
||||
setRoutePath([]);
|
||||
}
|
||||
if (!cancelled) setRoutePath(all);
|
||||
} catch {
|
||||
// Fall back to the straight-line skeleton on failure (quota, no route, etc.).
|
||||
} catch (e) {
|
||||
console.warn('OSRM route error:', e);
|
||||
if (!cancelled) setRoutePath([]);
|
||||
} finally {
|
||||
if (!cancelled) setRouteLoading(false);
|
||||
@@ -100,22 +90,7 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [isLoaded, dropPath]);
|
||||
|
||||
// Numbered step icon as a data URL — drawn fresh per render so we can pass
|
||||
// the step number into the SVG without juggling external assets. Color is
|
||||
// brand purple to match the planned-route polyline below.
|
||||
const stepIcon = (n, isFocused) => {
|
||||
const size = isFocused ? 38 : 32;
|
||||
const color = isFocused ? '#910E1D' : '#C01227';
|
||||
const svg = encodeURIComponent(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="${size}" height="${size}">` +
|
||||
`<circle cx="16" cy="16" r="14" fill="${color}" stroke="white" stroke-width="3"/>` +
|
||||
`<text x="16" y="21" text-anchor="middle" font-family="Arial,sans-serif" font-size="14" font-weight="700" fill="white">${n}</text>` +
|
||||
`</svg>`
|
||||
);
|
||||
return `data:image/svg+xml;charset=UTF-8,${svg}`;
|
||||
};
|
||||
}, [dropPath]);
|
||||
|
||||
const headerBar = (
|
||||
<Stack
|
||||
@@ -133,12 +108,8 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
|
||||
>
|
||||
<MdRoute size={20} />
|
||||
<Stack sx={{ flex: 1, minWidth: 0 }}>
|
||||
<Typography sx={{ fontWeight: 700, fontSize: 15, lineHeight: 1.2 }}>
|
||||
Planned route{riderName ? ` — ${riderName}` : ''}
|
||||
</Typography>
|
||||
{dateRange && (
|
||||
<Typography sx={{ fontSize: 12, opacity: 0.85 }}>{dateRange}</Typography>
|
||||
)}
|
||||
<Typography sx={{ fontWeight: 700, fontSize: 15, lineHeight: 1.2 }}>Planned route{riderName ? ` — ${riderName}` : ''}</Typography>
|
||||
{dateRange && <Typography sx={{ fontSize: 12, opacity: 0.85 }}>{dateRange}</Typography>}
|
||||
</Stack>
|
||||
{details && details.length > 0 && (
|
||||
<Typography sx={{ fontSize: 12, opacity: 0.9, fontWeight: 600 }}>
|
||||
@@ -154,16 +125,14 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
|
||||
</Stack>
|
||||
);
|
||||
|
||||
// Loading state — route fetch in flight OR Google Maps script not ready yet.
|
||||
if (loading || !isLoaded) {
|
||||
// Loading state — parent is still fetching the planned route data.
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{headerBar}
|
||||
<Stack alignItems="center" justifyContent="center" sx={{ flex: 1, gap: 1.5 }}>
|
||||
<CircularProgress size={32} />
|
||||
<Typography sx={{ color: '#64748b', fontSize: 13 }}>
|
||||
{loading ? 'Loading planned route…' : 'Loading map…'}
|
||||
</Typography>
|
||||
<Typography sx={{ color: '#64748b', fontSize: 13 }}>Loading planned route…</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
@@ -176,9 +145,7 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{headerBar}
|
||||
<Stack alignItems="center" justifyContent="center" sx={{ flex: 1, gap: 1, p: 3 }}>
|
||||
<Typography sx={{ color: '#1e293b', fontWeight: 700, fontSize: 16 }}>
|
||||
No planned route for this rider
|
||||
</Typography>
|
||||
<Typography sx={{ color: '#1e293b', fontWeight: 700, fontSize: 16 }}>No planned route for this rider</Typography>
|
||||
<Typography sx={{ color: '#64748b', fontSize: 13, textAlign: 'center', maxWidth: 360 }}>
|
||||
There are no deliveries with drop coordinates assigned to this rider for the selected date range.
|
||||
</Typography>
|
||||
@@ -191,53 +158,21 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{headerBar}
|
||||
<Box sx={{ flex: 1, minHeight: 0 }}>
|
||||
<GoogleMap
|
||||
mapContainerStyle={containerStyle}
|
||||
onLoad={(map) => (mapRef.current = map)}
|
||||
center={dropPath[0]}
|
||||
zoom={14}
|
||||
options={{
|
||||
streetViewControl: false,
|
||||
mapTypeControl: false,
|
||||
fullscreenControl: false
|
||||
}}
|
||||
>
|
||||
<MapContainer center={dropPath[0]} zoom={14} style={containerStyle} zoomControl={false}>
|
||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution="© OpenStreetMap contributors" />
|
||||
<FitBoundsController dropPath={dropPath} />
|
||||
|
||||
{routePath.length > 0 ? (
|
||||
<>
|
||||
{/* Translucent backdrop so the route stays legible on busy tiles. */}
|
||||
<Polyline
|
||||
path={routePath}
|
||||
options={{ strokeColor: '#C01227', strokeOpacity: 0.25, strokeWeight: 8 }}
|
||||
/>
|
||||
{/* Road-following planned route from the Directions API. */}
|
||||
<Polyline
|
||||
path={routePath}
|
||||
options={{ strokeColor: '#C01227', strokeOpacity: 0.95, strokeWeight: 4 }}
|
||||
/>
|
||||
<Polyline positions={routePath} pathOptions={{ color: '#C01227', opacity: 0.25, weight: 8 }} />
|
||||
{/* Road-following planned route from OSRM. */}
|
||||
<Polyline positions={routePath} pathOptions={{ color: '#C01227', opacity: 0.95, weight: 4 }} />
|
||||
</>
|
||||
) : (
|
||||
// Fallback while Directions is in flight (or if it fails) — dashed
|
||||
// Fallback while OSRM is in flight (or if it fails) — dashed
|
||||
// straight-line skeleton between drop pins in step order.
|
||||
<Polyline
|
||||
path={dropPath}
|
||||
options={{
|
||||
strokeColor: '#C01227',
|
||||
strokeOpacity: 0,
|
||||
strokeWeight: 0,
|
||||
icons: [
|
||||
{
|
||||
icon: {
|
||||
path: 'M 0,-1 0,1',
|
||||
strokeOpacity: 0.6,
|
||||
strokeColor: '#C01227',
|
||||
scale: 3
|
||||
},
|
||||
offset: '0',
|
||||
repeat: '14px'
|
||||
}
|
||||
]
|
||||
}}
|
||||
/>
|
||||
<Polyline positions={dropPath} pathOptions={{ color: '#C01227', opacity: 0.6, weight: 3, dashArray: '2 10', lineCap: 'round' }} />
|
||||
)}
|
||||
|
||||
{details.map((d, i) => {
|
||||
@@ -246,39 +181,29 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
|
||||
return (
|
||||
<Marker
|
||||
key={`step-${d.deliveryid || d.orderid || i}`}
|
||||
position={{ lat: d.dropLat, lng: d.dropLng }}
|
||||
icon={{ url: stepIcon(stepNum, isFocused) }}
|
||||
onClick={() => setFocusedStep(isFocused ? null : d.deliveryid)}
|
||||
zIndex={isFocused ? 1000 : stepNum}
|
||||
position={[d.dropLat, d.dropLng]}
|
||||
icon={stepIcon(stepNum, isFocused)}
|
||||
eventHandlers={{
|
||||
click: () => setFocusedStep(isFocused ? null : d.deliveryid)
|
||||
}}
|
||||
zIndexOffset={isFocused ? 1000 : stepNum}
|
||||
>
|
||||
{isFocused && (
|
||||
<InfoWindow onCloseClick={() => setFocusedStep(null)}>
|
||||
<Box sx={{ minWidth: 180, fontFamily: 'inherit' }}>
|
||||
<Typography sx={{ fontWeight: 800, fontSize: 13, color: '#0f172a' }}>
|
||||
Step {stepNum} · {d.customer}
|
||||
</Typography>
|
||||
{d.address && (
|
||||
<Typography sx={{ fontSize: 12, color: '#475569', mt: 0.5 }}>
|
||||
{d.address}
|
||||
</Typography>
|
||||
)}
|
||||
{d.expectedTime && (
|
||||
<Typography sx={{ fontSize: 12, color: '#64748b', mt: 0.5 }}>
|
||||
ETA {String(d.expectedTime).slice(11, 16) || d.expectedTime}
|
||||
</Typography>
|
||||
)}
|
||||
{d.orderid && (
|
||||
<Typography sx={{ fontSize: 11, color: '#94a3b8', mt: 0.5 }}>
|
||||
Order #{d.orderid}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</InfoWindow>
|
||||
)}
|
||||
<Popup onClose={() => setFocusedStep(null)}>
|
||||
<Box sx={{ minWidth: 180, fontFamily: 'inherit' }}>
|
||||
<Typography sx={{ fontWeight: 800, fontSize: 13, color: '#0f172a' }}>
|
||||
Step {stepNum} · {d.customer}
|
||||
</Typography>
|
||||
{d.address && <Typography sx={{ fontSize: 12, color: '#475569', mt: 0.5 }}>{d.address}</Typography>}
|
||||
{d.expectedTime && (
|
||||
<Typography sx={{ fontSize: 12, color: '#64748b', mt: 0.5 }}>ETA {String(d.expectedTime).slice(11, 16) || d.expectedTime}</Typography>
|
||||
)}
|
||||
{d.orderid && <Typography sx={{ fontSize: 11, color: '#94a3b8', mt: 0.5 }}>Order #{d.orderid}</Typography>}
|
||||
</Box>
|
||||
</Popup>
|
||||
</Marker>
|
||||
);
|
||||
})}
|
||||
</GoogleMap>
|
||||
</MapContainer>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ import { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import Loader from 'components/Loader';
|
||||
import Transitions from 'components/@extended/Transitions';
|
||||
import Autocomplete from 'react-google-autocomplete';
|
||||
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -63,8 +63,6 @@ import TablePagination from '@mui/material/TablePagination';
|
||||
import TableSortLabel from '@mui/material/TableSortLabel';
|
||||
import { visuallyHidden } from '@mui/utils';
|
||||
|
||||
import Geocode from 'react-geocode';
|
||||
|
||||
const Requests = () => {
|
||||
// let dispatch = useDispatch();
|
||||
|
||||
@@ -204,7 +202,6 @@ const Requests = () => {
|
||||
const [suburb, setSuburb] = useState('');
|
||||
const [currenttenantid] = useState('');
|
||||
const [latlong, setLatlong] = useState({});
|
||||
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
|
||||
const [alertmessage, setAlertmessage] = useState('');
|
||||
// const [toast, setToast] = useState(false);
|
||||
const [rolesarr, setRolesarr] = useState([]);
|
||||
@@ -286,27 +283,34 @@ const Requests = () => {
|
||||
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
Geocode.fromAddress(address).then(
|
||||
(response) => {
|
||||
if (response.status == 'OK') {
|
||||
const { lat, lng } = response.results[0].geometry.location;
|
||||
setLatlong({
|
||||
lat,
|
||||
lng
|
||||
});
|
||||
console.log(response);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log(error);
|
||||
const handleAddressPlaceSelected = (place) => {
|
||||
setAddress(place.formatted_address);
|
||||
setLatlong({ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() });
|
||||
let city1, state, zipcode1, suburb1;
|
||||
place.address_components.forEach((component) => {
|
||||
component.types.forEach((type) => {
|
||||
switch (type) {
|
||||
case 'locality':
|
||||
city1 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
state = component.long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
zipcode1 = component.long_name;
|
||||
break;
|
||||
case 'sublocality':
|
||||
case 'sublocality_level_1':
|
||||
suburb1 = component.long_name;
|
||||
break;
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}, [address]);
|
||||
});
|
||||
});
|
||||
setCity(city1 || '');
|
||||
setState1(state || '');
|
||||
setZipcode(zipcode1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log('rolesarr');
|
||||
@@ -1467,53 +1471,22 @@ const Requests = () => {
|
||||
|
||||
{/* } */}
|
||||
|
||||
<Autocomplete
|
||||
className="automap"
|
||||
apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '40px',
|
||||
borderRadius: '5px',
|
||||
border: '1px solid #e0e0e0',
|
||||
textIndent: '10px',
|
||||
outline: 'none'
|
||||
// ':hover': {
|
||||
// border: '1px solid #00b0ff !important',
|
||||
// backgroundColor:'blue'
|
||||
// }
|
||||
}}
|
||||
onPlaceSelected={(place) => {
|
||||
setAddress(place.formatted_address);
|
||||
let city1, state, zipcode1, suburb1;
|
||||
for (let i = 0; i < place.address_components.length; i++) {
|
||||
for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
switch (place.address_components[i].types[j]) {
|
||||
case 'locality':
|
||||
city1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
state = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
zipcode1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'sublocality':
|
||||
suburb1 = place.address_components[i].long_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setCity(city1 || '');
|
||||
setState1(state || '');
|
||||
setZipcode(zipcode1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
}}
|
||||
options={{
|
||||
types: ['address' || 'geocode']
|
||||
}}
|
||||
<AddressAutocomplete
|
||||
id="request-address-autocomplete"
|
||||
fullWidth
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
onChange={setAddress}
|
||||
onPlaceSelected={handleAddressPlaceSelected}
|
||||
TextFieldProps={{
|
||||
className: 'automap',
|
||||
InputProps: {
|
||||
style: {
|
||||
borderRadius: '5px',
|
||||
border: '1px solid #e0e0e0'
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
@@ -13,12 +13,10 @@ import { DT, tint } from 'themes/dt/tokens';
|
||||
import MainCard from 'components/MainCard';
|
||||
import axios from 'axios';
|
||||
// assets
|
||||
import { usePlacesWidget } from 'react-google-autocomplete';
|
||||
import AddressAutocomplete, { geocodeAddress } from 'components/nearle_components/AddressAutocomplete';
|
||||
import Loader from 'components/Loader';
|
||||
import Geocode from 'react-geocode';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router';
|
||||
// import { setLocationType } from 'react-geocode';
|
||||
|
||||
// const avatarImage = require.context('assets/images/users', true);
|
||||
|
||||
@@ -52,9 +50,6 @@ const Createrider = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
|
||||
// Geocode.setApiKey('AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8');
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -66,25 +61,15 @@ const Createrider = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
Geocode.fromAddress(address).then(
|
||||
(response) => {
|
||||
if (response.status == 'OK') {
|
||||
const { lat, lng } = response.results[0].geometry.location;
|
||||
setLatlong({
|
||||
lat,
|
||||
lng
|
||||
});
|
||||
console.log(response);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
let active = true;
|
||||
geocodeAddress(address).then((place) => {
|
||||
if (active && place) {
|
||||
setLatlong({ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() });
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [address]);
|
||||
|
||||
const opentoast = (message) => {
|
||||
@@ -115,45 +100,33 @@ const Createrider = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const { ref: materialRef } = usePlacesWidget({
|
||||
apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
|
||||
onPlaceSelected: (place) => {
|
||||
console.log(place);
|
||||
|
||||
setAddress(place.formatted_address);
|
||||
let city1, zipcode1, state1, suburb1;
|
||||
for (let i = 0; i < place.address_components.length; i++) {
|
||||
for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
switch (place.address_components[i].types[j]) {
|
||||
case 'locality':
|
||||
city1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
state1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
zipcode1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'sublocality':
|
||||
suburb1 = place.address_components[i].long_name;
|
||||
break;
|
||||
}
|
||||
const handleAddressPlaceSelected = (place) => {
|
||||
setAddress(place.formatted_address);
|
||||
let city1, zipcode1, state1, suburb1;
|
||||
for (let i = 0; i < place.address_components.length; i++) {
|
||||
for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
switch (place.address_components[i].types[j]) {
|
||||
case 'locality':
|
||||
city1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
state1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
zipcode1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'sublocality':
|
||||
case 'sublocality_level_1':
|
||||
suburb1 = place.address_components[i].long_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setCity(city1 || '');
|
||||
setState(state1 || '');
|
||||
setZipcode(zipcode1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
|
||||
// setAddress(place.formatted_address)
|
||||
},
|
||||
// inputAutocompleteValue: "country",
|
||||
options: {
|
||||
// componentRestrictions: 'us',
|
||||
// types: ["establishment"]
|
||||
types: ['address' || 'geocode']
|
||||
}
|
||||
});
|
||||
setCity(city1 || '');
|
||||
setState(state1 || '');
|
||||
setZipcode(zipcode1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
};
|
||||
|
||||
const createprofile = async () => {
|
||||
if (!firstname) {
|
||||
@@ -339,14 +312,13 @@ const Createrider = () => {
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-address">Address</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
|
||||
<AddressAutocomplete
|
||||
id="personal-address"
|
||||
fullWidth
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
inputRef={materialRef}
|
||||
onChange={setAddress}
|
||||
onPlaceSelected={handleAddressPlaceSelected}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
@@ -31,9 +31,8 @@ import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
// project import
|
||||
import MainCard from 'components/MainCard';
|
||||
import axios from 'axios';
|
||||
import { usePlacesWidget } from 'react-google-autocomplete';
|
||||
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
|
||||
import Loader from 'components/Loader';
|
||||
import Geocode from 'react-geocode';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import dayjs from 'dayjs';
|
||||
import CircularLoader from 'components/CircularLoader';
|
||||
@@ -58,8 +57,6 @@ const EditRider = () => {
|
||||
const [locaName, setLocoName] = useState();
|
||||
const userid = localStorage.getItem('userid');
|
||||
|
||||
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchRiderData = async (id) => {
|
||||
@@ -231,45 +228,30 @@ const EditRider = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const { ref: materialRef } = usePlacesWidget({
|
||||
apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
|
||||
onPlaceSelected: (place) => {
|
||||
console.log(place);
|
||||
setAddress(place.formatted_address);
|
||||
const handleAddressPlaceSelected = (place) => {
|
||||
setAddress(place.formatted_address);
|
||||
|
||||
let city1, zipcode1, state1, suburb1;
|
||||
for (let i = 0; i < place.address_components.length; i++) {
|
||||
for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
switch (place.address_components[i].types[j]) {
|
||||
case 'locality':
|
||||
city1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
state1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
zipcode1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'sublocality':
|
||||
suburb1 = place.address_components[i].long_name;
|
||||
break;
|
||||
}
|
||||
let city1, state1, suburb1;
|
||||
for (let i = 0; i < place.address_components.length; i++) {
|
||||
for (let j = 0; j < place.address_components[i].types.length; j++) {
|
||||
switch (place.address_components[i].types[j]) {
|
||||
case 'locality':
|
||||
city1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
state1 = place.address_components[i].long_name;
|
||||
break;
|
||||
case 'sublocality':
|
||||
case 'sublocality_level_1':
|
||||
suburb1 = place.address_components[i].long_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setCity(city1 || '');
|
||||
|
||||
setState(state1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
|
||||
// setAddress(place.formatted_address)
|
||||
},
|
||||
// inputAutocompleteValue: "country",
|
||||
options: {
|
||||
// componentRestrictions: 'us',
|
||||
// types: ["establishment"]
|
||||
types: ['address' || 'geocode']
|
||||
}
|
||||
});
|
||||
setCity(city1 || '');
|
||||
setState(state1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
};
|
||||
|
||||
const updateRider = async () => {
|
||||
setLoading(true);
|
||||
@@ -487,16 +469,13 @@ const EditRider = () => {
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-address">Address</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
|
||||
<AddressAutocomplete
|
||||
id="personal-address"
|
||||
fullWidth
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={(e) => {
|
||||
setAddress(e.target.value);
|
||||
}}
|
||||
inputRef={materialRef}
|
||||
onChange={setAddress}
|
||||
onPlaceSelected={handleAddressPlaceSelected}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect, useRef, Fragment } from 'react';
|
||||
import Geocode from 'react-geocode';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
@@ -491,8 +490,6 @@ const Riders = () => {
|
||||
}
|
||||
});
|
||||
|
||||
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
|
||||
|
||||
const handleChangetab = (i) => {
|
||||
setTabvalue(i);
|
||||
setLogsRow(null);
|
||||
|
||||
Reference in New Issue
Block a user