diff --git a/package.json b/package.json
index a2c09cb..ea0b2f3 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,8 @@
"@tailwindcss/postcss": "^4.0.3",
"@xyflow/react": "^12.4.2",
"clsx": "2.1.1",
+ "d3-force": "^3.0.0",
+ "d3-quadtree": "^3.0.1",
"framer-motion": "11.13.1",
"intl-messageformat": "^10.5.0",
"lucide-react": "^0.474.0",
diff --git a/src/app/investigations/[investigation_id]/page.tsx b/src/app/investigations/[investigation_id]/page.tsx
index 1d9caf4..5629a23 100644
--- a/src/app/investigations/[investigation_id]/page.tsx
+++ b/src/app/investigations/[investigation_id]/page.tsx
@@ -1,5 +1,6 @@
import { getInvestigationData } from '@/src/lib/actions/investigations'
import InvestigationGraph from '@/src/components/investigations/graph'
+import IndividualModal from '@/src/components/investigations/individual-modal'
const DashboardPage = async ({
params,
@@ -8,9 +9,11 @@ const DashboardPage = async ({
}) => {
const { investigation_id } = await (params)
const { nodes, edges } = await getInvestigationData(investigation_id)
+
return (
+
)
}
diff --git a/src/components/dashboard/new-case.tsx b/src/components/dashboard/new-case.tsx
index 68e1375..2c76b57 100644
--- a/src/components/dashboard/new-case.tsx
+++ b/src/components/dashboard/new-case.tsx
@@ -61,7 +61,7 @@ export default function NewCase() {
diff --git a/src/components/investigations/individual-modal.tsx b/src/components/investigations/individual-modal.tsx
new file mode 100644
index 0000000..1c79072
--- /dev/null
+++ b/src/components/investigations/individual-modal.tsx
@@ -0,0 +1,285 @@
+"use client"
+import type React from "react"
+import { useEffect, useState } from "react"
+import {
+ Flex,
+ Dialog,
+ TextField,
+ Button,
+ Box,
+ Skeleton,
+ Card,
+ Inset,
+ Separator,
+ Avatar,
+ Tabs,
+ IconButton,
+} from "@radix-ui/themes"
+import { usePathname, useRouter, useSearchParams } from "next/navigation"
+import { useIndividual } from "@/src/lib/hooks/use-individual"
+import { Pencil1Icon, Cross2Icon, PlusIcon, TrashIcon } from "@radix-ui/react-icons"
+import social from "./nodes/social"
+
+const IndividualModal = () => {
+ const router = useRouter()
+ const pathname = usePathname()
+ const searchParams = useSearchParams()
+ const individual_id = searchParams.get("individual_id")
+ const { individual, isLoading } = useIndividual(individual_id)
+ const [editMode, setEditMode] = useState(false)
+ const [emails, setEmails] = useState([""])
+ const [phones, setPhones] = useState([""])
+ const [accounts, setAccounts] = useState([""])
+ const [ipAddresses, setIpAddresses] = useState([""])
+
+ console.log(individual)
+ useEffect(() => {
+ setEmails(individual?.emails.map(({ email }: any) => email) || [""])
+ setPhones(individual?.phone_numbers.map(({ phone_number }: any) => phone_number) || [""])
+ setIpAddresses(individual?.ip_addresses.map(({ ip_address }: any) => ip_address) || [""])
+ setAccounts(individual?.social_accounts.map(({ username }: any) => username) || [""])
+ }, [individual])
+
+ const handleCloseModal = () => {
+ router.push(pathname)
+ }
+
+ const handleSave = (event: React.FormEvent) => {
+ event.preventDefault()
+ const formData = new FormData(event.currentTarget)
+ const formContent = Object.fromEntries(formData.entries())
+ formContent.emails = emails as any
+ formContent.phones = phones as any
+ formContent.ip_addresses = ipAddresses as any
+ alert(JSON.stringify(formContent, null, 2))
+ setEditMode(false)
+ }
+
+ const handleAddField = (setter: React.Dispatch>) => {
+ setter((prev) => [...prev, ""])
+ }
+
+ const handleRemoveField = (index: number, setter: React.Dispatch>) => {
+ setter((prev) => prev.filter((_, i) => i !== index))
+ }
+
+ const handleFieldChange = (index: number, value: string, setter: React.Dispatch>) => {
+ setter((prev) => prev.map((item, i) => (i === index ? value : item)))
+ }
+
+ if (!isLoading && !individual) {
+ return (
+
+
+ No data
+
+ No data found for this individual.
+
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+export default IndividualModal
+
diff --git a/src/components/investigations/new-actions.tsx b/src/components/investigations/new-actions.tsx
index 2f07462..dd412c2 100644
--- a/src/components/investigations/new-actions.tsx
+++ b/src/components/investigations/new-actions.tsx
@@ -42,6 +42,7 @@ export default function NewActions({ addNodes }: { addNodes: any }) {
/>
{
+ const params = new URLSearchParams(searchParams.toString())
+ params.set(name, value)
+ return params.toString()
+ },
+ [searchParams]
+ )
+ const handleOpenIndividualModal = () => router.push(pathname + '?' + createQueryString('individual_id', data.id))
return (
<>
- {settings.showNodeLabel ?
- setOpen(true)}>
+ {settings.showNodeLabel ?
+
:
- }
- setOpen(true)}>View and edit
+ View and edit
Duplicate
@@ -87,47 +99,6 @@ function Custom({ data }: any) {
-
-
- {data.full_name}
-
- {data.notes}
-
-
-
-
-
-
-
-
-
-
- Cancel
-
-
-
- Save
-
-
-
-
>
);
}
diff --git a/src/components/investigations/nodes/ip_address.tsx b/src/components/investigations/nodes/ip_address.tsx
index a3072f4..b9094fd 100644
--- a/src/components/investigations/nodes/ip_address.tsx
+++ b/src/components/investigations/nodes/ip_address.tsx
@@ -1,33 +1,48 @@
"use client"
import React, { memo } from 'react';
import { Handle, Position } from '@xyflow/react';
-import { Card, Box, Text, ContextMenu, Flex, Inset, Badge } from '@radix-ui/themes';
+import { Card, Box, Text, ContextMenu, Flex, Inset, Badge, Tooltip, Avatar } from '@radix-ui/themes';
import { NodeProvider, useNodeContext } from './node-context';
import { LocateIcon } from 'lucide-react';
import { cn } from '@/utils';
+import { useInvestigationContext } from '../investigation-provider';
function Custom({ data }: any) {
const { handleDeleteNode, loading } = useNodeContext()
+ const { settings } = useInvestigationContext()
return (
<>
-
-
-
-
-
-
-
-
- {data.label}
-
-
-
-
-
+ {settings.showNodeLabel ?
+
+
+
+
+
+
+
+
+ {data.label}
+
+
+
+
+
+ :
+
+
+ }
+ />
+
+ }