mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Merge pull request #2515 from divaltor/filter-projects-shortcut
feat(input): Add focus by Cmd + K shortcut to search input
This commit is contained in:
@@ -44,7 +44,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -144,12 +144,13 @@ export const ShowProjects = () => {
|
||||
<>
|
||||
<div className="flex max-sm:flex-col gap-4 items-center w-full">
|
||||
<div className="flex-1 relative max-sm:w-full">
|
||||
<Input
|
||||
<FocusShortcutInput
|
||||
placeholder="Filter projects..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pr-10"
|
||||
/>
|
||||
|
||||
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 min-w-48 max-sm:w-full">
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
import {
|
||||
extractServices,
|
||||
type Services,
|
||||
} from "@/components/dashboard/settings/users/add-permissions";
|
||||
import {
|
||||
MariadbIcon,
|
||||
MongodbIcon,
|
||||
@@ -20,13 +24,34 @@ import {
|
||||
CommandSeparator,
|
||||
} from "@/components/ui/command";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
// import {
|
||||
// extractServices,
|
||||
// type Services,
|
||||
// } from "@/pages/dashboard/project/[projectId]";
|
||||
import { api } from "@/utils/api";
|
||||
import { StatusTooltip } from "../shared/status-tooltip";
|
||||
|
||||
// Extended Services type to include environmentId and environmentName for search navigation
|
||||
type SearchServices = Services & {
|
||||
environmentId: string;
|
||||
environmentName: string;
|
||||
};
|
||||
|
||||
const extractAllServicesFromProject = (project: any): SearchServices[] => {
|
||||
const allServices: SearchServices[] = [];
|
||||
|
||||
// Iterate through all environments in the project
|
||||
project.environments?.forEach((environment: any) => {
|
||||
const environmentServices = extractServices(environment);
|
||||
const servicesWithEnvironmentId: SearchServices[] = environmentServices.map(
|
||||
(service) => ({
|
||||
...service,
|
||||
environmentId: environment.environmentId,
|
||||
environmentName: environment.name,
|
||||
}),
|
||||
);
|
||||
allServices.push(...servicesWithEnvironmentId);
|
||||
});
|
||||
|
||||
return allServices;
|
||||
};
|
||||
|
||||
export const SearchCommand = () => {
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
@@ -51,7 +76,7 @@ export const SearchCommand = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* <CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandInput
|
||||
placeholder={"Search projects or settings"}
|
||||
value={search}
|
||||
@@ -63,25 +88,37 @@ export const SearchCommand = () => {
|
||||
</CommandEmpty>
|
||||
<CommandGroup heading={"Projects"}>
|
||||
<CommandList>
|
||||
{data?.map((project) => (
|
||||
<CommandItem
|
||||
key={project.projectId}
|
||||
onSelect={() => {
|
||||
router.push(`/dashboard/project/${project.projectId}`);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<BookIcon className="size-4 text-muted-foreground mr-2" />
|
||||
{project.name}
|
||||
</CommandItem>
|
||||
))}
|
||||
{data?.map((project) => {
|
||||
console.log("project", project);
|
||||
const productionEnvironment = project.environments.find(
|
||||
(environment) => environment.name === "production",
|
||||
);
|
||||
|
||||
if (!productionEnvironment) return null;
|
||||
|
||||
return (
|
||||
<CommandItem
|
||||
key={project.projectId}
|
||||
onSelect={() => {
|
||||
router.push(
|
||||
`/dashboard/project/${project.projectId}/environment/${productionEnvironment!.environmentId}`,
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<BookIcon className="size-4 text-muted-foreground mr-2" />
|
||||
{project.name} / {productionEnvironment!.name}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandList>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading={"Services"}>
|
||||
<CommandList>
|
||||
{data?.map((project) => {
|
||||
const applications: Services[] = extractServices(project);
|
||||
const applications: SearchServices[] =
|
||||
extractAllServicesFromProject(project);
|
||||
return applications.map((application) => (
|
||||
<CommandItem
|
||||
key={application.id}
|
||||
@@ -114,7 +151,8 @@ export const SearchCommand = () => {
|
||||
<CircuitBoard className="h-6 w-6 mr-2" />
|
||||
)}
|
||||
<span className="flex-grow">
|
||||
{project.name} / {application.name}{" "}
|
||||
{project.name} / {application.environmentName} /{" "}
|
||||
{application.name}{" "}
|
||||
<div style={{ display: "none" }}>{application.id}</div>
|
||||
</span>
|
||||
<div>
|
||||
@@ -181,7 +219,7 @@ export const SearchCommand = () => {
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog> */}
|
||||
</CommandDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
36
apps/dokploy/components/shared/focus-shortcut-input.tsx
Normal file
36
apps/dokploy/components/shared/focus-shortcut-input.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
type Props = React.ComponentPropsWithoutRef<typeof Input>;
|
||||
|
||||
export const FocusShortcutInput = (props: Props) => {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const isMod = e.metaKey || e.ctrlKey;
|
||||
if (!isMod || e.key.toLowerCase() !== "k") return;
|
||||
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (target) {
|
||||
const tag = target.tagName;
|
||||
if (
|
||||
target.isContentEditable ||
|
||||
tag === "INPUT" ||
|
||||
tag === "TEXTAREA" ||
|
||||
tag === "SELECT" ||
|
||||
target.getAttribute("role") === "textbox"
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
}, []);
|
||||
|
||||
return <Input {...props} ref={inputRef} />;
|
||||
};
|
||||
@@ -80,7 +80,6 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -96,6 +95,7 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
|
||||
|
||||
export type Services = {
|
||||
appName: string;
|
||||
@@ -1197,7 +1197,7 @@ const EnvironmentPage = (
|
||||
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:gap-4 lg:items-center">
|
||||
<div className="w-full relative">
|
||||
<Input
|
||||
<FocusShortcutInput
|
||||
placeholder="Filter services..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
|
||||
Reference in New Issue
Block a user