diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 03ebe7a85..c405cb704 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -1,3 +1,17 @@ +import { + AlertTriangle, + ArrowUpDown, + BookIcon, + ExternalLinkIcon, + FolderInput, + Loader2, + MoreHorizontalIcon, + Search, + TrashIcon, +} from "lucide-react"; +import Link from "next/link"; +import { useEffect, useMemo, useState } from "react"; +import { toast } from "sonner"; import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { StatusTooltip } from "@/components/shared/status-tooltip"; @@ -31,20 +45,14 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; import { - AlertTriangle, - BookIcon, - ExternalLinkIcon, - FolderInput, - Loader2, - MoreHorizontalIcon, - Search, - TrashIcon, -} from "lucide-react"; -import Link from "next/link"; -import { useMemo, useState } from "react"; -import { toast } from "sonner"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { api } from "@/utils/api"; import { HandleProject } from "./handle-project"; import { ProjectEnvironment } from "./project-environment"; @@ -54,15 +62,65 @@ export const ShowProjects = () => { const { data: auth } = api.user.get.useQuery(); const { mutateAsync } = api.project.remove.useMutation(); const [searchQuery, setSearchQuery] = useState(""); + const [sortBy, setSortBy] = useState(() => { + if (typeof window !== "undefined") { + return localStorage.getItem("projectsSort") || "createdAt-desc"; + } + return "createdAt-desc"; + }); + + useEffect(() => { + localStorage.setItem("projectsSort", sortBy); + }, [sortBy]); const filteredProjects = useMemo(() => { if (!data) return []; - return data.filter( + + // First filter by search query + const filtered = data.filter( (project) => project.name.toLowerCase().includes(searchQuery.toLowerCase()) || project.description?.toLowerCase().includes(searchQuery.toLowerCase()), ); - }, [data, searchQuery]); + + // Then sort the filtered results + const [field, direction] = sortBy.split("-"); + return [...filtered].sort((a, b) => { + let comparison = 0; + switch (field) { + case "name": + comparison = a.name.localeCompare(b.name); + break; + case "createdAt": + comparison = + new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); + break; + case "services": { + const aTotalServices = + a.mariadb.length + + a.mongo.length + + a.mysql.length + + a.postgres.length + + a.redis.length + + a.applications.length + + a.compose.length; + const bTotalServices = + b.mariadb.length + + b.mongo.length + + b.mysql.length + + b.postgres.length + + b.redis.length + + b.applications.length + + b.compose.length; + comparison = aTotalServices - bTotalServices; + break; + } + default: + comparison = 0; + } + return direction === "asc" ? comparison : -comparison; + }); + }, [data, searchQuery, sortBy]); return ( <> @@ -98,14 +156,40 @@ export const ShowProjects = () => { ) : ( <> -
- setSearchQuery(e.target.value)} - className="pr-10" - /> - +
+
+ setSearchQuery(e.target.value)} + className="pr-10" + /> + +
+
+ + +
{filteredProjects?.length === 0 && (