|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import type * as SCHEMA from "@ctrlplane/db/schema"; |
| 4 | +import Link from "next/link"; |
| 5 | +import * as AccordionPrimitive from "@radix-ui/react-accordion"; |
| 6 | +import { |
| 7 | + IconChevronDown, |
| 8 | + IconPlant, |
| 9 | + IconShip, |
| 10 | + IconTopologyComplex, |
| 11 | +} from "@tabler/icons-react"; |
| 12 | + |
| 13 | +import { urls } from "~/app/urls"; |
| 14 | +import { api } from "~/trpc/react"; |
| 15 | + |
| 16 | +const ChevronIcon: React.FC<{ expandable?: boolean }> = ({ expandable }) => |
| 17 | + expandable ? ( |
| 18 | + <IconChevronDown |
| 19 | + className={ |
| 20 | + "h-3 w-3 shrink-0 text-accent-foreground/50 transition-transform duration-200" |
| 21 | + } |
| 22 | + /> |
| 23 | + ) : ( |
| 24 | + <div className="h-3 w-3"></div> |
| 25 | + ); |
| 26 | + |
| 27 | +const SystemDeployments: React.FC<{ |
| 28 | + workspaceSlug: string; |
| 29 | + systemId: string; |
| 30 | + systemSlug: string; |
| 31 | +}> = ({ workspaceSlug, systemId, systemSlug }) => { |
| 32 | + const { data: deployments } = api.deployment.bySystemId.useQuery(systemId, { |
| 33 | + placeholderData: (prev) => prev, |
| 34 | + }); |
| 35 | + return ( |
| 36 | + <AccordionPrimitive.Item value="deployments"> |
| 37 | + <AccordionPrimitive.Trigger className="flex w-full flex-1 items-center gap-2 rounded-md px-2 py-2 pl-6 transition-all hover:bg-accent/50 first:[&[data-state=open]>svg]:rotate-90"> |
| 38 | + <ChevronIcon expandable /> |
| 39 | + <IconShip className="h-4 w-4 text-blue-400" /> |
| 40 | + <span>Deployments</span> |
| 41 | + <div className="flex-grow" /> |
| 42 | + <div className="rounded-full border border-blue-500/50 bg-blue-500/10 px-2 py-0 text-xs text-blue-400"> |
| 43 | + {deployments?.length ?? "-"} |
| 44 | + </div> |
| 45 | + </AccordionPrimitive.Trigger> |
| 46 | + <AccordionPrimitive.Content> |
| 47 | + {deployments?.map((d) => ( |
| 48 | + <Link |
| 49 | + key={d.id} |
| 50 | + href={urls |
| 51 | + .workspace(workspaceSlug) |
| 52 | + .system(systemSlug) |
| 53 | + .deployment(d.slug) |
| 54 | + .baseUrl()} |
| 55 | + className="flex w-full flex-1 items-center gap-2 rounded-md px-2 py-2 pl-12 hover:bg-accent/50" |
| 56 | + > |
| 57 | + {d.name} |
| 58 | + </Link> |
| 59 | + ))} |
| 60 | + </AccordionPrimitive.Content> |
| 61 | + </AccordionPrimitive.Item> |
| 62 | + ); |
| 63 | +}; |
| 64 | + |
| 65 | +const SystemEnvironments: React.FC<{ |
| 66 | + workspaceSlug: string; |
| 67 | + systemId: string; |
| 68 | + systemSlug: string; |
| 69 | +}> = ({ workspaceSlug, systemId, systemSlug }) => { |
| 70 | + const { data: environments } = api.environment.bySystemId.useQuery(systemId, { |
| 71 | + placeholderData: (prev) => prev, |
| 72 | + }); |
| 73 | + |
| 74 | + return ( |
| 75 | + <AccordionPrimitive.Item value="environments"> |
| 76 | + <AccordionPrimitive.Trigger className="flex w-full flex-1 items-center gap-2 rounded-md px-2 py-2 pl-6 transition-all hover:bg-accent/50 first:[&[data-state=open]>svg]:rotate-90"> |
| 77 | + <ChevronIcon expandable /> |
| 78 | + <IconPlant className="h-4 w-4 text-green-400" /> |
| 79 | + <span>Environments</span> |
| 80 | + <div className="flex-grow" /> |
| 81 | + <div className="rounded-full border border-green-500/50 bg-green-500/10 px-2 py-0 text-xs text-green-400"> |
| 82 | + {environments?.length ?? "-"} |
| 83 | + </div> |
| 84 | + </AccordionPrimitive.Trigger> |
| 85 | + <AccordionPrimitive.Content> |
| 86 | + {environments?.map((e) => ( |
| 87 | + <Link |
| 88 | + key={e.id} |
| 89 | + href={urls |
| 90 | + .workspace(workspaceSlug) |
| 91 | + .system(systemSlug) |
| 92 | + .environment(e.id) |
| 93 | + .baseUrl()} |
| 94 | + className="flex w-full flex-1 items-center gap-2 rounded-md px-2 py-2 pl-12 hover:bg-accent/50" |
| 95 | + > |
| 96 | + {e.name} |
| 97 | + </Link> |
| 98 | + ))} |
| 99 | + </AccordionPrimitive.Content> |
| 100 | + </AccordionPrimitive.Item> |
| 101 | + ); |
| 102 | +}; |
| 103 | + |
| 104 | +export const SystemTreePageContent: React.FC<{ |
| 105 | + workspace: SCHEMA.Workspace; |
| 106 | +}> = ({ workspace }) => { |
| 107 | + const workspaceId = workspace.id; |
| 108 | + const { data } = api.system.list.useQuery( |
| 109 | + { workspaceId, query: undefined }, |
| 110 | + { placeholderData: (prev) => prev }, |
| 111 | + ); |
| 112 | + |
| 113 | + const systems = data?.items ?? []; |
| 114 | + |
| 115 | + return ( |
| 116 | + <div className="text-sm"> |
| 117 | + <AccordionPrimitive.Root type="multiple"> |
| 118 | + {systems.map((s) => ( |
| 119 | + <AccordionPrimitive.Item key={s.id} value={s.id}> |
| 120 | + <AccordionPrimitive.Trigger className="flex w-full flex-1 items-center gap-2 rounded-md px-2 py-2 transition-all hover:bg-accent/50 first:[&[data-state=open]>svg]:rotate-90"> |
| 121 | + <ChevronIcon expandable /> |
| 122 | + <IconTopologyComplex className="h-4 w-4" /> |
| 123 | + <span>{s.name}</span> |
| 124 | + </AccordionPrimitive.Trigger> |
| 125 | + <AccordionPrimitive.Content> |
| 126 | + <AccordionPrimitive.Root type="multiple"> |
| 127 | + <SystemDeployments |
| 128 | + workspaceSlug={workspace.slug} |
| 129 | + systemId={s.id} |
| 130 | + systemSlug={s.slug} |
| 131 | + /> |
| 132 | + <SystemEnvironments |
| 133 | + workspaceSlug={workspace.slug} |
| 134 | + systemId={s.id} |
| 135 | + systemSlug={s.slug} |
| 136 | + /> |
| 137 | + </AccordionPrimitive.Root> |
| 138 | + </AccordionPrimitive.Content> |
| 139 | + </AccordionPrimitive.Item> |
| 140 | + ))} |
| 141 | + </AccordionPrimitive.Root> |
| 142 | + </div> |
| 143 | + ); |
| 144 | +}; |
0 commit comments