From 594509f3d9b3659d9a97bcfb0cb963eb1e0b9dc9 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 18 Sep 2024 17:14:03 -0400 Subject: [PATCH 1/6] Initial styling and structure --- .../Components/Inputs/Autocomplete/index.jsx | 116 ----------------- Client/src/Components/Inputs/Search/index.jsx | 121 ++++++++++++++++++ Client/src/Pages/Monitors/Details/styled.jsx | 3 +- Client/src/Pages/Monitors/Home/index.jsx | 10 +- Client/src/Utils/Theme/lightTheme.js | 3 +- 5 files changed, 132 insertions(+), 121 deletions(-) delete mode 100644 Client/src/Components/Inputs/Autocomplete/index.jsx create mode 100644 Client/src/Components/Inputs/Search/index.jsx 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..ab6586119 --- /dev/null +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -0,0 +1,121 @@ +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"; + +const options = [ + "Google", + "Material UI", + "Alias", + "Twitter", + "Discord", + "YouTube", +]; + +const Search = ({ id, sx }) => { + const theme = useTheme(); + + return ( + ( + + + + ), + }} + 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.toLowerCase().includes(inputValue.toLowerCase()) + ); + + if (filtered.length === 0) { + return [{ label: "No monitors found", noOptions: true }]; + } + return filtered; + }} + renderOption={(props, option) => { + const { key, ...optionProps } = props; + return ( + + {option.noOptions ? option.label : option} + + ); + }} + 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, + }} + /> + ); +}; + +export default Search; diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 42cc3560d..1e34fbbc9 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -40,8 +40,7 @@ export const ChartBox = styled(Stack)(({ theme }) => ({ fill: theme.palette.text.tertiary, }, "& path": { - transition: "fill 300ms ease", - transition: "stroke-width 400ms ease", + transition: "fill 300ms ease, stroke-width 400ms ease", }, })); diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index b66076387..a6518f56b 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -12,6 +12,7 @@ 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(); @@ -26,6 +27,8 @@ const Monitors = ({ isAdmin }) => { let loading = monitorState?.isLoading && monitorState?.monitorsSummary?.monitors?.length === 0; + + console.log(monitorState.monitorsSummary.monitors); 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": { From 200c5fa52329f74cd3651b582ccc11141b2c8b96 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 18 Sep 2024 17:22:58 -0400 Subject: [PATCH 2/6] Hooked up search to state --- Client/src/Components/Inputs/Search/index.jsx | 22 ++++++++----------- Client/src/Pages/Monitors/Home/index.jsx | 10 ++++++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index ab6586119..b8eaf89b6 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -3,25 +3,21 @@ import { Box, ListItem, Autocomplete, TextField } from "@mui/material"; import { useTheme } from "@emotion/react"; import SearchIcon from "../../../assets/icons/search.svg?react"; -const options = [ - "Google", - "Material UI", - "Alias", - "Twitter", - "Discord", - "YouTube", -]; - -const Search = ({ id, sx }) => { +const Search = ({ id, options, value, handleChange, sx }) => { const theme = useTheme(); return ( { + handleChange(newValue); + }} fullWidth freeSolo disableClearable options={options} + getOptionLabel={(option) => option.name} renderInput={(params) => ( { )} filterOptions={(options, { inputValue }) => { const filtered = options.filter((option) => - option.toLowerCase().includes(inputValue.toLowerCase()) + option.name.toLowerCase().includes(inputValue.toLowerCase()) ); if (filtered.length === 0) { - return [{ label: "No monitors found", noOptions: true }]; + return [{ name: "No monitors found", noOptions: true }]; } return filtered; }} @@ -84,7 +80,7 @@ const Search = ({ id, sx }) => { : {} } > - {option.noOptions ? option.label : option} + {option.name} ); }} diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index a6518f56b..36921d1f5 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"; @@ -19,6 +19,7 @@ const Monitors = ({ isAdmin }) => { const navigate = useNavigate(); const monitorState = useSelector((state) => state.uptimeMonitors); const authState = useSelector((state) => state.auth); + const [search, setSearch] = useState(""); const dispatch = useDispatch({}); useEffect(() => { @@ -28,7 +29,6 @@ const Monitors = ({ isAdmin }) => { monitorState?.isLoading && monitorState?.monitorsSummary?.monitors?.length === 0; - console.log(monitorState.monitorsSummary.monitors); return ( {loading ? ( @@ -118,7 +118,11 @@ const Monitors = ({ isAdmin }) => { {monitorState?.monitorsSummary?.monitorCounts?.total || 0} - + From db4f509f8798ecab3d4b8c8becf23212f0af6308 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 18 Sep 2024 17:24:36 -0400 Subject: [PATCH 3/6] Added docs --- Client/src/Components/Inputs/Search/index.jsx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index b8eaf89b6..a148c4082 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -3,6 +3,17 @@ 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.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, value, handleChange, sx }) => { const theme = useTheme(); @@ -114,4 +125,16 @@ const Search = ({ id, options, value, handleChange, sx }) => { ); }; +Search.propTypes = { + id: PropTypes.string, + options: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + }) + ).isRequired, + value: PropTypes.string.isRequired, + handleChange: PropTypes.func.isRequired, + sx: PropTypes.object, +}; + export default Search; From 0aebad25120188ebc00cdec1d8e1259fc8393c40 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 18 Sep 2024 20:13:36 -0400 Subject: [PATCH 4/6] Memoized monitor table --- Client/src/Pages/Monitors/Home/MonitorTable/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index 5c43ad880..5b5883440 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -16,7 +16,7 @@ import { import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; 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"; @@ -106,7 +106,7 @@ TablePaginationActions.propTypes = { onPageChange: PropTypes.func.isRequired, }; -const MonitorTable = ({ isAdmin }) => { +const MonitorTable = memo(({ isAdmin }) => { const theme = useTheme(); const navigate = useNavigate(); const dispatch = useDispatch(); @@ -335,7 +335,7 @@ const MonitorTable = ({ isAdmin }) => { ); -}; +}); MonitorTable.propTypes = { isAdmin: PropTypes.bool, From 5b913d9820076cf5f5204d47f0a5eaa393ce29c7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 19 Sep 2024 08:18:16 +0800 Subject: [PATCH 5/6] Name anonymous export --- Client/src/Pages/Monitors/Home/MonitorTable/index.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index 5b5883440..2fbdf3ca6 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -106,7 +106,7 @@ TablePaginationActions.propTypes = { onPageChange: PropTypes.func.isRequired, }; -const MonitorTable = memo(({ isAdmin }) => { +const MonitorTable = ({ isAdmin }) => { const theme = useTheme(); const navigate = useNavigate(); const dispatch = useDispatch(); @@ -335,10 +335,11 @@ const MonitorTable = memo(({ isAdmin }) => { ); -}); +}; MonitorTable.propTypes = { isAdmin: PropTypes.bool, }; -export default MonitorTable; +const MemoizedMonitorTable = memo(MonitorTable); +export default MemoizedMonitorTable; From 732645929b32b311ecd8d92c1e0ef1a0242c0b8a Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 18 Sep 2024 20:27:55 -0400 Subject: [PATCH 6/6] Generalized filter --- Client/src/Components/Inputs/Search/index.jsx | 18 +++---- Client/src/Components/Search/index.css | 0 Client/src/Components/Search/index.jsx | 49 ------------------- Client/src/Pages/Monitors/Home/index.jsx | 1 + 4 files changed, 9 insertions(+), 59 deletions(-) delete mode 100644 Client/src/Components/Search/index.css delete mode 100644 Client/src/Components/Search/index.jsx diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index a148c4082..b69b25731 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -9,12 +9,13 @@ import SearchIcon from "../../../assets/icons/search.svg?react"; * @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, value, handleChange, sx }) => { +const Search = ({ id, options, filteredBy, value, handleChange, sx }) => { const theme = useTheme(); return ( @@ -28,7 +29,7 @@ const Search = ({ id, options, value, handleChange, sx }) => { freeSolo disableClearable options={options} - getOptionLabel={(option) => option.name} + getOptionLabel={(option) => option[filteredBy]} renderInput={(params) => ( { )} filterOptions={(options, { inputValue }) => { const filtered = options.filter((option) => - option.name.toLowerCase().includes(inputValue.toLowerCase()) + option[filteredBy].toLowerCase().includes(inputValue.toLowerCase()) ); if (filtered.length === 0) { - return [{ name: "No monitors found", noOptions: true }]; + return [{ [filteredBy]: "No monitors found", noOptions: true }]; } return filtered; }} @@ -91,7 +92,7 @@ const Search = ({ id, options, value, handleChange, sx }) => { : {} } > - {option.name} + {option[filteredBy]} ); }} @@ -127,11 +128,8 @@ const Search = ({ id, options, value, handleChange, sx }) => { Search.propTypes = { id: PropTypes.string, - options: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired, - }) - ).isRequired, + options: PropTypes.array.isRequired, + filteredBy: PropTypes.string.isRequired, value: PropTypes.string.isRequired, handleChange: PropTypes.func.isRequired, sx: PropTypes.object, 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/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index 36921d1f5..d51c3de2a 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -120,6 +120,7 @@ const Monitors = ({ isAdmin }) => {