Merge pull request #853 from bluewave-labs/feat/search-component
Feat/search component
This commit is contained in:
7 files changed
+157
-171
No files matched your search
@@ -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 options={options} />
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<Autocomplete
|
||||
freeSolo
|
||||
className="auto-complete-field"
|
||||
id={id}
|
||||
value={value}
|
||||
onChange={(event, newValue) => {
|
||||
setValue(newValue);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
onInputChange={(event, newInputValue) => {
|
||||
setInputValue(newInputValue);
|
||||
}}
|
||||
options={options}
|
||||
getOptionLabel={(option) => option.name}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
sx: {
|
||||
width: 360,
|
||||
height: 34,
|
||||
fontSize: 13,
|
||||
p: 0,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
"& input": {
|
||||
p: 0,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
renderOption={(props, option) => {
|
||||
const { key, ...optionProps } = props;
|
||||
return (
|
||||
<li key={option._id} {...optionProps}>
|
||||
<div>{<span>{option.name}</span>}</div>
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
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;
|
||||
@@ -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<Object>} 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 (
|
||||
<Autocomplete
|
||||
id={id}
|
||||
inputValue={value}
|
||||
onInputChange={(event, newValue) => {
|
||||
handleChange(newValue);
|
||||
}}
|
||||
fullWidth
|
||||
freeSolo
|
||||
disableClearable
|
||||
options={options}
|
||||
getOptionLabel={(option) => option[filteredBy]}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder="Type to search"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
startAdornment: (
|
||||
<Box
|
||||
mr={theme.spacing(4)}
|
||||
height={16}
|
||||
sx={{
|
||||
"& svg": {
|
||||
width: 16,
|
||||
height: 16,
|
||||
"& path": {
|
||||
stroke: theme.palette.text.tertiary,
|
||||
strokeWidth: 1.2,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</Box>
|
||||
),
|
||||
}}
|
||||
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 (
|
||||
<ListItem
|
||||
key={key}
|
||||
{...optionProps}
|
||||
sx={
|
||||
option.noOptions
|
||||
? {
|
||||
pointerEvents: "none",
|
||||
backgroundColor: theme.palette.background.main,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{option[filteredBy]}
|
||||
</ListItem>
|
||||
);
|
||||
}}
|
||||
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;
|
||||
Whitespace-only changes.
@@ -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 (
|
||||
<Box padding={theme.spacing(2)}> {/* Add padding to the container */}
|
||||
<Autocomplete
|
||||
multiple
|
||||
id="tags-outlined"
|
||||
options={teamMembers}
|
||||
getOptionLabel={(option) => option.title}
|
||||
filterSelectedOptions
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Team Members"
|
||||
placeholder="Favorites"
|
||||
/>
|
||||
)}
|
||||
renderTags={(value, getTagProps) =>
|
||||
value.map((option) => (
|
||||
<Chip
|
||||
key={option.title}
|
||||
variant="outlined"
|
||||
label={option.title}
|
||||
{...getTagProps({ option })}
|
||||
/>
|
||||
))
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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 (
|
||||
<Stack className="monitors" gap={theme.spacing(8)}>
|
||||
{loading ? (
|
||||
@@ -85,7 +88,8 @@ const Monitors = ({ isAdmin }) => {
|
||||
</Stack>
|
||||
<Box
|
||||
flex={1}
|
||||
p={theme.spacing(10)}
|
||||
px={theme.spacing(10)}
|
||||
py={theme.spacing(8)}
|
||||
border={1}
|
||||
borderColor={theme.palette.border.light}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
@@ -113,7 +117,14 @@ const Monitors = ({ isAdmin }) => {
|
||||
>
|
||||
{monitorState?.monitorsSummary?.monitorCounts?.total || 0}
|
||||
</Box>
|
||||
{/* TODO - add search bar */}
|
||||
<Box width="25%" minWidth={150} ml="auto">
|
||||
<Search
|
||||
options={monitorState?.monitorsSummary.monitors}
|
||||
filteredBy="name"
|
||||
value={search}
|
||||
handleChange={setSearch}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
<MonitorTable isAdmin={isAdmin} />
|
||||
</Box>
|
||||
|
||||
@@ -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":
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user