initial commit
This commit is contained in:
66
src/components/nearle_components/DebounceSearchBar.js
Normal file
66
src/components/nearle_components/DebounceSearchBar.js
Normal file
@@ -0,0 +1,66 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { OutlinedInput, InputAdornment, Tooltip, IconButton } from '@mui/material';
|
||||
import { SearchOutlined } from '@mui/icons-material';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
const DebounceSearchBar = ({
|
||||
value,
|
||||
onChange,
|
||||
onDebouncedChange, // 🔹 NEW
|
||||
debounceTime = 500,
|
||||
placeholder = 'Search (ctrl+k)',
|
||||
sx
|
||||
}) => {
|
||||
const textFieldRef = useRef(null);
|
||||
|
||||
const [debouncedValue] = useDebounce(value, debounceTime);
|
||||
|
||||
// fire debounced callback whenever debouncedValue changes
|
||||
useEffect(() => {
|
||||
if (onDebouncedChange) {
|
||||
onDebouncedChange(debouncedValue);
|
||||
}
|
||||
}, [debouncedValue, onDebouncedChange]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyPress = (event) => {
|
||||
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
textFieldRef.current?.focus();
|
||||
}
|
||||
if (event.key === 'Escape' && document.activeElement === textFieldRef.current) {
|
||||
textFieldRef.current.blur();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
return () => document.removeEventListener('keydown', handleKeyPress);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<OutlinedInput
|
||||
sx={{ ...sx }}
|
||||
inputRef={textFieldRef}
|
||||
placeholder={placeholder}
|
||||
autoComplete="off"
|
||||
value={value}
|
||||
fullWidth
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
startAdornment={
|
||||
<InputAdornment position="start" sx={{ mr: -0.5 }}>
|
||||
<SearchOutlined />
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={
|
||||
<Tooltip title="Clear">
|
||||
<IconButton sx={{ visibility: value ? 'visible' : 'hidden' }} onClick={() => onChange('')}>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebounceSearchBar;
|
||||
30
src/components/nearle_components/GlobalToast.js
Normal file
30
src/components/nearle_components/GlobalToast.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { enqueueSnackbar, closeSnackbar } from 'notistack';
|
||||
|
||||
import { setSnackbarId } from '../../store/reducers/toastSlice';
|
||||
import { store, dispatch } from 'store';
|
||||
|
||||
export const GlobalToast = (message, color = 'default', vertical = 'top') => {
|
||||
const id = enqueueSnackbar(message, {
|
||||
variant: color,
|
||||
anchorOrigin: { vertical, horizontal: 'right' },
|
||||
autoHideDuration: null,
|
||||
action: (snackbarId) => (
|
||||
<button onClick={() => closeSnackbar(snackbarId)} style={{ background: 'none', border: 'none', cursor: 'pointer' }}>
|
||||
❌
|
||||
</button>
|
||||
)
|
||||
});
|
||||
|
||||
// Save snackbarId globally
|
||||
dispatch(setSnackbarId(id));
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
// GLOBAL close function
|
||||
export const closeGlobalToast = () => {
|
||||
const id = store.getState().toastSlice.snackbarId;
|
||||
if (id) {
|
||||
closeSnackbar(id);
|
||||
}
|
||||
};
|
||||
34
src/components/nearle_components/LoaderWithImage.js
Normal file
34
src/components/nearle_components/LoaderWithImage.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// LoaderWithImage.jsx
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress } from '@mui/material';
|
||||
import nelogo from '../../assets/images/logo-sm.png';
|
||||
|
||||
export default function LoaderWithImage({ size = 70, imgSize = 40, alt = 'loader' }) {
|
||||
return (
|
||||
<Box position="relative" display="inline-flex" justifyContent="center" alignItems="center">
|
||||
<CircularProgress size={size} />
|
||||
|
||||
<Box
|
||||
position="absolute"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{
|
||||
width: imgSize,
|
||||
height: imgSize
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={nelogo}
|
||||
alt={alt}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
51
src/components/nearle_components/LocationAutocomplete.js
Normal file
51
src/components/nearle_components/LocationAutocomplete.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { forwardRef, useEffect, useState } from 'react';
|
||||
import { Autocomplete, TextField } from '@mui/material';
|
||||
import axios from 'axios';
|
||||
|
||||
const LocationAutocomplete = forwardRef(({ setAppId, setLocoName, setPage, sx, textfeildSx }, ref) => {
|
||||
const [locations, setLocations] = useState(JSON.parse(localStorage.getItem('applocations') || '[]'));
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLocations = async () => {
|
||||
try {
|
||||
const userid = localStorage.getItem('userid');
|
||||
if (!userid) return;
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/partners/getlocations/?userid=${userid}`);
|
||||
if (response.data.status) {
|
||||
const updatedLocations = [...response.data.details, { locationname: 'All', applocationid: 0 }];
|
||||
localStorage.setItem('applocations', JSON.stringify(updatedLocations));
|
||||
setLocations(updatedLocations);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching locations in LocationAutocomplete:', err);
|
||||
}
|
||||
};
|
||||
|
||||
if (locations.length === 0) {
|
||||
fetchLocations();
|
||||
}
|
||||
}, [locations.length]);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
id="location-autocomplete"
|
||||
options={locations || []}
|
||||
getOptionLabel={(option) => option?.locationname ?? ''}
|
||||
sx={{ ...sx }}
|
||||
onChange={(event, value, reason) => {
|
||||
if (reason === 'clear') {
|
||||
setAppId?.(0);
|
||||
setLocoName?.('');
|
||||
setPage?.(0);
|
||||
} else if (value) {
|
||||
setAppId?.(value.applocationid);
|
||||
setLocoName?.(value.locationname);
|
||||
setPage?.(0);
|
||||
}
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} inputRef={ref} label={'Select Zones'} sx={{ ...textfeildSx }} />}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LocationAutocomplete;
|
||||
77
src/components/nearle_components/SearchBar.js
Normal file
77
src/components/nearle_components/SearchBar.js
Normal file
@@ -0,0 +1,77 @@
|
||||
// DebouncedSearchBar.jsx
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { FormControl, OutlinedInput, InputAdornment, IconButton, useTheme } from '@mui/material';
|
||||
import SearchOutlined from '@mui/icons-material/SearchOutlined';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
|
||||
const SearchBar = ({ value, onChange, sx, placeholder = 'Search (Ctrl + K)', delay = 300 }) => {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef(null);
|
||||
|
||||
// Local input state for immediate UI update
|
||||
const [inputValue, setInputValue] = useState(value || '');
|
||||
|
||||
/* Sync external value when it changes */
|
||||
useEffect(() => {
|
||||
setInputValue(value || '');
|
||||
}, [value]);
|
||||
|
||||
/* ================================
|
||||
🔥 Debounce Logic
|
||||
================================ */
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
if (inputValue !== value) {
|
||||
onChange({ target: { value: inputValue } });
|
||||
}
|
||||
}, delay);
|
||||
|
||||
return () => clearTimeout(handler);
|
||||
}, [inputValue]);
|
||||
|
||||
/* CTRL + K / CMD + K & ESC behavior */
|
||||
useEffect(() => {
|
||||
const handleKeyPress = (event) => {
|
||||
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
|
||||
if (event.key === 'Escape' && document.activeElement === inputRef.current) {
|
||||
inputRef.current.blur();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
return () => document.removeEventListener('keydown', handleKeyPress);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<OutlinedInput
|
||||
inputRef={inputRef}
|
||||
placeholder={placeholder}
|
||||
sx={{
|
||||
borderRadius: 0,
|
||||
...sx
|
||||
}}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)} // No direct onChange
|
||||
autoComplete="off"
|
||||
size="large"
|
||||
startAdornment={
|
||||
<InputAdornment position="start" sx={{ mr: -0.5, color: theme.palette.secondary.main }}>
|
||||
<SearchOutlined />
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={
|
||||
<IconButton sx={{ visibility: inputValue ? 'visible' : 'hidden' }} onClick={() => setInputValue('')}>
|
||||
<ClearIcon style={{ fontSize: 'large', color: theme.palette.primary.main }} />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchBar;
|
||||
18
src/components/nearle_components/TableLoader.js
Normal file
18
src/components/nearle_components/TableLoader.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { TableRow, TableCell, Skeleton } from '@mui/material';
|
||||
|
||||
export default function TableLoader({ rows = 5, columns = 6, height = 40 }) {
|
||||
return (
|
||||
<>
|
||||
{[...Array(rows)].map((_, rowIndex) => (
|
||||
<TableRow key={rowIndex}>
|
||||
{[...Array(columns)].map((_, colIndex) => (
|
||||
<TableCell key={colIndex}>
|
||||
<Skeleton variant="rectangular" height={height} width={70} animation="wave" />
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
22
src/components/nearle_components/TitleCard.js
Normal file
22
src/components/nearle_components/TitleCard.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Stack, Typography, Box } from '@mui/material';
|
||||
|
||||
const TitleCard = ({ sx, title, children, starticon }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
...sx
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" flexWrap={'wrap'} alignItems="center" justifyContent="space-between" gap={1}>
|
||||
<Stack>
|
||||
{starticon && starticon}
|
||||
<Typography variant="h3">{title}</Typography>
|
||||
</Stack>
|
||||
{children}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TitleCard;
|
||||
Reference in New Issue
Block a user