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 {
Card,
CircularProgress,
FormControl,
Grid,
InputAdornment,
InputLabel,
MenuItem,
Select,
styled,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Typography,
useTheme
} from '@mui/material';
@ -19,6 +26,7 @@ import { FormattedMessage } from 'react-intl';
import { useLocation, useNavigate } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
interface FlatInstallationViewProps {
installations: I_Installation[];
@ -30,21 +38,101 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
const currentLocation = useLocation();
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) => {
// Compare the status field of each installation and sort them based on the status.
//Installations with alarms go first
let a_status = a.status;
let b_status = b.status;
const HoverableTableRow = styled(TableRow)(({ theme }) => ({
cursor: 'pointer',
'&:hover': {
backgroundColor: theme.palette.action.hover // Or any other color
}
}));
if (a_status > b_status) {
return -1;
const indexedData = useMemo(() => {
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 => {
console.log('when selecting installation', product);
@ -86,197 +174,299 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
};
return (
<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;
<>
<Grid container spacing={1}>
<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'
}}
>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: '16px', // spacing between elements
width: '100%'
}}
>
<FormControl sx={{ flex: 1 }}>
<TextField
placeholder="Search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchTwoToneIcon />
</InputAdornment>
)
}}
/>
</FormControl>
const status = installation.status;
const rowStyles =
isRowHovered === installation.id
? {
cursor: 'pointer',
backgroundColor: theme.colors.primary.lighter
<FormControl sx={{ width: 240 }}>
<InputLabel>
<FormattedMessage
id="sortByStatus"
defaultMessage="Sort By Status"
/>
</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 (
<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>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.location}
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.location}
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.country}
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.country}
</Typography>
</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>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.orderNumbers}
</Typography>
</TableCell>
<TableCell>
<div
style={{
width: '20px',
height: '20px',
marginLeft: '2px',
borderRadius: '50%',
backgroundColor:
status === 2
? 'red'
: status === 1
? 'orange'
: status === -1 || status === -2
? 'transparent'
: 'green'
display: 'flex',
alignItems: 'center',
marginLeft: '15px'
}}
/>
>
{status === -1 ? (
<CancelIcon
style={{
width: '23px',
height: '23px',
color: 'red',
borderRadius: '50%'
}}
/>
) : (
''
)}
{installation.testingMode && (
<BuildIcon
{status === -2 ? (
<CircularProgress
size={20}
sx={{
color: '#f7b34d'
}}
/>
) : (
''
)}
<div
style={{
width: '23px',
height: '23px',
color: 'purple',
width: '20px',
height: '20px',
marginLeft: '2px',
borderRadius: '50%',
position: 'relative',
zIndex: 1,
marginLeft: '15px'
backgroundColor:
status === 2
? 'red'
: status === 1
? 'orange'
: status === -1 || status === -2
? 'transparent'
: 'green'
}}
/>
)}
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Card>
{installation.testingMode && (
<BuildIcon
style={{
width: '23px',
height: '23px',
color: 'purple',
borderRadius: '50%',
position: 'relative',
zIndex: 1,
marginLeft: '15px'
}}
/>
)}
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Card>
</Grid>
</Grid>
</Grid>
</>
);
};

View File

@ -1,72 +1,20 @@
import React, { useContext, useEffect, useState } from 'react';
import { FormControl, Grid, InputAdornment, TextField } from '@mui/material';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
import React from 'react';
import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView';
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 Installation from './Installation';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
interface installationSearchProps {
installations: I_Installation[];
}
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 (
<>
<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} />
<FlatInstallationView installations={props.installations} />
<Routes>
{filteredData.map((installation) => {
{props.installations.map((installation) => {
return (
<Route
key={installation.id}
@ -84,6 +32,80 @@ function InstallationSearch(props: installationSearchProps) {
</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;

View File

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