diff --git a/editor.planx.uk/src/hooks/useSearch.ts b/editor.planx.uk/src/hooks/useSearch.ts index 410473237f..a186fe707f 100644 --- a/editor.planx.uk/src/hooks/useSearch.ts +++ b/editor.planx.uk/src/hooks/useSearch.ts @@ -1,15 +1,16 @@ -import Fuse, { IFuseOptions } from "fuse.js"; +import Fuse, { FuseOptionKey, IFuseOptions } from "fuse.js"; import { useEffect, useMemo, useState } from "react"; interface UseSearchProps { list: T[]; - keys: string[]; + keys: Array>; } export interface SearchResult { item: T; key: string; - matchIndices?: [number, number][]; + matchIndices: [number, number][]; + refIndex: number; } export type SearchResults = SearchResult[]; @@ -39,15 +40,20 @@ export const useSearch = ({ useEffect(() => { const fuseResults = fuse.search(pattern); setResults( - fuseResults.map((result) => ({ - item: result.item, - key: result.matches?.[0].key || "", - // We only display the first match - matchIndices: - (result.matches?.[0].indices as [number, number][]) || undefined, - })), + fuseResults.map((result) => { + // Required type narrowing for FuseResult + if (!result.matches) throw Error("Matches missing from FuseResults"); + + return { + item: result.item, + key: result.matches?.[0].key || "", + // We only display the first match + matchIndices: result.matches[0].indices as [number, number][], + refIndex: result.matches[0]?.refIndex || 0, + }; + }), ); - }, [pattern]); + }, [pattern, fuse]); return { results, search: setPattern }; }; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx index 7b25315d9a..3df77adb02 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx @@ -3,7 +3,7 @@ import Container from "@mui/material/Container"; import List from "@mui/material/List"; import ListItem from "@mui/material/ListItem"; import ListItemButton from "@mui/material/ListItemButton"; -import { styled, Theme } from "@mui/material/styles"; +import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; import { ComponentType, IndexedNode } from "@opensystemslab/planx-core/types"; import { ICONS } from "@planx/components/ui"; @@ -14,7 +14,6 @@ import { capitalize, get } from "lodash"; import { SLUGS } from "pages/FlowEditor/data/types"; import { useStore } from "pages/FlowEditor/lib/store"; import React, { useEffect } from "react"; -import { useNavigation } from "react-navi"; import { FONT_WEIGHT_BOLD, FONT_WEIGHT_SEMI_BOLD } from "theme"; import ChecklistItem from "ui/shared/ChecklistItem"; import Input from "ui/shared/Input"; @@ -76,7 +75,7 @@ const Headline: React.FC = ({ text, matchIndices, variant }) => { {text.split("").map((char, index) => ( ({ fontWeight: isHighlighted(index) ? FONT_WEIGHT_BOLD : "regular", @@ -104,7 +103,7 @@ const SearchResultCard: React.FC<{ result: SearchResult }> = ({ let Icon = ICONS[item.type]; // TODO: Generate display key from key let displayKey = "Data"; - const headline = get(item, key).toString() || ""; + let headline = get(item, key)?.toString() || ""; // For Answer nodes, update display values to match the parent question if (item.type === ComponentType.Answer) { @@ -114,6 +113,40 @@ const SearchResultCard: React.FC<{ result: SearchResult }> = ({ displayKey = "Option (data)"; } + if (item.type === ComponentType.FileUploadAndLabel) { + headline = + (item["data"]?.["fileTypes"] as [])[result.refIndex]["fn"] || ""; + displayKey = "File type (data)"; + } + + if (item.type === ComponentType.Calculate) { + if (result.key === "formula") { + headline = item.data!.formula; + displayKey = "Formula"; + } + if (result.key === "data.output") { + headline = item.data!.output; + displayKey = "Output (data)"; + } + // never? + } + + if (item.type === ComponentType.List) { + if (result.key === "data.schema.fields.data.fn") { + headline = (item.data as any).schema.fields[result.refIndex].data.fn; + displayKey = "Data"; + } + if (result.key === "data.schema.fields.data.options.data.val") { + // Fuse.js flattens deeply nested arrays when using refIndex + const options = (item.data as any).schema.fields.flatMap((field: any) => field.data.options) + console.log({options}) + headline = options[result.refIndex].data.val; + displayKey = "Option (data)"; + } + // never? + + } + return { Icon, componentType, @@ -175,7 +208,6 @@ const SearchResultCard: React.FC<{ result: SearchResult }> = ({ const ExternalPortalList: React.FC = () => { const externalPortals = useStore((state) => state.externalPortals); const hasExternalPortals = Object.keys(externalPortals).length; - const { navigate } = useNavigation(); if (!hasExternalPortals) return null; @@ -202,7 +234,21 @@ const ExternalPortalList: React.FC = () => { interface SearchNodes { input: string; - facets: ["data.fn", "data.val"]; + facets: [ + "data.fn", + "data.val", + // FUAL + "data.fileTypes.fn", + // Calculate + "data.output", + { + name: "formula", + getFn: (node: IndexedNode) => string[], + }, + // List + "data.schema.fields.data.fn", + "data.schema.fields.data.options.data.val", + ]; } const Search: React.FC = () => { @@ -213,10 +259,21 @@ const Search: React.FC = () => { useEffect(() => { if (!orderedFlow) setOrderedFlow(); - }, [setOrderedFlow]); + }, [setOrderedFlow, orderedFlow]); const formik = useFormik({ - initialValues: { input: "", facets: ["data.fn", "data.val"] }, + initialValues: { + input: "", + facets: [ + "data.fn", + "data.val", + "data.fileTypes.fn", + "data.output", + { name: "formula", getFn: (node: IndexedNode) => Object.keys(node.data?.defaults || {}) }, + "data.schema.fields.data.fn", + "data.schema.fields.data.options.data.val", + ] + }, onSubmit: ({ input }) => { search(input); },