import React, { useState, useEffect, useRef } from 'react'; import { Spinner } from '@astryxdesign/core/Spinner'; import { IconButton } from '@astryxdesign/core/IconButton'; import { MdMenu, MdSearch, MdClear, MdPlace, MdStorefront, MdMyLocation, MdAccessTime, MdLocalShipping, MdHourglassEmpty, MdCheckCircle, MdCancel, MdReceiptLong } from 'react-icons/md'; 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 { 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); 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: 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 tenantid = localStorage.getItem('tenantid'); 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'); 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 [, setPageCount] = React.useState(0); const [startdate] = useState(dayjs().format('YYYY-MM-DD')); const [enddate] = useState(dayjs().format('YYYY-MM-DD')); const [searchLocation, setSearchLocation] = useState(''); const [debouncedSearchLocation, setDebouncedSearchLocation] = useState(''); 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, pending: pendingLenght, delivered: deliveredlenght, cancelled: cancelledLenght }; useEffect(() => { const handler = setTimeout(() => { setDebouncedSearchLocation(searchLocation); }, 400); return () => clearTimeout(handler); }, [searchLocation]); useEffect(() => { const handler = setTimeout(() => { setDebouncedSearchword(searchword); }, 400); return () => clearTimeout(handler); }, [searchword]); const handleChangetab = (i) => { setSearchword(''); setRowsPerPage(10); setTabvalue(i); setCurrentStatus(STATUS_TABS[i].value); setPage(0); }; const { data: locations, isLoading: locationIsLoading, isError: locationIsError, error: locationError } = useQuery({ queryKey: ['locations', debouncedSearchLocation], queryFn: gettenantlocations }); // Auto-pick a sensible default whenever the locations list changes: // • Nothing selected yet → pick the first item. // • Current selection has been filtered out → also pick the first item // (otherwise the orders panel queries a locationid that's no longer // in the visible list, returning nothing and confusing the operator). useEffect(() => { if (!Array.isArray(locations) || locations.length === 0) return; const stillVisible = selectedLocation && locations.some((l) => l.locationid === selectedLocation.locationid); if (!stillVisible) setSelectedLocation(locations[0]); }, [locations]); const { data: ordersData, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ queryKey: [ 'orders', tenantid, selectedLocation?.locationid ?? null, // stable currentStatus, startdate, enddate, debouncedSearchword, 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: containerRef.current, 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) => { logger.info('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) => { logger.error(err); setLoading(false); }); } catch (err) { logger.error(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 && logger.info(errMessage); }, [errMessage]); // -------------------------------------------------------------------------- // Sidebar — searchable location list. Shared between the desktop persistent // drawer and the mobile temporary drawer. // -------------------------------------------------------------------------- const sidebarContent = (
{/* Sidebar header */}
Locations {Array.isArray(locations) ? `${locations.length} active` : '—'}
{/* Search pill */}
setSearchLocation(e.target.value)} autoComplete="off" style={{ flex: 1, fontSize: 13, fontWeight: 600, color: DT.textPrimary, border: 'none', outline: 'none', background: 'transparent' }} /> {searchLocation && ( } variant="ghost" size="sm" onClick={() => setSearchLocation('')} style={{ color: BRAND }} /> )}
{/* Location list */}
{locationIsLoading && Array.from({ length: 8 }).map((_, i) => (
))} {!locationIsLoading && Array.isArray(locations) && locations.length === 0 && (
)}
{locations?.map((row, index) => { const isSelected = row.locationid === selectedLocation?.locationid; return ( ); })}
); return ( {locationIsLoading && ( <> )}
{/* ---------------- LOCATION SIDEBAR ---------------- */} {(isDesktop || open) && ( <> {!isDesktop && (
(e.key === 'Enter' || e.key === ' ') && toggleDrawer()} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(15,23,42,0.35)', zIndex: 9, border: 'none' }} /> )}
{sidebarContent}
)} {/* ---------------- MAIN PANEL ---------------- */}
{/* ---------------- GRADIENT HEADER ---------------- */}
{selectedLocation?.locationname || 'Select a location'}
{selectedLocation?.suburb ? `${selectedLocation.suburb} · ` : ''}Live · {dayjs(startdate).format('DD MMM YYYY')}
{/* Order search pill */}
setSearchword(e.target.value)} autoComplete="off" style={{ flex: 1, fontSize: 13, fontWeight: 600, color: DT.textPrimary, border: 'none', outline: 'none', background: 'transparent' }} /> {searchword && ( } variant="ghost" size="sm" onClick={() => setSearchword('')} style={{ color: BRAND }} /> )}
{/* ---------------- STATUS FILTER PILLS ---------------- */}
{STATUS_TABS.map((item, index) => { const isActive = tabvalue === index; const Icon = item.icon; const count = statusCounts[item.value]; return ( ); })}
{/* ---------------- ORDERS TABLE ---------------- */}
{['#', 'Order', 'Pickup', 'Drop', 'Notes', 'Status'].map((h, i) => ( ))} {/* LOADING STATE */} {loading && Array.from({ length: 10 }).map((_, index) => ( {Array.from({ length: 6 }).map((__, i) => ( ))} ))} {/* EMPTY STATE */} {!loading && rows?.length === 0 && ( )} {/* DATA ROWS */} {!loading && rows?.map((row, index) => ( {/* Order Info */} {/* Pickup */} {/* Drop */} {/* Notes */} {/* Status */} ))} {rows?.length != 0 && ( )}
0 && i < 5 ? 140 : undefined }} > {h}
No orders found {searchword ? 'Try a different keyword or clear the search.' : 'No orders in this status for the selected location.'}
{page * rowsPerPage + index + 1}
{row.orderid}
{dayjs(row.deliverydate).utc().format('hh:mm A')} · {dayjs(row.deliverydate).utc().format('DD MMM YY')}
{row.pickupcustomer || '—'} {row.pickupcontactno} {row.pickupsuburb || (row.pickupaddress ? `${row.pickupaddress.slice(0, 20)}…` : '—')}
{row.deliverycustomer || '—'} {row.deliverycontactno} {row.deliverysuburb || (row.deliveryaddress ? `${row.deliveryaddress.slice(0, 20)}…` : '—')}
{row.ordernotes || '—'}
{isFetchingNextPage || hasNextPage ? ( ) : ( No more orders )}
); }; export default ResponsiveLocationDrawer;