Put search bar and filtering options to salimax and sodistoreMax

This commit is contained in:
Noe 2025-04-19 15:14:09 +02:00
parent f3168e7215
commit 4bcbd9ae7f
3 changed files with 464 additions and 252 deletions

View File

@ -1,14 +1,21 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { import {
Card, Card,
CircularProgress, CircularProgress,
FormControl,
Grid, Grid,
InputAdornment,
InputLabel,
MenuItem,
Select,
styled,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableContainer, TableContainer,
TableHead, TableHead,
TableRow, TableRow,
TextField,
Typography, Typography,
useTheme useTheme
} from '@mui/material'; } from '@mui/material';
@ -19,6 +26,7 @@ import { FormattedMessage } from 'react-intl';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
interface FlatInstallationViewProps { interface FlatInstallationViewProps {
installations: I_Installation[]; installations: I_Installation[];
@ -30,21 +38,101 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1); const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
const currentLocation = useLocation(); const currentLocation = useLocation();
const { product, setProduct } = useContext(ProductIdContext); const { product, setProduct } = useContext(ProductIdContext);
const BATCH_SIZE = 50;
const [visibleCount, setVisibleCount] = useState(BATCH_SIZE);
const observerRef = useRef<IntersectionObserver | null>(null);
const loadMoreRef = useRef<HTMLDivElement>(null);
const [searchTerm, setSearchTerm] = useState('');
const [sortByStatus, setSortByStatus] = useState('All Installations');
const [sortByAction, setSortByAction] = useState('All Installations');
const sortedInstallations = [...props.installations].sort((a, b) => { const HoverableTableRow = styled(TableRow)(({ theme }) => ({
// Compare the status field of each installation and sort them based on the status. cursor: 'pointer',
//Installations with alarms go first '&:hover': {
let a_status = a.status; backgroundColor: theme.palette.action.hover // Or any other color
let b_status = b.status; }
}));
if (a_status > b_status) { const indexedData = useMemo(() => {
return -1; return props.installations.map((item) => ({
...item,
nameLower: item.name.toLowerCase(),
locationLower: item.location.toLowerCase(),
regionLower: item.region.toLowerCase()
}));
}, [props.installations]);
const sortedInstallations = useMemo(() => {
let filtered = indexedData.filter(
(item) =>
item.nameLower.includes(searchTerm.toLowerCase()) ||
item.locationLower.includes(searchTerm.toLowerCase()) ||
item.regionLower.includes(searchTerm.toLowerCase())
);
// Apply the 'showOnly' filter
switch (sortByStatus) {
case 'Installations With Alarm':
filtered = filtered.filter((i) => i.status === 2);
break;
case 'Installations with Warning':
filtered = filtered.filter((i) => i.status === 1);
break;
case 'Functional Installations':
filtered = filtered.filter((i) => i.status === 0);
break;
case 'Offline Installations':
filtered = filtered.filter((i) => i.status === -1);
break;
} }
if (a_status < b_status) {
return 1; switch (sortByAction) {
case 'Installations With Action Flag':
filtered = filtered.filter((i) => i.testingMode === true);
break;
case 'Installations Without Action Flag':
filtered = filtered.filter((i) => i.testingMode === false);
break;
} }
return 0;
}); // Sort by status (alarms first)
return filtered.sort((a, b) => {
const a_status = a.status;
const b_status = b.status;
if (a_status > b_status) return -1;
if (a_status < b_status) return 1;
return 0;
});
}, [searchTerm, indexedData, sortByAction, sortByStatus]);
useEffect(() => {
if (observerRef.current) {
observerRef.current.disconnect();
}
observerRef.current = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setVisibleCount((prev) => prev + BATCH_SIZE);
}
},
{
rootMargin: '600px', // triggers before the element fully enters view
threshold: 0.5 // triggers when 10% of it is visible
}
);
if (loadMoreRef.current) {
observerRef.current.observe(loadMoreRef.current);
}
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
}
};
}, []);
const handleSelectOneInstallation = (installationID: number): void => { const handleSelectOneInstallation = (installationID: number): void => {
console.log('when selecting installation', product); console.log('when selecting installation', product);
@ -86,197 +174,299 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
}; };
return ( return (
<Grid container spacing={1} sx={{ marginTop: 0.1 }}> <>
<Grid <Grid container spacing={1}>
item <Grid
sx={{ item
display: xs={12}
currentLocation.pathname === routes.installations + 'list' || md={6}
currentLocation.pathname === routes.installations + routes.list || sx={{
currentLocation.pathname === display:
routes.sodistore_installations + 'list' || currentLocation.pathname === routes.installations + 'list' ||
currentLocation.pathname === currentLocation.pathname === routes.installations + routes.list ||
routes.sodistore_installations + routes.list currentLocation.pathname ===
? 'block' routes.sodistore_installations + 'list' ||
: 'none' currentLocation.pathname ===
}} routes.sodistore_installations + routes.list
> ? 'block'
<Card> : 'none'
<TableContainer> }}
<Table> >
<TableHead> <div
<TableRow> style={{
<TableCell> display: 'flex',
<FormattedMessage id="name" defaultMessage="Name" /> flexDirection: 'row',
</TableCell> alignItems: 'center',
<TableCell> gap: '16px', // spacing between elements
<FormattedMessage id="location" defaultMessage="Location" /> width: '100%'
</TableCell> }}
<TableCell> >
<FormattedMessage id="country" defaultMessage="Country" /> <FormControl sx={{ flex: 1 }}>
</TableCell> <TextField
<TableCell> placeholder="Search"
<FormattedMessage value={searchTerm}
id="orderNumbers" onChange={(e) => setSearchTerm(e.target.value)}
defaultMessage="Order Numbers" fullWidth
/> InputProps={{
</TableCell> startAdornment: (
<TableCell> <InputAdornment position="start">
<FormattedMessage id="status" defaultMessage="Status" /> <SearchTwoToneIcon />
</TableCell> </InputAdornment>
</TableRow> )
</TableHead> }}
<TableBody> />
{sortedInstallations.map((installation) => { </FormControl>
const isInstallationSelected =
installation.id === selectedInstallation;
const status = installation.status; <FormControl sx={{ width: 240 }}>
const rowStyles = <InputLabel>
isRowHovered === installation.id <FormattedMessage
? { id="sortByStatus"
cursor: 'pointer', defaultMessage="Sort By Status"
backgroundColor: theme.colors.primary.lighter />
</InputLabel>
<Select
value={sortByStatus}
onChange={(e) => setSortByStatus(e.target.value)}
label="Show Only"
>
{[
'All Installations',
'Installations With Alarm',
'Installations with Warning',
'Functional Installations',
'Offline Installations'
].map((type) => (
<MenuItem key={type} value={type}>
{type}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl sx={{ width: 240 }}>
<InputLabel>
<FormattedMessage
id="sortByActionFlag"
defaultMessage="Sort By Action Flag"
/>
</InputLabel>
<Select
value={sortByAction}
onChange={(e) => setSortByAction(e.target.value)}
label="Show Only"
>
{[
'All Installations',
'Installations With Action Flag',
'Installations Without Action Flag'
].map((type) => (
<MenuItem key={type} value={type}>
{type}
</MenuItem>
))}
</Select>
</FormControl>
</div>
</Grid>
</Grid>
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
<Grid
item
sx={{
display:
currentLocation.pathname === routes.installations + 'list' ||
currentLocation.pathname === routes.installations + routes.list ||
currentLocation.pathname ===
routes.sodistore_installations + 'list' ||
currentLocation.pathname ===
routes.sodistore_installations + routes.list
? 'block'
: 'none'
}}
>
<Card>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>
<FormattedMessage id="name" defaultMessage="Name" />
</TableCell>
<TableCell>
<FormattedMessage
id="location"
defaultMessage="Location"
/>
</TableCell>
<TableCell>
<FormattedMessage id="country" defaultMessage="Country" />
</TableCell>
<TableCell>
<FormattedMessage
id="orderNumbers"
defaultMessage="Order Numbers"
/>
</TableCell>
<TableCell>
<FormattedMessage id="status" defaultMessage="Status" />
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{sortedInstallations.map((installation) => {
const isInstallationSelected =
installation.id === selectedInstallation;
const status = installation.status;
const rowStyles =
isRowHovered === installation.id
? {
cursor: 'pointer',
backgroundColor: theme.colors.primary.lighter
}
: {};
return (
<TableRow
hover
key={installation.id}
selected={isInstallationSelected}
style={rowStyles}
onClick={() =>
handleSelectOneInstallation(installation.id)
} }
: {}; onMouseEnter={() =>
handleRowMouseEnter(installation.id)
}
onMouseLeave={() => handleRowMouseLeave()}
>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.name}
</Typography>
</TableCell>
return ( <TableCell>
<TableRow <Typography
hover variant="body2"
key={installation.id} fontWeight="bold"
selected={isInstallationSelected} color="text.primary"
style={rowStyles} gutterBottom
onClick={() => noWrap
handleSelectOneInstallation(installation.id) sx={{ marginTop: '10px', fontSize: 'small' }}
} >
onMouseEnter={() => handleRowMouseEnter(installation.id)} {installation.location}
onMouseLeave={() => handleRowMouseLeave()} </Typography>
> </TableCell>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.name}
</Typography>
</TableCell>
<TableCell> <TableCell>
<Typography <Typography
variant="body2" variant="body2"
fontWeight="bold" fontWeight="bold"
color="text.primary" color="text.primary"
gutterBottom gutterBottom
noWrap noWrap
sx={{ marginTop: '10px', fontSize: 'small' }} sx={{ marginTop: '10px', fontSize: 'small' }}
> >
{installation.location} {installation.country}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography <Typography
variant="body2" variant="body2"
fontWeight="bold" fontWeight="bold"
color="text.primary" color="text.primary"
gutterBottom gutterBottom
noWrap noWrap
sx={{ marginTop: '10px', fontSize: 'small' }} sx={{ marginTop: '10px', fontSize: 'small' }}
> >
{installation.country} {installation.orderNumbers}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.orderNumbers}
</Typography>
</TableCell>
<TableCell>
<div
style={{
display: 'flex',
alignItems: 'center',
marginLeft: '15px'
}}
>
{status === -1 ? (
<CancelIcon
style={{
width: '23px',
height: '23px',
color: 'red',
borderRadius: '50%'
}}
/>
) : (
''
)}
{status === -2 ? (
<CircularProgress
size={20}
sx={{
color: '#f7b34d'
}}
/>
) : (
''
)}
<TableCell>
<div <div
style={{ style={{
width: '20px', display: 'flex',
height: '20px', alignItems: 'center',
marginLeft: '2px', marginLeft: '15px'
borderRadius: '50%',
backgroundColor:
status === 2
? 'red'
: status === 1
? 'orange'
: status === -1 || status === -2
? 'transparent'
: 'green'
}} }}
/> >
{status === -1 ? (
<CancelIcon
style={{
width: '23px',
height: '23px',
color: 'red',
borderRadius: '50%'
}}
/>
) : (
''
)}
{installation.testingMode && ( {status === -2 ? (
<BuildIcon <CircularProgress
size={20}
sx={{
color: '#f7b34d'
}}
/>
) : (
''
)}
<div
style={{ style={{
width: '23px', width: '20px',
height: '23px', height: '20px',
color: 'purple', marginLeft: '2px',
borderRadius: '50%', borderRadius: '50%',
position: 'relative', backgroundColor:
zIndex: 1, status === 2
marginLeft: '15px' ? 'red'
: status === 1
? 'orange'
: status === -1 || status === -2
? 'transparent'
: 'green'
}} }}
/> />
)}
</div> {installation.testingMode && (
</TableCell> <BuildIcon
</TableRow> style={{
); width: '23px',
})} height: '23px',
</TableBody> color: 'purple',
</Table> borderRadius: '50%',
</TableContainer> position: 'relative',
</Card> zIndex: 1,
marginLeft: '15px'
}}
/>
)}
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Card>
</Grid>
</Grid> </Grid>
</Grid> </>
); );
}; };

View File

@ -1,72 +1,20 @@
import React, { useContext, useEffect, useState } from 'react'; import React from 'react';
import { FormControl, Grid, InputAdornment, TextField } from '@mui/material';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView'; import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView';
import { I_Installation } from '../../../interfaces/InstallationTypes'; import { I_Installation } from '../../../interfaces/InstallationTypes';
import { Route, Routes, useLocation } from 'react-router-dom'; import { Route, Routes } from 'react-router-dom';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import Installation from './Installation'; import Installation from './Installation';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
interface installationSearchProps { interface installationSearchProps {
installations: I_Installation[]; installations: I_Installation[];
} }
function InstallationSearch(props: installationSearchProps) { function InstallationSearch(props: installationSearchProps) {
const [searchTerm, setSearchTerm] = useState('');
const currentLocation = useLocation();
const [filteredData, setFilteredData] = useState(props.installations);
const { product, setProduct } = useContext(ProductIdContext);
useEffect(() => {
const filtered = props.installations.filter(
(item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.location.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredData(filtered);
}, [searchTerm, props.installations]);
return ( return (
<> <>
<Grid container> <FlatInstallationView installations={props.installations} />
<Grid
item
xs={12}
md={6}
sx={{
display:
currentLocation.pathname === routes.installations + 'list' ||
currentLocation.pathname === routes.installations + routes.list ||
currentLocation.pathname ===
routes.sodistore_installations + 'list' ||
currentLocation.pathname ===
routes.sodistore_installations + routes.list
? 'block'
: 'none'
}}
>
<FormControl variant="outlined">
<TextField
placeholder="Search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchTwoToneIcon />
</InputAdornment>
)
}}
/>
</FormControl>
</Grid>
</Grid>
<FlatInstallationView installations={filteredData} />
<Routes> <Routes>
{filteredData.map((installation) => { {props.installations.map((installation) => {
return ( return (
<Route <Route
key={installation.id} key={installation.id}
@ -84,6 +32,80 @@ function InstallationSearch(props: installationSearchProps) {
</Routes> </Routes>
</> </>
); );
//
// export default InstallationSearch;
//
// const [searchTerm, setSearchTerm] = useState('');
// const currentLocation = useLocation();
// const [filteredData, setFilteredData] = useState(props.installations);
// const { product, setProduct } = useContext(ProductIdContext);
//
// useEffect(() => {
// const filtered = props.installations.filter(
// (item) =>
// item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
// item.location.toLowerCase().includes(searchTerm.toLowerCase())
// );
// setFilteredData(filtered);
// }, [searchTerm, props.installations]);
//
// return (
// <>
// <Grid container>
// <Grid
// item
// xs={12}
// md={6}
// sx={{
// display:
// currentLocation.pathname === routes.installations + 'list' ||
// currentLocation.pathname === routes.installations + routes.list ||
// currentLocation.pathname ===
// routes.sodistore_installations + 'list' ||
// currentLocation.pathname ===
// routes.sodistore_installations + routes.list
// ? 'block'
// : 'none'
// }}
// >
// <FormControl variant="outlined">
// <TextField
// placeholder="Search"
// value={searchTerm}
// onChange={(e) => setSearchTerm(e.target.value)}
// fullWidth
// InputProps={{
// startAdornment: (
// <InputAdornment position="start">
// <SearchTwoToneIcon />
// </InputAdornment>
// )
// }}
// />
// </FormControl>
// </Grid>
// </Grid>
//
// <FlatInstallationView installations={filteredData} />
// <Routes>
// {filteredData.map((installation) => {
// return (
// <Route
// key={installation.id}
// path={routes.installation + installation.id + '*'}
// element={
// <Installation
// key={installation.id}
// current_installation={installation}
// type="installation"
// ></Installation>
// }
// />
// );
// })}
// </Routes>
// </>
// );
} }
export default InstallationSearch; export default InstallationSearch;

View File

@ -84,20 +84,20 @@ export const transformInputToBatteryViewDataJson = async (
const MAX_NUMBER = 9999999; const MAX_NUMBER = 9999999;
const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current']; const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
const pathCategories = const pathCategories =
product === 0 product === 3
? [ ? [
'.Soc',
'.Temperatures.Cells.Average',
'.Dc.Power',
'.Dc.Voltage',
'.Dc.Current'
]
: [
'.BatteryDeligreenDataRecord.Soc', '.BatteryDeligreenDataRecord.Soc',
'.BatteryDeligreenDataRecord.TemperaturesList.EnvironmentTemperature', '.BatteryDeligreenDataRecord.TemperaturesList.EnvironmentTemperature',
'.BatteryDeligreenDataRecord.Power', '.BatteryDeligreenDataRecord.Power',
'.BatteryDeligreenDataRecord.TotalBatteryVoltage', '.BatteryDeligreenDataRecord.TotalBatteryVoltage',
'.BatteryDeligreenDataRecord.BusCurrent' '.BatteryDeligreenDataRecord.BusCurrent'
]
: [
'.Soc',
'.Temperatures.Cells.Average',
'.Dc.Power',
'.Dc.Voltage',
'.Dc.Current'
]; ];
const pathsToSearch = [ const pathsToSearch = [