From 91af724e19a79626361c613ec1639cbdd4b2cd2b Mon Sep 17 00:00:00 2001 From: dextmorgn Date: Sun, 9 Feb 2025 18:30:24 +0100 Subject: [PATCH] feat: resizable panels --- package.json | 3 + .../[investigation_id]/individuals.tsx | 9 +- .../[investigation_id]/layout.tsx | 2 +- src/components/dashboard/seach-palette.tsx | 128 ++++++++++++++++++ src/components/investigations/graph.tsx | 6 +- src/components/investigations/layout.tsx | 40 ++++-- .../investigations/nodes/individual.tsx | 2 +- src/lib/actions/auth.ts | 1 - .../hooks/individuals/use-emails-breaches.ts | 1 - .../investigation/use-search-results.tsx | 19 +++ yarn.lock | 23 ++++ 11 files changed, 210 insertions(+), 24 deletions(-) create mode 100644 src/components/dashboard/seach-palette.tsx create mode 100644 src/lib/hooks/investigation/use-search-results.tsx diff --git a/package.json b/package.json index 5125bfc..ae8fe91 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,15 @@ "d3-quadtree": "^3.0.1", "framer-motion": "^12.3.1", "intl-messageformat": "^10.5.0", + "lodash.debounce": "^4.0.8", "lucide-react": "^0.474.0", "next": "15.1.6", "next-themes": "^0.4.4", "postcss": "^8.5.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-highlight-words": "^0.21.0", + "react-resizable-panels": "^2.1.7", "swr": "^2.3.0", "tailwindcss": "^4.0.3", "usehooks-ts": "^3.1.1", diff --git a/src/app/investigations/[investigation_id]/individuals.tsx b/src/app/investigations/[investigation_id]/individuals.tsx index f1f299a..add6499 100644 --- a/src/app/investigations/[investigation_id]/individuals.tsx +++ b/src/app/investigations/[investigation_id]/individuals.tsx @@ -5,15 +5,16 @@ import { useIndividuals } from '@/src/lib/hooks/individuals/use-individuals'; import { useInvestigationContext } from '@/src/components/contexts/investigation-provider'; import { cn } from '@/src/lib/utils'; import { Pencil1Icon } from '@radix-ui/react-icons'; +import { RotateCwIcon } from 'lucide-react'; const Filters = ({ investigation_id }: { investigation_id: string }) => { - const { individuals, isLoading } = useIndividuals(investigation_id) + const { individuals, isLoading, refetch } = useIndividuals(investigation_id) const { currentNode, setCurrentNode, handleOpenIndividualModal } = useInvestigationContext() return (
- Profiles - + Profiles refetch()} size={"1"} variant='ghost' color='gray'> + {isLoading && <> @@ -21,7 +22,7 @@ const Filters = ({ investigation_id }: { investigation_id: string }) => { } {individuals?.map((individual: any) => ( - + setCurrentNode(individual.id)}> - }> + }> {children} diff --git a/src/components/dashboard/seach-palette.tsx b/src/components/dashboard/seach-palette.tsx new file mode 100644 index 0000000..70f3c94 --- /dev/null +++ b/src/components/dashboard/seach-palette.tsx @@ -0,0 +1,128 @@ +"use client" +import { useState, useCallback, useEffect, SetStateAction } from 'react' +// @ts-ignore +import debounce from "lodash.debounce"; +import { SearchIcon } from "lucide-react" +// @ts-ignore +import Highlighter from "react-highlight-words"; +import Link from 'next/link'; +import { useSearchResults } from '../../lib/hooks/investigation/use-search-results'; +import { Card, Dialog, IconButton, TextField } from '@radix-ui/themes'; +import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; +import { useInvestigationContext } from '../contexts/investigation-provider'; + +const SearchModal = ({ investigation_id }: { investigation_id: string }) => { + const [search, setSearch] = useState("") + const [open, setOpen] = useState(false) + const { setCurrentNode } = useInvestigationContext() + const { + results, + error, + isLoading, + refetch, + } = useSearchResults(search, investigation_id); + + const changeHandler = (event: { target: { value: SetStateAction; }; }) => { + setSearch(event.target.value); + refetch && refetch(); + }; + const debouncedChangeHandler = useCallback(debounce(changeHandler, 300), []); + + const handleClose = () => { + () => setSearch('') + setOpen(false) + } + + useEffect(() => { + const handleKeyPress = (event: { ctrlKey: any; key: string; }) => { + if (event.ctrlKey && event.key === 'k') { + setOpen(true) + } + }; + window.addEventListener('keydown', handleKeyPress); + return () => { + window.removeEventListener('keydown', handleKeyPress); + }; + }, []); + + const SearchItem = ({ item }: any) => + (
  • setOpen(false)}> + setCurrentNode(item.id)}> + + Individual + + + + + + + + + {/* + + */} + + +
  • + ) + return ( + <> + + + setOpen(true)} color="gray" size="2" variant="soft"> + + + + Search + + Find the profile you're looking for. + + + + + + + +
    + {error && "An error occured."} + {isLoading && } + {results?.length === 0 && `No results found for "${search}".`} +
      {!error && !isLoading && Array.isArray(results) && results?.map((item) => ( + + ))} +
    + {search === '' && ( +
    +
    + )} +
    +
    +
    + + ) +} + +export default SearchModal \ No newline at end of file diff --git a/src/components/investigations/graph.tsx b/src/components/investigations/graph.tsx index 75a9f14..b0ae05e 100644 --- a/src/components/investigations/graph.tsx +++ b/src/components/investigations/graph.tsx @@ -235,8 +235,9 @@ const LayoutFlow = ({ initialNodes, initialEdges, theme }: { initialNodes: any, if (currentNode) { const internalNode = getNode(currentNode) if (!internalNode) return - updateNode(internalNode.id, { ...internalNode, zIndex: 2000, style: { ...internalNode.style, opacity: 1 } }) - setCenter(internalNode?.position.x + 100, internalNode?.position.y, { duration: 1000, zoom: 1.5 }) + updateNode(internalNode.id, { ...internalNode, zIndex: 5000, style: { ...internalNode.style, opacity: 1 } }) + //@ts-ignore + setCenter(internalNode?.position.x + (internalNode?.measured?.width / 2), internalNode?.position.y + (internalNode?.measured?.height / 2) + 20, { duration: 1000, zoom: 1.5 }) highlightPath(internalNode); } }, [currentNode, highlightPath, setCenter, resetNodeStyles]); @@ -265,6 +266,7 @@ const LayoutFlow = ({ initialNodes, initialEdges, theme }: { initialNodes: any, onConnect={onConnect} onNodeClick={onNodeClick} onPaneClick={onPaneClick} + minZoom={0.1} // edgesUpdatable={!isLocked} // edgesFocusable={!isLocked} // nodesDraggable={!isLocked} diff --git a/src/components/investigations/layout.tsx b/src/components/investigations/layout.tsx index a371a9f..d2545cb 100644 --- a/src/components/investigations/layout.tsx +++ b/src/components/investigations/layout.tsx @@ -5,35 +5,47 @@ import { ThemeSwitch } from '../theme-switch'; import MoreMenu from './more-menu'; import CaseSelector from './case-selector'; import NewCase from '../dashboard/new-case'; +import SearchModal from '../dashboard/seach-palette'; +import { ScrollArea } from '@radix-ui/themes'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; const InvestigationLayout = ({ children, - left + left, + investigation_id }: { children: React.ReactNode; left: React.ReactNode; + investigation_id: string }) => { return ( -
    -
    -
    -
    + + +
    +
    +
    -
    +
    {left}
    +
    + +
    + + + +
    +
    + +
    + {children}
    -
    -
    -
    - - -
    - {children} -
    + + + ) } diff --git a/src/components/investigations/nodes/individual.tsx b/src/components/investigations/nodes/individual.tsx index 6ed8121..de18e0e 100644 --- a/src/components/investigations/nodes/individual.tsx +++ b/src/components/investigations/nodes/individual.tsx @@ -38,7 +38,7 @@ function Custom(props: any) { : -