initialised commit on the ui converted in mui to astryx and changed the design to doormile
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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' }}>
|
||||
© 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">© 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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user