overall updates

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

View File

@@ -0,0 +1,596 @@
import React, { useState, useEffect, Fragment, useRef } from 'react';
import {
Box,
Drawer,
IconButton,
Toolbar,
Typography,
AppBar,
useMediaQuery,
Divider,
List,
ListItem,
ListItemText,
useTheme,
ListItemAvatar,
Avatar,
Tooltip,
TableCell,
Chip,
Stack,
TableRow,
TableBody,
TableHead,
Table,
TableContainer,
Tabs,
Tab,
CircularProgress
} from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
import SearchBar from 'components/nearle_components/SearchBar';
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { fetchOrders1, gettenantlocations } from '../api/api';
import Loader from 'components/Loader';
import CircularLoader from 'components/nearle_components/CircularLoader';
import { Empty, Skeleton } from 'antd';
import MainCard from 'components/MainCard';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import { CancelOutlined, CheckCircleOutline } from '@mui/icons-material';
import axios from 'axios';
import dayjs from 'dayjs';
var utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
const drawerWidth = 300;
const ResponsiveLocationDrawer = () => {
const loadMoreRef = useRef();
const containerRef = useRef();
const theme = useTheme();
const tenantid = localStorage.getItem('tenantid');
const isDesktop = useMediaQuery('(min-width:900px)');
const [open, setOpen] = useState(false);
const [selectedLocation, setSelectedLocation] = useState(null);
const [currentStatus, setCurrentStatus] = useState('created');
const [tabvalue, setTabvalue] = useState(0);
const [createdLenght, setCreatedLenght] = useState();
const [pendingLenght, setPendingLenght] = useState();
const [deliveredlenght, setDeliveredlenght] = useState();
const [cancelledLenght, setCancelledLenght] = useState();
const [loading, setLoading] = useState(false);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const [pageCount, setPageCount] = React.useState(0);
const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD'));
const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD'));
const [searchLocation, setSearchLocation] = useState('');
const [debouncedSearchLocation, setDebouncedSearchLocation] = useState('');
const [searchword, setSearchword] = useState('');
const [debouncedSearchword, setDebouncedSearchword] = useState('');
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearchLocation(searchLocation);
}, 400);
return () => clearTimeout(handler);
}, [searchLocation]);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearchword(searchword);
}, 400);
return () => clearTimeout(handler);
}, [searchword]);
const statusMap = [
{
label: 'Created',
value: 'created',
count: createdLenght,
icon: <AccessTimeIcon color="primary" fontSize="small" />
},
{
label: 'Pending',
value: 'pending',
count: pendingLenght,
icon: <LocalShippingOutlinedIcon color="primary" fontSize="small" />
},
{
label: 'Delivered',
value: 'delivered',
count: deliveredlenght,
icon: <CheckCircleOutline color="primary" fontSize="small" />
},
{
label: 'Cancelled',
value: 'cancelled',
count: cancelledLenght,
icon: <CancelOutlined color="primary" fontSize="small" />
}
];
const handleChangetab = (e, i) => {
setSearchword('');
setRowsPerPage(10);
setTabvalue(i);
setCurrentStatus(statusMap[i].value);
setPage(0);
};
const {
data: locations,
isLoading: locationIsLoading,
isError: locationIsError,
error: locationError
} = useQuery({
queryKey: ['locations', debouncedSearchLocation],
queryFn: gettenantlocations
});
useEffect(() => {
if (!searchLocation) locations?.length > 0 ? setSelectedLocation(locations[0]) : null;
}, [locations]);
const {
data: ordersData,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
error
} = useInfiniteQuery({
queryKey: [
'orders',
tenantid,
selectedLocation?.locationid ?? null, // stable
currentStatus,
startdate,
enddate,
debouncedSearchLocation,
rowsPerPage
],
queryFn: fetchOrders1,
getNextPageParam: (lastPage) => lastPage.nextPage
});
const rows = ordersData?.pages?.flatMap((page) => page.details) || [];
useEffect(() => {
if (!hasNextPage) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
fetchNextPage();
}
},
{
root: document.querySelector('.MuiTableContainer-root'), // 👈 or explicitly TableContainer
rootMargin: '0px',
threshold: 1.0
}
);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
return () => {
if (loadMoreRef.current) observer.unobserve(loadMoreRef.current);
};
}, [hasNextPage, fetchNextPage]);
const handleScroll = (event) => {
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
if (scrollTop + clientHeight >= scrollHeight - 50) {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}
};
const fetchorderscount = async () => {
setLoading(true);
try {
await axios
.get(
`${process.env.REACT_APP_URL}/orders/getordersummary/?tenantid=${tenantid}&locationid=${selectedLocation?.locationid}&fromdate=${startdate}&todate=${enddate}`
)
.then((res) => {
console.log('fetchorderscount', res.data.details);
setCreatedLenght(res.data.details.created);
setPendingLenght(res.data.details.pending);
setDeliveredlenght(res.data.details.delivered);
setCancelledLenght(res.data.details.cancelled);
tabvalue === 0 && setPageCount(res.data.details.created);
tabvalue === 1 && setPageCount(res.data.details.pending);
tabvalue === 2 && setPageCount(res.data.details.delivered);
tabvalue === 3 && setPageCount(res.data.details.cancelled);
setLoading(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
} catch (err) {
console.log(err);
setLoading(false);
}
};
useEffect(() => {
fetchorderscount();
}, [currentStatus, selectedLocation, startdate, enddate]);
useEffect(() => {
setOpen(isDesktop);
}, [isDesktop]);
const toggleDrawer = () => setOpen(!open);
const errMessage = locationIsError ? `${locationError.message}` : null;
useEffect(() => {
errMessage && console.log(errMessage);
}, [errMessage]);
return (
<React.Fragment>
{locationIsLoading && (
<>
<Loader /> <CircularLoader />
</>
)}
<Box sx={{ display: 'flex', width: '100%', height: '100%', position: 'relative' }}>
{/* ---------------- LOCAL DRAWER ---------------- */}
<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%',
overflowY: 'auto',
transition: 'transform 0.35s ease-in-out',
zIndex: 10,
/* vertical scrollbar */
'&::-webkit-scrollbar:vertical': {
width: '7px',
opacity: 0,
transition: 'opacity 0.3s'
},
/* horizontal scrollbar */
'&::-webkit-scrollbar:horizontal': {
height: '6px', // thinner horizontal bar
opacity: 0,
transition: 'opacity 0.3s'
},
/* show scrollbar when hovering drawer */
'&:hover::-webkit-scrollbar': {
opacity: 1
},
/* thumb styling */
'&::-webkit-scrollbar-thumb': {
backgroundColor: theme.palette.primary.main,
borderRadius: '8px'
},
'&::-webkit-scrollbar-thumb:hover': {
backgroundColor: theme.palette.primary.dark
},
/* track styling */
'&::-webkit-scrollbar-track': {
backgroundColor: theme.palette.primary.lighter
}
}
}}
>
<Box sx={{ position: 'sticky', top: 0, zIndex: 11, border: 'none' }}>
<SearchBar
value={searchLocation}
placeholder="Search Location"
onChange={(e) => setSearchLocation(e.target.value)}
sx={{
width: 'auto',
height: 60,
bgcolor: theme.palette.primary.lighter,
'& .MuiOutlinedInput-notchedOutline': {
border: 'none',
borderBottom: '1px solid ',
borderColor: theme.palette.secondary.light
}
}}
/>
</Box>
<List sx={{ mt: -1 }}>
{locations?.map((row, index) => (
<React.Fragment key={index}>
<ListItem
sx={{
cursor: 'pointer',
bgcolor: row.locationid == selectedLocation?.locationid ? theme.palette.secondary[200] : 'none',
'&:hover': {
bgcolor: theme.palette.secondary.lighter
}
}}
onClick={() => {
setSelectedLocation(row);
}}
>
<ListItemAvatar>
<Avatar
sx={{
bgcolor: 'primary.main', // background color
color: 'white' // text color
}}
>
{row.locationname[0].toUpperCase()}
</Avatar>{' '}
</ListItemAvatar>
<ListItemText primary={row.locationname} secondary={row.suburb} />
</ListItem>
<Divider />
</React.Fragment>
))}
</List>
</Drawer>
{/* -------------- LOCAL PAGE APPBAR -------------- */}
<AppBar
elevation={0}
position="absolute"
sx={{
top: 0,
left: open && isDesktop ? `${drawerWidth}px` : 0,
width: open && isDesktop ? `calc(100% - ${drawerWidth}px)` : '100%',
transition: 'left 0.3s ease, width 0.3s ease',
zIndex: 1100, // BELOW drawer, ABOVE content
backgroundColor: theme.palette.primary.lighter
}}
>
<Toolbar>
<Stack
sx={{ width: '100%' }}
display={'flex'}
flexDirection={'row'}
alignItems={'center'}
justifyContent={'space-between'}
flexWrap={'wrap'}
>
<Stack display={'flex'} flexDirection={'row'} alignItems={'center'}>
<IconButton color="primary" onClick={toggleDrawer} sx={{ mr: 1 }}>
<MenuIcon />
</IconButton>
<Typography variant="h5" color={'primary'} sx={{ whiteSpace: 'nowrap', ml: 2 }}>
{selectedLocation?.locationname}
</Typography>
</Stack>
<Stack flexGrow={1} sx={{ mx: { xs: 0, custom600: 3 } }}>
<SearchBar
value={searchword}
placeholder={'Search Order Details'}
onChange={(e) => setSearchword(e.target.value)}
sx={{
width: 'auto',
height: 40,
bgcolor: theme.palette.primary.lighter,
'& .MuiOutlinedInput-notchedOutline': {
border: 'none',
borderBottom: '1px solid ',
borderColor: theme.palette.secondary.light
}
}}
/>
</Stack>
</Stack>
</Toolbar>
</AppBar>
{/* ---------------- PAGE SCROLLABLE CONTENT ---------------- */}
<Box
sx={{
flexGrow: 1,
overflow: 'auto',
pt: '64px', // Height of AppBar
pl: isDesktop && open ? `${drawerWidth}px` : 0,
transition: 'padding-left 0.3s ease',
mt: -1
}}
>
<Stack
display={'flex'}
flexDirection={'row'}
justifyContent={'space-between'}
alignItems={'center'}
flexWrap={'wrap-reverse'}
gap={2}
sx={{
border: '1px solid ',
borderBottom: 0,
borderColor: 'bg.main',
p: 1.5
}}
>
{/* Tabs Wrapper */}
<Tabs value={tabvalue} onChange={handleChangetab} variant="scrollable" scrollButtons="auto" allowScrollButtonsMobile>
{statusMap.map((item, index) => (
<Tab
key={index}
label={
<Stack direction="row" alignItems="center" spacing={1}>
{item.icon}
<span>{item.label}</span>
<Chip label={item.count} color="primary" variant="light" size="small" />
</Stack>
}
/>
))}
</Tabs>
</Stack>
<MainCard
content={false}
sx={{
overflow: 'hidden',
height: 'calc(100vh - 200px)', // adjust as needed
display: 'flex',
flexDirection: 'column'
}}
>
<Fragment>
{/* Scrollable table container */}
<TableContainer
onScroll={handleScroll}
ref={containerRef}
sx={{
width: '100%',
flex: 1,
overflow: 'auto',
borderBottom: 1,
maxHeight: 'calc(100vh - 225px)',
borderColor: 'divider',
'&::-webkit-scrollbar': { width: '12px' },
'&::-webkit-scrollbar-thumb': {
backgroundColor: theme.palette.primary.main,
borderRadius: '8px'
},
'&::-webkit-scrollbar-thumb:hover': {
backgroundColor: theme.palette.primary.dark
},
'&::-webkit-scrollbar-track': {
backgroundColor: theme.palette.primary.lighter
}
}}
>
<Table stickyHeader>
{/* HEADER */}
<TableHead>
<TableRow>
<TableCell sx={{ backgroundColor: theme.palette.secondary.light, position: 'sticky !important' }}>S.No</TableCell>
<TableCell sx={{ backgroundColor: theme.palette.secondary.light, position: 'sticky !important' }}>Orders</TableCell>
<TableCell sx={{ backgroundColor: theme.palette.secondary.light, position: 'sticky !important' }}>Pickup</TableCell>
<TableCell sx={{ backgroundColor: theme.palette.secondary.light, position: 'sticky !important' }}>Drop</TableCell>
<TableCell sx={{ backgroundColor: theme.palette.secondary.light, position: 'sticky !important' }}>Notes</TableCell>
<TableCell sx={{ backgroundColor: theme.palette.secondary.light, position: 'sticky !important' }}>Status</TableCell>
</TableRow>
</TableHead>
{/* BODY */}
<TableBody>
{/* LOADING STATE */}
{loading &&
[...Array(10)].map((_, index) => (
<TableRow key={index}>
{[...Array(6)].map((__, i) => (
<TableCell key={i}>
<Skeleton animation="wave" />
</TableCell>
))}
</TableRow>
))}
{/* EMPTY STATE */}
{!loading && rows?.length === 0 && (
<TableRow>
<TableCell colSpan={6} sx={{ minWidth: '100%', height: 500 }} align="center">
<Empty description={'No Orders'} />
</TableCell>
</TableRow>
)}
{/* DATA ROWS */}
{!loading &&
rows?.map((row, index) => (
<TableRow key={index} sx={{ cursor: 'pointer' }}>
<TableCell>{page * rowsPerPage + index + 1}</TableCell>
{/* Order Info */}
<TableCell>
<Typography variant="body2" noWrap>
{row.orderid}
</Typography>
<Typography variant="caption" noWrap>
{dayjs(row.deliverydate).utc().format('DD/MM/YYYY')}
</Typography>
<Typography variant="caption" noWrap>
{dayjs(row.deliverydate).utc().format('hh:mm A')}
</Typography>
</TableCell>
{/* Pickup */}
<TableCell>
<Stack direction="row" spacing={1}>
<Avatar sx={{ width: 25, height: 25 }} />
<Stack>
<Typography variant="caption">{row.pickupcustomer}</Typography>
<Typography variant="caption">{row.pickupcontactno}</Typography>
<Tooltip title={row.pickupaddress}>
<Typography variant="caption">{row.pickupsuburb || row.pickupaddress.slice(0, 20)}</Typography>
</Tooltip>
</Stack>
</Stack>
</TableCell>
{/* Drop */}
<TableCell>
<Stack direction="row" spacing={1}>
<Avatar sx={{ width: 25, height: 25 }} />
<Stack>
<Typography variant="caption">{row.deliverycustomer}</Typography>
<Typography variant="caption">{row.deliverycontactno}</Typography>
<Tooltip title={row.deliveryaddress}>
<Typography variant="caption">{row.deliverysuburb || row.deliveryaddress.slice(0, 20)}</Typography>
</Tooltip>
</Stack>
</Stack>
</TableCell>
{/* Notes */}
<TableCell>{row.ordernotes}</TableCell>
{/* Status */}
<TableCell>
<Stack direction="row" spacing={1}>
{row.orderstatus === 'pending' && <Chip label="Pending" color="warning" size="small" />}
{row.orderstatus === 'confirmed' && <Chip label="Confirmed" color="success" size="small" />}
{row.orderstatus === 'cancelled' && <Chip label="Cancelled" color="error" size="small" />}
{row.orderstatus === 'delivered' && <Chip label="Completed" color="primary" size="small" />}
{row.orderstatus === 'processing' && <Chip label="Processing" color="primary" size="small" />}
{row.orderstatus === 'ready' && <Chip label="Accepted" color="info" size="small" />}
{row.orderstatus === 'active' && <Chip label="Picked" color="info" size="small" />}
{row.orderstatus === 'closed' && <Chip label="Closed" color="info" size="small" />}
{row.orderstatus === 'created' && <Chip label="Created" color="secondary" size="small" />}
</Stack>
</TableCell>
</TableRow>
))}
{rows?.length != 0 && (
<TableRow>
<TableCell colSpan={6} rowSpan={3}>
<div ref={loadMoreRef} style={{ height: 40, textAlign: 'center' }}>
{isFetchingNextPage ? <CircularProgress /> : hasNextPage ? <CircularProgress /> : 'No More Orders'}
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
</Fragment>
</MainCard>
</Box>
</Box>
</React.Fragment>
);
};
export default ResponsiveLocationDrawer;