import { React, useState, useEffect, useRef, useMemo } from 'react'; import axios from 'axios'; import { FaRegEdit } from 'react-icons/fa'; import LoaderWithImage from 'components/nearle_components/LoaderWithImage'; // material-ui import { Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Grid, Typography, IconButton, Stack, Tooltip, Dialog, DialogActions, DialogTitle, DialogContent, Button, TextField, Autocomplete, Avatar, Paper, useMediaQuery, useTheme } from '@mui/material'; import { MdPeopleAlt, MdMyLocation, MdPersonPin, MdPhone, MdLocationOn, MdEdit, MdGroups, MdHowToReg, MdPlace } from 'react-icons/md'; import Geocode from 'react-geocode'; import LocationOnIcon from '@mui/icons-material/LocationOn'; import parse from 'autosuggest-highlight/parse'; import { debounce } from '@mui/material/utils'; // project imports import Loader from 'components/Loader'; import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar'; import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { getallcustomers, getcustomersummary } from 'pages/api/api'; import { OpenToast } from 'components/third-party/OpenToast'; import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton'; import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard'; // ============================================================================ // Design tokens โ€” shared with the deliveries / tenants / pricing pages so every // surface (header, KPI tiles, table, badges, dialog) speaks the same visual // language. Keep this block in sync with deliveries.js:109โ€“162. // ============================================================================ const DT = { radiusPill: 999, radiusCard: 16, shadowSoft: '0 14px 40px rgba(15, 23, 42, 0.10)', shadowMd: '0 8px 24px rgba(15, 23, 42, 0.08)', shadowPop: '0 18px 50px rgba(15, 23, 42, 0.18)', textPrimary: '#0f172a', textSecondary: '#64748b', textMuted: '#94a3b8', borderSubtle: '#e2e8f0', divider: '#f1f5f9', surface: '#ffffff', surfaceAlt: '#f8fafc' }; const a = (c, suffix) => `${c}${suffix}`; const tint = (c) => a(c, '08'); const soft = (c) => a(c, '18'); const ring = (c) => a(c, '26'); const edge = (c) => a(c, '55'); const SoftPaper = (props) => ( ); const AccentAvatar = ({ color, selected, size = 24, children }) => ( {children} ); // ==============================|| google address ||============================== // const GOOGLE_MAPS_API_KEY = process.env.REACT_APP_GOOGLE_MAPS_API_KEY; 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 }; // ==============================|| MUI TABLE - ENHANCED ||============================== // export default function Customers() { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const containerRef = useRef(); const loadMoreRef = useRef(); const [rowsPerPage] = useState(50); const [page] = useState(0); const [appId, setAppId] = useState(0); const [locaName, setLocoName] = useState('All'); const [selectedCustomer, setSelectedCustomer] = useState({}); // to edit const [open, setOpen] = useState(false); const [address, setAddress] = useState(''); const [latlong, setLatlong] = useState({}); const [city, setCity] = useState(''); const [postcode, setPostcode] = useState(''); const [state, setState] = useState(''); const [suburb, setSuburb] = useState(''); const [searchword, setSearchword] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); // ==============================|| for google address ||============================== // const [value, setValue] = useState(null); const [inputValue, setInputValue] = useState(''); const [options, setOptions] = useState([]); const loaded = 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 = useMemo( () => debounce((request, callback) => { autocompleteService.current.getPlacePredictions(request, callback); }, 400), [] ); 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]); Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY); useEffect(() => { try { console.log('selected address =>', address); Geocode.fromAddress(address).then( (response) => { console.log('lat long response =>', response.results[0]); if (response.status == 'OK') { const { lat, lng } = response.results[0].geometry.location; setLatlong({ lat, lng }); // setSelectedCustomer({ // ...selectedCustomer, // latitude: lat, // longitude: lng // }); if (response.results[0].address_components) { let place = response.results[0]; let city1, zipcode1, state1, suburb1; for (let i = 0; i < place.address_components.length; i++) { for (let j = 0; j < place.address_components[i].types.length; j++) { switch (place.address_components[i].types[j]) { case 'locality': city1 = place.address_components[i].long_name; break; case 'administrative_area_level_1': state1 = place.address_components[i].long_name; break; case 'postal_code': zipcode1 = place.address_components[i].long_name; break; case 'sublocality': suburb1 = place.address_components[i].long_name; break; } } } setCity(city1 || ''); setState(state1 || ''); setPostcode(zipcode1 || ''); setSuburb(suburb1 || ''); setSelectedCustomer((prev) => ({ ...prev, city: city1 || '', state: state1 || '', postcode: zipcode1 || '', suburb: suburb1 || '', latitude: lat || '', longitude: lng || '' })); } } }, (error) => { console.log(error); } ); } catch (err) { console.log(err); } }, [address]); // useEffect(() => { // selectedCustomer && // setLatlong({ // lat: selectedCustomer.latitude, // lng: selectedCustomer.longitude // }); // }, [selectedCustomer]); // ==============================|| getallcustomers (customers) ||============================== // const { data, isLoading: customersIsLoading, isFetchingNextPage, fetchNextPage, hasNextPage, refetch: getallcustomersRefetch } = useInfiniteQuery({ queryKey: ['getAllCustomers', appId, debouncedSearch, rowsPerPage], queryFn: getallcustomers, getNextPageParam: (lastPage) => lastPage.nextPage, keepPreviousData: true }); const rows = data?.pages.flatMap((page) => page.data) || []; 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(); } } }; // ==============================|| getcustomersummary (customers) ||============================== // const { data: pageCount, isLoading: customerSummaryIsLoading } = useQuery({ queryKey: ['customersummary', appId], queryFn: getcustomersummary }); useEffect(() => { console.log('pageCount', pageCount); }, [pageCount]); // ==============================|| updateCustomer (post)||============================== // const updateCustomer = async () => { console.log('selectedCustomer', selectedCustomer); if (!selectedCustomer.firstname) { OpenToast('Enter Door NO', 'warning', 1500); } else if (!selectedCustomer.contactno) { OpenToast('Enter Contact Number ', 'warning', 1500); } else if (!selectedCustomer.address) { OpenToast('Enter Valid Address', 'warning', 1500); } else if (!selectedCustomer.suburb) { OpenToast('Enter Suburb', 'warning', 1500); } else if (!selectedCustomer.city) { OpenToast('Enter City ', 'warning', 1500); } else if (!selectedCustomer.state) { OpenToast('Enter State', 'warning', 1500); } else if (!selectedCustomer.postcode) { OpenToast('Enter PostCode', 'warning', 1500); } else if (!selectedCustomer.landmark) { OpenToast('Enter Landmark', 'warning', 1500); } else if (!selectedCustomer.latitude) { OpenToast('Enter Latitude', 'warning', 1500); } else if (!selectedCustomer.longitude) { OpenToast('Enter Longitude', 'warning', 1500); } else { try { const postUpdateResponse = await axios.put(`${process.env.REACT_APP_URL}/customers/update`, { customerid: selectedCustomer.customerid, configid: 1, firstname: selectedCustomer.firstname, applocationid: selectedCustomer.applocationid, profileimage: '', dialcode: '+91', contactno: selectedCustomer.contactno, devicetype: '', deviceid: '', customertoken: '123', address: selectedCustomer.address, suburb: suburb, city: city, state: state, postcode: postcode, landmark: selectedCustomer.landmark, doorno: selectedCustomer.doorno, latitude: selectedCustomer.latitude.toString(), longitude: selectedCustomer.longitude.toString() }); console.log('postUpdateResponse', postUpdateResponse); if (postUpdateResponse.data.status) { OpenToast(postUpdateResponse.data.message, 'success', 1500); setOpen(false); getallcustomersRefetch(); } } catch (error) { console.log('postUpdate error', error); } } }; const KPI_META = [ { key: 'total', label: 'Total Customers', color: '#662582', icon: MdGroups, value: pageCount?.Total ?? 0 }, { key: 'loaded', label: 'Loaded in View', color: '#0ea5e9', icon: MdHowToReg, value: rows.length }, { key: 'zone', label: 'Active Zone', color: '#10b981', icon: MdPlace, value: locaName || 'All Zones' } ]; return ( <> {(customerSummaryIsLoading || customersIsLoading) && } {/* ============================================= || Header | ============================================= */} Customers Live ยท {locaName || 'All Zones'} } placeholder="Select Zone" paperComponent={SoftPaper} sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }} /> {/* ============================================= || KPI Cards | ============================================= */} {KPI_META.map((item) => { const Icon = item.icon; return ( {item.label} {item.value} ); })} {/* ============================================= || Search header | ============================================= */} Directory {pageCount?.Total ?? 0} total ยท {rows.length} loaded {/* ============================================= || Table || ============================================= */} {isMobile ? ( {customersIsLoading && } {rows?.length === 0 && !customersIsLoading ? ( No customers to show {searchword ? 'Try a different keyword.' : 'Pick a zone above to load the directory.'} ) : ( rows?.map((row, index) => ( {row.firstname || 'โ€”'} ID #{row.customerid} { setSelectedCustomer(row); setTimeout(() => setOpen(true), 0); }} sx={{ bgcolor: soft('#8b5cf6'), color: '#8b5cf6', border: `1px solid ${edge('#8b5cf6')}`, flexShrink: 0, '&:hover': { bgcolor: '#8b5cf6', color: '#fff' } }} > } > {row.suburb ? ( {row.suburb} ) : ( โ€” )} {row.address || 'โ€”'} )) )} {rows?.length !== 0 && (
{isFetchingNextPage || hasNextPage ? ( ) : ( No more customers )}
)}
) : ( # Customer Contact Address Location Action {customersIsLoading && } {rows?.length === 0 && !customersIsLoading ? ( No customers to show {searchword ? 'Try a different keyword.' : 'Pick a zone above to load the directory.'} ) : ( rows?.map((row, index) => ( {String(index + 1 + page * rowsPerPage).padStart(2, '0')} {row.firstname || 'โ€”'} ID #{row.customerid} {row.contactno || 'โ€”'} {row.email && ( {row.email} )} {row.address || 'โ€”'} {row.suburb ? ( {row.suburb} ) : ( โ€” )} { setSelectedCustomer(row); setTimeout(() => setOpen(true), 0); }} sx={{ bgcolor: soft('#8b5cf6'), color: '#8b5cf6', border: `1px solid ${edge('#8b5cf6')}`, '&:hover': { bgcolor: '#8b5cf6', color: '#fff' } }} > )) )} {rows?.length !== 0 && (
{isFetchingNextPage || hasNextPage ? ( ) : ( No more customers )}
)}
)}
{/* ======================================== || Edit Dialog || ======================================== */} setOpen(false)} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" maxWidth="lg" fullWidth fullScreen={isMobile} PaperProps={{ sx: { borderRadius: { xs: 0, sm: 3 } } }} > Customer Edit {selectedCustomer?.firstname || 'Customer'} Customer Name { setSelectedCustomer({ ...selectedCustomer, firstname: e.target.value }); }} /> Contact Number { const value = e.target.value.replace(/\D/g, ''); // allow only digits setSelectedCustomer((prev) => ({ ...prev, contactno: value })); }} /> Address (typeof option === 'string' ? option : option?.description || '')} filterOptions={(x) => x} options={options} autoComplete includeInputInList filterSelectedOptions value={selectedCustomer?.address} noOptionsText="No locations" onChange={(event, newValue) => { setOptions(newValue ? [newValue, ...options] : options); setValue(newValue); console.log('newValue', newValue || ''); setAddress(newValue?.description); setSelectedCustomer({ ...selectedCustomer, address: newValue?.description }); }} onInputChange={(event, newInputValue) => { setInputValue(newInputValue); }} renderInput={(params) => } 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 (
  • {parts?.map((part, index) => ( {part.text} ))} {option?.structured_formatting.secondary_text}
  • ); }} />
    Location { const value = e.target.value; setSelectedCustomer((prev) => ({ ...prev, suburb: value })); // setSuburb(e.target.value); }} /> City { const value = e.target.value; setSelectedCustomer((prev) => ({ ...prev, city: value })); // setCity(e.target.value); }} /> State { const value = e.target.value; setSelectedCustomer((prev) => ({ ...prev, state: value })); // setState(e.target.value); }} /> Postcode { const value = e.target.value; setSelectedCustomer((prev) => ({ ...prev, postcode: value })); // setPostcode(e.target.value); }} /> Landmark { setSelectedCustomer({ ...selectedCustomer, landmark: e.target.value }); }} /> Latitude Longitude
    ); }