feat: default edges

This commit is contained in:
dextmorgn
2025-03-14 15:00:26 +01:00
parent 7af35d1778
commit d322f4773c
23 changed files with 521 additions and 428 deletions

View File

@@ -1,9 +1,150 @@
import React from 'react'
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Trash2 } from "lucide-react"
export default function TokensPage() {
const [tokens, setTokens] = useState([
{
id: "1",
name: "My key",
key: "****************************CmDi",
expiration: "Jamais",
},
])
const page = () => {
return (
<div>keys</div>
<>
<div>
<div className="flex justify-between items-start mb-4">
<div>
<h1 className="text-3xl font-bold">Vos clés API</h1>
<p className="text-muted-foreground mt-1">
Gérez vos clés API personnelles pour votre espace de travail actuel
</p>
</div>
<Button className="bg-primary text-white">Créer une nouvelle clé</Button>
</div>
<div className="border-t my-8"></div>
<p className="text-muted-foreground mb-8">
Vous pouvez créer jusqu&apos;à 10 clés API et les supprimer lorsque vous n&apos;en avez plus besoin.
</p>
<Tabs defaultValue="active">
<TabsList className="bg-transparent border-b w-full justify-start rounded-none p-0 h-auto">
<TabsTrigger
value="active"
className="rounded-none border-b-2 border-transparent data-[state=active]:shadow-none data-[state=active]:border-primary data-[state=active]:text-primary px-4 py-2 font-medium"
>
Actives
</TabsTrigger>
<TabsTrigger
value="expired"
className="rounded-none border-b-2 border-transparent data-[state=active]:shadow-none data-[state=active]:border-primary data-[state=active]:text-primary px-4 py-2 font-medium"
>
Expirées
</TabsTrigger>
</TabsList>
<TabsContent value="active" className="mt-6">
<div className="border rounded-md">
<table className="w-full">
<thead>
<tr className="border-b">
<th className="text-left py-3 px-4 font-medium opacity-60">Nom</th>
<th className="text-left py-3 px-4 font-medium opacity-60">Clé</th>
<th className="text-left py-3 px-4 font-medium opacity-60">Expiration</th>
<th className="py-3 px-4"></th>
</tr>
</thead>
<tbody>
{tokens.map((token) => (
<tr key={token.id} className="border-b last:border-0">
<td className="py-4 px-4">{token.name}</td>
<td className="py-4 px-4 font-mono">{token.key}</td>
<td className="py-4 px-4">{token.expiration}</td>
<td className="py-4 px-4 text-right">
<Button variant="ghost" size="icon">
<Trash2 className="h-5 w-5 text-gray-500" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</TabsContent>
<TabsContent value="expired" className="mt-6">
<div className="border rounded-md p-8 text-center text-muted-foreground">
Aucune clé API expirée
</div>
</TabsContent>
</Tabs>
</div>
<div>
<div className="flex justify-between items-start mb-4">
<div>
<h1 className="text-3xl font-bold">Vos clés de services tiers</h1>
<p className="text-muted-foreground mt-1">
Gérez vos clés de services tiers pour votre espace de travail actuel
</p>
</div>
{/* <Button className="bg-primary text-white">Créer une nouvelle clé</Button> */}
</div>
<Tabs defaultValue="active">
<TabsList className="bg-transparent border-b w-full justify-start rounded-none p-0 h-auto">
<TabsTrigger
value="active"
className="rounded-none border-b-2 border-transparent data-[state=active]:shadow-none data-[state=active]:border-primary data-[state=active]:text-primary px-4 py-2 font-medium"
>
Actives
</TabsTrigger>
<TabsTrigger
value="expired"
className="rounded-none border-b-2 border-transparent data-[state=active]:shadow-none data-[state=active]:border-primary data-[state=active]:text-primary px-4 py-2 font-medium"
>
Expirées
</TabsTrigger>
</TabsList>
<TabsContent value="active" className="mt-6">
<div className="border rounded-md">
<table className="w-full">
<thead>
<tr className="border-b">
<th className="text-left py-3 px-4 font-medium opacity-60">Nom</th>
<th className="text-left py-3 px-4 font-medium opacity-60">Clé</th>
<th className="text-left py-3 px-4 font-medium opacity-60">Expiration</th>
<th className="py-3 px-4"></th>
</tr>
</thead>
<tbody>
{tokens.map((token) => (
<tr key={token.id} className="border-b last:border-0">
<td className="py-4 px-4">{token.name}</td>
<td className="py-4 px-4 font-mono">{token.key}</td>
<td className="py-4 px-4">{token.expiration}</td>
<td className="py-4 px-4 text-right">
<Button variant="ghost" size="icon">
<Trash2 className="h-5 w-5 text-gray-500" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</TabsContent>
<TabsContent value="expired" className="mt-6">
<div className="border rounded-md p-8 text-center text-muted-foreground">
Aucune clé API expirée
</div>
</TabsContent>
</Tabs>
</div>
</>
)
}
export default page

View File

@@ -1,14 +1,5 @@
import { AppSidebar } from "@/components/app-sidebar"
import { createClient } from "@/lib/supabase/server";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import { Separator } from "@/components/ui/separator"
import {
SidebarInset,
SidebarProvider,
@@ -33,26 +24,9 @@ const DashboardLayout = async ({
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
{/* <Separator
orientation="vertical"
className="mr-2 data-[orientation=vertical]:h-4"
/>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb> */}
</div>
</header>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 p-8">
<div className="flex flex-1 flex-col gap-4 container mx-auto py-12 px-8">
{children}
</div>
</SidebarInset>

View File

@@ -1,32 +1,59 @@
"use client"
import { useState, useEffect } from "react"
import Investigation from "@/components/dashboard/investigation"
import InvestigationList from "@/components/dashboard/investigation-list"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { AlertCircle, ChevronDown, Clock, Filter, Grid3X3, List, Lock, Plus, Search, Target } from "lucide-react"
import { AlertCircle, ChevronDown, Clock, Filter, Lock, Plus, Search, Target } from "lucide-react"
import NewCase from "@/components/dashboard/new-case"
import { createClient } from "@/lib/supabase/server"
import { supabase } from "@/lib/supabase/client"
import { Input } from "@/components/ui/input"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { unauthorized } from "next/navigation"
import ViewToggle from "@/components/dashboard/view-toggle"
import { useQueryState } from "nuqs"
import Loader from "@/components/loader"
const DashboardPage = async () => {
const supabase = await createClient()
const { data: investigations, error } = await supabase.from("investigations").select("*, relations_count:relationships!inner(count), individuals_counts:individuals!inner(count)")
if (error) return unauthorized()
type ViewMode = "grid" | "list"
// Compter les investigations par statut
const DashboardPage = () => {
const [viewMode, setViewMode] = useQueryState<ViewMode>("view", {
defaultValue: "grid",
parse: (value) => (value === "grid" || value === "list" ? value : "grid")
})
const [investigations, setInvestigations] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const activeCount = investigations.filter((inv) => inv.status === "active").length || 0
const pendingCount = investigations.filter((inv) => inv.status === "pending").length || 0
const archivedCount = investigations.filter((inv) => inv.status === "archived").length || 0
const totalCount = investigations.length
useEffect(() => {
const fetchInvestigations = async () => {
setLoading(true)
const { data, error } = await supabase
.from("investigations")
.select("*, relations_count:relationships!inner(count), individuals_counts:individuals!inner(count)")
if (error) {
console.error("Erreur lors de la récupération des investigations:", error)
} else {
setInvestigations(data || [])
}
setLoading(false)
}
fetchInvestigations()
}, [])
const getGridClasses = () => {
return viewMode === "grid" ? "grid gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3" : "grid gap-2 grid-cols-1"
}
return (
<div className="space-y-6 w-full">
<div className="flex flex-col gap-4 md:gap-8">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight">Investigations</h1>
<h1 className="text-3xl font-bold tracking-tight">Investigations</h1>
<p className="text-muted-foreground">Gérez et suivez vos investigations OSINT en cours.</p>
</div>
<div className="flex items-center gap-2">
@@ -38,7 +65,6 @@ const DashboardPage = async () => {
</NewCase>
</div>
</div>
<div className="flex flex-col md:flex-row gap-4 md:items-center">
<div className="relative flex-1 md:max-w-sm">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
@@ -48,7 +74,6 @@ const DashboardPage = async () => {
className="w-full rounded-lg bg-background pl-8 md:max-w-sm"
/>
</div>
<div className="flex items-center gap-2">
<Popover>
<PopoverTrigger asChild>
@@ -83,88 +108,101 @@ const DashboardPage = async () => {
</Command>
</PopoverContent>
</Popover>
<div className="flex items-center gap-1 md:gap-2">
<Button variant="default" size="icon" className="h-8 w-8">
<Grid3X3 className="h-4 w-4" />
<span className="sr-only">Grid view</span>
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8">
<List className="h-4 w-4" />
<span className="sr-only">List view</span>
</Button>
</div>
<Button variant="outline" size="sm" className="h-8 gap-1">
<span>Trier par</span>
<ChevronDown className="h-3.5 w-3.5" />
</Button>
<ViewToggle currentView={viewMode} onViewChange={setViewMode} />
</div>
</div>
<Tabs defaultValue="active" className="w-full">
<TabsList>
<TabsTrigger value="active" className="gap-1.5">
<AlertCircle className="h-4 w-4" />
<span>Actives</span>
<Badge className="ml-1" variant={"outline"}>{activeCount}</Badge>
<Badge className="ml-1" variant={"outline"}>
{activeCount}
</Badge>
</TabsTrigger>
<TabsTrigger value="pending" className="gap-1.5">
<Clock className="h-4 w-4" />
<span>En cours</span>
<Badge className="ml-1" variant={"outline"}>{pendingCount}</Badge>
<Badge className="ml-1" variant={"outline"}>
{pendingCount}
</Badge>
</TabsTrigger>
<TabsTrigger value="archived" className="gap-1.5">
<Lock className="h-4 w-4" />
<span>Archivées</span>
<Badge className="ml-1" variant={"outline"}>{archivedCount}</Badge>
<Badge className="ml-1" variant={"outline"}>
{archivedCount}
</Badge>
</TabsTrigger>
<TabsTrigger value="all" className="gap-1.5">
<Target className="h-4 w-4" />
<span>Toutes</span>
<Badge className="ml-1" variant={"outline"}>{totalCount}</Badge>
<Badge className="ml-1" variant={"outline"}>
{totalCount}
</Badge>
</TabsTrigger>
</TabsList>
<TabsContent value="all" className="mt-6">
<div className="grid gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
{investigations.map((investigation) => (
<Investigation key={investigation.id} investigation={investigation} />
))}
{loading ? (
<div className="mt-6 flex justify-center">
<p><Loader /> Chargement des investigations...</p>
</div>
</TabsContent>
<TabsContent value="active" className="mt-6">
<div className="grid gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
{investigations
.filter((inv) => inv.status === "active")
.map((investigation) => (
<Investigation key={investigation.id} investigation={investigation} />
))}
</div>
</TabsContent>
<TabsContent value="pending" className="mt-6">
<div className="grid gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
{investigations
.filter((inv) => inv.status === "pending")
.map((investigation) => (
<Investigation key={investigation.id} investigation={investigation} />
))}
</div>
</TabsContent>
<TabsContent value="archived" className="mt-6">
<div className="grid gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
{investigations
.filter((inv) => inv.status === "archived")
.map((investigation) => (
<Investigation key={investigation.id} investigation={investigation} />
))}
</div>
</TabsContent>
) : (
<>
<TabsContent value="all" className="mt-6">
<div className={getGridClasses()}>
{investigations.map((investigation) =>
viewMode === "grid" ? (
<Investigation key={investigation.id} investigation={investigation} />
) : (
<InvestigationList key={investigation.id} investigation={investigation} />
),
)}
</div>
</TabsContent>
<TabsContent value="active" className="mt-6">
<div className={getGridClasses()}>
{investigations
.filter((inv) => inv.status === "active")
.map((investigation) =>
viewMode === "grid" ? (
<Investigation key={investigation.id} investigation={investigation} />
) : (
<InvestigationList key={investigation.id} investigation={investigation} />
),
)}
</div>
</TabsContent>
<TabsContent value="pending" className="mt-6">
<div className={getGridClasses()}>
{investigations
.filter((inv) => inv.status === "pending")
.map((investigation) =>
viewMode === "grid" ? (
<Investigation key={investigation.id} investigation={investigation} />
) : (
<InvestigationList key={investigation.id} investigation={investigation} />
),
)}
</div>
</TabsContent>
<TabsContent value="archived" className="mt-6">
<div className={getGridClasses()}>
{investigations
.filter((inv) => inv.status === "archived")
.map((investigation) =>
viewMode === "grid" ? (
<Investigation key={investigation.id} investigation={investigation} />
) : (
<InvestigationList key={investigation.id} investigation={investigation} />
),
)}
</div>
</TabsContent>
</>
)}
</Tabs>
</div>
</div >
</div>
)
}

View File

@@ -1,31 +1,11 @@
"use client"
import * as React from "react"
import {
AudioWaveform,
BookOpen,
Bot,
Command,
FolderSearch,
Frame,
GalleryVerticalEnd,
Globe,
KeyIcon,
Map,
MapPin,
Network,
PieChart,
Settings2,
SettingsIcon,
SquareTerminal,
UserIcon,
Users,
} from "lucide-react"
import type * as React from "react"
import { usePathname } from "next/navigation"
import Link from "next/link"
import { FolderSearch, Globe, KeyIcon, MapPin, Network, SettingsIcon, UserIcon, Users } from "lucide-react"
import { NavMain } from "@/components/nav-main"
import { NavProjects } from "@/components/nav-projects"
import { NavUser } from "@/components/nav-user"
import { TeamSwitcher } from "@/components/team-switcher"
import {
Sidebar,
SidebarContent,
@@ -39,224 +19,138 @@ import {
SidebarRail,
SidebarSeparator,
} from "@/components/ui/sidebar"
// This is sample data.
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
teams: [
{
name: "Acme Inc",
logo: GalleryVerticalEnd,
plan: "Enterprise",
},
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup",
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free",
},
],
navMain: [
{
title: "Playground",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
],
},
{
title: "Models",
url: "#",
icon: Bot,
items: [
{
title: "Genesis",
url: "#",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
url: "#",
},
],
},
{
title: "Documentation",
url: "#",
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
],
projects: [
{
name: "Investigations",
url: "/dashboard",
icon: Frame,
},
],
import { TeamSwitcher } from "./team-switcher"
// Define navigation item type
interface NavItem {
title: string
href: string
icon: React.ElementType
}
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
user: any;
user: any
}
export function AppSidebar({ user, ...props }: AppSidebarProps) {
const pathname = usePathname()
// Main navigation items
const mainNavItems: NavItem[] = [
{
title: "Investigations",
href: "/dashboard",
icon: FolderSearch,
},
{
title: "Networks",
href: "/networks",
icon: Network,
},
{
title: "Entities",
href: "/entities",
icon: Users,
},
{
title: "OSINT sources",
href: "/sources",
icon: Globe,
},
{
title: "Map",
href: "/map",
icon: MapPin,
},
]
// Team navigation items
const teamNavItems: NavItem[] = [
{
title: "My account",
href: "/profile",
icon: UserIcon,
},
{
title: "Team members",
href: "/team",
icon: Users,
},
]
// Preferences navigation items
const preferencesNavItems: NavItem[] = [
{
title: "Settings",
href: "/dashboard/settings",
icon: SettingsIcon,
},
{
title: "API keys",
href: "/dashboard/keys",
icon: KeyIcon,
},
]
// Function to check if a link is active
const isActive = (href: string) => {
// Exact match for dashboard
if (href === "/dashboard" && pathname === "/dashboard") {
return true
}
// For other routes, check if pathname starts with href (for nested routes)
return href !== "/dashboard" && pathname.startsWith(href)
}
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<TeamSwitcher teams={data.teams} />
<TeamSwitcher />
</SidebarHeader>
<SidebarContent className="p-0 overflow-hidden">
<SidebarGroup className="group-data-[collapsible=icon]:p-auto">
<SidebarGroupLabel>NAVIGATION</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild isActive>
<a href="/dashboard">
<FolderSearch className="h-4 w-4" />
<span>Investigations</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/networks">
<Network className="h-4 w-4" />
<span>Networks</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/entities">
<Users className="h-4 w-4" />
<span>Entities</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/sources">
<Globe className="h-4 w-4" />
<span>OSINT sources</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/map">
<MapPin className="h-4 w-4" />
<span>Map</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
{mainNavItems.map((item) => (
<SidebarMenuItem key={item.href}>
<SidebarMenuButton asChild isActive={isActive(item.href)}>
<Link href={item.href}>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
<SidebarSeparator />
<SidebarGroup className="group-data-[collapsible=icon]:p-auto">
<SidebarGroupLabel>TEAMS</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/profile">
<UserIcon className="h-4 w-4" />
<span>My account</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/team">
<Users className="h-4 w-4" />
<span>Team members</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
{teamNavItems.map((item) => (
<SidebarMenuItem key={item.href}>
<SidebarMenuButton asChild isActive={isActive(item.href)}>
<Link href={item.href}>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
<SidebarGroup className="group-data-[collapsible=icon]:p-auto">
<SidebarGroupLabel>Preferences</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/dashboard/settings">
<SettingsIcon className="h-4 w-4" />
<span>Settings</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/dashboard/keys">
<KeyIcon className="h-4 w-4" />
<span>API keys</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
{preferencesNavItems.map((item) => (
<SidebarMenuItem key={item.href}>
<SidebarMenuButton asChild isActive={isActive(item.href)}>
<Link href={item.href}>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
@@ -264,6 +158,6 @@ export function AppSidebar({ user, ...props }: AppSidebarProps) {
<NavUser user={user} />
</SidebarFooter>
<SidebarRail />
</Sidebar >
</Sidebar>
)
}
}

View File

@@ -0,0 +1,79 @@
import { Badge } from "@/components/ui/badge"
import { Card, CardDescription, CardTitle } from "@/components/ui/card"
import { AlertCircle, Calendar, Users } from "lucide-react"
import Link from "next/link"
interface Investigation {
id: string
title: string
description: string
status: string
priority: string
created_at: string
relations_count: { count: number }
individuals_counts: { count: number }
}
export default function InvestigationList({ investigation }: { investigation: Investigation }) {
const priorityColors = {
high: "text-red-500",
medium: "text-amber-500",
low: "text-green-500",
}
const statusColors = {
active: "bg-green-500/10 text-green-500 border-green-500/20",
pending: "bg-amber-500/10 text-amber-500 border-amber-500/20",
archived: "bg-slate-500/10 text-slate-500 border-slate-500/20",
}
const statusText = {
active: "Actif",
pending: "En cours",
archived: "Archivé",
}
const formattedDate = new Date(investigation.created_at).toLocaleDateString("fr-FR", {
day: "numeric",
month: "short",
year: "numeric",
})
return (
<Card className="overflow-hidden border shadow-none bg-background">
<div className="flex items-center p-4 gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<Link href={`/investigations/${investigation.id}`} className="hover:underline">
<CardTitle className="text-lg">{investigation.title}</CardTitle>
</Link>
<Badge variant="outline" className={statusColors[investigation.status as keyof typeof statusColors]}>
{statusText[investigation.status as keyof typeof statusText]}
</Badge>
</div>
<CardDescription className="line-clamp-1">{investigation.description}</CardDescription>
<div className="flex items-center gap-4 mt-2 text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Calendar className="h-3.5 w-3.5" />
<span>{formattedDate}</span>
</div>
<div className="flex items-center gap-1">
<Users className="h-3.5 w-3.5" />
<span>{investigation.individuals_counts.count} individus</span>
</div>
<div className="flex items-center gap-1">
<AlertCircle
className={`h-3.5 w-3.5 ${priorityColors[investigation.priority as keyof typeof priorityColors]}`}
/>
<span className={priorityColors[investigation.priority as keyof typeof priorityColors]}>
Priorité {investigation.priority}
</span>
</div>
</div>
</div>
</div>
</Card>
)
}

View File

@@ -94,9 +94,9 @@ export default function Investigation({ investigation }: { investigation: any })
description = "No description provided.",
status = "active",
progress = 35,
lastUpdated = new Date().toISOString(),
lastUpdated = investigation.created_at ?? new Date().toISOString(),
tags = ["investigation", "osint", "pedo"],
team = ["E", "J"],
team = ["E"],
priority = "medium",
entities = investigation?.["individuals_counts"]?.[0]?.count || 0,
connections = investigation?.["relations_counts"]?.[0]?.count || 0,
@@ -124,17 +124,6 @@ export default function Investigation({ investigation }: { investigation: any })
<div className="flex items-center justify-between text-sm">
<StatusBadge status={status} />
</div>
{/* <div className="space-y-1.5">
<div className="flex justify-between text-xs">
<span className="text-muted-foreground">Progression</span>
<span className="font-medium">{progress}%</span>
</div>
<ProgressBar value={progress} />
</div> */}
{/* <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<MapPin className="h-3.5 w-3.5" />
<span>{location}</span>
</div> */}
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<Network className="h-3.5 w-3.5" />
<span>

View File

@@ -0,0 +1,36 @@
"use client"
import { Button } from "@/components/ui/button"
import { Grid3X3, List } from "lucide-react"
type ViewMode = "grid" | "list"
interface ViewToggleProps {
onViewChange: (view: ViewMode) => void
currentView: ViewMode
}
export default function ViewToggle({ onViewChange, currentView }: ViewToggleProps) {
return (
<div className="flex items-center gap-1 md:gap-2">
<Button
variant={currentView === "grid" ? "default" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={() => onViewChange("grid")}
>
<Grid3X3 className="h-4 w-4" />
<span className="sr-only">Vue en grille</span>
</Button>
<Button
variant={currentView === "list" ? "default" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={() => onViewChange("list")}
>
<List className="h-4 w-4" />
<span className="sr-only">Vue en liste</span>
</Button>
</div>
)
}

View File

@@ -2,7 +2,7 @@
import { memo, useMemo } from "react"
import { getEdgeParams } from "@/lib/utils"
import { EdgeLabelRenderer, getSmoothStepPath, getStraightPath, useInternalNode } from "@xyflow/react"
import { EdgeLabelRenderer, getBezierPath, getSmoothStepPath, getStraightPath, useInternalNode } from "@xyflow/react"
import { useInvestigationStore } from "@/store/investigation-store"
import { Badge } from "@/components/ui/badge"
@@ -36,7 +36,7 @@ function FloatingEdge({
// Mémorisation du calcul du chemin et des coordonnées du label
const pathData = useMemo(() => {
const { sx, sy, tx, ty } = edgeParameters
return getStraightPath({
return getBezierPath({
sourceX: sx,
sourceY: sy,
targetX: tx,
@@ -51,8 +51,9 @@ function FloatingEdge({
transform: `translate(-50%, -50%) translate(${pathData[1]}px,${pathData[2]}px)`,
pointerEvents: "all" as const,
opacity: style?.opacity || 1,
stroke: style?.stroke || "#b1b1b750",
}),
[pathData, style?.opacity],
[pathData, style?.opacity, style?.stroke],
)
// Mémorisation du contenu du badge

View File

@@ -254,7 +254,7 @@ const LayoutFlow = ({ refetch, theme }: LayoutFlowProps) => {
proOptions={{ hideAttribution: true }}
nodeTypes={nodeTypes}
// @ts-ignore
edgeTypes={edgeTypes}
// edgeTypes={edgeTypes}
className="!bg-background"
>
<FlowControls

View File

@@ -332,7 +332,7 @@ const IndividualModal = () => {
<CardContent className="flex items-center gap-3 p-0">
<Avatar className="h-8 w-8">
<AvatarImage src={relation?.image_url} alt={relation.full_name} />
<AvatarFallback>{relation.full_name[0]}</AvatarFallback>
<AvatarFallback>{relation.full_name?.[0] || "?"}</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-semibold">{relation.full_name}</p>

View File

@@ -34,8 +34,7 @@ const InvestigationLayout = ({
<PanelGroup autoSaveId="conditional" className='h-screen w-screen flex' direction="horizontal">
{panelOpen && <Panel id="left" order={1} className='h-screen' defaultSize={20} minSize={10}>
<div className='flex flex-col w-full h-full rounded-none shadow-none border-r'>
<div className='w-full rounded-none shadow-none h-12 border-b flex items-center gap-1 flex-row justify-between p-2'>
<Logo />
<div className='w-full rounded-none shadow-none h-12 border-b flex items-center gap-1 flex-row justify-end p-2'>
<div className='flex gap-1'>
<SearchModal investigation_id={investigation_id} />
<NewCase>

View File

@@ -101,7 +101,7 @@ function EmailNode({ data }: any) {
// Mémorisation de la poignée
const handle = useMemo(
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500")} />,
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500 opacity-0")} />,
[],
)

View File

@@ -134,12 +134,12 @@ function Custom(props: any) {
<Handle
type="target"
position={Position.Top}
// className={cn("w-16 bg-teal-500", showContent ? "group-hover:opacity-100 opacity-0" : "opacity-0")}
className={cn("w-16 bg-teal-500 opacity-0")}
/>
<Handle
type="source"
position={Position.Bottom}
// className={cn("w-16 bg-teal-500", showContent ? "group-hover:opacity-100 opacity-0" : "opacity-0")}
className={cn("w-16 bg-teal-500 opacity-0")}
/>
</>
),

View File

@@ -77,7 +77,7 @@ function IpNode({ data }: any) {
// Mémorisation de la poignée
const handle = useMemo(
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500")} />,
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500 opacity-0")} />,
[],
)

View File

@@ -112,12 +112,12 @@ function Custom(props: any) {
<Handle
type="target"
position={Position.Top}
// className={cn("w-16 bg-teal-500", showContent ? "group-hover:opacity-100 opacity-0" : "opacity-0")}
// className={cn("w-16 bg-teal-500 opacity-0", showContent ? "group-hover:opacity-100 opacity-0" : "opacity-0")}
/>
<Handle
type="source"
position={Position.Bottom}
// className={cn("w-16 bg-teal-500", showContent ? "group-hover:opacity-100 opacity-0" : "opacity-0")}
// className={cn("w-16 bg-teal-500 opacity-0", showContent ? "group-hover:opacity-100 opacity-0" : "opacity-0")}
/>
</>
),

View File

@@ -71,7 +71,7 @@ function PhoneNode({ data }: any) {
}, [settings.showNodeLabel, showContent, currentNode, data.id, data.label, settings.showCopyIcon])
const handle = useMemo(
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500")} />,
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500 opacity-0")} />,
[],
)
const contextMenu = useMemo(

View File

@@ -53,7 +53,7 @@ function AddressNode({ data }: any) {
}, [currentNode, data.id, data.label, settings.showCopyIcon])
const handle = useMemo(
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500")} />,
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500 opacity-0")} />,
[],
)
const contextMenu = useMemo(

View File

@@ -97,7 +97,7 @@ function SocialNode({ data }: any) {
// Mémorisation de la poignée
const handle = useMemo(
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500")} />,
() => <Handle type="target" position={Position.Top} className={cn("w-16 bg-teal-500 opacity-0")} />,
[],
)

View File

@@ -1,14 +1,8 @@
import React from 'react'
import { HomeIcon } from 'lucide-react'
import Link from 'next/link'
import { Button } from './ui/button'
const Logo = () => {
return (
<Link href="/dashboard">
<Button variant="outline" size="icon">
<HomeIcon className="h-5" />
</Button>
</Link>
<HomeIcon className='h-4 w-4' />
)
}

View File

@@ -18,72 +18,20 @@ import {
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar"
import Logo from "./logo"
import Link from "next/link"
export function TeamSwitcher({
teams,
}: {
teams: {
name: string
logo: React.ElementType
plan: string
}[]
}) {
const { isMobile } = useSidebar()
const [activeTeam, setActiveTeam] = React.useState(teams[0])
export function TeamSwitcher() {
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div className="bg-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<activeTeam.logo className="size-4" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{activeTeam.name}
</span>
<span className="truncate text-xs">{activeTeam.plan}</span>
</div>
<ChevronsUpDown className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
align="start"
side={isMobile ? "bottom" : "right"}
sideOffset={4}
>
<DropdownMenuLabel className="text-muted-foreground text-xs">
Teams
</DropdownMenuLabel>
{teams.map((team, index) => (
<DropdownMenuItem
key={team.name}
onClick={() => setActiveTeam(team)}
className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-xs border">
<team.logo className="size-4 shrink-0" />
</div>
{team.name}
<DropdownMenuShortcut>{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="bg-background flex size-6 items-center justify-center rounded-md border">
<Plus className="size-4" />
</div>
<div className="text-muted-foreground font-medium">Add team</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
<Link href="/dashboard">
<div
className="data-[state=open]:text-sidebar-accent-foreground"
>
<div className="bg-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<Logo />
</div>
</div>
</Link>
)
}

View File

@@ -28,7 +28,7 @@ function AvatarImage({
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
className={cn("aspect-square object-cover size-full", className)}
{...props}
/>
)

View File

@@ -128,7 +128,7 @@ const createStore = (initialNodes: AppNode[] = [], initialEdges: Edge[] = []) =>
set({
edges: get().edges.map(edge => ({
...edge,
animated: false, // Désactive l'animation
animated: false,
style: {
...edge.style,
stroke: "#b1b1b750",

View File

@@ -15,7 +15,7 @@
--card-foreground: hsl(0 0% 3.9%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(0 0% 3.9%);
--primary: hsl(258.3 89.5% 66.3%);
--primary: hsl(24.6 95% 53.1%);
--primary-foreground: hsl(0 0% 98%);
--secondary: hsl(0 0% 96.1%);
--secondary-foreground: hsl(0 0% 9%);
@@ -28,7 +28,7 @@
--border: hsla(0, 0%, 90%, 0.807);
--input: hsl(0 0% 89.8%);
--ring: hsl(0 0% 3.9%);
--chart-1: hsl(258.3 89.5% 66.3%);
--chart-1: hsl(24.6 95% 53.1%);
--chart-2: hsl(173 58% 39%);
--chart-3: hsl(197 37% 24%);
--chart-4: hsl(43 74% 66%);
@@ -43,7 +43,7 @@
--sidebar-accent: hsl(240 4.8% 95.9%);
--sidebar-accent-foreground: hsl(240 5.9% 10%);
--sidebar-border: hsl(220 13% 91%);
--sidebar-ring: hsl(258.3 89.5% 66.3%);
--sidebar-ring: hsl(24.6 95% 53.1%);
}
.dark {
@@ -53,7 +53,7 @@
--card-foreground: hsl(0 0% 98%);
--popover: hsl(240 0% 9.9%);
--popover-foreground: hsl(0 0% 98%);
--primary: hsl(258.3 89.5% 66.3%);
--primary: hsl(24.6 95% 53.1%);
--primary-foreground: hsl(0, 0%, 100%);
--secondary: hsl(0 0% 14.9%);
--secondary-foreground: hsl(0 0% 98%);
@@ -66,14 +66,14 @@
--border: hsla(0, 0%, 23%, 0.67);
--input: hsl(0 0% 14.9%);
--ring: hsl(0 0% 83.1%);
--chart-1: hsl(258.3 89.5% 66.3%);
--chart-1: hsl(24.6 95% 53.1%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-4: hsl(24.6 95% 53.1%);
--chart-5: hsl(340 75% 55%);
--sidebar: hsl(240 0% 9.9%);
--sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-primary: hsl(262.1 83.3% 57.8%);
--sidebar-primary: hsl(24.6 95% 53.1%);
--sidebar-primary-foreground: hsl(0 0% 100%);
--sidebar-accent: hsl(240 3.7% 15.9%);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);