diff --git a/src/Components/FilterHeader/index.jsx b/src/Components/FilterHeader/index.jsx index 46a17221b..96c151db0 100644 --- a/src/Components/FilterHeader/index.jsx +++ b/src/Components/FilterHeader/index.jsx @@ -1,108 +1,78 @@ -import { - Checkbox, - FormControl, - InputLabel, - ListItemText, - MenuItem, - Select, -} from "@mui/material"; +import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; +/** + * A reusable filter header component that displays a dropdown menu with selectable options. + * + * @component + * @param {Object} props - The component props. + * @param {string} props.header - The header text to display when no options are selected. + * @param {Array} props.options - An array of options to display in the dropdown menu. Each option should have a `value` and `label`. + * @param {Array} [props.value] - The currently selected values. + * @param {Function} props.onChange - The callback function to handle changes in the selected values. + * @param {boolean} [props.multiple=true] - Whether multiple options can be selected. + * @returns {JSX.Element} The rendered FilterHeader component. + */ + const FilterHeader = ({ header, options, value, onChange, multiple = true }) => { const theme = useTheme(); - const selectStyles = { - "& .MuiOutlinedInput-input": { - color: theme.palette.primary.contrastText, - }, - "& .MuiOutlinedInput-notchedOutline": { - borderColor: theme.palette.primary.lowContrast, - borderRadius: theme.shape.borderRadius, - }, - "& .MuiSelect-icon": { - color: theme.palette.primary.contrastText, - }, - "&:hover": { - backgroundColor: theme.palette.primary.main, - }, - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: theme.palette.primary.lowContrast, - }, - }; - - const menuItemStyles = { - "&:hover": { - backgroundColor: theme.palette.secondary.main, - }, - }; - return ( -
- + ( - ( + + - )} - value={value} - onChange={onChange} - renderValue={(selected) => selected.join(", ")} - sx={selectStyles} - > - {options.map((option) => ( - - - - - ))} - - -
+ + + ))} + + ); }; FilterHeader.propTypes = { - header: PropTypes.string, - options: PropTypes.arrayOf(PropTypes.string), + header: PropTypes.string.isRequired, + options: PropTypes.arrayOf( + PropTypes.shape({ + value: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + }) + ).isRequired, value: PropTypes.arrayOf(PropTypes.string), - onChange: PropTypes.func, + onChange: PropTypes.func.isRequired, multiple: PropTypes.bool, }; diff --git a/src/Pages/Uptime/Monitors/Components/Filter/index.jsx b/src/Pages/Uptime/Monitors/Components/Filter/index.jsx index 7a8798e6a..f99ac3980 100644 --- a/src/Pages/Uptime/Monitors/Components/Filter/index.jsx +++ b/src/Pages/Uptime/Monitors/Components/Filter/index.jsx @@ -1,30 +1,61 @@ import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; import FilterHeader from "../../../../../Components/FilterHeader"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { Box, Button } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; +import { useTranslation } from "react-i18next"; -const Filter = ({ selectedTypes, setSelectedTypes, setToFilterStatus, setToFilterActive }) => { +/** + * Filter Component + * + * A high-level component that provides filtering options for type, status, and state. + * It allows users to select multiple options for each filter and reset the filters. + * + * @component + * @param {Object} props - The component props. + * @param {string[]} props.selectedTypes - An array of selected type values. + * @param {function} props.setSelectedTypes - A function to set the selected type values. + * @param {string[]} props.selectedStatus - An array of selected status values. + * @param {function} props.setSelectedStatus - A function to set the selected status values. + * @param {string[]} props.selectedState - An array of selected state values. + * @param {function} props.setSelectedState - A function to set the selected state values. + * @param {function} props.setToFilterStatus - A function to set the filter status based on selected status values. + * @param {function} props.setToFilterActive - A function to set the filter active state based on selected state values. + * @param {function} props.handleReset - A function to reset all filters. + * + * @returns {JSX.Element} The rendered Filter component. + */ + +const Filter = ({ + selectedTypes, + setSelectedTypes, + selectedStatus, + setSelectedStatus, + selectedState, + setSelectedState, + setToFilterStatus, + setToFilterActive, + handleReset, +}) => { const theme = useTheme(); - - const [selectedState, setSelectedState] = useState([]); - const [selectedStatus, setSelectedStatus] = useState([]); + const { t } = useTranslation(); const handleTypeChange = (event) => { - setSelectedTypes(event.target.value); + const selectedValues = event.target.value; + setSelectedTypes(selectedValues.length > 0 ? selectedValues : undefined); }; const handleStatusChange = (event) => { const selectedValues = event.target.value; - setSelectedStatus(selectedValues); + setSelectedStatus(selectedValues.length > 0 ? selectedValues : undefined); if (selectedValues.length === 0 || selectedValues.length === 2) { setToFilterStatus(null); } else { setToFilterStatus(selectedValues[0] === "Up" ? "true" : "false"); } - } + }; const handleStateChange = (event) => { const selectedValues = event.target.value; @@ -37,21 +68,30 @@ const Filter = ({ selectedTypes, setSelectedTypes, setToFilterStatus, setToFilte } }; - const handleReset = () => { - setSelectedState([]); - setSelectedTypes([]); - setSelectedStatus([]); - setToFilterStatus(null); - setToFilterActive(null); - }; - const isFilterActive = useMemo(() => { - return selectedTypes.length > 0 || selectedState.length > 0 || selectedStatus.length > 0; + return ( + (selectedTypes?.length ?? 0) > 0 || + (selectedState?.length ?? 0) > 0 || + (selectedStatus?.length ?? 0) > 0 + ); }, [selectedState, selectedTypes, selectedStatus]); - const typeOptions = ["http", "ping", "docker", "port"]; - const statusOptions = ["Up", "Down"]; - const stateOptions = ["Active", "Paused"]; + const typeOptions = [ + { value: "http", label: "HTTP(S)" }, + { value: "ping", label: "Ping" }, + { value: "docker", label: "Docker" }, + { value: "port", label: "Port" }, + ]; + + const statusOptions = [ + { value: "Up", label: "Up" }, + { value: "Down", label: "Down" }, + ]; + + const stateOptions = [ + { value: "Active", label: "Active" }, + { value: "Paused", label: "Paused" }, + ]; return ( + + + - - - ); }; Filter.propTypes = { - selectedTypes: PropTypes.arrayOf(PropTypes.string), - setSelectedTypes: PropTypes.func, - setToFilterStatus: PropTypes.func, - setToFilterActive: PropTypes.func, + selectedTypes: PropTypes.arrayOf(PropTypes.string).isRequired, + setSelectedTypes: PropTypes.func.isRequired, + selectedStatus: PropTypes.arrayOf(PropTypes.string).isRequired, + setSelectedStatus: PropTypes.func.isRequired, + selectedState: PropTypes.arrayOf(PropTypes.string).isRequired, + setSelectedState: PropTypes.func.isRequired, + setToFilterStatus: PropTypes.func.isRequired, + setToFilterActive: PropTypes.func.isRequired, + handleReset: PropTypes.func.isRequired, }; export default Filter; diff --git a/src/Pages/Uptime/Monitors/index.jsx b/src/Pages/Uptime/Monitors/index.jsx index 8d339a734..d90e525d2 100644 --- a/src/Pages/Uptime/Monitors/index.jsx +++ b/src/Pages/Uptime/Monitors/index.jsx @@ -70,7 +70,9 @@ const UptimeMonitors = () => { const [sort, setSort] = useState(undefined); const [isSearching, setIsSearching] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); - const [selectedTypes, setSelectedTypes] = useState([]); + const [selectedTypes, setSelectedTypes] = useState(undefined); + const [selectedState, setSelectedState] = useState(undefined); + const [selectedStatus, setSelectedStatus] = useState(undefined); const [toFilterStatus, setToFilterStatus] = useState(null); const [toFilterActive, setToFilterActive] = useState(null); @@ -107,19 +109,28 @@ const UptimeMonitors = () => { types: TYPES, monitorUpdateTrigger, }); - - let field = sort?.field; - let filter = search; - if (toFilterStatus !== null) { - field = "status"; - filter = toFilterStatus; - } else if (toFilterActive !== null) { - field = "isActive"; - filter = toFilterActive; - } else { - field = sort?.field; - filter = search; - } + + const handleReset = () => { + setSelectedState(undefined); + setSelectedTypes(undefined); + setSelectedStatus(undefined); + setToFilterStatus(null); + setToFilterActive(null); + }; + + const field = + toFilterStatus !== null + ? "status" + : toFilterActive !== null + ? "isActive" + : sort?.field; + + const filter = + toFilterStatus !== null + ? toFilterStatus + : toFilterActive !== null + ? toFilterActive + : search; const [ monitorsWithChecks, @@ -199,9 +210,13 @@ const UptimeMonitors = () => { ({ }, }, }, + MuiListItemText: { + styleOverrides: { + root: ({ theme }) => ({ + "& .MuiTypography-root": { + color: theme.palette.primary.contrastText, + }, + }), + }, + }, MuiMenuItem: { styleOverrides: { root: ({ theme }) => ({ @@ -453,6 +462,28 @@ const baseTheme = (palette) => ({ }), }, }, + MuiSelect: { + styleOverrides: { + root: ({ theme }) => ({ + "& .MuiOutlinedInput-input": { + color: theme.palette.primary.contrastText, + }, + "& .MuiOutlinedInput-notchedOutline": { + borderColor: theme.palette.primary.lowContrast, + borderRadius: theme.shape.borderRadius, + }, + "& .MuiSelect-icon": { + color: theme.palette.primary.contrastTextSecondary, // Dropdown + color + }, + "&:hover": { + backgroundColor: theme.palette.primary.main, // Background on hover + }, + "&:hover .MuiOutlinedInput-notchedOutline": { + borderColor: theme.palette.primary.lowContrast, + }, + }), + }, + }, MuiButtonGroup: { styleOverrides: { root: ({ theme }) => ({ diff --git a/src/locales/gb.json b/src/locales/gb.json index 05a4bdc8e..cc22e6b80 100644 --- a/src/locales/gb.json +++ b/src/locales/gb.json @@ -111,6 +111,7 @@ "configure": "Configure", "networkError": "Network error", "responseTime": "Response time:", + "reset": "Reset", "ms": "ms", "bar": "Bar", "area": "Area", @@ -316,6 +317,7 @@ "statusCode": "Status code", "date&Time": "Date & Time", "type": "Type", + "state": "State", "statusPageName": "Status page name", "publicURL": "Public URL", "repeat": "Repeat",