diff --git a/Client/src/Components/Inputs/Autocomplete/index.jsx b/Client/src/Components/Inputs/Autocomplete/index.jsx deleted file mode 100644 index 5256efccc..000000000 --- a/Client/src/Components/Inputs/Autocomplete/index.jsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Autocomplete, TextField } from "@mui/material"; -import React from "react"; -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; - -/** - * @example - * - * const options = [ - * { _id: "66d6119ef959cbc681e034f0", name: "Googler" }, - * { _id: "66d6119ef959cbc681e034f0", name: "CNN" }, - * { _id: "66d61a1bf959cbc681e0353f", name: "X Corp." }, - * ]; - * - * - * - */ - -/** - * AutoCompleteField component. - * - * @component - * @param {Object} props - The component props. - * @param {string} props.id - The ID of the autocomplete field. - * @param {string} props.type - The type of the input field (text or number). - * @param {Array} props.options - The options for the autocomplete field. - * @param {string} props.placeholder - The placeholder text for the input field. - * @param {boolean} props.disabled - Indicates if the field is disabled. - * @returns {JSX.Element} The AutoCompleteField component. - */ - -const AutoCompleteField = ({ - id, - type, - options, - placeholder = "Type to search", - disabled, -}) => { - const [value, setValue] = React.useState(); - const [inputValue, setInputValue] = React.useState(""); - const theme = useTheme(); - - return ( - { - setValue(newValue); - }} - inputValue={inputValue} - onInputChange={(event, newInputValue) => { - setInputValue(newInputValue); - }} - options={options} - getOptionLabel={(option) => option.name} - renderInput={(params) => ( - - )} - renderOption={(props, option) => { - const { key, ...optionProps } = props; - return ( -
  • -
    {{option.name}}
    -
  • - ); - }} - slotProps={{ - popper: { - sx: { - "& ul": { p: 0 }, - "& li": { borderRadius: theme.shape.borderRadius }, - }, - }, - paper: { - sx: { - p: 2, - fontSize: 13, - }, - }, - }} - /> - ); -}; - -AutoCompleteField.displayName = "AutoCompleteField"; - -AutoCompleteField.propTypes = { - id: PropTypes.string, - type: PropTypes.oneOf(["text", "number"]), - options: PropTypes.array, - placeholder: PropTypes.string, - disabled: PropTypes.bool, - setWidth: PropTypes.string, -}; - -export default AutoCompleteField; diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx new file mode 100644 index 000000000..b69b25731 --- /dev/null +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -0,0 +1,138 @@ +import PropTypes from "prop-types"; +import { Box, ListItem, Autocomplete, TextField } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import SearchIcon from "../../../assets/icons/search.svg?react"; + +/** + * Search component using Material UI's Autocomplete. + * + * @param {Object} props + * @param {string} props.id - Unique identifier for the Autocomplete component + * @param {Array} props.options - Options to display in the Autocomplete dropdown + * @param {string} props.filteredBy - Key to access the option label from the options + * @param {string} props.value - Current input value for the Autocomplete + * @param {Function} props.handleChange - Function to call when the input changes + * @param {Object} props.sx - Additional styles to apply to the component + * @returns {JSX.Element} The rendered Search component + */ +const Search = ({ id, options, filteredBy, value, handleChange, sx }) => { + const theme = useTheme(); + + return ( + { + handleChange(newValue); + }} + fullWidth + freeSolo + disableClearable + options={options} + getOptionLabel={(option) => option[filteredBy]} + renderInput={(params) => ( + + + + ), + }} + sx={{ + "& fieldset": { + borderColor: theme.palette.border.light, + borderRadius: theme.shape.borderRadius, + }, + "& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset": + { + borderColor: theme.palette.border.light, + }, + }} + /> + )} + filterOptions={(options, { inputValue }) => { + const filtered = options.filter((option) => + option[filteredBy].toLowerCase().includes(inputValue.toLowerCase()) + ); + + if (filtered.length === 0) { + return [{ [filteredBy]: "No monitors found", noOptions: true }]; + } + return filtered; + }} + renderOption={(props, option) => { + const { key, ...optionProps } = props; + return ( + + {option[filteredBy]} + + ); + }} + slotProps={{ + popper: { + keepMounted: true, + sx: { + "& ul": { p: 2 }, + "& li.MuiAutocomplete-option": { + color: theme.palette.text.secondary, + px: 4, + borderRadius: theme.shape.borderRadius, + }, + "& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover": + { + backgroundColor: theme.palette.background.fill, + }, + "& .MuiAutocomplete-noOptions": { + px: theme.spacing(6), + py: theme.spacing(5), + }, + }, + }, + }} + sx={{ + height: 34, + "&.MuiAutocomplete-root .MuiAutocomplete-input": { p: 0 }, + ...sx, + }} + /> + ); +}; + +Search.propTypes = { + id: PropTypes.string, + options: PropTypes.array.isRequired, + filteredBy: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + handleChange: PropTypes.func.isRequired, + sx: PropTypes.object, +}; + +export default Search; diff --git a/Client/src/Components/Search/index.css b/Client/src/Components/Search/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/Client/src/Components/Search/index.jsx b/Client/src/Components/Search/index.jsx deleted file mode 100644 index 391b95c02..000000000 --- a/Client/src/Components/Search/index.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import Autocomplete from '@mui/material/Autocomplete'; -import TextField from '@mui/material/TextField'; -import Chip from '@mui/material/Chip'; -import Box from '@mui/material/Box'; -import { useTheme } from "@mui/material"; - -const teamMembers = [ - { title: 'John Doe'}, - { title: 'Jane Smith'}, - { title: 'Alex Johnson'}, -]; - -/** - * @component - * @returns {JSX.Element} - */ - -export default function Search() - { - const theme = useTheme(); - return ( - {/* Add padding to the container */} - option.title} - filterSelectedOptions - renderInput={(params) => ( - - )} - renderTags={(value, getTagProps) => - value.map((option) => ( - - )) - } - /> - - ); -} diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index 4e464946e..732351be0 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -17,7 +17,7 @@ import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; import { setRowsPerPage } from "../../../../Features/UI/uiSlice"; -import { useState, useEffect } from "react"; +import { useState, useEffect, memo } from "react"; import { useNavigate } from "react-router"; import { logger } from "../../../../Utils/Logger"; import Host from "../host"; @@ -408,4 +408,5 @@ MonitorTable.propTypes = { isAdmin: PropTypes.bool, }; -export default MonitorTable; +const MemoizedMonitorTable = memo(MonitorTable); +export default MemoizedMonitorTable; diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index b66076387..d51c3de2a 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -1,5 +1,5 @@ import "./index.css"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useSelector, useDispatch } from "react-redux"; import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { useNavigate } from "react-router-dom"; @@ -12,12 +12,14 @@ import StatusBox from "./StatusBox"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import Greeting from "../../../Utils/greeting"; import MonitorTable from "./MonitorTable"; +import Search from "../../../Components/Inputs/Search"; const Monitors = ({ isAdmin }) => { const theme = useTheme(); const navigate = useNavigate(); const monitorState = useSelector((state) => state.uptimeMonitors); const authState = useSelector((state) => state.auth); + const [search, setSearch] = useState(""); const dispatch = useDispatch({}); useEffect(() => { @@ -26,6 +28,7 @@ const Monitors = ({ isAdmin }) => { let loading = monitorState?.isLoading && monitorState?.monitorsSummary?.monitors?.length === 0; + return ( {loading ? ( @@ -85,7 +88,8 @@ const Monitors = ({ isAdmin }) => { { > {monitorState?.monitorsSummary?.monitorCounts?.total || 0} - {/* TODO - add search bar */} + + + diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index c80633a20..5d8d76cde 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -151,12 +151,14 @@ const lightTheme = createTheme({ styleOverrides: { root: { marginTop: 4, + padding: 0, border: 1, borderStyle: "solid", borderColor: border.light, borderRadius: 4, boxShadow: shadow, backgroundColor: background.main, + backgroundImage: "none", }, }, }, @@ -183,7 +185,6 @@ const lightTheme = createTheme({ color: text.secondary, fontSize: 13, margin: 2, - marginBottom: 0, minWidth: 100, "&:hover, &.Mui-selected, &.Mui-selected:hover, &.Mui-selected.Mui-focusVisible": {