diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx index e778a820bd1cfbd6fd04add06c431f8906553681..4afff02392582cd12b6f6741398ebc245cb2a43d 100644 --- a/apps/web/src/components/navbar/navbar.tsx +++ b/apps/web/src/components/navbar/navbar.tsx @@ -25,7 +25,7 @@ import { import { DatabaseMenu } from './databasemenu'; import { NewDatabaseForm } from './AddDatabaseForm/newdatabaseform'; import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice'; -import SearchBar from './search/searchBar'; +import { SearchBar } from './search/SearchBar'; /** NavbarComponentProps is an interface containing the NavbarViewModel. */ export interface NavbarComponentProps { diff --git a/apps/web/src/components/navbar/search/SearchBar.tsx b/apps/web/src/components/navbar/search/SearchBar.tsx index cf835d926ed7a2b3bec997beb2fc08dc6e374860..a92202f379c490459446cfc8bde610c0d43ae3f2 100644 --- a/apps/web/src/components/navbar/search/SearchBar.tsx +++ b/apps/web/src/components/navbar/search/SearchBar.tsx @@ -5,6 +5,7 @@ import { useSchemaGraph, useQuerybuilderGraph, useSearchResult, + AppDispatch, } from '@graphpolaris/shared/lib/data-access'; import { filterData } from './similarity'; import { @@ -12,26 +13,29 @@ import { addSearchResultSchema, addSearchResultQueryBuilder, resetSearchResults, + CATEGORY_KEYS, } from '@graphpolaris/shared/lib/data-access/store/searchResultSlice'; import { Close } from '@mui/icons-material'; const SIMILARITY_THRESHOLD = 0.7; -const CATEGORY_ACTIONS = { - data: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch) => { +const CATEGORY_ACTIONS: { + [key in CATEGORY_KEYS]: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => void; +} = { + data: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => { dispatch(addSearchResultData(payload)); }, - schema: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch) => { + schema: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => { dispatch(addSearchResultSchema(payload)); }, - querybuilder: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch) => { + querybuilder: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => { dispatch(addSearchResultQueryBuilder(payload)); }, }; -const SEARCH_CATEGORIES: string[] = Object.keys(CATEGORY_ACTIONS); +const SEARCH_CATEGORIES: CATEGORY_KEYS[] = Object.keys(CATEGORY_ACTIONS) as CATEGORY_KEYS[]; -export default function SearchBar({}) { +export function SearchBar({}) { const inputRef = React.useRef<HTMLInputElement>(null); const dispatch = useAppDispatch(); const results = useSearchResult(); @@ -41,7 +45,9 @@ export default function SearchBar({}) { const [search, setSearch] = React.useState<string>(''); const [inputActive, setInputActive] = React.useState<boolean>(false); - const dataSources = { + const dataSources: { + [key: string]: { nodes: Record<string, any>[]; edges: Record<string, any>[] }; + } = { data: graphData, schema: schema, querybuilder: querybuilderData, @@ -61,7 +67,7 @@ export default function SearchBar({}) { const cat = category.substring(1); if (cat in CATEGORY_ACTIONS) { - const categoryAction = CATEGORY_ACTIONS[cat]; + const categoryAction = CATEGORY_ACTIONS[cat as CATEGORY_KEYS]; const data = dataSources[cat]; const payload = { @@ -88,8 +94,8 @@ export default function SearchBar({}) { }; React.useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (inputRef.current && !inputRef.current.contains(event.target)) { + const handleClickOutside = ({ target }: MouseEvent) => { + if (inputRef.current && target && !inputRef.current.contains(target as Node)) { setSearch(''); // dispatch(resetSearchResults()); inputRef.current.blur(); @@ -131,42 +137,42 @@ export default function SearchBar({}) { )} </div> {search !== '' && ( - <div className="absolute top-[calc(100%+0.5rem)] z-[1] bg-white rounded-none card-bordered w-full"> + <div className="absolute top-[calc(100%+0.5rem)] z-10 bg-white rounded-none card-bordered w-full overflow-auto max-h-[60vh]"> {SEARCH_CATEGORIES.map((category, index) => { if (results[category].nodes.length > 0 || results[category].edges.length > 0) { return ( <div key={index}> - <div className="flex justify-between p-2"> - <p className="text-entity-500">{category.charAt(0).toUpperCase() + category.slice(1)}</p> - <p className="font-thin text-slate-400">{results[category].nodes.length + results[category].edges.length} results</p> + <div className="flex justify-between p-2 text-lg"> + <p className="text-entity-500 font-bold">{category.charAt(0).toUpperCase() + category.slice(1)}</p> + <p className="font-light text-slate-800">{results[category].nodes.length + results[category].edges.length} results</p> </div> <div className="h-[1px] w-full bg-offwhite-300"></div> - {[].concat(...Object.values(results[category])).map((item, index) => ( - <div - key={index} - className="flex flex-col hover:bg-slate-300 p-2 cursor-pointer" - title={JSON.stringify(item)} - onClick={() => { - CATEGORY_ACTIONS[category]( - { - nodes: results[category].nodes.includes(item) ? [item] : [], - edges: results[category].edges.includes(item) ? [item] : [], - }, - dispatch, - ); - }} - > - <p style={{ fontSize: '14px', fontWeight: '600' }}> - {item.key.slice(0, 18) || item.id.slice(0, 18) || Object.values(item)[0].slice(0, 18)} - </p> - <p className="font-thin text-slate-400" style={{ fontSize: '12px' }}> - {JSON.stringify(item).substring(0, 40)}... - </p> - </div> - ))} + {Object.values(Object.values(results[category])) + .flat() + .map((item, index) => ( + <div + key={index} + className="flex flex-col hover:bg-slate-300 p-2 cursor-pointer" + title={JSON.stringify(item)} + onClick={() => { + CATEGORY_ACTIONS[category]( + { + nodes: results[category].nodes.includes(item) ? [item] : [], + edges: results[category].edges.includes(item) ? [item] : [], + }, + dispatch, + ); + }} + > + <div className="font-semibold text-md"> + {item?.key?.slice(0, 18) || item?.id?.slice(0, 18) || Object.values(item)?.[0]?.slice(0, 18)} + </div> + <div className="font-light text-slate-800 text-xs">{JSON.stringify(item).substring(0, 40)}...</div> + </div> + ))} </div> ); - } + } else return <></>; })} </div> )} diff --git a/libs/shared/lib/data-access/store/searchResultSlice.ts b/libs/shared/lib/data-access/store/searchResultSlice.ts index f15d1aecdf33b464278e969a9ffe1d75c5d51f08..32c58a255e69ca30510f5d974bb2d57b734dff75 100644 --- a/libs/shared/lib/data-access/store/searchResultSlice.ts +++ b/libs/shared/lib/data-access/store/searchResultSlice.ts @@ -1,20 +1,11 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { RootState } from './store'; +export type CATEGORY_KEYS = 'data' | 'schema' | 'querybuilder'; + // Define the initial state using that type export const initialState: { - data: { - nodes: Record<string, any>[]; - edges: Record<string, any>[]; - }; - schema: { - nodes: Record<string, any>[]; - edges: Record<string, any>[]; - }; - querybuilder: { - nodes: Record<string, any>[]; - edges: Record<string, any>[]; - }; + [key in CATEGORY_KEYS]: { nodes: Record<string, any>[]; edges: Record<string, any>[] }; } = { data: { nodes: [], @@ -43,7 +34,13 @@ export const searchResultSlice = createSlice({ addSearchResultQueryBuilder: (state, action: PayloadAction<{ nodes: Record<string, any>[]; edges: Record<string, any>[] }>) => { state.querybuilder = action.payload; }, - addSearchResultSelected: (state, action: PayloadAction<{ category: string; value: Record<string, any> }>) => { + addSearchResultSelected: ( + state, + action: PayloadAction<{ + category: CATEGORY_KEYS; + value: { nodes: Record<string, any>[]; edges: Record<string, any>[] }; + }> + ) => { state.data = { nodes: [], edges: [] }; state.schema = { nodes: [], edges: [] }; state.querybuilder = { nodes: [], edges: [] }; diff --git a/libs/shared/lib/querybuilder/model/graphology/model.ts b/libs/shared/lib/querybuilder/model/graphology/model.ts index 0125076ceb9b8d4af8f7f9d6b5cc91d2c6f7b652..3021f94b93f1976c2d932fd38c6b9a752f3dcc71 100644 --- a/libs/shared/lib/querybuilder/model/graphology/model.ts +++ b/libs/shared/lib/querybuilder/model/graphology/model.ts @@ -23,6 +23,7 @@ export type NodeDefaults = { width?: number; height?: number; attributes?: NodeAttribute[]; + selected?: boolean; }; /** Interface for the data in an entity node. */