initialised commit on the ui converted in mui to astryx and changed the design to doormile

This commit is contained in:
2026-08-01 15:38:42 +05:30
parent 403b2cf5e7
commit 79fefda61a
198 changed files with 13757 additions and 31562 deletions

View File

@@ -20,7 +20,6 @@
// ============================================================================
import React from 'react';
import { Avatar, Box, Paper, Stack, Typography } from '@mui/material';
import {
MdLocalShipping,
MdHourglassEmpty,
@@ -50,8 +49,8 @@ export const DT = {
surfaceAlt: '#f8fafc'
};
export const BRAND = '#662582';
export const BRAND_LIGHT = '#9255AB';
export const BRAND = '#C01227';
export const BRAND_LIGHT = '#D25463';
const dtA = (c, suffix) => `${c}${suffix}`;
export const tint = (c) => dtA(c, '08');
@@ -108,15 +107,14 @@ export const StatusBadge = ({ status, minWidth = 86 }) => {
};
const Icon = meta.icon;
return (
<Box
sx={{
<span
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 1.125,
py: 0.375,
gap: 4,
padding: '3px 9px',
borderRadius: 999,
bgcolor: meta.color,
backgroundColor: meta.color,
color: '#fff',
fontSize: 11,
fontWeight: 700,
@@ -128,7 +126,7 @@ export const StatusBadge = ({ status, minWidth = 86 }) => {
}}
>
<Icon size={12} /> {meta.label}
</Box>
</span>
);
};
@@ -136,39 +134,39 @@ export const StatusBadge = ({ status, minWidth = 86 }) => {
// TimelineCell — time large/bold/high-contrast, date small/muted/secondary.
// No decorative dot.
// ----------------------------------------------------------------------------
const noWrapStyle = { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' };
export const TimelineCell = ({ value, utc: useUtc = false }) => {
if (!value) {
return (
<Typography sx={{ fontSize: 12, color: DT.textMuted, fontWeight: 700 }}></Typography>
);
return <span style={{ fontSize: 12, color: DT.textMuted, fontWeight: 700 }}></span>;
}
const d = useUtc ? dayjs(value).utc() : dayjs(value);
return (
<Stack spacing={0} sx={{ lineHeight: 1.1 }}>
<Typography
sx={{
<div style={{ display: 'flex', flexDirection: 'column', lineHeight: 1.1 }}>
<span
style={{
fontSize: 12.5,
fontWeight: 800,
color: DT.textPrimary,
letterSpacing: 0.1,
lineHeight: 1.15
lineHeight: 1.15,
...noWrapStyle
}}
noWrap
>
{d.format('hh:mm A')}
</Typography>
<Typography
sx={{
</span>
<span
style={{
fontSize: 10.5,
fontWeight: 600,
color: DT.textMuted,
lineHeight: 1.2
lineHeight: 1.2,
...noWrapStyle
}}
noWrap
>
{d.format('DD MMM YYYY')}
</Typography>
</Stack>
</span>
</div>
);
};
@@ -180,22 +178,17 @@ export const MetricPill = ({ value, color, icon, isMoney = false }) => {
const display = isMoney ? formatNumberToRupees(n) : Number.isFinite(n) ? n : value || 0;
const isZero = !Number.isFinite(n) || n === 0;
if (isZero) {
return (
<Typography sx={{ fontSize: 11.5, color: DT.textMuted, fontWeight: 700 }}>
{display}
</Typography>
);
return <span style={{ fontSize: 11.5, color: DT.textMuted, fontWeight: 700 }}>{display}</span>;
}
return (
<Box
sx={{
<span
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 0.875,
py: 0.25,
gap: 4,
padding: '2px 7px',
borderRadius: 999,
bgcolor: tint(color),
backgroundColor: tint(color),
border: `1px solid ${edge(color)}`,
color,
fontSize: 12,
@@ -205,23 +198,23 @@ export const MetricPill = ({ value, color, icon, isMoney = false }) => {
>
{icon}
{display}
</Box>
</span>
);
};
// ----------------------------------------------------------------------------
// SoftPaper — autocomplete dropdown surface.
// ----------------------------------------------------------------------------
export const SoftPaper = (props) => (
<Paper
export const SoftPaper = ({ style, ...props }) => (
<div
{...props}
sx={{
mt: 0.75,
borderRadius: 2,
style={{
marginTop: 6,
borderRadius: 8,
boxShadow: DT.shadowPop,
border: '1px solid',
borderColor: 'divider',
overflow: 'hidden'
border: `1px solid ${DT.borderSubtle}`,
overflow: 'hidden',
...style
}}
/>
);
@@ -230,17 +223,22 @@ export const SoftPaper = (props) => (
// AccentAvatar — colored circular icon used inside filter chips & headers.
// ----------------------------------------------------------------------------
export const AccentAvatar = ({ color, selected, size = 24, children }) => (
<Avatar
sx={{
<div
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: size,
height: size,
bgcolor: selected ? color : soft(color),
flexShrink: 0,
borderRadius: '50%',
backgroundColor: selected ? color : soft(color),
color: selected ? '#fff' : color,
transition: 'background-color 0.15s, color 0.15s'
}}
>
{children}
</Avatar>
</div>
);
// ----------------------------------------------------------------------------

View File

@@ -1,4 +1,3 @@
import { Grid, List, ListItem, Stack, Typography, useMediaQuery } from '@mui/material';
import { enqueueSnackbar } from 'notistack';
import axios from 'axios';
@@ -9,8 +8,29 @@ import Loader from 'components/Loader';
import Footer from 'layout/MainLayout/Footer';
import TitleCard from './titleCard';
import { VStack } from '@astryxdesign/core/VStack';
import { Text } from '@astryxdesign/core/Text';
import { Divider } from '@astryxdesign/core/Divider';
import logger from 'utils/logger';
const Field = ({ label, value }) => (
<VStack gap={0.5} padding={0}>
<Text type="supporting">{label}</Text>
<Text>{value || ''}</Text>
</VStack>
);
const Row = ({ children, hasDivider }) => (
<VStack gap={0} padding={0}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', gap: 24, padding: '20px 24px' }}>
{children}
</div>
{hasDivider && <Divider />}
</VStack>
);
const Accountsettings = () => {
const matchDownMD = useMediaQuery((theme) => theme.breakpoints.down('md'));
const [info, setInfo] = useState({});
const [loading, setLoading] = useState(false);
@@ -25,14 +45,14 @@ const Accountsettings = () => {
await axios
.get(`${process.env.REACT_APP_URL}/tenants/gettenantinfo/?tenantid=${tid}`)
.then((res) => {
console.log(res);
logger.info(res);
if (res.data.status) {
setInfo(res.data.details);
}
setLoading(false);
})
.catch((err) => {
console.log(err);
logger.error(err);
enqueueSnackbar(err.message, {
variant: 'error',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
@@ -47,101 +67,31 @@ const Accountsettings = () => {
{loading && <Loader />}
<TitleCard title={'Profile'} />
<Grid container spacing={3}>
<Grid item xs={12}>
<MainCard>
<List sx={{ py: 0 }}>
<ListItem divider={!matchDownMD}>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">Name</Typography>
<Typography>{info.tenantname || ''}</Typography>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">Company Name</Typography>
<Typography>{info.companyname || ''}</Typography>
</Stack>
</Grid>
</Grid>
</ListItem>
<ListItem divider={!matchDownMD}>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">Registration No</Typography>
<Typography>{info.registrationno || ''}</Typography>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">Info</Typography>
<Typography>{info.info || ''}</Typography>
</Stack>
</Grid>
</Grid>
</ListItem>
<ListItem divider={!matchDownMD}>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">Phone</Typography>
<Typography>{info.primarycontact || ''}</Typography>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">State</Typography>
<Typography>{info.state || ''}</Typography>
</Stack>
</Grid>
</Grid>
</ListItem>
<ListItem divider={!matchDownMD}>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">Email</Typography>
<Typography>{info.primaryemail || ''}</Typography>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">City</Typography>
<Typography>{info.city || ''}</Typography>
</Stack>
</Grid>
</Grid>
</ListItem>
<ListItem divider={!matchDownMD}>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">Suburb</Typography>
<Typography>{info.suburb || ''}</Typography>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={0.5}>
<Typography color="secondary">Zip Code</Typography>
<Typography>{info.postcode || ''}</Typography>
</Stack>
</Grid>
</Grid>
</ListItem>
<ListItem>
<Stack spacing={0.5}>
<Typography color="secondary">Address</Typography>
<Typography>{info.address || ''}</Typography>
</Stack>
</ListItem>
</List>
</MainCard>
</Grid>
</Grid>
<MainCard content={false}>
<Row hasDivider>
<Field label="Name" value={info.tenantname} />
<Field label="Company Name" value={info.companyname} />
</Row>
<Row hasDivider>
<Field label="Registration No" value={info.registrationno} />
<Field label="Info" value={info.info} />
</Row>
<Row hasDivider>
<Field label="Phone" value={info.primarycontact} />
<Field label="State" value={info.state} />
</Row>
<Row hasDivider>
<Field label="Email" value={info.primaryemail} />
<Field label="City" value={info.city} />
</Row>
<Row hasDivider>
<Field label="Suburb" value={info.suburb} />
<Field label="Zip Code" value={info.postcode} />
</Row>
<Row>
<Field label="Address" value={info.address} />
</Row>
</MainCard>
<Footer />
</>

View File

@@ -1,5 +1,6 @@
import axios from 'axios';
import { OpenToast } from 'components/nearle_components/OpenToast';
import logger from 'utils/logger';
const tenid = localStorage.getItem('tenantid');
export const fetchOrders = async ({ pageParam = 1, queryKey }) => {
@@ -55,14 +56,14 @@ export const fetchorderscount = async ({ queryKey }) => {
// ==============================|| fetchOrderSummary (orders)||============================== //
export const fetchOrderSummary = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/orders/getordersummary`);
console.log('fetchOrderSummary', response.data.details);
logger.info('fetchOrderSummary', response.data.details);
return response.data.details;
};
// ==============================|| fetchLocationSummary (orders)||============================== //
export const fetchLocationSummary = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/orders/getlocationsummary`);
console.log('fetchLocationSummary', response.data.details);
logger.info('fetchLocationSummary', response.data.details);
return response.data.details;
};
@@ -70,7 +71,7 @@ export const fetchLocationSummary = async () => {
// ==============================|| fetchOrderInsight (orders)||============================== //
export const fetchOrderInsight = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/orders/getorderinsight`);
console.log('fetchOrderInsight', response.data.details);
logger.info('fetchOrderInsight', response.data.details);
return response.data.details;
};
@@ -78,14 +79,14 @@ export const fetchOrderInsight = async () => {
// ==============================|| fetchDeliveryInsight (delivery)||============================== //
export const fetchDeliveryInsight = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/deliveries/getdeliveryinsight`);
console.log('fetchDeliveryInsight', response.data.details);
logger.info('fetchDeliveryInsight', response.data.details);
return response.data.details;
};
// ==============================|| fetchDeliveryLocationSummary (delivery)||============================== //
export const fetchDeliveryLocationSummary = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/deliveries/getlocationsummary`);
console.log('fetchDeliveryLocationSummary', response.data.details);
logger.info('fetchDeliveryLocationSummary', response.data.details);
return response.data.details;
};
@@ -121,7 +122,7 @@ export const fetchAllTenants = async ({ queryKey }) => {
const response = await axios.get(url);
// tenants/search/?keyword=${search}
console.log('fetchAllTenants', response.data.details);
logger.info('fetchAllTenants', response.data.details);
return response.data.details;
};
@@ -129,7 +130,7 @@ export const fetchAllTenants = async ({ queryKey }) => {
export const fetchCustomersList = async ({ queryKey }) => {
const [pages] = queryKey;
const response = await axios.get(`${process.env.REACT_APP_URL}/customers/getallcustomers/?pageno=${pages}&pagesize=10`);
console.log('fetchCustomersList', response.data.details);
logger.info('fetchCustomersList', response.data.details);
return response.data.details;
};
// ==============================|| gettenantcustomers (customers)||============================== //
@@ -158,39 +159,39 @@ export const gettenantcustomers = async ({ pageParam = 1, queryKey }) => {
export const fetchCustomersListBySearch = async ({ queryKey }) => {
const [search] = queryKey;
const response = await axios.get(search.lenght > 3 && `${process.env.REACT_APP_URL}/customers/search/?keyword=${search}`);
console.log('fetchCustomersListBySearch', response.data.details);
logger.info('fetchCustomersListBySearch', response.data.details);
return response.data.details;
};
// ==============================|| fetchOrdersSummary (rider summary)||============================== //
export const fetchOrdersSummary = async ({ queryKey }) => {
console.log('queryKey for fetchOrdersSummary', queryKey);
logger.info('queryKey for fetchOrdersSummary', queryKey);
const [startdate, enddate] = queryKey;
const response = await axios.get(
`${process.env.REACT_APP_URL}/deliveries/getreportsummary/?tenantid=${tenid}&fromdate=${startdate}&todate=${enddate}`
);
console.log('fetchOrdersSummary', response.data.details);
logger.info('fetchOrdersSummary', response.data.details);
return response.data.details;
};
// ==============================|| getreportlocationsummary (orders summary)||============================== //
export const getreportlocationsummary = async ({ queryKey }) => {
console.log('queryKey for getreportlocationsummary', queryKey);
logger.info('queryKey for getreportlocationsummary', queryKey);
const [startdate, enddate, locationId, debouncedSearch] = queryKey;
const response = await axios.get(
`${process.env.REACT_APP_URL}/deliveries/getreportlocationsummary/?tenantid=${tenid}&locationid=${locationId}&fromdate=${startdate}&todate=${enddate}&keyword=${debouncedSearch}`
);
console.log('getreportlocationsummary', response.data.details);
logger.info('getreportlocationsummary', response.data.details);
return response.data.details;
};
// ==============================|| getriderlocationreportsummary (orders summary)||============================== //
export const getriderlocationreportsummary = async ({ queryKey }) => {
console.log('queryKey for getriderlocationreportsummary', queryKey);
logger.info('queryKey for getriderlocationreportsummary', queryKey);
const [startdate, enddate, locationId] = queryKey;
const response = await axios.get(
`${process.env.REACT_APP_URL}/deliveries/getriderlocationreportsummary/?tenantid=${tenid}&locationid=${locationId}&fromdate=${startdate}&todate=${enddate}`
);
console.log('getriderlocationreportsummary', response.data.details);
logger.info('getriderlocationreportsummary', response.data.details);
return response.data.details;
};
@@ -202,7 +203,7 @@ export const fetchLocations = async () => {
...response.data.details,
{ partnername: 'All', partnerid: -1 } // Add your new object here
];
console.log('fetchLocations', updatedLocations);
logger.info('fetchLocations', updatedLocations);
return updatedLocations;
};
@@ -216,7 +217,7 @@ export const gettenantlocations = async ({ queryKey }) => {
} catch (error) {
// Must return an array — downstream consumers do `.map`/`.length` and a
// string here crashes the entire Locations page.
console.error('Error fetching tenant locations:', error);
logger.error('Error fetching tenant locations:', error);
return [];
}
};
@@ -227,7 +228,7 @@ export const fetchDeliverySummary = async ({ queryKey }) => {
const response = await axios.get(
`${process.env.REACT_APP_URL}/deliveries/deliverysummary?tenantid=${tenid}&locationid=${locationId}&fromdate=${startdate}&todate=${enddate}`
);
console.log('fetchDeliverySummary', response.data.details);
logger.info('fetchDeliverySummary', response.data.details);
return response.data.details;
};
// ==============================|| fetchAppLocations (report summary))||============================== //
@@ -238,7 +239,7 @@ export const fetchAppLocations = async () => {
...response.data.details,
{ partnername: 'All', applocationid: -1 } // Add your new object here
];
console.log('fetchAppLocations', updatedLocations);
logger.info('fetchAppLocations', updatedLocations);
return updatedLocations;
};
@@ -246,12 +247,12 @@ export const fetchAppLocations = async () => {
// ==============================|| fetchRidersSummary (riders summary)||============================== //
export const fetchRidersSummary = async ({ queryKey }) => {
console.log('queryKey for fetchRidersSummary', queryKey);
logger.info('queryKey for fetchRidersSummary', queryKey);
const [tenantid, startdate, enddate] = queryKey;
const response = await axios.get(
`${process.env.REACT_APP_URL}/deliveries/getridersummary/?tenantid=${tenantid}&fromdate=${startdate}&todate=${enddate}`
);
console.log('fetchRidersSummary', response.data.details);
logger.info('fetchRidersSummary', response.data.details);
return response.data.details;
};
@@ -281,19 +282,19 @@ export const fetchorderdetails = async ({ pageParam = 0, queryKey }) => {
};
// export const fetchorderdetails = async ({ queryKey }) => {
// console.log('queryKey of fetchorderdetails', queryKey);
// logger.info('queryKey of fetchorderdetails', queryKey);
// const [startdate, enddate, currentStatus] = queryKey;
// const response = await axios.get(
// `${process.env.REACT_APP_URL}/deliveries/getdeliveries/?tenantid=${tenid}&fromdate=${startdate}&todate=${enddate}&status=${
// currentStatus == 'All' ? '' : currentStatus
// }`
// );
// console.log('fetchorderdetails', response.data.details);
// logger.info('fetchorderdetails', response.data.details);
// return response.data.details;
// };
// ==============================|| fetchCount (orders detail)||============================== //
export const fetchCount = async ({ queryKey }) => {
console.log('queryKey of fetchCount', queryKey);
logger.info('queryKey of fetchCount', queryKey);
const [startdate, enddate] = queryKey;
const response = await axios.get(`${process.env.REACT_APP_URL}/orders/getorders/?fromdate=${startdate}&todate=${enddate}`);
const calculateOrderCounts = () => {
@@ -314,7 +315,7 @@ export const fetchCount = async ({ queryKey }) => {
return { deliveredCount, pendingCount, cancelledCount };
};
console.log('fetchCount', calculateOrderCounts());
logger.info('fetchCount', calculateOrderCounts());
return calculateOrderCounts();
};
@@ -333,7 +334,7 @@ export const fetchRidersLogs = async ({ queryKey }) => {
startdate || ''
}&keyword=${riderSearch || ''}`
);
console.log('fetchRidersLogs', riderLogsResponse.data.details);
logger.info('fetchRidersLogs', riderLogsResponse.data.details);
return riderLogsResponse.data.details;
};
@@ -475,7 +476,7 @@ export const getallriders = async () => {
const res = await axios.get(`${process.env.REACT_APP_URL}/partners/getallriders?partnerid=64`);
return res.data.details;
} catch (err) {
console.log('getallriders', err.message);
logger.error('getallriders', err.message);
}
};

View File

@@ -1,41 +1,29 @@
import { React, useEffect, useState } from 'react';
// material-ui
import { useTheme } from '@mui/material/styles';
import {
Box,
Button,
FormLabel,
Grid,
InputLabel,
MenuItem,
Select,
Stack,
TextField,
Typography,
IconButton,
Autocomplete,
InputAdornment
} from '@mui/material';
import MyLocationIcon from '@mui/icons-material/MyLocation';
import { Button } from '@astryxdesign/core/Button';
import { TextInput } from '@astryxdesign/core/TextInput';
import { HStack } from '@astryxdesign/core/HStack';
import { Text } from '@astryxdesign/core/Text';
// project import
import Avatar from 'components/@extended/Avatar';
import MainCard from 'components/MainCard';
import axios from 'axios';
import { usePlacesWidget } from 'react-google-autocomplete';
import Loader from 'components/Loader';
import Geocode from 'react-geocode';
import { enqueueSnackbar } from 'notistack';
import { useNavigate } from 'react-router';
import CloseIcon from '@mui/icons-material/Close';
import TitleCard from '../titleCard';
import logger from 'utils/logger';
const FieldGrid = ({ children }) => (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 24 }}>{children}</div>
);
const CreateCustomer = () => {
const appId = localStorage.getItem('applocationid');
const theme = useTheme();
const [selectedImage, setSelectedImage] = useState(undefined);
const [avatar, setAvatar] = useState();
const [businessname, setBusinessname] = useState('');
@@ -103,8 +91,8 @@ const CreateCustomer = () => {
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
logger.info('new place', place); // Do something with the selected place
logger.info(' 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() });
setAddress(`${place.name} ${place.formatted_address}`);
@@ -164,7 +152,7 @@ const CreateCustomer = () => {
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
console.log('Pick Address:', address);
logger.info('Pick Address:', address);
});
}
}, [inputValue2]);
@@ -174,18 +162,18 @@ const CreateCustomer = () => {
await axios
.get(`${process.env.REACT_APP_URL}/utils/getapplocations/?applocationid=${appId}`)
.then((res) => {
console.log('getapplocations', res);
const { opentime, closetime, latitude, longitude, radius } = res.data.details[0];
logger.info('getapplocations', res);
const { opentime, closetime, latitude, longitude, radius } = res.data.details?.[0] || {};
if (res.data.status) {
setAppLocaLat(latitude);
setAppLocaLng(longitude);
setAppLocaRadius(radius);
console.log('radius', radius);
logger.info('radius', radius);
}
setLoading(false);
})
.catch((err) => {
console.log(err);
logger.error(err);
setLoading(false);
});
};
@@ -201,11 +189,11 @@ const CreateCustomer = () => {
// ...locationRes.data.details,
// { partnername: 'All', applocationid: -1 }
// ];
// console.log('fetchAppLocations', updatedLocations);
console.log('fetchAppLocations', locationRes.data.details);
// logger.info('fetchAppLocations', updatedLocations);
logger.info('fetchAppLocations', locationRes.data.details);
setLocations(locationRes.data.details);
} catch (err) {
console.log('locationRes', err);
logger.error('locationRes', err);
}
};
useEffect(() => {
@@ -220,7 +208,7 @@ const CreateCustomer = () => {
.get(`${process.env.REACT_APP_URL}/tenants/gettenants/?applocationid=${id}&status=active`)
.then((res) => {
console.log(res);
logger.info(res);
if (res.data.status) {
let arr = [];
res.data.details.map((val) => {
@@ -234,7 +222,7 @@ const CreateCustomer = () => {
setLoading(false);
})
.catch((err) => {
console.log(err);
logger.error(err);
setLoading(false);
});
};
@@ -242,7 +230,7 @@ const CreateCustomer = () => {
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);
logger.info('gettenantlocations', res.data.details);
if (res.data.details.length == 1) {
setIsLocation(true);
setTenantlocations(res.data.details);
@@ -254,7 +242,7 @@ const CreateCustomer = () => {
setIsBusiness(false); // became true after select from tenanatLocations
}
} catch (err) {
console.log('gettenantlocations', err);
logger.error('gettenantlocations', err);
}
};
@@ -271,14 +259,14 @@ const CreateCustomer = () => {
await axios
.get(`${process.env.REACT_APP_URL}/tenants/gettenantinfo/?tenantid=${tid}`)
.then((res) => {
console.log('fetchtenantinfo', res.data.details);
logger.info('fetchtenantinfo', res.data.details);
if (res.data.status) {
setTenantinfo(res.data.details);
}
setLoading(false);
})
.catch((err) => {
console.log(err);
logger.error(err);
setLoading(false);
});
};
@@ -314,21 +302,21 @@ const CreateCustomer = () => {
customertoken: '',
primaryaddress: 1
};
console.log(obj);
logger.info(obj);
setLoading(true);
try {
await axios
.post(`${process.env.REACT_APP_URL}/customers/create`, obj)
.then((res) => {
console.log(res);
logger.info(res);
if (res.data.status) {
enqueueSnackbar(' Created Successfully ', {
variant: 'success',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: 2000
});
navigate('/customers');
navigate('/nearle/customers');
} else if (res.data.message == 'Customer Already available') {
enqueueSnackbar('Customer Already available', {
variant: 'error',
@@ -339,7 +327,7 @@ const CreateCustomer = () => {
setLoading(false);
})
.catch((err) => {
console.log(err);
logger.error(err);
setLoading(false);
enqueueSnackbar(err.message, {
@@ -349,7 +337,7 @@ const CreateCustomer = () => {
});
});
} catch (err) {
console.log(err);
logger.error(err);
setLoading(false);
}
};
@@ -359,241 +347,136 @@ const CreateCustomer = () => {
{loading && <Loader />}
<TitleCard
title={'Create Customer'}
secondary={
<Button variant="contained" color="secondary" onClick={() => navigate('/nearle/customers')}>
Back
</Button>
}
secondary={<Button label="Back" variant="secondary" onClick={() => navigate('/nearle/customers')} />}
/>
<MainCard>
<Grid container spacing={3}>
<Grid item xs={12}>
<Grid container spacing={3}>
{/* ===================================================== || Name|| ===================================================== */}
<Grid item xs={12} sm={6}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-last-name">Name</InputLabel>
<TextField
fullWidth
id="personal-last-name"
placeholder="Name"
onChange={(e) => setFirstname(e.target.value)}
value={firstname}
autoComplete="off"
/>
</Stack>
</Grid>
{/* ===================================================== || Phone Number || ===================================================== */}
<Grid item xs={12} sm={6}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-phone">Phone Number</InputLabel>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Select defaultValue="+1" disabled sx={{ cursor: 'not-allowed' }}>
<MenuItem value="+1">+91</MenuItem>
</Select>
<TextField
type="number"
id="personal-phone"
fullWidth
placeholder="Phone Number"
onChange={(e) => {
if (e.target.value.toString().length <= 10) {
setMobilenumber(e.target.value);
}
}}
value={mobilenumber}
autoComplete="off"
// disabled
sx={{ cursor: 'not-allowed' }}
/>
</Stack>
</Stack>
</Grid>
{/* ===================================================== || Email|| ===================================================== */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
<FieldGrid>
{/* ===================================================== || Name|| ===================================================== */}
<TextInput label="Name" placeholder="Name" onChange={setFirstname} value={firstname} />
<Grid item xs={12} sm={6}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-email">Email Address</InputLabel>
<TextField
type="email"
fullWidth
// defaultValue="stebin.ben@gmail.com"
id="personal-email"
placeholder="Email Address"
onChange={(e) => setEmailaddress(e.target.value)}
value={emailaddress}
autoComplete="off"
/>
</Stack>
</Grid>
{/* ===================================================== || door no || ===================================================== */}
{/* ===================================================== || Phone Number || ===================================================== */}
<div>
<Text type="strong" size="sm" style={{ marginBottom: 6, display: 'block' }}>
Phone Number
</Text>
<HStack gap={2} vAlign="center">
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: 36,
padding: '0 12px',
borderRadius: 10,
border: '1px solid var(--color-border)',
color: 'var(--color-text-secondary)',
cursor: 'not-allowed'
}}
>
+91
</div>
<TextInput
label="Phone Number"
isLabelHidden
type="number"
placeholder="Phone Number"
width="100%"
onChange={(v) => {
if (v.toString().length <= 10) setMobilenumber(v);
}}
value={mobilenumber}
/>
</HStack>
</div>
<Grid item xs={12} sm={6}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-location">Door No</InputLabel>
<TextField
fullWidth
// defaultValue="New York"
id="personal-location"
placeholder="Door No"
onChange={(e) => setDoorno(e.target.value)}
value={doorno}
autoComplete="off"
/>
</Stack>
</Grid>
{/* ===================================================== || Email|| ===================================================== */}
<TextInput label="Email Address" type="email" placeholder="Email Address" onChange={setEmailaddress} value={emailaddress} />
{/* ===================================================== || Address || ===================================================== */}
{/* ===================================================== || door no || ===================================================== */}
<TextInput label="Door No" placeholder="Door No" onChange={setDoorno} value={doorno} />
</FieldGrid>
<Grid item xs={12}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-email"> Address</InputLabel>
<TextField
variant="outlined"
id="addressAuto1"
fullWidth
value={inputValue2}
onChange={(e) => setInputValue2(e.target.value)}
InputProps={{
endAdornment: (
<IconButton
onClick={() => {
setInputValue2('');
setPickCust({
...pickCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setStartPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon />
</IconButton>
)
}}
/>
</Stack>
</Grid>
{/* ===================================================== || Address || ===================================================== */}
<TextInput
label="Address"
id="addressAuto1"
width="100%"
hasClear
value={inputValue2}
onChange={(v) => {
setInputValue2(v);
if (!v) {
setPickCust({
...pickCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setStartPoint({ latitude: 0, longitude: 0 });
}
}}
/>
<Grid item xs={12} sm={6}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-location">Location</InputLabel>
<TextField
fullWidth
// defaultValue="New York"
id="personal-location"
placeholder="Location"
onChange={(e) => setPickCust({ ...pickCust, suburb: e.target.value })}
value={pickCust.suburb}
autoComplete="off"
/>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-zipcode">City</InputLabel>
<TextField
fullWidth
id="personal-zipcode"
placeholder="City"
onChange={(e) => setPickCust({ ...pickCust, city: e.target.value })}
value={pickCust.city}
autoComplete="off"
/>
</Stack>
</Grid>
<FieldGrid>
<TextInput
label="Location"
placeholder="Location"
onChange={(v) => setPickCust({ ...pickCust, suburb: v })}
value={pickCust.suburb}
/>
<TextInput label="City" placeholder="City" onChange={(v) => setPickCust({ ...pickCust, city: v })} value={pickCust.city} />
<TextInput label="State" placeholder="State" onChange={(v) => setPickCust({ ...pickCust, state: v })} value={pickCust.state} />
<TextInput
label="Post Code"
type="number"
placeholder="Zipcode"
onChange={(v) => setPickCust({ ...pickCust, postcode: v })}
value={pickCust.postcode}
/>
</FieldGrid>
<Grid item xs={12} sm={6}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-location">State</InputLabel>
<TextField
fullWidth
// defaultValue="New York"
id="personal-location"
placeholder="State"
onChange={(e) => setPickCust({ ...pickCust, state: e.target.value })}
value={pickCust.state}
autoComplete="off"
/>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-zipcode">Post Code</InputLabel>
<TextField
fullWidth
// defaultValue="956754"
type="number"
id="personal-zipcode"
placeholder="Zipcode"
onChange={(e) => setPickCust({ ...pickCust, postcode: e.target.value })}
value={pickCust.postcode}
autoComplete="off"
/>
</Stack>
</Grid>
<TextInput label="Landmark" placeholder="Landmark" width="100%" onChange={setLandmark} value={landmark} />
<Grid item xs={12}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-email">Landmark</InputLabel>
<TextField
type="email"
fullWidth
// defaultValue="stebin.ben@gmail.com"
id="personal-email"
placeholder="Landmark"
onChange={(e) => setLandmark(e.target.value)}
value={landmark}
autoComplete="off"
/>
</Stack>
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Stack direction="row" justifyContent="flex-end" alignItems="center" spacing={2}>
<Button
variant="contained"
onClick={() => {
if (appId === '') {
opentoast('Select Applocation ');
} else if (tid === '') {
opentoast('Select Tenant');
} else if (firstname === '') {
opentoast('Enter Name');
} else if (mobilenumber === '') {
opentoast('Enter Mobile Number ');
} else if (address === '') {
opentoast('Enter Address ');
} else if (pickCust.city === '') {
opentoast('Enter City ');
} else if (pickCust.state === '') {
opentoast('Enter State ');
} else if (pickCust.suburb === '') {
opentoast('Enter location ');
} else if (pickCust.postcode === '') {
opentoast('Enter Post Code ');
} else if (landmark === '') {
opentoast('Enter Land Mark ');
} else if (pickCust.latitude === '') {
opentoast('Invalid latitude ');
} else if (pickCust.longitude === '') {
opentoast('Invaiid Longitude ');
} else {
createprofile();
}
}}
>
Create
</Button>
</Stack>
</Grid>
</Grid>
<HStack justify="end">
<Button
label="Create"
variant="primary"
onClick={() => {
if (appId === '') {
opentoast('Select Applocation ');
} else if (tid === '') {
opentoast('Select Tenant');
} else if (firstname === '') {
opentoast('Enter Name');
} else if (mobilenumber === '') {
opentoast('Enter Mobile Number ');
} else if (address === '') {
opentoast('Enter Address ');
} else if (pickCust.city === '') {
opentoast('Enter City ');
} else if (pickCust.state === '') {
opentoast('Enter State ');
} else if (pickCust.suburb === '') {
opentoast('Enter location ');
} else if (pickCust.postcode === '') {
opentoast('Enter Post Code ');
} else if (landmark === '') {
opentoast('Enter Land Mark ');
} else if (pickCust.latitude === '') {
opentoast('Invalid latitude ');
} else if (pickCust.longitude === '') {
opentoast('Invaiid Longitude ');
} else {
createprofile();
}
}}
/>
</HStack>
</div>
</MainCard>
</>
);

View File

@@ -1,38 +1,22 @@
import React, { useRef, useEffect, useState } from 'react';
import TitleCard from '../titleCard';
import {
Button,
Chip,
CircularProgress,
FormControl,
IconButton,
InputAdornment,
OutlinedInput,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography
} from '@mui/material';
import { Button } from '@astryxdesign/core/Button';
import { Badge } from '@astryxdesign/core/Badge';
import { Spinner } from '@astryxdesign/core/Spinner';
import MainCard from 'components/MainCard';
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { useInfiniteQuery } from '@tanstack/react-query';
import { gettenantcustomers } from '../api/api';
import { useTheme } from '@mui/material/styles';
import { useNavigate } from 'react-router';
import { FormOutlined, SearchOutlined } from '@ant-design/icons';
import SearchBar from 'components/nearle_components/SearchBar';
import { Empty } from 'antd';
import logger from 'utils/logger';
const Customers = () => {
const navigate = useNavigate();
const theme = useTheme();
const loadMoreRef = useRef();
const containerRef = useRef();
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const [rowsPerPage] = React.useState(10);
const [customerSearch, setCustomerSearch] = useState('');
const [debounceCustomerSearch, setDebounceCustomerSearch] = useState('');
@@ -46,9 +30,6 @@ const Customers = () => {
const {
data: customerdata,
isLoading: isLoadingCustomer,
isError: isErrorCustomer,
error: errorCustomer,
fetchNextPage,
hasNextPage,
isFetchingNextPage
@@ -59,7 +40,7 @@ const Customers = () => {
});
const rows = customerdata ? customerdata.pages.flatMap((p) => p.details) : [];
useEffect(() => {
customerdata && console.log(customerdata);
customerdata && logger.info(customerdata);
}, [customerdata]);
useEffect(() => {
if (!hasNextPage) return;
@@ -70,7 +51,7 @@ const Customers = () => {
}
},
{
root: document.querySelector('.MuiTableContainer-root'), // 👈 or explicitly TableContainer
root: containerRef.current,
rootMargin: '0px',
threshold: 1.0
}
@@ -94,112 +75,80 @@ const Customers = () => {
<>
<TitleCard
title={'Customers'}
secondary={
<Button variant="contained" onClick={() => navigate('/nearle/customers/create')}>
Create Customers
</Button>
}
secondary={<Button label="Create Customers" variant="primary" onClick={() => navigate('/nearle/customers/create')} />}
/>
<MainCard
content={false}
sx={{ width: '100%', overflow: 'hidden' }}
title={
<SearchBar
value={customerSearch}
placeholder="Search Location"
onChange={(e) => setCustomerSearch(e.target.value)}
sx={{
width: '100%',
maxWidth: 300,
minWidth: 200,
bgcolor: 'white'
}}
/>
<SearchBar value={customerSearch} placeholder="Search Location" onChange={(e) => setCustomerSearch(e.target.value)} style={{ width: '100%', maxWidth: 300, minWidth: 200 }} />
}
>
<TableContainer
<div
onScroll={handleScroll}
ref={containerRef}
sx={{
style={{
width: '100%',
borderBottom: 1,
borderColor: 'divider',
borderBottom: `1px solid var(--color-border)`,
maxHeight: 'calc(100vh - 200px)',
overflow: 'auto',
'&::-webkit-scrollbar': {
width: '12px', // scroll bar widthP
cursor: 'pointer'
},
'&::-webkit-scrollbar-thumb': {
backgroundColor: theme.palette.primary.main, // thumb color
borderRadius: '8px',
cursor: 'pointer'
},
'&::-webkit-scrollbar-thumb:hover': {
backgroundColor: theme.palette.primary.dark, // hover color
cursor: 'pointer'
},
'&::-webkit-scrollbar-track': {
backgroundColor: theme.palette.primary.lighter,
cursor: 'pointer'
}
overflow: 'auto'
}}
>
<Table stickyHeader>
<TableHead sx={{}}>
<TableRow sx={{ backgroundColor: theme.palette.primary.main }}>
<TableCell sx={{ position: 'sticky!important', bgcolor: 'secondary.light' }}>#</TableCell>
<TableCell sx={{ position: 'sticky!important', bgcolor: 'secondary.light' }}>Name</TableCell>
<TableCell sx={{ position: 'sticky!important', bgcolor: 'secondary.light' }}>suburb</TableCell>
<TableCell sx={{ position: 'sticky!important', bgcolor: 'secondary.light' }}>Address</TableCell>
<TableCell sx={{ position: 'sticky!important', bgcolor: 'secondary.light' }}>Lat/Lng</TableCell>
</TableRow>
</TableHead>
<TableBody>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead style={{ position: 'sticky', top: 0, zIndex: 1 }}>
<tr style={{ backgroundColor: 'var(--color-background-muted)' }}>
{['#', 'Name', 'suburb', 'Address', 'Lat/Lng'].map((h) => (
<th key={h} style={{ textAlign: 'left', padding: '10px 12px', position: 'sticky' }}>
{h}
</th>
))}
</tr>
</thead>
<tbody>
{rows?.length == 0 && (
<TableRow>
<TableCell colSpan={5}>
<tr>
<td colSpan={5} style={{ padding: '24px 12px' }}>
<Empty description={'Customer Not Found'} />
</TableCell>
</TableRow>
</td>
</tr>
)}
{rows?.map((customer, index) => (
<TableRow key={index}>
<TableCell>{index + 1}</TableCell>
<TableCell>
<Stack>
<Typography variant="subtitle1">{customer.firstname}</Typography>
<Typography variant="subtitle2">{customer.contactno}</Typography>
<Typography variant="subtitle2">{`ID : ${customer.customerid}`}</Typography>
</Stack>
</TableCell>
<TableCell>
<Stack>
<Typography variant="subtitle1">{customer.suburb}</Typography>
<Typography variant="subtitle2">{customer.city}</Typography>
</Stack>
</TableCell>
<TableCell>{customer.address}</TableCell>
<TableCell>
<Stack display={'flex'} flexDirection={'column'} gap={2}>
<Chip size="small" label={customer.latitude} />
<Chip size="small" label={customer.longitude} />
</Stack>
</TableCell>
</TableRow>
<tr key={index}>
<td style={{ padding: '10px 12px', borderTop: '1px solid var(--color-border)' }}>{index + 1}</td>
<td style={{ padding: '10px 12px', borderTop: '1px solid var(--color-border)' }}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span style={{ fontWeight: 600 }}>{customer.firstname}</span>
<span style={{ fontSize: 13, color: 'var(--color-text-secondary)' }}>{customer.contactno}</span>
<span style={{ fontSize: 13, color: 'var(--color-text-secondary)' }}>{`ID : ${customer.customerid}`}</span>
</div>
</td>
<td style={{ padding: '10px 12px', borderTop: '1px solid var(--color-border)' }}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span style={{ fontWeight: 600 }}>{customer.suburb}</span>
<span style={{ fontSize: 13, color: 'var(--color-text-secondary)' }}>{customer.city}</span>
</div>
</td>
<td style={{ padding: '10px 12px', borderTop: '1px solid var(--color-border)' }}>{customer.address}</td>
<td style={{ padding: '10px 12px', borderTop: '1px solid var(--color-border)' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<Badge label={customer.latitude} />
<Badge label={customer.longitude} />
</div>
</td>
</tr>
))}
{rows?.length != 0 && (
<TableRow>
<TableCell colSpan={15} rowSpan={3}>
<div ref={loadMoreRef} style={{ height: 40, textAlign: 'center' }}>
{isFetchingNextPage ? <CircularProgress /> : hasNextPage ? <CircularProgress /> : 'No More Orders'}
<tr>
<td colSpan={15} style={{ height: 40, textAlign: 'center' }}>
<div ref={loadMoreRef} style={{ height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{isFetchingNextPage || hasNextPage ? <Spinner size="md" /> : 'No More Orders'}
</div>
</TableCell>
</TableRow>
</td>
</tr>
)}
</TableBody>
</Table>
</TableContainer>
</tbody>
</table>
</div>
</MainCard>
</>
);

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ import {
MdInventory2
} from 'react-icons/md';
import dayjs from 'dayjs';
import { CircularProgress } from '@mui/material';
import { Spinner } from '@astryxdesign/core/Spinner';
import { getStatusStyle, getActiveOrder } from './dispatchShared';
import { OpenToast } from 'components/nearle_components/OpenToast';
@@ -87,7 +87,7 @@ const ActiveSection = ({
if (isLoading) {
return (
<div className="empty-slot" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', minHeight: '200px' }}>
<CircularProgress size={30} style={{ color: '#7b1fa2', marginBottom: '16px' }} />
<Spinner size="xl" style={{ marginBottom: '16px' }} />
<div className="empty-slot-title">Loading active deliveries...</div>
</div>
);

View File

@@ -3,11 +3,11 @@
--bg-sub: #f8fafc;
--bg-card: #ffffff;
--border: #e2e8f0;
--border-active: #9255AB;
--border-active: #D25463;
--text: #1e293b;
--text-muted: #64748b;
--accent: #9255AB;
--accent-soft: rgba(146, 85, 171, 0.08);
--accent: #D25463;
--accent-soft: rgba(210, 84, 99, 0.08);
--kitchen: #f59e0b;
--kitchen-soft: rgba(245, 158, 11, 0.1);
--success: #22c55e;
@@ -68,7 +68,7 @@
width: 32px;
height: 32px;
border-radius: 8px;
background: linear-gradient(135deg, #9255AB, #662582);
background: linear-gradient(135deg, #D25463, #C01227);
display: flex;
align-items: center;
justify-content: center;
@@ -1321,7 +1321,7 @@
/* Unified active style for hourly slot chips — single accent gradient instead of
per-wave colors since 12 different colors would be visually noisy. */
.dispatch-container .batch-btn.batch-slot.active {
background: linear-gradient(135deg, #9255AB, #662582);
background: linear-gradient(135deg, #D25463, #C01227);
}
.dispatch-container .batch-btn-icon {
@@ -1684,14 +1684,14 @@
}
.dispatch-container .sidebar-toggle-tab:hover {
background: linear-gradient(135deg, #7b2fad, #9255AB);
background: linear-gradient(135deg, #DD7E8A, #D25463);
color: #fff;
transform: translate(-50%, -50%) scale(1.06);
box-shadow: 0 6px 16px rgba(146, 85, 171, 0.35);
box-shadow: 0 6px 16px rgba(210, 84, 99, 0.35);
}
.dispatch-container .sidebar-toggle-tab:focus-visible {
outline: 2px solid var(--accent, #9255AB);
outline: 2px solid var(--accent, #D25463);
outline-offset: 2px;
}
@@ -1755,7 +1755,7 @@
width: 3px;
height: 14px;
border-radius: 2px;
background: linear-gradient(180deg, var(--accent), #662582);
background: linear-gradient(180deg, var(--accent), #C01227);
}
.dispatch-container .sb-title-text {
@@ -1802,7 +1802,7 @@
padding: 4px 10px 4px 8px;
border-radius: 999px;
background: var(--accent-soft);
border: 1px solid rgba(146, 85, 171, 0.22);
border: 1px solid rgba(210, 84, 99, 0.22);
color: var(--accent);
font-size: 11px;
font-weight: 700;
@@ -2158,7 +2158,7 @@
.dispatch-container .adcard.is-active {
border-color: var(--ad-accent, var(--accent));
box-shadow: 0 0 0 2px rgba(146, 85, 171, 0.15), var(--shadow-lg);
box-shadow: 0 0 0 2px rgba(210, 84, 99, 0.15), var(--shadow-lg);
background: var(--accent-soft);
}

View File

@@ -50,7 +50,6 @@ import {
MdInsights,
MdRefresh
} from 'react-icons/md';
import { CircularProgress } from '@mui/material';
import { fetchDeliveries, fetchAppLocations, getRiderPeriodicLogs, fetchRidersLogs, fetchBatchEfficiency } from '../api/api';
import {
STATUS_STYLES,
@@ -1819,7 +1818,7 @@ const Dispatch = ({
queryKey: ['deliveryLogs', deliveryid],
queryFn: async () => {
const res = await axios.get(
`${process.env.REACT_APP_URL3}/deliveries/getdeliverylogs/?deliveryid=${deliveryid}`
`${process.env.REACT_APP_URL}/deliveries/getdeliverylogs/?deliveryid=${deliveryid}`
);
// Accept several possible response shapes — the live API has shipped
// {details:[…]}, plain arrays, and {data:[…]} variants over time, and
@@ -2103,7 +2102,7 @@ const Dispatch = ({
setOsrmRoutes(prev => ({ ...prev, [cacheKey]: false }));
}
} catch (e) {
console.error('OSRM Fetch error:', e);
logger.error('OSRM Fetch error:', e);
osrmRoutesRef.current[cacheKey] = false;
setOsrmRoutes(prev => ({ ...prev, [cacheKey]: false }));
}
@@ -2174,7 +2173,7 @@ const Dispatch = ({
}
}
} catch (e) {
console.warn('OSRM Match error, trying route fallback:', e);
logger.warn('OSRM Match error, trying route fallback:', e);
}
// Attempt 2 — waypoint routing through a coarser subsample. Always
@@ -2194,7 +2193,7 @@ const Dispatch = ({
}
storeFailure();
} catch (e) {
console.error('OSRM Route fallback error:', e);
logger.error('OSRM Route fallback error:', e);
storeFailure();
}
}, []);

View File

@@ -1,25 +1,17 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import {
Backdrop,
Box,
Button,
Card,
Chip,
IconButton,
Stack,
Tab,
Tabs,
Tooltip,
Typography
} from '@mui/material';
import { useMutation } from '@tanstack/react-query';
import dayjs from 'dayjs';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { HiOutlineArrowLeft } from 'react-icons/hi';
import { IoReload } from 'react-icons/io5';
import { MdTwoWheeler } from 'react-icons/md';
import { Button } from '@astryxdesign/core/Button';
import { IconButton } from '@astryxdesign/core/IconButton';
import { Tooltip } from '@astryxdesign/core/Tooltip';
import { Stack } from '@astryxdesign/core/Stack';
import { Heading } from '@astryxdesign/core/Heading';
import {
createAutomationDeliveries,
createOptimisationDeliveries,
@@ -32,6 +24,7 @@ import CSVExport from 'components/third-party/ReactTable';
import CircularLoader from 'components/nearle_components/CircularLoader';
import Dispatch from './Dispatch';
import { stepColor } from './dispatchShared';
import { BRAND, DT } from '../_shared/ordersDesign';
const tuningTypes = [
{ tuneid: 1, type: 'Balanced', value: 'balanced' },
@@ -416,81 +409,79 @@ const Preview = () => {
};
return (
<Box
sx={{
<div
className="dispatch-preview-shell"
style={{
display: 'flex',
flexDirection: 'column',
height: { xs: 'calc(100vh - 56px)', sm: 'calc(100vh - 64px)' },
height: 'calc(100vh - var(--appshell-header-height, 64px))',
overflow: 'hidden',
position: 'relative',
mt: { xs: -2, sm: -3 },
mx: { xs: -2, sm: -3 }
position: 'relative'
}}
>
<Backdrop
sx={{ position: 'absolute', color: '#fff', zIndex: (theme) => theme.zIndex.modal + 1 }}
open={isLoading}
>
<CircularLoader color="inherit" />
</Backdrop>
{isLoading && (
<div
style={{
position: 'absolute',
inset: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
zIndex: 1301,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<CircularLoader />
</div>
)}
<Box
sx={{
py: 1.5,
px: 2.5,
borderBottom: '1px solid #e2e8f0',
background: 'linear-gradient(135deg, rgba(102,37,130,0.06) 0%, rgba(146,85,171,0.06) 100%)',
<div
style={{
padding: '12px 20px',
borderBottom: `1px solid ${DT.borderSubtle}`,
background: 'linear-gradient(135deg, rgba(192, 18, 39,0.06) 0%, rgba(210, 84, 99,0.06) 100%)',
flexShrink: 0
}}
>
<Stack direction="row" alignItems="center" justifyContent="space-between">
<Stack direction="row" alignItems="center" spacing={1.5}>
<Tooltip title="Back to orders" placement="top">
<Stack direction="horizontal" vAlign="center" justify="between">
<Stack direction="horizontal" vAlign="center" gap={1.5}>
<Tooltip content="Back to orders" placement="above">
<IconButton
label="Back to orders"
icon={<HiOutlineArrowLeft size={18} />}
variant="secondary"
onClick={() => navigate('/nearle/orders')}
sx={{
bgcolor: '#ffffff',
border: '1px solid #e2e8f0',
color: '#662582',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)',
'&:hover': { bgcolor: '#f8fafc', borderColor: '#662582' }
style={{
backgroundColor: '#ffffff',
border: `1px solid ${DT.borderSubtle}`,
color: BRAND,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)'
}}
>
<HiOutlineArrowLeft size={18} />
</IconButton>
/>
</Tooltip>
<Typography variant="h3" fontWeight={700} sx={{ color: '#0f172a' }}>
<Heading level={1} style={{ margin: 0, fontSize: 20, fontWeight: 700, color: DT.textPrimary }}>
Assign Orders
</Typography>
</Heading>
</Stack>
<Button
variant="contained"
label="Assign Orders"
variant="primary"
onClick={handleFinalCreateDelivery}
sx={{
style={{
borderRadius: 999,
px: 3.5,
py: 0.875,
bgcolor: '#662582',
padding: '7px 28px',
backgroundColor: BRAND,
color: '#ffffff',
textTransform: 'none',
fontWeight: 800,
fontSize: 13,
boxShadow: '0 4px 14px rgba(102, 37, 130, 0.25)',
'&:hover': {
bgcolor: '#9255ab',
transform: 'translateY(-1px)',
boxShadow: '0 6px 20px rgba(102, 37, 130, 0.35)'
},
transition: 'all 0.2s'
boxShadow: '0 4px 14px rgba(192, 18, 39, 0.25)'
}}
>
Assign Orders
</Button>
/>
</Stack>
</Box>
</div>
<Box sx={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
{dispatchPreviewData && (
<Dispatch
key={dispatchPreviewData?.__cacheKey || JSON.stringify(reconcileRiders.length)}
@@ -498,9 +489,23 @@ const Preview = () => {
embedded
/>
)}
</Box>
</div>
</Box>
<style>{`
.dispatch-preview-shell {
margin-top: -24px;
margin-left: -24px;
margin-right: -24px;
}
@media (max-width: 480px) {
.dispatch-preview-shell {
margin-top: -16px;
margin-left: -16px;
margin-right: -16px;
}
}
`}</style>
</div>
);
};

View File

@@ -1,42 +1,25 @@
import React, { useEffect, useState, useRef } from 'react';
import {
Avatar,
Box,
Grid,
IconButton,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TablePagination,
TableRow,
Tooltip,
Typography,
Skeleton,
InputBase
} from '@mui/material';
import { IconButton } from '@astryxdesign/core/IconButton';
import { Tooltip } from '@astryxdesign/core/Tooltip';
import { Pagination } from '@astryxdesign/core/Pagination';
import axios from 'axios';
import dayjs from 'dayjs';
import { useNavigate } from 'react-router-dom';
import { Skeleton } from 'antd';
import {
MdReceiptLong,
MdAccessTime,
MdCheckCircle,
MdHourglassEmpty,
MdWarning,
MdSearch,
MdClear,
MdVisibility,
MdInventory2,
MdCalendarMonth,
MdEventBusy
} from 'react-icons/md';
import Loader from 'components/Loader';
import logger from 'utils/logger';
// ============================================================================
// Design tokens — shared with the rest of the redesigned operator pages.
// ============================================================================
@@ -60,15 +43,17 @@ const soft = (c) => dtA(c, '18');
const ring = (c) => dtA(c, '26');
const edge = (c) => dtA(c, '55');
const BRAND = '#662582';
const BRAND_LIGHT = '#9255AB';
const BRAND = '#C01227';
const BRAND_LIGHT = '#D25463';
const noWrapStyle = { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' };
// Status palette for the four invoice buckets.
const INVOICE_STATUS_TABS = [
{ idx: 0, billStatus: 0, label: 'All', color: BRAND, icon: MdReceiptLong, countKey: 'totalcount' },
{ idx: 1, billStatus: 1, label: 'Open', color: '#f59e0b', icon: MdHourglassEmpty, countKey: 'pendingcount' },
{ idx: 2, billStatus: 2, label: 'Overdue', color: '#ef4444', icon: MdEventBusy, countKey: 'overduecount' },
{ idx: 3, billStatus: 3, label: 'Paid', color: '#10b981', icon: MdCheckCircle, countKey: 'paidcount' }
{ idx: 0, billStatus: 0, label: 'All', color: BRAND, icon: MdReceiptLong, countKey: 'totalcount' },
{ idx: 1, billStatus: 1, label: 'Open', color: BRAND, icon: MdHourglassEmpty, countKey: 'pendingcount' },
{ idx: 2, billStatus: 2, label: 'Overdue', color: BRAND, icon: MdEventBusy, countKey: 'overduecount' },
{ idx: 3, billStatus: 3, label: 'Paid', color: BRAND, icon: MdCheckCircle, countKey: 'paidcount' }
];
function formatNumberToRupees(value) {
@@ -79,6 +64,17 @@ function formatNumberToRupees(value) {
}).format(value || 0);
}
// Brand-styled scrollbar + row hover reused across the page.
const scrollbarStyle = `
.inv-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.inv-scroll::-webkit-scrollbar-thumb { background-color: ${edge(BRAND)}; border-radius: 8px; }
.inv-scroll::-webkit-scrollbar-thumb:hover { background-color: ${BRAND}; }
.inv-scroll::-webkit-scrollbar-track { background-color: ${DT.surfaceAlt}; }
.inv-row:hover { background-color: ${tint(BRAND)}; box-shadow: inset 3px 0 0 ${BRAND}; }
.inv-kpi-card:hover { transform: translateY(-1px); box-shadow: ${DT.shadowMd}; }
.inv-search-pill:focus-within { border-color: ${BRAND} !important; box-shadow: 0 0 0 3px ${ring(BRAND)}; }
`;
const Invoice = () => {
const navigate = useNavigate();
const [page, setPage] = useState(0);
@@ -114,9 +110,9 @@ const Invoice = () => {
return () => clearTimeout(t);
}, [search]);
const handleChangePage = (event, newPage) => setPage(newPage);
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(+event.target.value);
const handleChangePage = (newPage) => setPage(newPage - 1);
const handleChangeRowsPerPage = (newRowsPerPage) => {
setRowsPerPage(newRowsPerPage);
setPage(0);
};
@@ -126,7 +122,7 @@ const Invoice = () => {
const insightResponse = await axios.get(`${process.env.REACT_APP_URL}/invoice/getinvoiceinsight/?tenantid=${tenid}`);
setInsightdata(insightResponse.data.details || {});
} catch (error) {
console.log('insightResponse', error);
logger.error('insightResponse', error);
}
};
useEffect(() => {
@@ -141,7 +137,7 @@ const Invoice = () => {
const deliveyResponse = await axios.get(url);
setDeliveryList(deliveyResponse.data.details || []);
} catch (error) {
console.log('fetchdeliverylist', error);
logger.error('fetchdeliverylist', error);
} finally {
setIsLoader(false);
}
@@ -171,436 +167,363 @@ const Invoice = () => {
return (
<>
<style>{scrollbarStyle}</style>
{isloader && <Loader />}
{/* ============================================= || Header (compact) || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1, md: 1.25 },
px: { xs: 1.5, sm: 2 },
py: { xs: 1, sm: 1.25 },
borderRadius: 2,
border: '1px solid',
borderColor: DT.borderSubtle,
<div
style={{
marginBottom: 10,
padding: '10px 16px',
borderRadius: 8,
border: `1px solid ${DT.borderSubtle}`,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack direction="row" alignItems="center" spacing={1.25}>
<Avatar
variant="rounded"
sx={{
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 36,
height: 36,
bgcolor: BRAND,
flexShrink: 0,
backgroundColor: BRAND,
color: '#fff',
borderRadius: 1.5,
borderRadius: 12,
boxShadow: `0 4px 12px ${ring(BRAND)}`
}}
>
<MdReceiptLong size={19} />
</Avatar>
<Stack spacing={0.125}>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.1rem', sm: '1.25rem', md: '1.375rem' }
}}
>
Invoices
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75}>
<Box
sx={{
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<span style={{ fontWeight: 800, color: DT.textPrimary, lineHeight: 1.1, fontSize: '1.25rem' }}>Invoices</span>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 6 }}>
<span
style={{
width: 7,
height: 7,
borderRadius: '50%',
bgcolor: '#10b981',
backgroundColor: '#10b981',
boxShadow: '0 0 0 3px rgba(16,185,129,0.18)'
}}
/>
<Typography sx={{ fontSize: 11.5, color: DT.textSecondary, fontWeight: 600 }}>
<span style={{ fontSize: 11.5, color: DT.textSecondary, fontWeight: 600 }}>
Live · {INVOICE_STATUS_TABS[value].label}
</Typography>
</Stack>
</Stack>
</Stack>
</Paper>
</span>
</div>
</div>
</div>
</div>
{/* ============================================= || KPI Cards (compact, clickable) || ============================================= */}
<Grid container spacing={{ xs: 1, sm: 1.25, md: 1.5 }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(170px, 1fr))', gap: 10 }}>
{kpiCards.map((item) => {
const Icon = item.icon;
const active = value === item.idx;
return (
<Grid item key={item.label} xs={6} sm={6} md={3}>
<Paper
elevation={0}
onClick={() => handleChangetab(item.idx)}
sx={{
cursor: 'pointer',
position: 'relative',
overflow: 'hidden',
px: { xs: 1.25, sm: 1.5 },
py: { xs: 0.875, sm: 1.125 },
borderRadius: 2,
border: '1px solid',
borderColor: active ? edge(item.color) : DT.borderSubtle,
background: '#fff',
boxShadow: active ? `0 4px 14px ${ring(item.color)}` : 'none',
transition: 'transform 0.15s, box-shadow 0.15s, border-color 0.15s',
'&:hover': {
transform: 'translateY(-1px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
width: 3,
background: item.color
}}
/>
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1} sx={{ pl: 0.5 }}>
<Stack spacing={0.125} sx={{ minWidth: 0, flex: 1 }}>
<Typography
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: 10.5,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
lineHeight: 1.2
}}
>
{item.label} Invoices
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.15,
fontSize: { xs: '0.95rem', sm: '1.1rem', md: '1.2rem' }
}}
noWrap
>
{insightdata && insightdata[item.countKey] != null ? (
insightdata[item.countKey]
) : (
<Skeleton sx={{ width: 40 }} animation="wave" />
)}
</Typography>
</Stack>
<Avatar
variant="rounded"
sx={{
width: 30,
height: 30,
bgcolor: soft(item.color),
color: item.color,
borderRadius: 1.25,
flexShrink: 0
<div
key={item.label}
className="inv-kpi-card"
role="button"
tabIndex={0}
onClick={() => handleChangetab(item.idx)}
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && handleChangetab(item.idx)}
style={{
cursor: 'pointer',
position: 'relative',
overflow: 'hidden',
padding: '10px 14px',
borderRadius: 8,
border: `1px solid ${active ? edge(item.color) : DT.borderSubtle}`,
background: '#fff',
boxShadow: active ? `0 4px 14px ${ring(item.color)}` : 'none',
transition: 'transform 0.15s, box-shadow 0.15s, border-color 0.15s'
}}
>
<div style={{ position: 'absolute', top: 0, left: 0, bottom: 0, width: 3, background: item.color }} />
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8, paddingLeft: 4 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0, flex: 1 }}>
<span
style={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: 10.5,
lineHeight: 1.2,
...noWrapStyle
}}
>
<Icon size={15} />
</Avatar>
</Stack>
</Paper>
</Grid>
{item.label} Invoices
</span>
<span style={{ fontWeight: 800, color: DT.textPrimary, lineHeight: 1.15, fontSize: '1.1rem', ...noWrapStyle }}>
{insightdata && insightdata[item.countKey] != null ? (
insightdata[item.countKey]
) : (
<Skeleton.Input active size="small" style={{ width: 40, minWidth: 40, height: 16 }} />
)}
</span>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 30,
height: 30,
flexShrink: 0,
backgroundColor: soft(item.color),
color: item.color,
borderRadius: 10
}}
>
<Icon size={15} />
</div>
</div>
</div>
);
})}
</Grid>
</div>
{/* ============================================= || Status Tabs + Search (compact) || ============================================= */}
<Paper
elevation={0}
sx={{
mt: { xs: 1, md: 1.25 },
p: { xs: 0.875, md: 1.125 },
borderTopLeftRadius: 2,
borderTopRightRadius: 2,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
border: '1px solid',
borderColor: DT.borderSubtle,
<div
style={{
marginTop: 10,
padding: 10,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
border: `1px solid ${DT.borderSubtle}`,
borderBottom: 0,
background: '#fff'
}}
>
<Stack direction="row" alignItems="center" justifyContent="space-between" gap={1.5} sx={{ flexWrap: 'wrap-reverse' }}>
<Stack
direction="row"
spacing={0.75}
sx={{
flex: 1,
overflowX: 'auto',
py: 0.5,
px: 0.25,
'&::-webkit-scrollbar': { height: 6 },
'&::-webkit-scrollbar-thumb': { backgroundColor: DT.borderSubtle, borderRadius: 4 }
}}
>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap-reverse' }}>
<div className="inv-scroll" style={{ display: 'flex', flex: 1, gap: 6, overflowX: 'auto', padding: '4px 2px' }}>
{INVOICE_STATUS_TABS.map((t) => {
const Icon = t.icon;
const active = value === t.idx;
const count = insightdata?.[t.countKey] ?? 0;
return (
<Box
<div
key={t.label}
role="button"
tabIndex={0}
onClick={() => handleChangetab(t.idx)}
sx={{
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && handleChangetab(t.idx)}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: { xs: 0.625, md: 0.875 },
pl: 0.5,
pr: { xs: 1, md: 1.25 },
py: 0.5,
gap: 7,
paddingLeft: 4,
paddingRight: 10,
paddingTop: 4,
paddingBottom: 4,
flexShrink: 0,
cursor: 'pointer',
borderRadius: 999,
border: `1.5px solid ${active ? t.color : edge(t.color)}`,
bgcolor: active ? t.color : tint(t.color),
backgroundColor: active ? t.color : tint(t.color),
color: active ? '#fff' : t.color,
fontWeight: 700,
boxShadow: active ? `0 6px 18px ${ring(t.color)}` : 'none',
transition: 'all 0.18s',
'&:hover': {
borderColor: t.color,
boxShadow: active ? `0 6px 18px ${ring(t.color)}` : `0 0 0 3px ${ring(t.color)}`
}
transition: 'all 0.18s'
}}
>
<Avatar
sx={{
width: { xs: 22, md: 26 },
height: { xs: 22, md: 26 },
bgcolor: active ? 'rgba(255,255,255,0.22)' : soft(t.color),
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 24,
height: 24,
borderRadius: '50%',
backgroundColor: active ? 'rgba(255,255,255,0.22)' : soft(t.color),
color: active ? '#fff' : t.color
}}
>
<Icon size={13} />
</Avatar>
<Typography variant="caption" sx={{ fontWeight: 800, fontSize: { xs: 11.5, md: 13 }, lineHeight: 1 }}>
{t.label}
</Typography>
<Box
sx={{
minWidth: { xs: 22, md: 26 },
height: { xs: 18, md: 22 },
px: 0.625,
</div>
<span style={{ fontWeight: 800, fontSize: 12.5, lineHeight: 1 }}>{t.label}</span>
<span
style={{
minWidth: 24,
height: 20,
padding: '0 6px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 999,
fontSize: { xs: 10, md: 11 },
fontSize: 11,
fontWeight: 800,
bgcolor: active ? 'rgba(255,255,255,0.22)' : '#fff',
backgroundColor: active ? 'rgba(255,255,255,0.22)' : '#fff',
color: active ? '#fff' : t.color,
border: active ? 'none' : `1px solid ${edge(t.color)}`
}}
>
{count}
</Box>
</Box>
</span>
</div>
);
})}
</Stack>
</div>
<Box sx={{ width: { xs: '100%', sm: 260, lg: 300 }, flex: { xs: '1 1 100%', sm: '0 0 auto' } }}>
<Box
sx={{
<div style={{ width: 280, maxWidth: '100%', flex: '0 0 auto' }}>
<div
className="inv-search-pill"
style={{
display: 'flex',
alignItems: 'center',
gap: 0.75,
px: 1.25,
py: 0.5,
gap: 8,
padding: '6px 12px',
borderRadius: 999,
bgcolor: tint(BRAND),
backgroundColor: tint(BRAND),
border: `1.5px solid ${edge(BRAND)}`,
transition: 'all 0.18s',
'&:focus-within': {
borderColor: BRAND,
boxShadow: `0 0 0 3px ${ring(BRAND)}`
}
transition: 'all 0.18s'
}}
>
<MdSearch size={16} style={{ color: BRAND, flexShrink: 0 }} />
<InputBase
inputRef={textFieldRef}
<input
ref={textFieldRef}
placeholder="Search invoice no (ctrl+k)"
value={search}
onChange={(e) => setSearch(e.target.value)}
autoComplete="off"
sx={{
style={{
flex: 1,
fontSize: 13,
fontWeight: 600,
color: DT.textPrimary,
'& input::placeholder': { color: DT.textMuted, opacity: 1 }
border: 'none',
outline: 'none',
background: 'transparent'
}}
/>
{search && (
<IconButton size="small" onClick={() => setSearch('')} sx={{ p: 0.25, color: BRAND }}>
<MdClear size={14} />
</IconButton>
<IconButton
label="Clear search"
icon={<MdClear size={14} />}
variant="ghost"
size="sm"
onClick={() => setSearch('')}
style={{ color: BRAND }}
/>
)}
</Box>
</Box>
</Stack>
</Paper>
</div>
</div>
</div>
</div>
{/* ============================================= || Table (dense, sticky header) || ============================================= */}
<Paper
elevation={0}
sx={{
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomLeftRadius: 2,
borderBottomRightRadius: 2,
border: '1px solid',
borderColor: DT.borderSubtle,
<div
style={{
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
border: `1px solid ${DT.borderSubtle}`,
overflow: 'hidden',
background: '#fff'
}}
>
<TableContainer
sx={{
minHeight: 320,
maxHeight: { xs: 'calc(100vh - 380px)', md: 'calc(100vh - 350px)' },
overflow: 'auto',
'&::-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 size="small" sx={{ minWidth: 880 }}>
<TableHead>
<TableRow
sx={{
'& th': {
backgroundColor: DT.surfaceAlt,
color: DT.textSecondary,
fontSize: 10.5,
fontWeight: 800,
letterSpacing: 0.5,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${DT.borderSubtle}`,
py: 0.75,
px: 1
}
}}
>
<TableCell>#</TableCell>
<TableCell>Client</TableCell>
<TableCell>Invoice Id</TableCell>
<TableCell>Invoice Date</TableCell>
<TableCell>Due Date</TableCell>
<TableCell align="center">Count</TableCell>
<TableCell align="right">Amount</TableCell>
<TableCell align="center">Action</TableCell>
</TableRow>
</TableHead>
<div className="inv-scroll" style={{ minHeight: 320, maxHeight: 'calc(100vh - 350px)', overflow: 'auto' }}>
<table style={{ width: '100%', minWidth: 880, borderCollapse: 'collapse', fontSize: 12.5 }}>
<thead style={{ position: 'sticky', top: 0, zIndex: 1 }}>
<tr>
{['#', 'Client', 'Invoice Id', 'Invoice Date', 'Due Date', 'Count', 'Amount', 'Action'].map((h, i) => (
<th
key={h}
style={{
textAlign: i === 5 || i === 7 ? 'center' : i === 6 ? 'right' : 'left',
backgroundColor: DT.surfaceAlt,
color: DT.textSecondary,
fontSize: 10.5,
fontWeight: 800,
letterSpacing: 0.5,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${DT.borderSubtle}`,
padding: '6px 8px'
}}
>
{h}
</th>
))}
</tr>
</thead>
<TableBody>
{isloader && filteredList.length === 0 && (
<tbody>
{isloader &&
filteredList.length === 0 &&
Array.from({ length: 10 }).map((_, idx) => (
<TableRow key={`sk-${idx}`}>
<tr key={`sk-${idx}`}>
{Array.from({ length: 8 }).map((__, ci) => (
<TableCell key={ci} sx={{ borderBottom: `1px solid ${DT.divider}`, py: 0.625, px: 1 }}>
<Skeleton animation="wave" height={20} />
</TableCell>
<td key={ci} style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px' }}>
<Skeleton.Input active size="small" style={{ width: '100%', height: 18 }} />
</td>
))}
</TableRow>
))
)}
</tr>
))}
{!isloader && filteredList.length === 0 && (
<TableRow>
<TableCell colSpan={8} sx={{ py: 7, borderBottom: 'none' }}>
<Stack alignItems="center" spacing={1.25}>
<Avatar
variant="rounded"
sx={{
<tr>
<td colSpan={8} style={{ padding: '56px 0', borderBottom: 'none' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 56,
height: 56,
bgcolor: soft('#94a3b8'),
backgroundColor: soft('#94a3b8'),
color: DT.textMuted,
borderRadius: 2
borderRadius: 16
}}
>
<MdReceiptLong size={26} />
</Avatar>
<Typography sx={{ fontWeight: 700, color: DT.textPrimary, fontSize: 14 }}>
</div>
<span style={{ fontWeight: 700, color: DT.textPrimary, fontSize: 14 }}>
No {INVOICE_STATUS_TABS[value].label.toLowerCase()} invoices
</Typography>
<Typography sx={{ color: DT.textSecondary, fontSize: 12 }}>
</span>
<span style={{ color: DT.textSecondary, fontSize: 12 }}>
{search ? 'Try a different invoice number or clear the search.' : 'Switch tabs above to load invoices.'}
</Typography>
</span>
{search && (
<Box
component="button"
<button
onClick={() => setSearch('')}
sx={{
mt: 0.5,
style={{
marginTop: 4,
border: 'none',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
gap: 4,
height: 28,
px: 1,
padding: '0 8px',
fontSize: 12,
fontWeight: 700,
color: BRAND,
borderRadius: 1.25,
background: 'transparent',
'&:hover': { bgcolor: tint(BRAND) }
borderRadius: 6,
background: 'transparent'
}}
>
<MdClear size={14} /> Clear search
</Box>
</button>
)}
</Stack>
</TableCell>
</TableRow>
</div>
</td>
</tr>
)}
{filteredList.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((item, index) => {
const tabMeta = INVOICE_STATUS_TABS[value];
return (
<TableRow
<tr
key={`${item.invoiceno}-${index}`}
sx={{
cursor: 'pointer',
transition: 'background-color 0.12s, box-shadow 0.12s',
'& td': {
borderBottom: `1px solid ${DT.divider}`,
py: 0.5,
px: 1,
verticalAlign: 'middle'
},
'&:hover': {
backgroundColor: tint(BRAND),
boxShadow: `inset 3px 0 0 ${BRAND}`
}
}}
className="inv-row"
style={{ cursor: 'pointer', transition: 'background-color 0.12s, box-shadow 0.12s' }}
onClick={() => {
setIsLoader(true);
setTimeout(() => {
@@ -609,31 +532,28 @@ const Invoice = () => {
}, 300);
}}
>
<TableCell>
<Typography sx={{ fontWeight: 700, fontSize: 12, color: DT.textMuted }}>
{page * rowsPerPage + index + 1}
</Typography>
</TableCell>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px', verticalAlign: 'middle' }}>
<span style={{ fontWeight: 700, fontSize: 12, color: DT.textMuted }}>{page * rowsPerPage + index + 1}</span>
</td>
<TableCell>
<Typography sx={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25 }} noWrap>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px', verticalAlign: 'middle' }}>
<div style={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25, ...noWrapStyle }}>
{item.tenantname}
</Typography>
<Typography sx={{ fontSize: 11, fontWeight: 600, color: DT.textSecondary, lineHeight: 1.3 }} noWrap>
</div>
<div style={{ fontSize: 11, fontWeight: 600, color: DT.textSecondary, lineHeight: 1.3, ...noWrapStyle }}>
{item.contactperson}
</Typography>
</TableCell>
</div>
</td>
<TableCell>
<Box
sx={{
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px', verticalAlign: 'middle' }}>
<span
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 0.875,
py: 0.25,
gap: 4,
padding: '2px 7px',
borderRadius: 999,
bgcolor: tint(BRAND),
backgroundColor: tint(BRAND),
border: `1px solid ${edge(BRAND)}`,
color: BRAND,
fontSize: 11,
@@ -642,49 +562,48 @@ const Invoice = () => {
}}
>
<MdReceiptLong size={11} /> {item.invoiceno}
</Box>
</TableCell>
</span>
</td>
<TableCell>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px', verticalAlign: 'middle' }}>
{item.transactiondate ? (
<Stack spacing={0} sx={{ lineHeight: 1.1 }}>
<Typography sx={{ fontSize: 12.5, fontWeight: 800, color: DT.textPrimary, lineHeight: 1.15 }} noWrap>
<div style={{ display: 'flex', flexDirection: 'column', lineHeight: 1.1 }}>
<span style={{ fontSize: 12.5, fontWeight: 800, color: DT.textPrimary, lineHeight: 1.15, ...noWrapStyle }}>
{dayjs(item.transactiondate).format('hh:mm A')}
</Typography>
<Typography sx={{ fontSize: 10.5, fontWeight: 600, color: DT.textMuted, lineHeight: 1.2 }} noWrap>
</span>
<span style={{ fontSize: 10.5, fontWeight: 600, color: DT.textMuted, lineHeight: 1.2, ...noWrapStyle }}>
{dayjs(item.transactiondate).format('DD MMM YYYY')}
</Typography>
</Stack>
</span>
</div>
) : (
<Typography sx={{ fontSize: 12, color: DT.textMuted, fontWeight: 700 }}></Typography>
<span style={{ fontSize: 12, color: DT.textMuted, fontWeight: 700 }}></span>
)}
</TableCell>
</td>
<TableCell>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px', verticalAlign: 'middle' }}>
{item.duedate ? (
<Stack spacing={0} sx={{ lineHeight: 1.1 }}>
<Typography sx={{ fontSize: 12.5, fontWeight: 800, color: DT.textPrimary, lineHeight: 1.15 }} noWrap>
<div style={{ display: 'flex', flexDirection: 'column', lineHeight: 1.1 }}>
<span style={{ fontSize: 12.5, fontWeight: 800, color: DT.textPrimary, lineHeight: 1.15, ...noWrapStyle }}>
{dayjs(item.duedate).format('hh:mm A')}
</Typography>
<Typography sx={{ fontSize: 10.5, fontWeight: 600, color: DT.textMuted, lineHeight: 1.2 }} noWrap>
</span>
<span style={{ fontSize: 10.5, fontWeight: 600, color: DT.textMuted, lineHeight: 1.2, ...noWrapStyle }}>
{dayjs(item.duedate).format('DD MMM YYYY')}
</Typography>
</Stack>
</span>
</div>
) : (
<Typography sx={{ fontSize: 12, color: DT.textMuted, fontWeight: 700 }}></Typography>
<span style={{ fontSize: 12, color: DT.textMuted, fontWeight: 700 }}></span>
)}
</TableCell>
</td>
<TableCell align="center">
<Box
sx={{
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px', verticalAlign: 'middle', textAlign: 'center' }}>
<span
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 0.875,
py: 0.25,
gap: 4,
padding: '2px 7px',
borderRadius: 999,
bgcolor: tint('#0ea5e9'),
backgroundColor: tint('#0ea5e9'),
border: `1px solid ${edge('#0ea5e9')}`,
color: '#0ea5e9',
fontSize: 12,
@@ -692,19 +611,21 @@ const Invoice = () => {
}}
>
<MdInventory2 size={11} /> {item.itemcount ?? 0}
</Box>
</TableCell>
</span>
</td>
<TableCell align="right">
<Typography sx={{ fontWeight: 800, color: tabMeta.color, fontSize: 13, whiteSpace: 'nowrap' }}>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px', verticalAlign: 'middle', textAlign: 'right' }}>
<span style={{ fontWeight: 800, color: tabMeta.color, fontSize: 13, whiteSpace: 'nowrap' }}>
{formatNumberToRupees(item.totalamount)}
</Typography>
</TableCell>
</span>
</td>
<TableCell align="center">
<Tooltip title="Preview Invoice">
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px', verticalAlign: 'middle', textAlign: 'center' }}>
<Tooltip content="Preview Invoice">
<IconButton
size="small"
label="Preview Invoice"
icon={<MdVisibility size={14} />}
size="sm"
onClick={(e) => {
e.stopPropagation();
setIsLoader(true);
@@ -713,43 +634,35 @@ const Invoice = () => {
navigate('/nearle/invoice/preview', { state: item });
}, 300);
}}
sx={{
bgcolor: tint(BRAND),
style={{
backgroundColor: tint(BRAND),
border: `1px solid ${edge(BRAND)}`,
color: BRAND,
borderRadius: 999,
p: 0.75,
'&:hover': {
bgcolor: soft(BRAND),
borderColor: BRAND
}
borderRadius: 999
}}
>
<MdVisibility size={14} />
</IconButton>
/>
</Tooltip>
</TableCell>
</TableRow>
</td>
</tr>
);
})}
</TableBody>
</Table>
</TableContainer>
</tbody>
</table>
</div>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
component="div"
count={filteredList.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
sx={{
borderTop: `1px solid ${DT.divider}`,
'& .MuiTablePagination-toolbar': { color: DT.textSecondary, fontWeight: 600 }
}}
/>
</Paper>
<div style={{ borderTop: `1px solid ${DT.divider}`, padding: '10px 12px', display: 'flex', justifyContent: 'flex-end' }}>
<Pagination
page={page + 1}
onChange={handleChangePage}
totalItems={filteredList.length}
pageSize={rowsPerPage}
pageSizeOptions={[5, 10, 25, 100]}
onPageSizeChange={handleChangeRowsPerPage}
variant="count"
size="sm"
/>
</div>
</div>
</>
);
};

View File

@@ -1,61 +1,58 @@
import React, { useRef, useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useTheme } from '@mui/material/styles';
// import nearleLogo from '../../assets/images/nearleLogo.png';
import logo_nearle1 from '../../../assets/images/logo-nearle1.png';
import logo_nearle1 from '../../../assets/images/doormile-mark.png';
import axios from 'axios';
import dayjs from 'dayjs';
import Loader from 'components/Loader';
import { enqueueSnackbar } from 'notistack';
import { DownloadOutlined, PrinterFilled } from '@ant-design/icons';
import ReactToPrint, { useReactToPrint } from 'react-to-print';
import { SearchOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
// import jsPDF from 'jspdf';
import { PrinterFilled } from '@ant-design/icons';
import { useReactToPrint } from 'react-to-print';
import { useNavigate } from 'react-router-dom';
import { FaArrowLeft } from 'react-icons/fa6';
import { FaIndianRupeeSign } from 'react-icons/fa6';
// import jsPDF from 'jspdf';
import logger from 'utils/logger';
// import autoTable from 'jspdf-autotable';
import {
Grid,
Button,
Divider,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TablePagination,
TableRow,
Tabs,
Tab,
Typography,
Box,
OutlinedInput,
InputAdornment,
IconButton,
Tooltip,
Dialog,
DialogTitle,
DialogContent,
Stack,
Chip,
DialogActions,
TextField
} from '@mui/material';
import { Button } from '@astryxdesign/core/Button';
import { IconButton } from '@astryxdesign/core/IconButton';
import { Tooltip } from '@astryxdesign/core/Tooltip';
import { Badge } from '@astryxdesign/core/Badge';
import { Dialog, DialogHeader } from '@astryxdesign/core/Dialog';
import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout';
import { TextInput } from '@astryxdesign/core/TextInput';
import { TextArea } from '@astryxdesign/core/TextArea';
// ============================================================================
// Design tokens — kept in sync with the Invoices list page (invoice.js) so the
// list → preview flow reads as one surface.
// ============================================================================
const DT = {
textPrimary: '#0f172a',
textSecondary: '#64748b',
textMuted: '#94a3b8',
borderSubtle: '#e2e8f0',
divider: '#f1f5f9',
surfaceAlt: '#f8fafc'
};
const dtA = (c, suffix) => `${c}${suffix}`;
const soft = (c) => dtA(c, '18');
const BRAND = '#C01227';
const SUCCESS = '#10b981';
const ERROR = '#ef4444';
const InvoicePreview = () => {
const [selected, setselected] = useState({});
const location = useLocation();
const navigate = useNavigate();
console.log('previewSelect', location.state);
logger.info('previewSelect', location.state);
const componentRef = useRef(null);
const handlePrint = useReactToPrint({ contentRef: componentRef });
const [tabletype, settabletype] = useState(true);
const [paydialog, setpaydialog] = useState(false);
const [refnumber, setRefnumber] = useState('');
const [remarks, setRemarks] = useState('');
const theme = useTheme();
useEffect(() => {
setselected(location.state);
}, []);
@@ -70,8 +67,8 @@ const InvoicePreview = () => {
}
useEffect(() => {
console.log('refnumber', refnumber);
console.log('remarks', remarks);
logger.info('refnumber', refnumber);
logger.info('remarks', remarks);
}, [refnumber, remarks]);
// ================================================= || updatePayment || =================================================
@@ -92,390 +89,311 @@ const InvoicePreview = () => {
autoHideDuration: 1000
});
}
console.log('updateResponse', updateResponse);
logger.info('updateResponse', updateResponse);
} catch (error) {
console.log('updateResponse', error);
logger.error('updateResponse', error);
}
};
return (
<>
<Stack direction="row" justifyContent="Space-between" alignItems={'center'} spacing={2} sx={{ px: 2.5, py: 1, bgcolor: '#eeeeee' }}>
<div
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 16,
padding: '8px 20px',
backgroundColor: '#eeeeee'
}}
>
{/* // ================================================= || Invoice Details || ================================================= */}
<Stack direction={'row'} alignItems={'center'} spacing={2}>
<Tooltip title="back">
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 16 }}>
<Tooltip content="back">
<IconButton
color="primary"
label="Back"
icon={<FaArrowLeft size={18} />}
variant="ghost"
onClick={() => {
navigate('/nearle/invoice');
}}
>
<FaArrowLeft size={'large'} />
</IconButton>
style={{ color: BRAND }}
/>
</Tooltip>
<Stack alignItems={'center'}>
<Typography variant="h3" color={'primary'}>
Invoice Details
</Typography>
<Chip
size="small"
color="warning"
variant="outlined"
sx={{ bgcolor: theme.palette.warning.lighter }}
label={`Invoice No :${'\u00a0\u00a0'}${selected.invoiceno}`}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
<span style={{ fontWeight: 800, fontSize: '1.25rem', color: BRAND }}>Invoice Details</span>
<Badge
variant="warning"
label={`Invoice No :${'  '}${selected.invoiceno}`}
style={{ backgroundColor: soft('#f59e0b') }}
/>
</Stack>
</Stack>
</div>
</div>
<Stack direction={'row'} spacing={2}>
<div style={{ display: 'flex', flexDirection: 'row', gap: 16 }}>
{/* <Button
variant="outlined"
color="primary"
sx={{
'&:hover': {
backgroundColor: 'primary.main',
color: 'primary.contrastText'
}
}}
label="Update Payment"
variant="secondary"
icon={<FaIndianRupeeSign />}
onClick={() => {
setpaydialog(true);
}}
>
{' '}
<FaIndianRupeeSign />
Update Payment
</Button> */}
<ReactToPrint
trigger={() => (
<Button
size="small"
startIcon={<PrinterFilled />}
variant="outlined"
color="primary"
sx={{
'&:hover': {
backgroundColor: 'primary.main',
color: 'primary.contrastText'
}
}}
>
Print
</Button>
)}
content={() => componentRef.current}
/>
</Stack>
</Stack>
/> */}
<Button label="Print" size="sm" icon={<PrinterFilled />} variant="secondary" onClick={handlePrint} />
</div>
</div>
{/* // ================================================= || Date row || ================================================= */}
<Box sx={{ pb: 2.5, border: '1px solid #eee' }}>
<div style={{ paddingBottom: 20, border: `1px solid ${DT.borderSubtle}` }}>
<div ref={componentRef} style={{ width: '100%' }}>
<Box id="print" sx={{ p: 2.5 }}>
<Box sx={{ pb: 2.5 }}>
<Stack
sx={{
<div id="print" style={{ padding: 20 }}>
<div style={{ paddingBottom: 20 }}>
<div
style={{
display: 'flex',
flexDirection: 'row',
// bgcolor: theme.palette.primary.main,
border: '1px solid #eee',
px: 3
justifyContent: 'space-between',
border: `1px solid ${DT.borderSubtle}`,
padding: '0 24px'
}}
justifyContent="space-between"
>
<Box sx={{ pt: 0.5 }}>
<Stack direction="row" spacing={2}>
<img src={logo_nearle1} style={{ width: '150px', height: '50px' }} />{' '}
</Stack>
<div style={{ paddingTop: 4 }}>
<div style={{ display: 'flex', flexDirection: 'row', gap: 16 }}>
<img src={logo_nearle1} style={{ width: '50px', height: '50px' }} alt="Nearle" />{' '}
</div>
<Stack direction="row" justifyContent="space-between">
<Typography
sx={{
overflow: 'hidden',
color: theme.palette.primary.main
}}
variant="subtitle1"
>
Invoice No :
</Typography>
<Typography sx={{ color: theme.palette.primary.main }}>{`${'\u00a0\u00a0'}${selected.invoiceno}`}</Typography>
</Stack>
</Box>
<Box sx={{ pt: 2.5, pb: 1.75 }}>
<Stack direction="row" justifyContent="space-between">
<Typography sx={{ pl: 4, color: theme.palette.primary.main }} variant="subtitle1">
Date :{' '}
</Typography>
<Typography sx={{ color: theme.palette.primary.main }}>
{dayjs(selected.transactiondate).format('DD-MM-YYYY')}
</Typography>
</Stack>
<Stack direction="row" justifyContent="space-between">
<Typography
sx={{
pr: 2,
overflow: 'hidden',
color: theme.palette.primary.main
}}
variant="subtitle1"
>
Due Date :
</Typography>
<Typography sx={{ color: theme.palette.primary.main }}>{dayjs(selected.dueDate).format('DD-MM-YYYY')}</Typography>
</Stack>
</Box>
</Stack>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<span style={{ overflow: 'hidden', color: BRAND, fontSize: 15, fontWeight: 700 }}>Invoice No :</span>
<span style={{ color: BRAND }}>{`${'  '}${selected.invoiceno}`}</span>
</div>
</div>
<div style={{ paddingTop: 20, paddingBottom: 14 }}>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<span style={{ paddingLeft: 32, color: BRAND, fontSize: 15, fontWeight: 700 }}>Date : </span>
<span style={{ color: BRAND }}>{dayjs(selected.transactiondate).format('DD-MM-YYYY')}</span>
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<span style={{ paddingRight: 16, overflow: 'hidden', color: BRAND, fontSize: 15, fontWeight: 700 }}>Due Date :</span>
<span style={{ color: BRAND }}>{dayjs(selected.duedate).format('DD-MM-YYYY')}</span>
</div>
</div>
</div>
{/* // ================================================= || from to || ================================================= */}
<Box sx={{ pt: 2.5 }}>
<Grid container spacing={2} justifyContent="space-between" direction="row">
<Grid item xs={6}>
<Box
sx={{
border: 1,
minHeight: 240,
borderColor: 'grey.200',
borderRadius: 0.5,
p: 2.5
}}
>
<Grid container direction="row">
<Grid item md={8}>
<Stack spacing={2}>
<Typography variant="h5">From:</Typography>
<Stack sx={{ width: '100%' }}>
<Typography variant="subtitle1">Nearle Technology Privite Limited.</Typography>
<Typography color="secondary">
424, 4<sup>th</sup>floor,
</Typography>
<Typography color="secondary">Red rose towers,</Typography>
<Typography color="secondary">DB Road, RS Puram,</Typography>
<Typography color="secondary">641002.</Typography>
<Typography color="secondary">care@nearle.in</Typography>
<Typography color="secondary">9047968666</Typography>
</Stack>
</Stack>
</Grid>
</Grid>
</Box>
</Grid>
<Grid item xs={6}>
<Box
sx={{
border: 1,
minHeight: 240,
borderColor: 'grey.200',
borderRadius: 0.5,
p: 2.5
}}
>
<Grid container direction="row">
<Grid item md={8}>
<Stack spacing={2}>
<Typography variant="h5">To:</Typography>
<Stack sx={{ width: '100%' }}>
<Typography variant="subtitle1">{selected.tenantname}</Typography>
<Typography color="secondary">{selected.address}</Typography>
<Typography color="secondary">{selected.suburb}</Typography>
<Typography color="secondary">{selected.city}</Typography>
<Typography color="secondary">{selected.state}</Typography>{' '}
</Stack>
</Stack>
</Grid>
</Grid>
</Box>
</Grid>
</Grid>
</Box>
</Box>
<div style={{ paddingTop: 20 }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 16 }}>
<div
style={{
border: `1px solid ${DT.borderSubtle}`,
minHeight: 240,
borderRadius: 4,
padding: 20
}}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<span style={{ fontSize: '1.25rem', fontWeight: 700, color: DT.textPrimary }}>From:</span>
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
<span style={{ fontWeight: 600, color: DT.textPrimary }}>Nearle Technology Privite Limited.</span>
<span style={{ color: DT.textSecondary }}>
424, 4<sup>th</sup>floor,
</span>
<span style={{ color: DT.textSecondary }}>Red rose towers,</span>
<span style={{ color: DT.textSecondary }}>DB Road, RS Puram,</span>
<span style={{ color: DT.textSecondary }}>641002.</span>
<span style={{ color: DT.textSecondary }}>care@nearle.in</span>
<span style={{ color: DT.textSecondary }}>9047968666</span>
</div>
</div>
</div>
<div
style={{
border: `1px solid ${DT.borderSubtle}`,
minHeight: 240,
borderRadius: 4,
padding: 20
}}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<span style={{ fontSize: '1.25rem', fontWeight: 700, color: DT.textPrimary }}>To:</span>
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
<span style={{ fontWeight: 600, color: DT.textPrimary }}>{selected.tenantname}</span>
<span style={{ color: DT.textSecondary }}>{selected.address}</span>
<span style={{ color: DT.textSecondary }}>{selected.suburb}</span>
<span style={{ color: DT.textSecondary }}>{selected.city}</span>
<span style={{ color: DT.textSecondary }}>{selected.state}</span>{' '}
</div>
</div>
</div>
</div>
</div>
</div>
{/* // ================================================= || invoice table || ================================================= */}
<TableContainer
// sx={{
// ...(tabletype
// ? {
// maxHeight: 430,
// mt: -3,
// "&::-webkit-scrollbar": {
// width: "4px", // Width of vertical scrollbar
// height: "4px", // Height of horizontal scrollbar
// },
// "&::-webkit-scrollbar-thumb": {
// backgroundColor: "#65387A", // Color of the scrollbar thumb
// },
// }
// : {}),
// }}
>
<Table>
<TableHead>
<TableRow>
<TableCell>S.No</TableCell>
<TableCell>Particulars</TableCell>
<TableCell>Unit</TableCell>
<TableCell>Quantity</TableCell>
<TableCell align="right">Rate</TableCell>
{/* {selected && selected.pricingtypeid === 73 && ( */}
<TableCell>Other Charges</TableCell>
{/* )} */}
<TableCell align="right">Amount</TableCell>
</TableRow>
</TableHead>
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
{['S.No', 'Particulars', 'Unit', 'Quantity', 'Rate', 'Other Charges', 'Amount'].map((h, i) => (
<th
key={h}
style={{
textAlign: i === 4 || i === 6 ? 'right' : 'left',
padding: '10px 12px',
borderBottom: `1px solid ${DT.borderSubtle}`,
color: DT.textSecondary,
fontWeight: 700,
fontSize: 13
}}
>
{h}
</th>
))}
</tr>
</thead>
{selected.tenantsalesdetails && (
<TableBody>
<TableRow>
<TableCell>1</TableCell>
<TableCell>
<Typography>
<tbody>
<tr>
<td style={{ padding: '10px 12px', borderBottom: `1px solid ${DT.divider}` }}>1</td>
<td style={{ padding: '10px 12px', borderBottom: `1px solid ${DT.divider}` }}>
<span>
{`Invoice from ${dayjs(selected.tenantsalesdetails[0].fromdate).format('DD-MM-YYYY')} to ${dayjs(
selected.tenantsalesdetails[0].todate
).format('DD-MM-YYYY')}`}
</Typography>
</TableCell>
<TableCell>
<Typography>{selected.tenantsalesdetails[0].pricingtype}</Typography>
</TableCell>
</span>
</td>
<td style={{ padding: '10px 12px', borderBottom: `1px solid ${DT.divider}` }}>
<span>{selected.tenantsalesdetails[0].pricingtype}</span>
</td>
<TableCell>
<Typography>{`${selected.tenantsalesdetails[0].quantity}km`}</Typography>
</TableCell>
<TableCell>
<Typography align="right">{`${selected.tenantsalesdetails[0].baserate.toFixed(2)}`}</Typography>
</TableCell>
{/* {selected.tenantsalesdetails[0].pricingtypeid == 73 && ( */}
<TableCell align="center">
<Typography>{`${selected.tenantsalesdetails[0].othercharges}.00`}</Typography>
</TableCell>
{/* )} */}
<TableCell align="right">
<Typography>{`${selected.tenantsalesdetails[0].amount}.00`}</Typography>
</TableCell>
</TableRow>
</TableBody>
<td style={{ padding: '10px 12px', borderBottom: `1px solid ${DT.divider}` }}>
<span>{`${selected.tenantsalesdetails[0].quantity}km`}</span>
</td>
<td style={{ padding: '10px 12px', borderBottom: `1px solid ${DT.divider}`, textAlign: 'right' }}>
<span>{`${selected.tenantsalesdetails[0].baserate.toFixed(2)}`}</span>
</td>
<td style={{ padding: '10px 12px', borderBottom: `1px solid ${DT.divider}`, textAlign: 'center' }}>
<span>{`${selected.tenantsalesdetails[0].othercharges}.00`}</span>
</td>
<td style={{ padding: '10px 12px', borderBottom: `1px solid ${DT.divider}`, textAlign: 'right' }}>
<span>{`${selected.tenantsalesdetails[0].amount}.00`}</span>
</td>
</tr>
</tbody>
)}
</Table>
</TableContainer>
<Divider />
<Box sx={{ p: 2.5 }}>
<Grid container direction="row" justifyContent="flex-end">
<Grid item md={4}>
<Stack spacing={2}>
<Stack direction="row" justifyContent="space-between">
<Typography color="secondary">Sub Total:</Typography>
<Typography variant="h6">{formatNumberToRupees(selected.salesamount)}</Typography>
</Stack>
<Stack direction="row" justifyContent="space-between">
<Typography color="secondary">Discount:</Typography>
<Typography variant="h6" color={theme.palette.error.main}>
</table>
</div>
<div style={{ borderTop: `1px solid ${DT.borderSubtle}`, margin: '8px 0' }} />
<div style={{ padding: 20 }}>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<div style={{ width: '100%', maxWidth: 320 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<span style={{ color: DT.textSecondary }}>Sub Total:</span>
<span style={{ fontSize: '1.125rem', fontWeight: 600 }}>{formatNumberToRupees(selected.salesamount)}</span>
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<span style={{ color: DT.textSecondary }}>Discount:</span>
<span style={{ fontSize: '1.125rem', fontWeight: 600, color: ERROR }}>
- {formatNumberToRupees(selected.discountamt)}
</Typography>
</Stack>
<Stack direction="row" justifyContent="space-between">
<Typography color={theme.palette.grey[500]}>Tax:</Typography>
<Typography variant="h6" color={theme.palette.success.main}>
</span>
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<span style={{ color: DT.textSecondary }}>Tax:</span>
<span style={{ fontSize: '1.125rem', fontWeight: 600, color: SUCCESS }}>
+ {formatNumberToRupees(selected.taxamount)}
</Typography>
</Stack>
<Stack direction="row" justifyContent="space-between">
<Typography sx={{ pr: 2 }} variant="subtitle1">
Grand Total:
</Typography>
<Typography variant="h6">{formatNumberToRupees(Math.round(selected.totalamount))}</Typography>
</Stack>
</Stack>
</Grid>
</Grid>
</Box>
</Box>
<Divider />
<Box sx={{ p: 2.5 }}>
<Typography>Notes: {selected.remarks}</Typography>
</Box>
</span>
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<span style={{ paddingRight: 16, fontWeight: 700, color: DT.textPrimary }}>Grand Total:</span>
<span style={{ fontSize: '1.125rem', fontWeight: 600 }}>
{formatNumberToRupees(Math.round(selected.totalamount))}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div style={{ borderTop: `1px solid ${DT.borderSubtle}`, margin: '8px 0' }} />
<div style={{ padding: 20 }}>
<span>Notes: {selected.remarks}</span>
</div>
<Divider />
<div style={{ borderTop: `1px solid ${DT.borderSubtle}`, margin: '8px 0' }} />
</div>
</Box>
</div>
{/* ================================================= || updatePayment Dialog || ================================================= */}
<Dialog
open={paydialog}
onClose={() => {
setpaydialog(false);
isOpen={paydialog}
onOpenChange={(open) => {
setpaydialog(open);
}}
maxWidth={'sm'}
fullWidth
width={440}
purpose="form"
>
<DialogTitle sx={{ bgcolor: theme.palette.primary.main }}>
<Stack direction={'row'} spacing={1}>
<Typography variant="h2" sx={{ color: 'white' }}>
</Typography>
<Typography variant="h3" sx={{ color: 'white' }}>
Update Payment
</Typography>
</Stack>
</DialogTitle>
<DialogContent dividers>
<Stack spacing={1} sx={{ mb: 2 }}>
<Typography>Reference No</Typography>
<TextField
type="number"
placeholder="Enter Reference Number"
sx={{ width: '100%' }}
onChange={(e) => {
setRefnumber(e.target.value);
}}
<Layout
header={
<DialogHeader
title="₹ Update Payment"
onOpenChange={(open) => setpaydialog(open)}
style={{ backgroundColor: BRAND, color: '#fff' }}
/>
</Stack>
<Stack spacing={2} sx={{ mb: 2 }}>
<Typography>Remarks</Typography>
<TextField
multiline
required
placeholder="Enter Remarks"
sx={{ width: '100%' }}
onChange={(e) => {
setRemarks(e.target.value);
}}
/>
</Stack>
</DialogContent>
<DialogActions>
<Button
variant="outlined"
sx={{
'&:hover': {
backgroundColor: 'primary.main',
color: 'primary.contrastText'
},
m: 2
}}
onClick={() => {
setpaydialog(false);
}}
>
Cancel
</Button>
<Button
variant="outlined"
disabled={refnumber == '' || remarks == ''}
sx={{
'&:hover': {
backgroundColor: 'primary.main',
color: 'primary.contrastText'
},
m: 2
}}
onClick={() => {
setpaydialog(false);
updatePayment();
navigate('/invoice');
}}
>
Update
</Button>
</DialogActions>
}
content={
<LayoutContent>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<TextInput
label="Reference No"
placeholder="Enter Reference Number"
width="100%"
value={refnumber}
onChange={(v) => {
setRefnumber(v);
}}
/>
<TextArea
label="Remarks"
isRequired
placeholder="Enter Remarks"
width="100%"
value={remarks}
onChange={(v) => {
setRemarks(v);
}}
/>
</div>
</LayoutContent>
}
footer={
<LayoutFooter hasDivider>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
<Button
label="Cancel"
variant="secondary"
onClick={() => {
setpaydialog(false);
}}
/>
<Button
label="Update"
variant="primary"
isDisabled={refnumber == '' || remarks == ''}
onClick={() => {
setpaydialog(false);
updatePayment();
navigate('/invoice');
}}
/>
</div>
</LayoutFooter>
}
/>
</Dialog>
</>
);

View File

@@ -1,24 +1,18 @@
import React from 'react';
import { Box, useTheme } from '@mui/material';
import ResponsiveLocationDrawer from './ResponsiveLocationDrawer';
const Locations = () => {
const theme = useTheme();
return (
<Box
sx={{
width: '100%',
height: 'calc(100vh - 144px)', // below global header
overflow: 'hidden',
position: 'relative',
border: '0.1px solid',
borderColor: theme.palette.secondary.light
}}
>
<ResponsiveLocationDrawer />
</Box>
);
};
const Locations = () => (
<div
style={{
width: '100%',
height: 'calc(100vh - 144px)', // below global header
overflow: 'hidden',
position: 'relative',
border: '0.1px solid var(--color-border)'
}}
>
<ResponsiveLocationDrawer />
</div>
);
export default Locations;

View File

@@ -1,25 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import {
Box,
Drawer,
IconButton,
Typography,
useMediaQuery,
useTheme,
Tooltip,
TableCell,
Stack,
TableRow,
TableBody,
TableHead,
Table,
TableContainer,
CircularProgress,
InputBase,
Paper,
Avatar,
ButtonBase
} from '@mui/material';
import { Spinner } from '@astryxdesign/core/Spinner';
import { IconButton } from '@astryxdesign/core/IconButton';
import {
MdMenu,
MdSearch,
@@ -39,19 +20,10 @@ import { fetchOrders1, gettenantlocations } from '../api/api';
import Loader from 'components/Loader';
import CircularLoader from 'components/nearle_components/CircularLoader';
import { Empty, Skeleton } from 'antd';
import {
DT,
BRAND,
BRAND_LIGHT,
tint,
soft,
ring,
edge,
StatusBadge,
AccentAvatar
} from '../_shared/ordersDesign';
import { DT, BRAND, BRAND_LIGHT, tint, soft, ring, edge, StatusBadge, AccentAvatar } from '../_shared/ordersDesign';
import axios from 'axios';
import dayjs from 'dayjs';
import logger from 'utils/logger';
var utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
@@ -60,18 +32,29 @@ const drawerWidth = 300;
// Status filter tabs — colors aligned with STATUS_META in the shared design system
// (blue=created, amber=pending, green=delivered, red=cancelled).
const STATUS_TABS = [
{ label: 'Created', value: 'created', color: '#3b82f6', icon: MdLocalShipping },
{ label: 'Pending', value: 'pending', color: '#f59e0b', icon: MdHourglassEmpty },
{ label: 'Delivered', value: 'delivered', color: '#10b981', icon: MdCheckCircle },
{ label: 'Cancelled', value: 'cancelled', color: '#ef4444', icon: MdCancel }
{ label: 'Created', value: 'created', color: BRAND, icon: MdLocalShipping },
{ label: 'Pending', value: 'pending', color: BRAND, icon: MdHourglassEmpty },
{ label: 'Delivered', value: 'delivered', color: BRAND, icon: MdCheckCircle },
{ label: 'Cancelled', value: 'cancelled', color: BRAND, icon: MdCancel }
];
// Brand-styled scrollbar reused on the sidebar + table.
const scrollbarStyle = `
.rld-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.rld-scroll::-webkit-scrollbar-thumb { background-color: ${edge(BRAND)}; border-radius: 8px; }
.rld-scroll::-webkit-scrollbar-thumb:hover { background-color: ${BRAND}; }
.rld-scroll::-webkit-scrollbar-track { background-color: ${DT.surfaceAlt}; }
.rld-pill:focus-within { border-color: ${BRAND} !important; box-shadow: 0 0 0 3px ${ring(BRAND)}; }
.rld-toggle:hover { background-color: ${tint(BRAND)}; border-color: ${BRAND}; }
`;
const noWrapStyle = { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' };
const ResponsiveLocationDrawer = () => {
const loadMoreRef = useRef();
const containerRef = useRef();
const theme = useTheme();
const tenantid = localStorage.getItem('tenantid');
const isDesktop = useMediaQuery('(min-width:900px)');
const [isDesktop, setIsDesktop] = useState(() => (typeof window !== 'undefined' ? window.innerWidth >= 900 : true));
const [open, setOpen] = useState(false);
const [selectedLocation, setSelectedLocation] = useState(null);
const [currentStatus, setCurrentStatus] = useState('created');
@@ -92,6 +75,12 @@ const ResponsiveLocationDrawer = () => {
const [searchword, setSearchword] = useState('');
const [debouncedSearchword, setDebouncedSearchword] = useState('');
useEffect(() => {
const handleResize = () => setIsDesktop(window.innerWidth >= 900);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// Per-status counts keyed by tab value, so the filter pills can show a badge.
const statusCounts = {
created: createdLenght,
@@ -177,7 +166,7 @@ const ResponsiveLocationDrawer = () => {
}
},
{
root: document.querySelector('.MuiTableContainer-root'),
root: containerRef.current,
rootMargin: '0px',
threshold: 1.0
}
@@ -205,7 +194,7 @@ const ResponsiveLocationDrawer = () => {
`${process.env.REACT_APP_URL}/orders/getordersummary/?tenantid=${tenantid}&locationid=${selectedLocation?.locationid}&fromdate=${startdate}&todate=${enddate}`
)
.then((res) => {
console.log('fetchorderscount', res.data.details);
logger.info('fetchorderscount', res.data.details);
setCreatedLenght(res.data.details.created);
setPendingLenght(res.data.details.pending);
setDeliveredlenght(res.data.details.delivered);
@@ -217,11 +206,11 @@ const ResponsiveLocationDrawer = () => {
setLoading(false);
})
.catch((err) => {
console.log(err);
logger.error(err);
setLoading(false);
});
} catch (err) {
console.log(err);
logger.error(err);
setLoading(false);
}
};
@@ -237,303 +226,320 @@ const ResponsiveLocationDrawer = () => {
const errMessage = locationIsError ? `${locationError.message}` : null;
useEffect(() => {
errMessage && console.log(errMessage);
errMessage && logger.info(errMessage);
}, [errMessage]);
// Brand-styled scrollbar reused on the sidebar + table.
const scrollbarSx = {
'&::-webkit-scrollbar': { width: 8, height: 8 },
'&::-webkit-scrollbar-thumb': {
backgroundColor: edge(BRAND),
borderRadius: 8,
'&:hover': { backgroundColor: BRAND }
},
'&::-webkit-scrollbar-track': { backgroundColor: DT.surfaceAlt }
};
// --------------------------------------------------------------------------
// Sidebar — searchable location list. Shared between the desktop persistent
// drawer and the mobile temporary drawer.
// --------------------------------------------------------------------------
const sidebarContent = (
<Stack sx={{ height: '100%', bgcolor: '#fff' }}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', backgroundColor: '#fff' }}>
{/* Sidebar header */}
<Box sx={{ px: 1.5, pt: 1.5, pb: 1 }}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 1.25 }}>
<Avatar
variant="rounded"
sx={{ width: 30, height: 30, bgcolor: BRAND, color: '#fff', borderRadius: 1.5, boxShadow: `0 4px 12px ${ring(BRAND)}` }}
<div style={{ padding: '12px 12px 8px' }}>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 10 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 30,
height: 30,
flexShrink: 0,
backgroundColor: BRAND,
color: '#fff',
borderRadius: 12,
boxShadow: `0 4px 12px ${ring(BRAND)}`
}}
>
<MdStorefront size={16} />
</Avatar>
<Stack spacing={0}>
<Typography sx={{ fontSize: 13.5, fontWeight: 800, color: DT.textPrimary, lineHeight: 1.1 }}>Locations</Typography>
<Typography sx={{ fontSize: 10.5, fontWeight: 600, color: DT.textMuted }}>
</div>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span style={{ fontSize: 13.5, fontWeight: 800, color: DT.textPrimary, lineHeight: 1.1 }}>Locations</span>
<span style={{ fontSize: 10.5, fontWeight: 600, color: DT.textMuted }}>
{Array.isArray(locations) ? `${locations.length} active` : '—'}
</Typography>
</Stack>
</Stack>
</span>
</div>
</div>
{/* Search pill */}
<Box
sx={{
<div
className="rld-pill"
style={{
display: 'flex',
alignItems: 'center',
gap: 0.75,
px: 1.25,
py: 0.75,
gap: 6,
padding: '6px 10px',
borderRadius: 999,
bgcolor: tint(BRAND),
backgroundColor: tint(BRAND),
border: `1.5px solid ${edge(BRAND)}`,
transition: 'all 0.18s',
'&:focus-within': { borderColor: BRAND, boxShadow: `0 0 0 3px ${ring(BRAND)}` }
transition: 'all 0.18s'
}}
>
<MdSearch size={16} style={{ color: BRAND, flexShrink: 0 }} />
<InputBase
<input
placeholder="Search location"
value={searchLocation}
onChange={(e) => setSearchLocation(e.target.value)}
autoComplete="off"
sx={{
flex: 1,
fontSize: 13,
fontWeight: 600,
color: DT.textPrimary,
'& input::placeholder': { color: DT.textMuted, opacity: 1 }
}}
style={{ flex: 1, fontSize: 13, fontWeight: 600, color: DT.textPrimary, border: 'none', outline: 'none', background: 'transparent' }}
/>
{searchLocation && (
<IconButton size="small" onClick={() => setSearchLocation('')} sx={{ p: 0.25, color: BRAND }}>
<MdClear size={14} />
</IconButton>
<IconButton
label="Clear search"
icon={<MdClear size={14} />}
variant="ghost"
size="sm"
onClick={() => setSearchLocation('')}
style={{ color: BRAND }}
/>
)}
</Box>
</Box>
</div>
</div>
{/* Location list */}
<Box sx={{ flex: 1, overflowY: 'auto', px: 1, pb: 1, ...scrollbarSx }}>
<div className="rld-scroll" style={{ flex: 1, overflowY: 'auto', padding: '0 8px 8px' }}>
{locationIsLoading &&
Array.from({ length: 8 }).map((_, i) => (
<Box key={i} sx={{ px: 1, py: 1 }}>
<div key={i} style={{ padding: '8px' }}>
<Skeleton avatar active paragraph={{ rows: 1 }} title={false} />
</Box>
</div>
))}
{!locationIsLoading && Array.isArray(locations) && locations.length === 0 && (
<Box sx={{ py: 5 }}>
<div style={{ padding: '40px 0' }}>
<Empty description="No locations" />
</Box>
</div>
)}
<Stack spacing={0.5}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{locations?.map((row, index) => {
const isSelected = row.locationid === selectedLocation?.locationid;
return (
<ButtonBase
<button
key={index}
onClick={() => setSelectedLocation(row)}
sx={{
style={{
display: 'flex',
width: '100%',
alignItems: 'center',
justifyContent: 'flex-start',
textAlign: 'left',
gap: 1,
px: 1,
py: 0.875,
borderRadius: 2,
gap: 8,
padding: '7px 8px',
borderRadius: 16,
border: 'none',
position: 'relative',
cursor: 'pointer',
transition: 'background-color 0.14s, box-shadow 0.14s',
bgcolor: isSelected ? tint(BRAND) : 'transparent',
boxShadow: isSelected ? `inset 3px 0 0 ${BRAND}` : 'none',
'&:hover': { bgcolor: isSelected ? tint(BRAND) : DT.surfaceAlt }
backgroundColor: isSelected ? tint(BRAND) : 'transparent',
boxShadow: isSelected ? `inset 3px 0 0 ${BRAND}` : 'none'
}}
>
<AccentAvatar color={BRAND} selected={isSelected} size={36}>
{row.locationname?.[0]?.toUpperCase() || '?'}
</AccentAvatar>
<Stack spacing={0} sx={{ minWidth: 0, flex: 1 }}>
<Typography
sx={{ fontSize: 13, fontWeight: 700, color: isSelected ? BRAND : DT.textPrimary, lineHeight: 1.2 }}
noWrap
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 0, minWidth: 0, flex: 1 }}>
<span style={{ fontSize: 13, fontWeight: 700, color: isSelected ? BRAND : DT.textPrimary, lineHeight: 1.2, ...noWrapStyle }}>
{row.locationname}
</Typography>
<Stack direction="row" alignItems="center" spacing={0.375}>
</span>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 3 }}>
<MdPlace size={11} style={{ color: DT.textMuted, flexShrink: 0 }} />
<Typography sx={{ fontSize: 11, fontWeight: 600, color: DT.textSecondary }} noWrap>
{row.suburb || '—'}
</Typography>
</Stack>
</Stack>
</ButtonBase>
<span style={{ fontSize: 11, fontWeight: 600, color: DT.textSecondary, ...noWrapStyle }}>{row.suburb || '—'}</span>
</div>
</div>
</button>
);
})}
</Stack>
</Box>
</Stack>
</div>
</div>
</div>
);
return (
<React.Fragment>
<style>{scrollbarStyle}</style>
{locationIsLoading && (
<>
<Loader /> <CircularLoader />
</>
)}
<Box sx={{ display: 'flex', width: '100%', height: '100%', position: 'relative', bgcolor: DT.surfaceAlt }}>
<div style={{ display: 'flex', width: '100%', height: '100%', position: 'relative', backgroundColor: DT.surfaceAlt }}>
{/* ---------------- LOCATION SIDEBAR ---------------- */}
<Drawer
variant={isDesktop ? 'persistent' : 'temporary'}
open={open}
onClose={() => !isDesktop && toggleDrawer()}
ModalProps={{ keepMounted: true }}
sx={{
'& .MuiDrawer-paper': {
width: drawerWidth,
boxSizing: 'border-box',
position: 'absolute',
left: 0,
top: 0,
height: '100%',
overflow: 'hidden',
borderRight: `1px solid ${DT.borderSubtle}`,
transition: 'transform 0.35s ease-in-out',
zIndex: 10
}
}}
>
{sidebarContent}
</Drawer>
{(isDesktop || open) && (
<>
{!isDesktop && (
<div
role="button"
tabIndex={0}
aria-label="Close locations sidebar"
onClick={toggleDrawer}
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && toggleDrawer()}
style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(15,23,42,0.35)', zIndex: 9, border: 'none' }}
/>
)}
<div
style={{
width: drawerWidth,
boxSizing: 'border-box',
position: 'absolute',
left: 0,
top: 0,
height: '100%',
overflow: 'hidden',
borderRight: `1px solid ${DT.borderSubtle}`,
transform: open ? 'translateX(0)' : 'translateX(-100%)',
transition: 'transform 0.35s ease-in-out',
zIndex: 10
}}
>
{sidebarContent}
</div>
</>
)}
{/* ---------------- MAIN PANEL ---------------- */}
<Box
sx={{
<div
style={{
flexGrow: 1,
height: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
pl: isDesktop && open ? `${drawerWidth}px` : 0,
paddingLeft: isDesktop && open ? drawerWidth : 0,
transition: 'padding-left 0.3s ease'
}}
>
{/* ---------------- GRADIENT HEADER ---------------- */}
<Paper
elevation={0}
sx={{
<div
style={{
flexShrink: 0,
px: { xs: 1.25, sm: 1.75 },
py: { xs: 1, sm: 1.25 },
borderRadius: 0,
padding: '10px 14px',
borderBottom: `1px solid ${DT.borderSubtle}`,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`
}}
>
<Stack
direction={{ xs: 'column', md: 'row' }}
alignItems={{ xs: 'stretch', md: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1, md: 1.5 }}
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 12,
flexWrap: 'wrap'
}}
>
<Stack direction="row" alignItems="center" spacing={1.25}>
<Tooltip title={open ? 'Hide locations' : 'Show locations'} arrow>
<IconButton
onClick={toggleDrawer}
sx={{
width: 34,
height: 34,
borderRadius: 1.5,
bgcolor: '#fff',
border: `1px solid ${DT.borderSubtle}`,
color: BRAND,
'&:hover': { bgcolor: tint(BRAND), borderColor: BRAND }
}}
>
<MdMenu size={18} />
</IconButton>
</Tooltip>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<button
title={open ? 'Hide locations' : 'Show locations'}
onClick={toggleDrawer}
className="rld-toggle"
style={{
width: 34,
height: 34,
borderRadius: 12,
backgroundColor: '#fff',
border: `1px solid ${DT.borderSubtle}`,
color: BRAND,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
transition: 'background-color 0.15s, border-color 0.15s'
}}
>
<MdMenu size={18} />
</button>
<Avatar
variant="rounded"
sx={{ width: 36, height: 36, bgcolor: BRAND, color: '#fff', borderRadius: 1.5, boxShadow: `0 4px 12px ${ring(BRAND)}` }}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 36,
height: 36,
flexShrink: 0,
backgroundColor: BRAND,
color: '#fff',
borderRadius: 12,
boxShadow: `0 4px 12px ${ring(BRAND)}`
}}
>
<MdMyLocation size={19} />
</Avatar>
<Stack spacing={0.125}>
<Typography
variant="h3"
sx={{
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<span
style={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.05rem', sm: '1.2rem', md: '1.3rem' }
fontSize: '1.2rem',
...noWrapStyle
}}
noWrap
>
{selectedLocation?.locationname || 'Select a location'}
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75}>
<Box sx={{ width: 7, height: 7, borderRadius: '50%', bgcolor: '#10b981', boxShadow: '0 0 0 3px rgba(16,185,129,0.18)' }} />
<Typography sx={{ fontSize: 11.5, color: DT.textSecondary, fontWeight: 600 }} noWrap>
</span>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 6 }}>
<span style={{ width: 7, height: 7, borderRadius: '50%', backgroundColor: '#10b981', boxShadow: '0 0 0 3px rgba(16,185,129,0.18)' }} />
<span style={{ fontSize: 11.5, color: DT.textSecondary, fontWeight: 600, ...noWrapStyle }}>
{selectedLocation?.suburb ? `${selectedLocation.suburb} · ` : ''}Live · {dayjs(startdate).format('DD MMM YYYY')}
</Typography>
</Stack>
</Stack>
</Stack>
</span>
</div>
</div>
</div>
{/* Order search pill */}
<Box
sx={{
<div
className="rld-pill"
style={{
display: 'flex',
alignItems: 'center',
gap: 0.75,
px: 1.25,
py: 0.75,
gap: 6,
padding: '6px 10px',
borderRadius: 999,
bgcolor: '#fff',
backgroundColor: '#fff',
border: `1.5px solid ${edge(BRAND)}`,
minWidth: { xs: '100%', md: 280 },
maxWidth: { md: 360 },
transition: 'all 0.18s',
'&:focus-within': { borderColor: BRAND, boxShadow: `0 0 0 3px ${ring(BRAND)}` }
minWidth: 280,
maxWidth: 360,
flex: '1 1 280px',
transition: 'all 0.18s'
}}
>
<MdSearch size={16} style={{ color: BRAND, flexShrink: 0 }} />
<InputBase
<input
placeholder="Search order details"
value={searchword}
onChange={(e) => setSearchword(e.target.value)}
autoComplete="off"
sx={{
flex: 1,
fontSize: 13,
fontWeight: 600,
color: DT.textPrimary,
'& input::placeholder': { color: DT.textMuted, opacity: 1 }
}}
style={{ flex: 1, fontSize: 13, fontWeight: 600, color: DT.textPrimary, border: 'none', outline: 'none', background: 'transparent' }}
/>
{searchword && (
<IconButton size="small" onClick={() => setSearchword('')} sx={{ p: 0.25, color: BRAND }}>
<MdClear size={14} />
</IconButton>
<IconButton
label="Clear search"
icon={<MdClear size={14} />}
variant="ghost"
size="sm"
onClick={() => setSearchword('')}
style={{ color: BRAND }}
/>
)}
</Box>
</Stack>
</Paper>
</div>
</div>
</div>
{/* ---------------- STATUS FILTER PILLS ---------------- */}
<Box
sx={{
<div
className="rld-scroll"
style={{
flexShrink: 0,
px: { xs: 1, sm: 1.5 },
py: 1,
bgcolor: '#fff',
padding: '8px 12px',
backgroundColor: '#fff',
borderBottom: `1px solid ${DT.borderSubtle}`,
display: 'flex',
gap: 0.75,
overflowX: 'auto',
...scrollbarSx
gap: 6,
overflowX: 'auto'
}}
>
{STATUS_TABS.map((item, index) => {
@@ -541,229 +547,231 @@ const ResponsiveLocationDrawer = () => {
const Icon = item.icon;
const count = statusCounts[item.value];
return (
<ButtonBase
<button
key={index}
onClick={() => handleChangetab(index)}
sx={{
style={{
flexShrink: 0,
gap: 0.75,
px: 1.25,
py: 0.625,
display: 'flex',
alignItems: 'center',
gap: 6,
padding: '5px 10px',
borderRadius: 999,
fontWeight: 700,
cursor: 'pointer',
transition: 'all 0.15s',
border: `1.5px solid ${isActive ? item.color : DT.borderSubtle}`,
bgcolor: isActive ? item.color : '#fff',
backgroundColor: isActive ? item.color : '#fff',
color: isActive ? '#fff' : DT.textSecondary,
boxShadow: isActive ? `0 4px 12px ${ring(item.color)}` : 'none',
'&:hover': {
borderColor: item.color,
color: isActive ? '#fff' : item.color,
bgcolor: isActive ? item.color : tint(item.color)
}
boxShadow: isActive ? `0 4px 12px ${ring(item.color)}` : 'none'
}}
>
<Icon size={14} />
<Typography sx={{ fontSize: 12.5, fontWeight: 700, lineHeight: 1 }}>{item.label}</Typography>
<Box
sx={{
<span style={{ fontSize: 12.5, fontWeight: 700, lineHeight: 1 }}>{item.label}</span>
<span
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
minWidth: 20,
height: 18,
px: 0.625,
padding: '0 5px',
borderRadius: 999,
fontSize: 10.5,
fontWeight: 800,
bgcolor: isActive ? 'rgba(255,255,255,0.25)' : soft(item.color),
backgroundColor: isActive ? 'rgba(255,255,255,0.25)' : soft(item.color),
color: isActive ? '#fff' : item.color
}}
>
{count ?? 0}
</Box>
</ButtonBase>
</span>
</button>
);
})}
</Box>
</div>
{/* ---------------- ORDERS TABLE ---------------- */}
<Box sx={{ flex: 1, overflow: 'hidden', p: { xs: 1, sm: 1.5 } }}>
<Paper
elevation={0}
sx={{
<div style={{ flex: 1, overflow: 'hidden', padding: 12 }}>
<div
style={{
height: '100%',
display: 'flex',
flexDirection: 'column',
borderRadius: 2,
borderRadius: 8,
border: `1px solid ${DT.borderSubtle}`,
overflow: 'hidden',
background: '#fff',
boxShadow: DT.shadowSoft
}}
>
<TableContainer
onScroll={handleScroll}
ref={containerRef}
sx={{ flex: 1, overflow: 'auto', ...scrollbarSx }}
>
<Table stickyHeader size="small">
<TableHead>
<TableRow
sx={{
'& th': {
backgroundColor: DT.surfaceAlt,
color: DT.textSecondary,
fontSize: 10.5,
fontWeight: 800,
letterSpacing: 0.5,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${DT.borderSubtle}`,
py: 0.75,
px: 1
}
}}
>
<TableCell sx={{ width: 36 }}>#</TableCell>
<TableCell sx={{ minWidth: 150 }}>Order</TableCell>
<TableCell sx={{ minWidth: 150 }}>Pickup</TableCell>
<TableCell sx={{ minWidth: 150 }}>Drop</TableCell>
<TableCell sx={{ minWidth: 140 }}>Notes</TableCell>
<TableCell sx={{ width: 120 }}>Status</TableCell>
</TableRow>
</TableHead>
<div className="rld-scroll" onScroll={handleScroll} ref={containerRef} style={{ flex: 1, overflow: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
<thead style={{ position: 'sticky', top: 0, zIndex: 1 }}>
<tr>
{['#', 'Order', 'Pickup', 'Drop', 'Notes', 'Status'].map((h, i) => (
<th
key={h}
style={{
textAlign: 'left',
backgroundColor: DT.surfaceAlt,
color: DT.textSecondary,
fontSize: 10.5,
fontWeight: 800,
letterSpacing: 0.5,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${DT.borderSubtle}`,
padding: '6px 8px',
width: i === 0 ? 36 : i === 5 ? 120 : undefined,
minWidth: i > 0 && i < 5 ? 140 : undefined
}}
>
{h}
</th>
))}
</tr>
</thead>
<TableBody>
<tbody>
{/* LOADING STATE */}
{loading &&
Array.from({ length: 10 }).map((_, index) => (
<TableRow key={index}>
<tr key={index}>
{Array.from({ length: 6 }).map((__, i) => (
<TableCell key={i} sx={{ borderBottom: `1px solid ${DT.divider}`, py: 0.625, px: 1 }}>
<td key={i} style={{ borderBottom: `1px solid ${DT.divider}`, padding: '5px 8px' }}>
<Skeleton.Input active size="small" style={{ width: '100%', height: 18 }} />
</TableCell>
</td>
))}
</TableRow>
</tr>
))}
{/* EMPTY STATE */}
{!loading && rows?.length === 0 && (
<TableRow>
<TableCell colSpan={6} sx={{ py: 7, borderBottom: 'none' }}>
<Stack alignItems="center" spacing={1.25}>
<Avatar variant="rounded" sx={{ width: 56, height: 56, bgcolor: soft('#94a3b8'), color: DT.textMuted, borderRadius: 2 }}>
<tr>
<td colSpan={6} style={{ padding: '56px 0', borderBottom: 'none' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 56,
height: 56,
backgroundColor: soft('#94a3b8'),
color: DT.textMuted,
borderRadius: 16
}}
>
<MdReceiptLong size={26} />
</Avatar>
<Typography sx={{ fontWeight: 700, color: DT.textPrimary, fontSize: 14 }}>No orders found</Typography>
<Typography sx={{ color: DT.textSecondary, fontSize: 12 }}>
</div>
<span style={{ fontWeight: 700, color: DT.textPrimary, fontSize: 14 }}>No orders found</span>
<span style={{ color: DT.textSecondary, fontSize: 12 }}>
{searchword ? 'Try a different keyword or clear the search.' : 'No orders in this status for the selected location.'}
</Typography>
</Stack>
</TableCell>
</TableRow>
</span>
</div>
</td>
</tr>
)}
{/* DATA ROWS */}
{!loading &&
rows?.map((row, index) => (
<TableRow
<tr
key={index}
sx={{
cursor: 'pointer',
transition: 'background-color 0.12s, box-shadow 0.12s',
'& td': { borderBottom: `1px solid ${DT.divider}`, py: 0.75, px: 1, verticalAlign: 'top' },
'&:hover': { backgroundColor: tint(BRAND), boxShadow: `inset 3px 0 0 ${BRAND}` }
}}
className="rld-row"
style={{ cursor: 'pointer', transition: 'background-color 0.12s, box-shadow 0.12s' }}
>
<TableCell>
<Typography sx={{ fontWeight: 700, fontSize: 12, color: DT.textMuted }}>{page * rowsPerPage + index + 1}</Typography>
</TableCell>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '6px 8px', verticalAlign: 'top' }}>
<span style={{ fontWeight: 700, fontSize: 12, color: DT.textMuted }}>{page * rowsPerPage + index + 1}</span>
</td>
{/* Order Info */}
<TableCell>
<Typography sx={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25 }} noWrap>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '6px 8px', verticalAlign: 'top' }}>
<div style={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25, ...noWrapStyle }}>
{row.orderid}
</Typography>
<Stack direction="row" alignItems="center" spacing={0.5} sx={{ mt: 0.125 }}>
</div>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 4, marginTop: 1 }}>
<MdAccessTime size={10} style={{ color: DT.textMuted, flexShrink: 0 }} />
<Typography sx={{ fontSize: 10.5, color: DT.textSecondary, fontWeight: 700 }} noWrap>
<span style={{ fontSize: 10.5, color: DT.textSecondary, fontWeight: 700, ...noWrapStyle }}>
{dayjs(row.deliverydate).utc().format('hh:mm A')}
</Typography>
<Typography sx={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600 }} noWrap>
</span>
<span style={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600, ...noWrapStyle }}>
· {dayjs(row.deliverydate).utc().format('DD MMM YY')}
</Typography>
</Stack>
</TableCell>
</span>
</div>
</td>
{/* Pickup */}
<TableCell>
<Stack spacing={0.125}>
<Typography sx={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25 }} noWrap>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '6px 8px', verticalAlign: 'top' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<span style={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25, ...noWrapStyle }}>
{row.pickupcustomer || '—'}
</Typography>
<Typography sx={{ fontSize: 11, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.3 }} noWrap>
</span>
<span style={{ fontSize: 11, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.3, ...noWrapStyle }}>
{row.pickupcontactno}
</Typography>
<Tooltip title={row.pickupaddress || ''}>
<Typography sx={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600, lineHeight: 1.3 }} noWrap>
{row.pickupsuburb || (row.pickupaddress ? `${row.pickupaddress.slice(0, 20)}` : '—')}
</Typography>
</Tooltip>
</Stack>
</TableCell>
</span>
<span
title={row.pickupaddress || ''}
style={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600, lineHeight: 1.3, ...noWrapStyle }}
>
{row.pickupsuburb || (row.pickupaddress ? `${row.pickupaddress.slice(0, 20)}` : '—')}
</span>
</div>
</td>
{/* Drop */}
<TableCell>
<Stack spacing={0.125}>
<Typography sx={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25 }} noWrap>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '6px 8px', verticalAlign: 'top' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<span style={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25, ...noWrapStyle }}>
{row.deliverycustomer || '—'}
</Typography>
<Typography sx={{ fontSize: 11, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.3 }} noWrap>
</span>
<span style={{ fontSize: 11, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.3, ...noWrapStyle }}>
{row.deliverycontactno}
</Typography>
<Tooltip title={row.deliveryaddress || ''}>
<Typography sx={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600, lineHeight: 1.3 }} noWrap>
{row.deliverysuburb || (row.deliveryaddress ? `${row.deliveryaddress.slice(0, 20)}` : '—')}
</Typography>
</Tooltip>
</Stack>
</TableCell>
</span>
<span
title={row.deliveryaddress || ''}
style={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600, lineHeight: 1.3, ...noWrapStyle }}
>
{row.deliverysuburb || (row.deliveryaddress ? `${row.deliveryaddress.slice(0, 20)}` : '—')}
</span>
</div>
</td>
{/* Notes */}
<TableCell>
<Typography sx={{ fontSize: 11.5, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.35 }}>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '6px 8px', verticalAlign: 'top' }}>
<span style={{ fontSize: 11.5, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.35 }}>
{row.ordernotes || '—'}
</Typography>
</TableCell>
</span>
</td>
{/* Status */}
<TableCell>
<td style={{ borderBottom: `1px solid ${DT.divider}`, padding: '6px 8px', verticalAlign: 'top' }}>
<StatusBadge status={row.orderstatus} />
</TableCell>
</TableRow>
</td>
</tr>
))}
{rows?.length != 0 && (
<TableRow>
<TableCell colSpan={6} sx={{ borderBottom: 'none' }}>
<Stack ref={loadMoreRef} alignItems="center" justifyContent="center" sx={{ height: 40 }}>
<tr>
<td colSpan={6} style={{ borderBottom: 'none' }}>
<div ref={loadMoreRef} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 40 }}>
{isFetchingNextPage || hasNextPage ? (
<CircularProgress size={20} sx={{ color: BRAND }} />
<Spinner size="md" style={{ color: BRAND }} />
) : (
<Typography sx={{ fontSize: 11.5, fontWeight: 700, color: DT.textMuted }}>No more orders</Typography>
<span style={{ fontSize: 11.5, fontWeight: 700, color: DT.textMuted }}>No more orders</span>
)}
</Stack>
</TableCell>
</TableRow>
</div>
</td>
</tr>
)}
</TableBody>
</Table>
</TableContainer>
</Paper>
</Box>
</Box>
</Box>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<style>{`.rld-row:hover { background-color: ${tint(BRAND)}; box-shadow: inset 3px 0 0 ${BRAND}; }`}</style>
</React.Fragment>
);
};

View File

@@ -1,44 +1,79 @@
import { useState, useEffect } from 'react';
// import { useSelector } from 'react-redux';
// import AuthWrapper from 'sections/auth/AuthWrapper';
import {
Box,
Grid,
Card,
CardContent,
Stack,
TextField,
Button,
Typography,
CardHeader,
Container,
Link,
InputAdornment,
IconButton
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import AnimateButton from 'components/@extended/AnimateButton';
import logo from 'assets/images/logo-nearle1.png';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import Loader from 'components/Loader';
import { OpenToast } from 'components/nearle_components/OpenToast';
import axios from 'axios';
import { Formik } from 'formik';
import * as Yup from 'yup';
import { Visibility, VisibilityOff } from '@mui/icons-material';
import Loader from 'components/Loader';
import { OpenToast } from 'components/nearle_components/OpenToast';
import logo from 'assets/images/doormile-logo.png';
import { DT } from 'themes/dt/tokens';
// Astryx design system — see themes/astryx.js. The brand gradient panel and
// the two logo images are plain native elements with inline `style` since
// custom Astryx styling (xstyle/stylex.create()) isn't wired up for this
// build — see the note in config-overrides.js.
import { AppShell } from '@astryxdesign/core/AppShell';
import { Theme } from '@astryxdesign/core/theme';
import { HStack } from '@astryxdesign/core/HStack';
import { VStack } from '@astryxdesign/core/VStack';
import { Center } from '@astryxdesign/core/Center';
import { Card } from '@astryxdesign/core/Card';
import { Heading } from '@astryxdesign/core/Heading';
import { Text } from '@astryxdesign/core/Text';
import { TextInput } from '@astryxdesign/core/TextInput';
import { Button } from '@astryxdesign/core/Button';
import { Link } from '@astryxdesign/core/Link';
import { Divider } from '@astryxdesign/core/Divider';
import { dailygrubsTheme } from 'themes/astryx';
import logger from 'utils/logger';
const brandPanelStyle = {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
position: 'relative',
overflow: 'hidden',
width: '46%',
height: '100vh',
color: '#fff',
padding: 48,
background: `linear-gradient(150deg, ${DT.brand} 0%, #D25463 100%)`
};
const logoLockupStyle = { position: 'absolute', top: 48, left: 48, maxHeight: 60 };
// doormile-logo.png is a white asset; recolour to brand red for this
// white-background card (the brand-panel logo stays white as-is).
const formLogoStyle = {
maxHeight: 48,
filter: 'brightness(0) saturate(100%) invert(15%) sepia(93%) saturate(5437%) hue-rotate(346deg) brightness(81%) contrast(92%)'
};
const bulletDotStyle = {
width: 22,
height: 22,
borderRadius: '50%',
backgroundColor: 'rgba(255, 255, 255, 0.18)',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 13,
fontWeight: 700,
flexShrink: 0
};
const BULLETS = ['Live order tracking', 'Rider dispatch & routing', 'Tenant, pricing & invoice control'];
const LoginSchema = Yup.object().shape({
username: Yup.string().email('Enter a valid email address').required('Email is required'),
password: Yup.string().required('Password is required')
});
const Login = () => {
const theme = useTheme();
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
let navigate = useNavigate();
const [submitting, setSubmitting] = useState(false);
const LoginSchema = Yup.object().shape({
username: Yup.string().email('Enter a valid email address').required('Email is required'),
password: Yup.string().required('Password is required')
});
const navigate = useNavigate();
useEffect(() => {
if (localStorage.getItem('authname')) {
@@ -46,10 +81,8 @@ const Login = () => {
}
}, []);
const loginsend = async (values) => {
console.log('values', values);
const loginsend = async (values, { setSubmitting }) => {
setLoading(true);
setSubmitting(true);
try {
const res = await axios.post(`${process.env.REACT_APP_URL}/users/tenant/weblogin`, {
@@ -58,7 +91,6 @@ const Login = () => {
roleid: 1,
password: values.password
});
console.log(res.data);
if (res.data.code == 200 && res.data.status == true) {
OpenToast('Login Successful', 'success', 1000);
// save to localStorage
@@ -77,200 +109,109 @@ const Login = () => {
localStorage.setItem('userid', d.userid);
localStorage.setItem('sessionStartTime', String(Date.now()));
setSubmitting(false);
setLoading(false);
navigate('/nearle/dispatch');
} else {
OpenToast(res.data.message, 'warning', 2000);
setLoading(false);
setSubmitting(false);
}
} catch (err) {
console.log(err);
logger.error(err);
} finally {
setSubmitting(false);
setLoading(false);
}
};
return (
<>
{/* <AuthWrapper> */}
<Box sx={{ minHeight: '100vh' }}>
<Theme theme={dailygrubsTheme} mode="light">
<AppShell contentPadding={0}>
{loading && <Loader />}
<Grid
container
direction="column"
justifyContent="flex-start"
sx={{
minHeight: '100vh'
}}
>
<Grid item xs={12} sx={{ ml: 3, mt: 1 }}>
<img src={logo} alt="legendary" width="200px" />
</Grid>
<Grid
container
justifyContent="center"
alignItems="center"
sx={{
minHeight: {
xs: 'calc(100vh - 180px)',
sm: 'calc(100vh - 120px)',
md: 'calc(100vh - 130px)'
},
px: 2
}}
>
<Box
sx={{
width: '100%',
maxWidth: { xs: 380, sm: 420, md: 450 }
}}
>
<Card
sx={{
border: '1px solid',
borderColor: theme.palette.divider,
borderRadius: 1.5,
boxShadow: 'inherit',
p: 2.5
}}
>
<CardHeader
title={
<Typography variant="h3" color={'primary'}>
Login
</Typography>
}
sx={{ textAlign: 'center', pb: 3 }}
/>
<CardContent sx={{ pt: 1 }}>
<Formik
initialValues={{ username: '', password: '' }}
validationSchema={LoginSchema}
onSubmit={(values) => loginsend(values)}
>
{({ values, errors, touched, handleChange, handleSubmit, handleBlur }) => (
<HStack gap={0} height="100vh" wrap="nowrap">
{/* ---- Left brand panel ---- */}
<div style={brandPanelStyle}>
<img src={logo} alt="logo" style={logoLockupStyle} />
<VStack gap={2} maxWidth={430}>
<Heading level={1} color="inherit">
Operate your deliveries, end to end.
</Heading>
<Text type="large" color="inherit">
Orders, dispatch, live rider tracking and billing all in one console.
</Text>
<VStack gap={1.5} padding={0}>
{BULLETS.map((t) => (
<HStack key={t} gap={1.25} vAlign="center">
<span style={bulletDotStyle}></span>
<Text color="inherit">{t}</Text>
</HStack>
))}
</VStack>
</VStack>
</div>
{/* ---- Right form panel ---- */}
<Center axis="both" width="54%" height="100vh">
<VStack width="100%" maxWidth={440} gap={4} padding={3}>
<Card padding={8} elevation="med">
<VStack gap={6} padding={0}>
<VStack gap={1.5} hAlign="center" padding={0}>
<img src={logo} alt="logo" style={formLogoStyle} />
<Heading level={2}>Welcome back</Heading>
<Text type="supporting">Sign in to your console</Text>
</VStack>
<Divider />
<Formik initialValues={{ username: '', password: '' }} validationSchema={LoginSchema} onSubmit={loginsend}>
{({ values, errors, touched, setFieldValue, setFieldTouched, handleSubmit, isSubmitting }) => (
<form noValidate onSubmit={handleSubmit}>
<Grid container gap={3}>
{/* USERNAME */}
<Grid item xs={12}>
<TextField
fullWidth
id="username"
name="username"
label="E-mail Address"
variant="outlined"
autoComplete="email"
value={values.username}
onChange={handleChange}
onBlur={handleBlur}
error={touched.username && Boolean(errors.username)}
helperText={touched.username && errors.username}
/>
</Grid>
<VStack gap={4} padding={0}>
<TextInput
hasAutoFocus
label="E-mail Address"
type="email"
size="lg"
isRequired
value={values.username}
onChange={(value) => setFieldValue('username', value)}
onBlur={() => setFieldTouched('username', true)}
status={touched.username && errors.username ? { type: 'error', message: errors.username } : undefined}
/>
{/* PASSWORD */}
<Grid item xs={12}>
<TextField
fullWidth
id="password"
name="password"
label="Password"
type={showPassword ? 'text' : 'password'}
variant="outlined"
autoComplete="current-password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
error={touched.password && Boolean(errors.password)}
helperText={touched.password && errors.password}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setShowPassword((prev) => !prev)}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
}}
/>
</Grid>
<TextInput
label="Password"
type="password"
size="lg"
isRequired
value={values.password}
onChange={(value) => setFieldValue('password', value)}
onBlur={() => setFieldTouched('password', true)}
status={touched.password && errors.password ? { type: 'error', message: errors.password } : undefined}
/>
{/* SUBMIT BUTTON */}
<Grid item xs={12}>
<AnimateButton>
<Button disabled={submitting} fullWidth size="large" type="submit" variant="contained" color="primary">
Login
</Button>
</AnimateButton>
</Grid>
</Grid>
<Button label="Login" type="submit" variant="primary" size="lg" width="100%" isDisabled={isSubmitting} />
</VStack>
</form>
)}
</Formik>
</CardContent>
</VStack>
</Card>
</Box>
</Grid>
<Grid item xs={12} sx={{ mb: 1 }}>
<Container maxWidth="xl">
<Stack
direction={{ sx: 'column', md: 'row' }}
justifyContent={{ sx: 'center', md: 'space-between' }}
spacing={2}
alignItems={{ sx: 'center', md: 'inherit' }}
width="100%"
>
<Stack direction="row" justifyContent="center" spacing={1}>
<Typography variant="subtitle2" color="secondary" component="span" sx={{ display: 'flex' }}>
&copy; All rights reserved
</Typography>
</Stack>
<Stack
direction={{ sx: 'column', md: 'row' }}
spacing={{ sx: 1, md: 3 }}
textAlign={{ sx: 'center', md: 'inherit' }}
alignItems={{ sx: 'center', md: 'inherit' }}
// width='100%'
>
<Typography
variant="subtitle2"
color="secondary"
component={Link}
href="https://nearle.in/terms"
target="_blank"
underline="hover"
textAlign="center"
>
Terms and Conditions
</Typography>
<Typography
variant="subtitle2"
color="secondary"
component={Link}
href="https://nearle.in/privacy"
target="_blank"
underline="hover"
textAlign="center"
>
Privacy Policy
</Typography>
</Stack>
</Stack>
</Container>
</Grid>
</Grid>
</Box>
{/* </AuthWrapper> */}
</>
{/* footer */}
<HStack justify="center" wrap="wrap" gap={2}>
<Text type="supporting">&copy; All rights reserved</Text>
<Link href="https://nearle.in/terms" target="_blank" isExternalLink type="supporting">
Terms and Conditions
</Link>
<Link href="https://nearle.in/privacy" target="_blank" isExternalLink type="supporting">
Privacy Policy
</Link>
</HStack>
</VStack>
</Center>
</HStack>
</AppShell>
</Theme>
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +0,0 @@
import { TableRow, TableCell, Skeleton, Stack } from '@mui/material';
export const OrdersTableSkeleton = ({ rowsPerPage = 5, col = 1 }) => {
return (
<>
{Array.from(new Array(rowsPerPage)).map((_, index) => (
<TableRow key={index}>
{/* Checkbox */}
<TableCell>
<Skeleton variant="circular" width={24} height={24} />
</TableCell>
{/* Serial Number */}
<TableCell>
<Skeleton variant="text" width={30} />
</TableCell>
{/* Delivery Info */}
{Array.from({ length: col }).map((_, index) => (
<TableCell key={index}>
<Stack spacing={0.5}>
<Skeleton variant="text" width={100} />
<Skeleton variant="text" width={80} />
</Stack>
</TableCell>
))}
{/* Notes */}
<TableCell>
<Skeleton variant="text" width={150} />
</TableCell>
{/* Order Status */}
<TableCell>
<Skeleton variant="rounded" width={60} height={24} />
</TableCell>
{/* Actions */}
<TableCell align="center">
<Stack direction="row" spacing={1} justifyContent="flex-end">
<Skeleton variant="circular" width={28} height={28} />
<Skeleton variant="circular" width={28} height={28} />
</Stack>
</TableCell>
</TableRow>
))}
</>
);
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,845 +0,0 @@
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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,18 @@
import { Button } from '@mui/material';
import { LoadScriptNext, GoogleMap, Marker, OverlayView } from '@react-google-maps/api';
import logger from 'utils/logger';
import { DT, tint } from 'themes/dt/tokens';
const containerStyle = {
width: '100%',
height: 'calc(100vh - 150px)'
};
const C_ACTIVE = '#10b981';
const C_INACTIVE = '#ef4444';
export default function RiderLocationMap({ riderLocations }) {
console.log('riderLocations', riderLocations);
logger.info('riderLocations', riderLocations);
const center = {
lat: Number(riderLocations?.[0]?.latitude || 11.0056),
@@ -32,39 +37,74 @@ export default function RiderLocationMap({ riderLocations }) {
riderLocations?.map((r, index) => {
const lat = Number(r.latitude);
const lng = Number(r.longitude);
const isActive = r.status == 'active';
const statusColor = isActive ? C_ACTIVE : C_INACTIVE;
return (
<div key={index}>
{/* Marker */}
<Marker
position={{ lat, lng }}
icon={r.status == 'active' ? GreenIcon : RedIcon}
label={{
fontSize: '14px',
fontWeight: 'bold'
}}
/>
<Marker position={{ lat, lng }} icon={isActive ? GreenIcon : RedIcon} />
{/* Rider details card — floats just above the pin, doesn't cover it */}
<OverlayView position={{ lat, lng }} mapPaneName={OverlayView.OVERLAY_LAYER}>
<div
style={{
background: 'none',
color: 'green',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: 600,
whiteSpace: 'nowrap',
transform: 'translate(-50%, -140%)',
transform: 'translate(-50%, -100%)',
marginTop: -46,
pointerEvents: 'none',
ml: 20
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Button variant="contained" color="primary" size="small">
{` ${r.username} `}
{/* <br /> */}
{/* {`${r.contactno || '##### ##### '} `} */}
<br />
{`(${r.orderid || ''}) `}
</Button>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 6,
padding: '6px 10px',
borderRadius: DT.radiusInner,
backgroundColor: DT.surface,
border: `1px solid ${DT.borderSubtle}`,
boxShadow: DT.shadowPop,
whiteSpace: 'nowrap'
}}
>
<span
style={{
width: 7,
height: 7,
borderRadius: '50%',
backgroundColor: statusColor,
boxShadow: `0 0 0 3px ${tint(statusColor)}`,
flexShrink: 0
}}
/>
<span style={{ fontSize: 12, fontWeight: 700, color: DT.textPrimary }}>{r.username}</span>
{r.orderid && (
<span
style={{
fontSize: 10.5,
fontWeight: 700,
color: DT.brand,
backgroundColor: tint(DT.brand),
padding: '1px 6px',
borderRadius: DT.radiusPill
}}
>
#{r.orderid}
</span>
)}
</div>
{/* Pointer connecting the card to the marker below it */}
<div
style={{
width: 0,
height: 0,
borderLeft: '5px solid transparent',
borderRight: '5px solid transparent',
borderTop: `6px solid ${DT.surface}`,
filter: 'drop-shadow(0 1px 1px rgba(15, 23, 42, 0.08))'
}}
/>
</div>
</OverlayView>
</div>

View File

@@ -3,11 +3,13 @@ import { MapContainer, TileLayer, Marker, Polyline, Tooltip } from 'react-leafle
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import dayjs from 'dayjs';
import { Chip, Grid, IconButton, Stack, ToggleButton, Typography } from '@mui/material';
import DisabledByDefaultOutlinedIcon from '@mui/icons-material/DisabledByDefaultOutlined';
import { useTheme } from '@emotion/react';
import { Badge } from '@astryxdesign/core/Badge';
import { HStack } from '@astryxdesign/core/HStack';
import { Text } from '@astryxdesign/core/Text';
import { IconButton } from '@astryxdesign/core/IconButton';
import { CloseCircleOutlined } from '@ant-design/icons';
import logger from 'utils/logger';
var utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
@@ -31,15 +33,13 @@ const endIcon = new L.Icon({
});
const MapWithRoute = ({ coordinates, additionalProps, order, setMapOpen }) => {
console.log('setMapOpen:', typeof setMapOpen);
console.log('coordinates', coordinates);
console.log(additionalProps.riderStart);
console.log(additionalProps.riderEnd);
console.log('order', order);
logger.info('setMapOpen:', typeof setMapOpen);
logger.info('coordinates', coordinates);
logger.info(additionalProps.riderStart);
logger.info(additionalProps.riderEnd);
logger.info('order', order);
const mapRef = useRef(null);
const theme = useTheme;
useEffect(() => {
if (mapRef.current && coordinates.length > 0) {
const bounds = calculateBounds(coordinates);
@@ -69,12 +69,12 @@ const MapWithRoute = ({ coordinates, additionalProps, order, setMapOpen }) => {
const showList = (primary, secondary) => {
return (
<Stack direction="row" spacing={1} alignItems="center">
<Typography variant="h5" color="text.primary" sx={{ fontSize: 13 }}>
<HStack gap={1} vAlign="center">
<Text type="strong" style={{ fontSize: 13 }}>
{primary}:
</Typography>
<Chip label={secondary || 'N/A'} color="primary" variant="combined" size="small" sx={{ fontWeight: 1000, fontSize: 14 }} />
</Stack>
</Text>
<Badge label={secondary || 'N/A'} variant="info" />
</HStack>
);
};
@@ -90,42 +90,31 @@ const MapWithRoute = ({ coordinates, additionalProps, order, setMapOpen }) => {
position: 'relative'
}}
>
<Chip
label="close"
color="primary"
variant="combined"
onClick={() => setMapOpen(false)}
size="large"
sx={{
position: 'absolute',
top: 8,
right: 8,
zIndex: 1000,
'&:hover': {},
cursor: 'pointer'
}}
<IconButton
label="Close"
icon={<CloseCircleOutlined />}
variant="primary"
onClick={() => setMapOpen(false)}
style={{ position: 'absolute', top: 8, right: 8, zIndex: 1000 }}
/>
{/* Overlay */}
<Stack
direction="row"
<HStack
gap={2}
flexWrap="wrap"
alignItems="center"
justifyContent="flex-start"
sx={{
wrap="wrap"
vAlign="center"
justify="start"
style={{
position: 'absolute',
bottom: 0,
zIndex: 1000,
left: 0,
right: 0,
px: { xs: 1, custom500: 2, custom700: 2 },
py: { xs: 1, custom500: 2, custom900: 4 },
padding: '8px 16px',
backgroundColor: 'rgba(255, 255, 255, 0.95)',
maxWidth: '100%',
width: 'auto',
boxShadow: (theme) => theme.shadows[3]
boxShadow: 'var(--shadow-md)'
}}
>
{showList('Tenant', order?.tenantname)}
@@ -135,7 +124,7 @@ const MapWithRoute = ({ coordinates, additionalProps, order, setMapOpen }) => {
{showList('Kms', order?.kms)}
{showList('Actual kms', order?.actualkms)}
{showList('Rider kms', order?.riderkms)}
</Stack>
</HStack>
{/* Map */}
<MapContainer center={center} zoom={15} scrollWheelZoom={false} style={{ height: '100%', width: '100%' }} ref={mapRef}>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +1,28 @@
import React from 'react';
import { useTheme } from '@mui/material/styles';
import {
Avatar,
Chip,
Grid,
Link,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
CardContent,
Skeleton,
Stack,
CardActions,
Button,
List
} from '@mui/material';
const TitleCard = ({ title, secondary, sx }) => {
const theme = useTheme();
return (
<Grid container spacing={2}>
<CardActions
sx={{
position: 'sticky',
top: '60px',
// top:0,
bgcolor: theme.palette.background.default,
zIndex: 1,
// borderBottom: `1px solid ${theme.palette.divider}`,
width: '100%',
...sx
}}
>
<Grid item xs={12}>
<Stack direction={'row'} justifyContent={'space-between'} sx={{ p: 1, flexWrap: 'wrap' }}>
<Typography variant="h3">{title}</Typography>
{secondary && secondary}
</Stack>
</Grid>
</CardActions>
</Grid>
);
};
import { HStack } from '@astryxdesign/core/HStack';
import { Heading } from '@astryxdesign/core/Heading';
// Sticky page-header bar. Offset by the real AppShell nav height (not a
// hardcoded px guess) and a modest z-index so it never wins the stacking
// fight against the nav — see memory astryx-sticky-header-zindex-bug.
const TitleCard = ({ title, secondary, sx }) => (
<div
style={{
position: 'sticky',
top: 'var(--appshell-header-height, 0px)',
zIndex: 100,
background: 'var(--color-background-body)',
width: '100%',
padding: 8,
...sx
}}
>
<HStack justify="space-between" wrap="wrap">
<Heading level={3}>{title}</Heading>
{secondary && secondary}
</HStack>
</div>
);
export default TitleCard;