From 0628d5a3712fd75f6b1b4385ac3c3cf8b77fcdb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Tue, 16 Jul 2024 08:52:39 +0100 Subject: [PATCH] feat: Initial search UI (feature flagged) (#3371) --- editor.planx.uk/src/lib/featureFlags.ts | 2 +- .../FlowEditor/components/Sidebar/Search.tsx | 134 ++++++++++++++++++ .../FlowEditor/components/Sidebar/index.tsx | 18 ++- editor.planx.uk/src/ui/shared/Checkbox.tsx | 24 +++- .../src/ui/shared/ChecklistItem.tsx | 9 +- 5 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx diff --git a/editor.planx.uk/src/lib/featureFlags.ts b/editor.planx.uk/src/lib/featureFlags.ts index 31cbfdf23c..90a52dfe3f 100644 --- a/editor.planx.uk/src/lib/featureFlags.ts +++ b/editor.planx.uk/src/lib/featureFlags.ts @@ -1,5 +1,5 @@ // add/edit/remove feature flags in array below -const AVAILABLE_FEATURE_FLAGS = [] as const; +const AVAILABLE_FEATURE_FLAGS = ["SEARCH"] as const; type FeatureFlag = (typeof AVAILABLE_FEATURE_FLAGS)[number]; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx new file mode 100644 index 0000000000..679377c8cb --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx @@ -0,0 +1,134 @@ +import Box from "@mui/material/Box"; +import Container from "@mui/material/Container"; +import Typography from "@mui/material/Typography"; +import { ComponentType } from "@opensystemslab/planx-core/types"; +import { ICONS } from "@planx/components/ui"; +import { debounce } from "lodash"; +import { useStore } from "pages/FlowEditor/lib/store"; +import React, { ChangeEvent, useCallback, useState } from "react"; +import useSWR from "swr"; +import { FONT_WEIGHT_SEMI_BOLD } from "theme"; +import InputLabel from "ui/editor/InputLabel"; +import ChecklistItem from "ui/shared/ChecklistItem"; +import Input from "ui/shared/Input"; + +const mockData: SearchResult[] = [ + { + nodeId: "abc123", + nodeType: ComponentType.Question, + nodeTitle: "Is the property in Lambeth?", + text: "Lambeth example biodiversity text", + path: ["_root", "xyz123", "abc123"], + }, + { + nodeId: "abc456", + nodeType: ComponentType.Notice, + nodeTitle: "It looks like the property is not in Lambeth", + text: "Lambeth example biodiversity text", + path: ["_root", "xyz123", "abc123"], + }, + { + nodeId: "abc789", + nodeType: ComponentType.Question, + nodeTitle: "What are you applying about?", + text: "Lambeth example biodiversity text", + path: ["_root", "xyz123", "abc123"], + }, +]; + +export interface SearchResult { + nodeId: string; + nodeType: ComponentType; + nodeTitle?: string; + text: string; + path: string[]; +} + +const SearchResults: React.FC<{ results: SearchResult[] }> = ({ results }) => { + return ( + + {results.map((result) => ( + + ))} + + ); +}; + +const SearchResultCard: React.FC = ({ + nodeTitle, + text, + nodeType, +}) => { + const Icon = ICONS[nodeType]; + + return ( + ({ + pb: 2, + borderBottom: `1px solid ${theme.palette.border.main}`, + })} + > + + {Icon && } + + Question + {nodeTitle && ` - ${nodeTitle}`} + + + {text} + + ); +}; + +const Search: React.FC = () => { + const [flowId] = useStore((state) => [state.id]); + const [query, setQuery] = useState(""); + const [debouncedQuery, setDebouncedQuery] = useState(query); + + const debounceSearch = useCallback( + debounce((input) => setDebouncedQuery(input), 500), + [], + ); + + const handleChange = (e: ChangeEvent) => { + const input = e.target.value; + setQuery(input); + debounceSearch(input); + }; + + const fetcher = (url: string) => fetch(url).then((r) => r.json()); + const endpoint = `${process.env.REACT_APP_API_URL}/flows/${flowId}/search`; + const { data, error } = useSWR( + debouncedQuery ? `${endpoint}?find=${debouncedQuery}` : null, + fetcher, + ); + + return ( + + + + + {}} + /> + + {error && "Something went wrong"} + {mockData ? : "Loading..."} + + + ); +}; + +export default Search; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/index.tsx index 1cc8df1652..ed5ed9ab49 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/index.tsx @@ -19,6 +19,7 @@ import Tabs from "@mui/material/Tabs"; import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; import { AxiosError } from "axios"; +import { hasFeatureFlag } from "lib/featureFlags"; import { formatLastPublishMessage } from "pages/FlowEditor/utils"; import React, { useState } from "react"; import { useAsync } from "react-use"; @@ -34,8 +35,9 @@ import { ValidationCheck, ValidationChecks, } from "./PublishDialog"; +import Search from "./Search"; -type SidebarTabs = "PreviewBrowser" | "History"; +type SidebarTabs = "PreviewBrowser" | "History" | "Search"; const Console = styled(Box)(() => ({ overflow: "auto", @@ -449,6 +451,15 @@ const Sidebar: React.FC<{ value="History" label="History" /> + {hasFeatureFlag("SEARCH") && ( + + )} {activeTab === "PreviewBrowser" && ( @@ -463,6 +474,11 @@ const Sidebar: React.FC<{ )} + {activeTab === "Search" && ( + + + + )} {showDebugConsole && } ); diff --git a/editor.planx.uk/src/ui/shared/Checkbox.tsx b/editor.planx.uk/src/ui/shared/Checkbox.tsx index db2fe034f2..90e816b41b 100644 --- a/editor.planx.uk/src/ui/shared/Checkbox.tsx +++ b/editor.planx.uk/src/ui/shared/Checkbox.tsx @@ -1,9 +1,11 @@ -import Box from "@mui/material/Box"; +import Box, { BoxProps } from "@mui/material/Box"; import { styled } from "@mui/material/styles"; import React from "react"; import { borderedFocusStyle } from "theme"; -const Root = styled(Box)(({ theme }) => ({ +const Root = styled(Box, { + shouldForwardProp: (prop) => !["disabled"].includes(prop.toString()), +})(({ theme, disabled }) => ({ display: "inline-flex", flexShrink: 0, position: "relative", @@ -13,6 +15,10 @@ const Root = styled(Box)(({ theme }) => ({ border: "2px solid", backgroundColor: theme.palette.common.white, "&:focus-within": borderedFocusStyle, + ...(disabled && { + border: `2px solid ${theme.palette.grey[400]}`, + backgroundColor: theme.palette.grey[400], + }), })); const Input = styled("input")(() => ({ @@ -24,11 +30,13 @@ const Input = styled("input")(() => ({ interface IconProps extends React.HTMLAttributes { checked: boolean; + disabled?: boolean; } const Icon = styled("span", { - shouldForwardProp: (prop) => prop !== "checked", -})(({ theme, checked }) => ({ + shouldForwardProp: (prop) => + !["checked", "disabled"].includes(prop.toString()), +})(({ theme, checked, disabled }) => ({ display: checked ? "block" : "none", content: "''", position: "absolute", @@ -41,6 +49,10 @@ const Icon = styled("span", { top: "42%", transform: "translate(-50%, -50%) rotate(45deg)", cursor: "pointer", + ...(disabled && { + borderBottom: `5px solid white`, + borderRight: `5px solid white`, + }), })); export interface Props { @@ -62,7 +74,7 @@ export default function Checkbox({ }; return ( - + - + ); } diff --git a/editor.planx.uk/src/ui/shared/ChecklistItem.tsx b/editor.planx.uk/src/ui/shared/ChecklistItem.tsx index 85f1c90674..b1cc24e8a3 100644 --- a/editor.planx.uk/src/ui/shared/ChecklistItem.tsx +++ b/editor.planx.uk/src/ui/shared/ChecklistItem.tsx @@ -26,6 +26,7 @@ interface Props { label: string; checked: boolean; onChange: (event?: React.MouseEvent) => void; + inputProps?: React.InputHTMLAttributes; } export default function ChecklistItem({ @@ -33,10 +34,16 @@ export default function ChecklistItem({ onChange, checked, id, + inputProps, }: Props): FCReturn { return ( - +