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"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; import { HandleProject } from "./handle-project"; import { ProjectEnvironment } from "./project-environment"; export const ShowProjects = () => { const utils = api.useUtils(); const { data, isLoading } = api.project.all.useQuery(); 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 []; // First filter by search query const filtered = data.filter( (project) => project.name.toLowerCase().includes(searchQuery.toLowerCase()) || project.description?.toLowerCase().includes(searchQuery.toLowerCase()), ); // 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.environments.length; const bTotalServices = b.environments.length; comparison = aTotalServices - bTotalServices; break; } default: comparison = 0; } return direction === "asc" ? comparison : -comparison; }); }, [data, searchQuery, sortBy]); return ( <>
Projects Create and manage your projects {(auth?.role === "owner" || auth?.canCreateProjects) && (
)}
{isLoading ? (
Loading...
) : ( <>
setSearchQuery(e.target.value)} className="pr-10" />
{filteredProjects?.length === 0 && (
No projects found
)}
{filteredProjects?.map((project) => { const emptyServices = project?.environments .map( (env) => env.applications.length === 0 && env.mariadb.length === 0 && env.mongo.length === 0 && env.mysql.length === 0 && env.postgres.length === 0 && env.redis.length === 0 && env.applications.length === 0 && env.compose.length === 0, ) .every(Boolean); const totalServices = project?.environments .map( (env) => env.mariadb.length + env.mongo.length + env.mysql.length + env.postgres.length + env.redis.length + env.applications.length + env.compose.length, ) .reduce((acc, curr) => acc + curr, 0); const haveServicesWithDomains = project?.environments .map( (env) => env.applications.length > 0 || env.compose.length > 0, ) .some(Boolean); return (
{haveServicesWithDomains ? ( e.stopPropagation()} > {project.environments.some( (env) => env.applications.length > 0, ) && ( Applications {project.environments.map((env) => env.applications.map((app) => (
{app.name} {app.domains.map((domain) => ( {domain.host} ))}
)), )}
)} {project.environments.some( (env) => env.compose.length > 0, ) && ( Compose {project.environments.map((env) => env.compose.map((comp) => (
{comp.name} {comp.domains.map((domain) => ( {domain.host} ))}
)), )}
)}
) : null}
{project.name}
{project.description}
e.stopPropagation()} > Actions
e.stopPropagation()} >
e.stopPropagation()} >
e.stopPropagation()} > {(auth?.role === "owner" || auth?.canDeleteProjects) && ( e.preventDefault() } > Delete Are you sure to delete this project? {!emptyServices ? (
You have active services, please delete them first
) : ( This action cannot be undone )}
Cancel { await mutateAsync({ projectId: project.projectId, }) .then(() => { toast.success( "Project deleted successfully", ); }) .catch(() => { toast.error( "Error deleting this project", ); }) .finally(() => { utils.project.all.invalidate(); }); }} > Delete
)}
Created {totalServices}{" "} {totalServices === 1 ? "service" : "services"}
); })}
)}
); };