initial commit

This commit is contained in:
2026-05-13 17:48:36 +05:30
commit 5a80256856
305 changed files with 80994 additions and 0 deletions

View 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;

View 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);
}
};

View 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>
);
}

View 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;

View 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;

View 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>
))}
</>
);
}

View 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;