mirror of
https://github.com/reconurge/flowsint.git
synced 2026-03-12 01:44:42 -05:00
feat: default edges
This commit is contained in:
@@ -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'à 10 clés API et les supprimer lorsque vous n'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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
79
flowsint-web/src/components/dashboard/investigation-list.tsx
Normal file
79
flowsint-web/src/components/dashboard/investigation-list.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
36
flowsint-web/src/components/dashboard/view-toggle.tsx
Normal file
36
flowsint-web/src/components/dashboard/view-toggle.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -254,7 +254,7 @@ const LayoutFlow = ({ refetch, theme }: LayoutFlowProps) => {
|
||||
proOptions={{ hideAttribution: true }}
|
||||
nodeTypes={nodeTypes}
|
||||
// @ts-ignore
|
||||
edgeTypes={edgeTypes}
|
||||
// edgeTypes={edgeTypes}
|
||||
className="!bg-background"
|
||||
>
|
||||
<FlowControls
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")} />,
|
||||
[],
|
||||
)
|
||||
|
||||
|
||||
@@ -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")}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
|
||||
@@ -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")} />,
|
||||
[],
|
||||
)
|
||||
|
||||
|
||||
@@ -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")}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")} />,
|
||||
[],
|
||||
)
|
||||
|
||||
|
||||
@@ -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' />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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%);
|
||||
|
||||
Reference in New Issue
Block a user