Skip to content

Commit

Permalink
feat: add search to Team list (#4174)
Browse files Browse the repository at this point in the history
Co-authored-by: Ian Jones <51156018+ianjon3s@users.noreply.github.com>
  • Loading branch information
RODO94 and ianjon3s committed Jan 30, 2025
1 parent 678d092 commit 291500b
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ import { debounce } from "lodash";
import { useStore } from "pages/FlowEditor/lib/store";
import React, { useEffect, useMemo, useState } from "react";
import { Components, Virtuoso } from "react-virtuoso";
import { SEARCH_DEBOUNCE_MS } from "ui/shared/constants";

import { ExternalPortalList } from "./ExternalPortalList/ExternalPortalList";
import { ALL_FACETS, SearchFacets } from "./facets";
import { SearchHeader } from "./SearchHeader";
import { SearchResultCard } from "./SearchResultCard";

const DEBOUNCE_MS = 500;

interface SearchNodes {
pattern: string;
facets: SearchFacets;
Expand Down Expand Up @@ -87,8 +86,8 @@ const Search: React.FC = () => {
search(pattern);
setLastPattern(pattern);
setIsSearching(false);
}, DEBOUNCE_MS),
[search]
}, SEARCH_DEBOUNCE_MS),
[search],
);

return (
Expand Down
36 changes: 15 additions & 21 deletions editor.planx.uk/src/pages/Team.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import MenuItem from "@mui/material/MenuItem";
import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { hasFeatureFlag } from "lib/featureFlags";
import { isEmpty } from "lodash";
import React, { useCallback, useEffect, useState } from "react";
import { Link, useNavigation } from "react-navi";
import { borderedFocusStyle, FONT_WEIGHT_SEMI_BOLD } from "theme";
import { AddButton } from "ui/editor/AddButton";
import SelectInput from "ui/editor/SelectInput/SelectInput";
import { SortableFields, SortControl } from "ui/editor/SortControl";
import InputLabel from "ui/public/InputLabel";
import { SearchBox } from "ui/shared/SearchBox/SearchBox";
import { slugify } from "utils";

import FlowCard, { Card, CardContent } from "./FlowCard";
Expand All @@ -37,7 +39,7 @@ const DashboardList = styled("ul")(({ theme }) => ({
},
}));

const GetStarted: React.FC<{ flows: FlowSummary[] }> = ({ flows }) => (
const GetStarted: React.FC<{ flows: FlowSummary[] | null }> = ({ flows }) => (
<DashboardList sx={{ paddingTop: 0 }}>
<Card>
<CardContent>
Expand Down Expand Up @@ -153,6 +155,9 @@ const Team: React.FC = () => {
(state) => [state.getTeam(), state.canUserEditTeam, state.getFlows],
);
const [flows, setFlows] = useState<FlowSummary[] | null>(null);
const [filteredFlows, setFilteredFlows] = useState<FlowSummary[] | null>(
null,
);

const sortOptions: SortableFields<FlowSummary>[] = [
{
Expand Down Expand Up @@ -180,14 +185,15 @@ const Team: React.FC = () => {
),
);
setFlows(sortedFlows);
setFilteredFlows(sortedFlows);
});
}, [teamId, setFlows, getFlows]);

useEffect(() => {
fetchFlows();
}, [fetchFlows]);

const teamHasFlows = flows && Boolean(flows.length);
const teamHasFlows = !isEmpty(filteredFlows) && !isEmpty(flows);
const showAddFlowButton = teamHasFlows && canUserEditTeam(slug);

return (
Expand Down Expand Up @@ -216,25 +222,13 @@ const Team: React.FC = () => {
</Typography>
{showAddFlowButton && <AddFlowButton flows={flows} />}
</Box>
<Box maxWidth={360}>
<InputRow>
<InputRowLabel>
<strong>Search</strong>
</InputRowLabel>
<InputRowItem>
<Box sx={{ position: "relative" }}>
<Input
sx={{
borderColor: (theme) => theme.palette.border.input,
pr: 5,
}}
name="search"
id="search"
/>
</Box>
</InputRowItem>
</InputRow>
</Box>
{hasFeatureFlag("SORT_FLOWS") && flows && (
<SearchBox<FlowSummary>
records={flows}
setRecords={setFilteredFlows}
searchKey={["name", "slug"]}
/>
)}
</Box>
<Box
sx={{
Expand Down
1 change: 0 additions & 1 deletion editor.planx.uk/src/ui/editor/SimpleMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import React, { PropsWithChildren, useState } from "react";

interface Props {
className?: string;
children?: React.ReactNode;
items: Array<{
label: string;
disabled?: boolean;
Expand Down
131 changes: 131 additions & 0 deletions editor.planx.uk/src/ui/shared/SearchBox/SearchBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import ClearIcon from "@mui/icons-material/Clear";
import Box from "@mui/material/Box";
import CircularProgress from "@mui/material/CircularProgress";
import IconButton from "@mui/material/IconButton";
import { useFormik } from "formik";
import { FuseOptionKey } from "fuse.js";
import { useSearch } from "hooks/useSearch";
import { debounce } from "lodash";
import React, { useEffect, useMemo, useState } from "react";

import { SEARCH_DEBOUNCE_MS } from "../constants";
import Input from "../Input/Input";
import InputRow from "../InputRow";
import InputRowItem from "../InputRowItem";
import InputRowLabel from "../InputRowLabel";

interface SearchBoxProps<T> {
records: T[] | null;
setRecords: React.Dispatch<React.SetStateAction<T[] | null>>;
searchKey: FuseOptionKey<T>[];
}

export const SearchBox = <T extends object>({
records,
setRecords,
searchKey,
}: SearchBoxProps<T>) => {
const [isSearching, setIsSearching] = useState(false);
const [searchedTerm, setSearchedTerm] = useState<string>();

const searchKeys = useMemo(() => searchKey, []);

const formik = useFormik({
initialValues: { pattern: "" },
onSubmit: ({ pattern }) => {
setIsSearching(true);
debouncedSearch(pattern);
},
});

const { results, search } = useSearch({
list: records || [],
keys: searchKeys,
});

const debouncedSearch = useMemo(
() =>
debounce((pattern: string) => {
search(pattern);
setSearchedTerm(pattern);
setIsSearching(false);
}, SEARCH_DEBOUNCE_MS),
[search],
);

useEffect(() => {
if (results && searchedTerm) {
const mappedResults = results.map((result) => result.item);
setRecords(mappedResults);
}
if (results && !searchedTerm) {
records && setRecords(records);
}
}, [results, setRecords, searchedTerm, records]);

return (
<Box maxWidth={360}>
<InputRow>
<InputRowLabel>
<strong>Search</strong>
</InputRowLabel>
<InputRowItem>
<Box sx={{ position: "relative" }}>
<Input
sx={{
pr: 5,
}}
name="search"
id="search"
value={formik.values.pattern}
onChange={(e) => {
formik.setFieldValue("pattern", e.target.value);
formik.submitForm();
}}
/>
{searchedTerm && !isSearching && (
<IconButton
aria-label="clear search"
onClick={() => {
formik.setFieldValue("pattern", "");
formik.submitForm();
}}
size="small"
sx={{
position: "absolute",
right: (theme) => theme.spacing(1),
top: "50%",
transform: "translateY(-50%)",
padding: 0.5,
zIndex: 1,
}}
>
<ClearIcon fontSize="small" />
</IconButton>
)}
{isSearching && (
<IconButton
aria-label="clear search"
onClick={() => {
formik.setFieldValue("pattern", "");
formik.submitForm();
}}
size="small"
sx={{
position: "absolute",
right: (theme) => theme.spacing(1),
top: "50%",
transform: "translateY(-50%)",
padding: 0.5,
zIndex: 1,
}}
>
<CircularProgress size={"1.5rem"} />
</IconButton>
)}
</Box>
</InputRowItem>
</InputRow>
</Box>
);
};
1 change: 1 addition & 0 deletions editor.planx.uk/src/ui/shared/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SEARCH_DEBOUNCE_MS = 500;

0 comments on commit 291500b

Please sign in to comment.