overall updates

This commit is contained in:
joshikannan
2025-11-26 18:24:03 +05:30
parent 12df2e9dc4
commit e71e44319c
35 changed files with 3145 additions and 2404 deletions

View File

@@ -0,0 +1,78 @@
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
// distance function (same)
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 center = [11.015181, 76.953682];
const riders = [
{ id: 1, lat: 11.04362, lng: 76.924667 },
{ id: 2, lat: 11.00988, lng: 76.949966 },
{ id: 3, lat: 11.020983, lng: 76.966331 }
];
export default function RidersPinPointOSM() {
const sortedRiders = riders
.map((r) => ({
...r,
distance: distance(center[0], center[1], r.lat, r.lng)
}))
.sort((a, b) => a.distance - b.distance);
// purple center marker
const centerIcon = L.icon({
iconUrl: 'https://maps.google.com/mapfiles/ms/icons/purple-dot.png',
iconSize: [32, 32]
});
// basic numbered marker icon
const createMarkerIcon = (number) =>
L.divIcon({
className: 'custom-marker',
html: `
<div style="
background:#007bff;
color:white;
width:28px;
height:28px;
border-radius:50%;
display:flex;
align-items:center;
justify-content:center;
font-weight:bold;
border:2px solid white;
">
${number}
</div>
`,
iconSize: [30, 30],
iconAnchor: [15, 15]
});
return (
<MapContainer center={center} zoom={14} style={{ height: '300px', width: '100%' }}>
{/* OSM tiles */}
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
{/* Purple center marker */}
<Marker position={center} icon={centerIcon} />
{/* Sorted rider markers */}
{sortedRiders.map((r, index) => (
<Marker key={r.id} position={[r.lat, r.lng]} icon={createMarkerIcon(index + 1)}>
<Popup>Rider {index + 1}</Popup>
</Marker>
))}
</MapContainer>
);
}

View File

@@ -940,7 +940,7 @@ const Createorder1 = () => {
sendnotifications();
}
navigate('/orders');
navigate('/nearle/orders');
} else {
opentoast('Something went wrong, Cannot create order', 'warning');
}

View File

@@ -16,6 +16,7 @@ var utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
import { enqueueSnackbar } from 'notistack';
import { useNavigate } from 'react-router';
import { GoogleMap, LoadScript, Marker } from '@react-google-maps/api';
import {
FormControl,
@@ -43,9 +44,12 @@ import {
TableBody,
TableRow,
Paper,
TableHead
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();
@@ -62,7 +66,7 @@ const MultipleOrders = () => {
const [basePrice, setBasePrice] = useState(0);
const [pricePerKm, setPricePerKm] = useState(0);
const [minKm, setMinKm] = useState(0);
const [pickCust, setPickCust] = useState({});
const [pickCust, setPickCust] = useState(null);
const [dropCust, setDropCust] = useState([]);
const [isCustomerOpen, setIsCustomerOpen] = useState(false);
const [searchCustList, setSearchCustList] = useState('');
@@ -77,9 +81,10 @@ const MultipleOrders = () => {
const [totaldist, settotaldist] = useState(0);
const [totalAmt, settotalAmt] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [showMap, setShowMap] = useState(false);
useEffect(() => {
console.log('dropCust', dropCust);
dropCust && console.log('dropCust', dropCust);
}, [dropCust]);
// =============================================== || opentoast || ===============================================
@@ -180,7 +185,7 @@ const MultipleOrders = () => {
const calculateTotal = () => {
let a1 = 0;
let a2 = 0;
dropCust.map((customer) => {
dropCust?.map((customer) => {
a1 += customer.distance;
a2 += customer.totalcharge;
});
@@ -188,7 +193,7 @@ const MultipleOrders = () => {
settotalAmt(a2);
};
useEffect(() => {
calculateTotal();
dropCust && calculateTotal();
}, [dropCust]);
// ========================================================= || handleCheckboxChange || =========================================================
@@ -360,9 +365,7 @@ const MultipleOrders = () => {
useEffect(() => {
console.log('pickCust', pickCust);
}, [pickCust]);
useEffect(() => {
console.log('dropCust', dropCust);
}, [dropCust]);
// ==================================================== || fetchtenantinfo || ====================================================
const fetchtenantinfo = async () => {
setLoading(true);
@@ -424,9 +427,9 @@ const MultipleOrders = () => {
setLoading(false);
});
};
// =============================================== || createorders || ===============================================
const createorders = async () => {
const arr = dropCust.map((customer) => ({
// =============================================== || creategrouporders || ===============================================
const creategrouporders = async () => {
const arr = dropCust?.map((customer) => ({
applocationid: pickCust.applocationid,
cancellled: '',
// categoryid: +tenant.categoryid,
@@ -500,7 +503,7 @@ const MultipleOrders = () => {
// notifyadmin(admintoken);
sendnotifications();
}
navigate('/orders');
navigate('/nearle/orders');
} else {
opentoast(res.data.message, 'warning');
}
@@ -515,101 +518,93 @@ const MultipleOrders = () => {
}
console.log(arr);
};
return (
<>
{loading && <Loader />}
<Grid
container
spacing={3}
sx={{
position: 'sticky',
top: '60px',
bgcolor: theme.palette.background.default,
zIndex: 100,
display: 'flex',
flexDirection: 'column',
justifyContent: 'start',
alignItems: 'center',
spacing: 5
}}
>
<Grid item xs={12} sx={{ width: '100%', mt: -1 }}>
<Stack direction={'row'} alignItems={'center'} justifyContent={'space-between'}>
<Typography variant="h3"> Group Orders</Typography>
{tenantLocations.length == 1 ? (
<TextField
// disabled={!isAppLocation || !isClient}
variant="outlined"
fullWidth
label={'Business Location'}
value={tenantLocations[0].locationname}
focused
sx={{ width: '350px' }}
InputProps={{
style: { color: theme.palette.primary.main },
startAdornment: (
<InputAdornment position="start">
<MyLocationIcon color="primary" />
</InputAdornment>
)
}}
/>
) : (
<Autocomplete
fullWidth
// disabled={!isAppLocation || !isClient}
id="combo-box-demo"
options={tenantLocations || []}
getOptionLabel={(option) => `${option.locationname} (${option.suburb})` || ''}
renderInput={(params) => <TextField {...params} label="Select Business Locations" color="primary" />}
sx={{ width: '350px' }}
onChange={(event, value, reason) => {
if (value) {
console.log('Business Locations', value);
setTid(value.tenantid);
setIsLocation(true);
setPickCust(value);
}
if (reason == 'clear') {
setIsLocation(false);
}
}}
/>
)}
{/* <RidersPinPointOSM /> */}
<Grid container sx={{ mb: 2 }}>
<Grid item xs={12} sm={3} md={6}>
<Stack>
<Typography variant="h3" whiteSpace="nowrap">
Group 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>
{/* ===================================================== || Pickup || ===================================================== */}
<Grid item xs={12} sx={{ width: '100%' }}>
<MainCard
sx={{ height: '100%' }}
title={'Pickup'}
secondary={
<LocalizationProvider dateAdapter={AdapterDayjs} sx={{}}>
{/* 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 dateres11 = dayjs().diff(dayjs(`${dayjs(e).format('YYYY-MM-DD')}`), 'd');
console.log('dateres11');
console.log(dateres11);
let diff = dayjs().diff(dayjs(dayjs(e).format('YYYY-MM-DD')), 'd');
if (dateres11 <= 0) {
console.log('startdate', e);
if (diff <= 0) {
setStartdate(e);
let arr = [];
timeslotarr.map((val) => {
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]) || '',
deliverytime: dayjs(arr[0]),
deliverylocationid: '',
clientname: '',
contactno: '',
@@ -621,160 +616,171 @@ const MultipleOrders = () => {
setOrderarr([]);
}
} else {
setAlertmessage('choose Upcoming Date');
opentoast('choose Upcoming Date', 'warning');
setStartdate(NaN);
}
}}
value={dayjs(startdate)}
sx={{ width: '100%' }}
disablePast
/>
</LocalizationProvider>
}
>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Tenant</TableCell>
<TableCell>Address</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>{pickCust.locationname}</TableCell>
<TableCell>{pickCust.address}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</MainCard>
</Stack>
</Stack>
</Grid>
{/* ===================================================== || Drop || ===================================================== */}
<Grid item xs={12} sx={{ width: '100%', mt: -2 }}>
<MainCard
sx={{ height: '100%' }}
title={`Drop (${dropCust?.length || 0})`}
secondary={
</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"
size="small"
onClick={() => {
setLoading(true);
setBtnLoading(true);
creategrouporders();
setTimeout(() => {
setLoading(false);
setBtnLoading(false);
}, 2000);
}}
sx={{
'&:hover': {
bgcolor: theme.palette.primary.main,
color: 'white'
}
}}
onClick={() => {
if (!isLocation) {
opentoast('Select Business Location', 'warning');
} else {
setIsCustomerOpen(true);
setSearchCustList('');
transform: 'scale(1.05)',
transition: 'transform 0.3s ease'
}
}}
>
Select Customers
{btnLoading ? <CircularProgress color="primary" size={20} thickness={10} /> : 'Create'}
</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.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>
</Grid>
</Stack>
</Grid>
</MainCard>
)}
<Grid item xs={12} sx={{ width: '100%', mt: -2 }}>
{/* ================================================= || Notes || ================================================= */}
<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);
createorders();
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>
</Grid>
</Grid>
{/* ============================================= || saved address Dialog || ============================================= */}
<Dialog
open={isCustomerOpen}
@@ -787,7 +793,7 @@ const MultipleOrders = () => {
{isLoading && <CircularLoader />}
<DialogTitle sx={{ bgcolor: theme.palette.primary.main, color: 'white' }}>
<Stack>
<Typography variant="h4"> {`Select Drop Customers (${dropCust.length || 0})`}</Typography>
<Typography variant="h4"> {`Select Drop Customers (${dropCust?.length || 0})`}</Typography>
<FormControl
sx={{
width: '100%',
@@ -842,7 +848,7 @@ const MultipleOrders = () => {
<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`
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)}
/>
}
@@ -866,19 +872,22 @@ const MultipleOrders = () => {
<Divider />
<DialogActions sx={{ p: 2.5 }}>
<Button
color={dropCust.length !== 0 ? 'primary' : 'error'}
color={dropCust?.length !== 0 ? 'primary' : 'error'}
variant="outlined"
sx={{
'&:hover': {
bgcolor: dropCust.length !== 0 ? theme.palette.primary.main : theme.palette.error.main,
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'}
{dropCust?.length !== 0 ? 'Continue' : 'Close'}
</Button>
</DialogActions>
</Dialog>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
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>
);
}

View File

@@ -740,7 +740,7 @@ const Createorder1 = () => {
setLoading(true);
await axios
.post(`${process.env.REACT_APP_URL}/orders/createorders`, arr)
.post(`${process.env.REACT_APP_URL}/orders/create/grouporders`, arr)
.then((res) => {
if (res.data.status) {
enqueueSnackbar('Order Created Successfully', {
@@ -753,7 +753,7 @@ const Createorder1 = () => {
sendnotifications();
}
navigate('/orders');
navigate('/nearle/orders');
} else {
opentoast(res.data.message, 'warning');
}