mirror of
https://github.com/reconurge/flowsint.git
synced 2026-04-30 03:09:05 -05:00
feat: custom node card
This commit is contained in:
@@ -63,7 +63,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className={cn("flex flex-col gap-1 w-full items-start")}>
|
<div className={cn("flex flex-col gap-1 w-full items-start")}>
|
||||||
<Card className="bg-transparent border-0">
|
<Card className="bg-transparent shadow-none border-0">
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<ReactMarkdown>{m.content}</ReactMarkdown>
|
<ReactMarkdown>{m.content}</ReactMarkdown>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -82,7 +82,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex flex-col gap-1 items-start w-full">
|
<div className="flex flex-col gap-1 items-start w-full">
|
||||||
<span className="text-sm font-bold text-red-500">Error</span>
|
<span className="text-sm font-bold text-red-500">Error</span>
|
||||||
<Card className="max-w-[80%]">
|
<Card className="bg-transparent shadow-none border-0">
|
||||||
<CardContent className="p-3 text-red-500">
|
<CardContent className="p-3 text-red-500">
|
||||||
Oops, an error occurred. Make sure you provided a valid Mistral API key.
|
Oops, an error occurred. Make sure you provided a valid Mistral API key.
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
89
src/components/investigations/current-node-card.tsx
Normal file
89
src/components/investigations/current-node-card.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardHeader } from "@/components/ui/card"
|
||||||
|
import { Phone, User2, XIcon } from "lucide-react"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useInvestigationContext } from "../contexts/investigation-provider"
|
||||||
|
|
||||||
|
interface PhoneNumber {
|
||||||
|
id: string
|
||||||
|
country: string | null
|
||||||
|
phone_number: string
|
||||||
|
individual_id: string
|
||||||
|
investigation_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Individual {
|
||||||
|
id: string
|
||||||
|
investigation_id: string
|
||||||
|
full_name: string
|
||||||
|
birth_date: string | null
|
||||||
|
gender: string
|
||||||
|
nationality: string
|
||||||
|
notes: string
|
||||||
|
image_url: string
|
||||||
|
ip_addresses: string[]
|
||||||
|
phone_numbers: PhoneNumber[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CurrentNodeProps {
|
||||||
|
individual: Partial<Individual>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CurrentNode({ individual }: CurrentNodeProps) {
|
||||||
|
const { handleOpenIndividualModal } = useInvestigationContext()
|
||||||
|
const [show, setShow] = useState(true)
|
||||||
|
if (!show) return
|
||||||
|
return (
|
||||||
|
<Card className="w-full max-w-sm overflow-hidden bg-background">
|
||||||
|
<div className="h-24 bg-primary relative">
|
||||||
|
<Button onClick={() => setShow(false)} className="text-white hover:text-white hover:bg-background/5 absolute top-2 right-2" size={"icon"} variant="ghost">
|
||||||
|
<XIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute -top-12 left-1/2 -translate-x-1/2">
|
||||||
|
<Avatar className="h-24 w-24 border-4 border-background">
|
||||||
|
<AvatarImage src={individual.image_url} />
|
||||||
|
<AvatarFallback className="bg-muted">
|
||||||
|
<span className="text-3xl">{individual?.full_name?.[0]}</span>
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CardHeader className="mt-8 text-center">
|
||||||
|
<h2 className="text-2xl font-bold">{individual.full_name}</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">{individual.notes || <span className="italic">No notes.</span>}</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-4">
|
||||||
|
{individual?.phone_numbers && individual?.phone_numbers?.length > 0 && (
|
||||||
|
<div className="flex items-center gap-2 justify-center text-sm">
|
||||||
|
<Phone className="h-4 w-4 text-muted-foreground" />
|
||||||
|
{individual?.phone_numbers?.map((phone) => (
|
||||||
|
<span key={phone.id}>{phone.phone_number}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex gap-2 justify-center">
|
||||||
|
<Button onClick={() => handleOpenIndividualModal(individual.id)}
|
||||||
|
variant="outline">View Details</Button>
|
||||||
|
{/* <Button>Contact</Button> */}
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between gap-8 text-sm text-muted-foreground border-t pt-4">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="font-medium">Nationality</p>
|
||||||
|
<p>{individual.nationality || "N/A"}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="font-medium">Gender</p>
|
||||||
|
<p>{individual.gender || "N/A"}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="font-medium">IPs</p>
|
||||||
|
<p>{individual?.ip_addresses?.length}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import { Card } from '@/components/ui/card';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useInvestigationContext } from '@/components/contexts/investigation-provider';
|
import { useInvestigationContext } from '@/components/contexts/investigation-provider';
|
||||||
import { useFlowStore } from '../contexts/use-flow-store';
|
import { useFlowStore } from '../contexts/use-flow-store';
|
||||||
|
import CurrentNode from './current-node-card';
|
||||||
|
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
"custom": FloatingEdge
|
"custom": FloatingEdge
|
||||||
@@ -139,12 +140,15 @@ const LayoutFlow = ({ theme }: { theme: ColorMode }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<NewActions addNodes={addNodes} />
|
<NewActions addNodes={addNodes} />
|
||||||
</div>
|
</div>
|
||||||
{currentNode && (
|
{currentNode && getNode(currentNode) && (
|
||||||
<Card className='p-3'>
|
getNode(currentNode)?.type === "individual" ?
|
||||||
<>
|
// @ts-ignore
|
||||||
{getNode(currentNode)?.data?.label}
|
<CurrentNode individual={getNode(currentNode).data} /> :
|
||||||
</>
|
<Card className='p-3 bg-background'>
|
||||||
</Card>
|
<>
|
||||||
|
{getNode(currentNode)?.data.label}
|
||||||
|
</>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ function SocialNode({ data }: any) {
|
|||||||
</div>
|
</div>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem onClick={() => handleOpenSearchModal(data.email)}>
|
<ContextMenuItem onClick={() => handleOpenSearchModal(data.label)}>
|
||||||
Launch search
|
Launch search
|
||||||
<Zap className="ml-2 h-4 w-4 text-orange-500" />
|
<Zap className="ml-2 h-4 w-4 text-orange-500" />
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
|||||||
Reference in New Issue
Block a user