Merge pull request #238 from Dokploy/202-feature-categorize-templates-for-improved-ux

refactor(templates): add tag input selector
This commit is contained in:
Mauricio Siu 2024-07-20 15:15:48 -06:00 committed by GitHub
commit 0ce055c001
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 249 additions and 136 deletions

View File

@ -12,6 +12,13 @@ import {
} from "@/components/ui/alert-dialog";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
@ -22,8 +29,23 @@ import {
} from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { Code, Github, Globe, PuzzleIcon } from "lucide-react";
import { ScrollArea } from "@radix-ui/react-scroll-area";
import {
CheckIcon,
ChevronsUpDown,
Code,
Github,
Globe,
PuzzleIcon,
SearchIcon,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { toast } from "sonner";
@ -34,13 +56,23 @@ interface Props {
export const AddTemplate = ({ projectId }: Props) => {
const [query, setQuery] = useState("");
const { data } = api.compose.templates.useQuery();
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const { data: tags, isLoading: isLoadingTags } =
api.compose.getTags.useQuery();
const utils = api.useUtils();
const { mutateAsync, isLoading, error, isError } =
api.compose.deployTemplate.useMutation();
const templates = data?.filter((t) =>
t.name.toLowerCase().includes(query.toLowerCase()),
);
const templates =
data?.filter((template) => {
const matchesTags =
selectedTags.length === 0 ||
template.tags.some((tag) => selectedTags.includes(tag));
const matchesQuery =
query === "" ||
template.name.toLowerCase().includes(query.toLowerCase());
return matchesTags && matchesQuery;
}) || [];
return (
<Dialog>
@ -62,13 +94,86 @@ export const AddTemplate = ({ projectId }: Props) => {
</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="flex flex-col md:flex-row gap-2">
<Input
placeholder="Search Template"
onChange={(e) => setQuery(e.target.value)}
className="w-full"
value={query}
/>
<Popover modal={true}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className={cn(
"md:max-w-[15rem] w-full justify-between !bg-input",
// !field.value && "text-muted-foreground",
)}
>
{isLoadingTags
? "Loading...."
: selectedTags.length > 0
? `Selected ${selectedTags.length} tags`
: "Select tag"}
<ChevronsUpDown className="ml-2 h-4 w-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput placeholder="Search tag..." className="h-9" />
{isLoadingTags && (
<span className="py-6 text-center text-sm">
Loading Tags....
</span>
)}
<CommandEmpty>No tags found.</CommandEmpty>
<ScrollArea className="h-96 overflow-y-auto">
<CommandGroup>
{tags?.map((tag) => {
return (
<CommandItem
value={tag}
key={tag}
onSelect={() => {
if (selectedTags.includes(tag)) {
setSelectedTags(
selectedTags.filter((t) => t !== tag),
);
return;
}
setSelectedTags([...selectedTags, tag]);
}}
>
{tag}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
selectedTags.includes(tag)
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
);
})}
</CommandGroup>
</ScrollArea>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="p-6">
</div>
<div className="p-6 w-full">
{templates.length === 0 ? (
<div className="flex justify-center items-center w-full gap-2 min-h-[50vh]">
<SearchIcon className="text-muted-foreground size-6" />
<div className="text-xl font-medium text-muted-foreground">
No templates found
</div>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 w-full gap-4">
{templates?.map((template, index) => (
<div key={`template-${index}`}>
@ -202,6 +307,7 @@ export const AddTemplate = ({ projectId }: Props) => {
</div>
))}
</div>
)}
</div>
</DialogContent>
</Dialog>

View File

@ -30,6 +30,7 @@ import {
} from "@/templates/utils";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import _ from "lodash";
import { nanoid } from "nanoid";
import { findAdmin } from "../services/admin";
import {
@ -283,4 +284,10 @@ export const composeRouter = createTRPCRouter({
return templatesData;
}),
getTags: protectedProcedure.query(async ({ input }) => {
const allTags = templates.flatMap((template) => template.tags);
const uniqueTags = _.uniq(allTags);
return uniqueTags;
}),
});