mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-28 15:04:58 +02:00
✨ Command palette with search & actions
This commit is contained in:
@@ -1626,6 +1626,7 @@
|
|||||||
"commandBilling": "Billing",
|
"commandBilling": "Billing",
|
||||||
"commandEnterpriseLicenses": "Licenses",
|
"commandEnterpriseLicenses": "Licenses",
|
||||||
"commandSettings": "Settings",
|
"commandSettings": "Settings",
|
||||||
|
"commandSearchResults": "Search Results",
|
||||||
"alertingTitle": "Alerting",
|
"alertingTitle": "Alerting",
|
||||||
"alertingDescription": "Define sources, triggers, and actions for notifications",
|
"alertingDescription": "Define sources, triggers, and actions for notifications",
|
||||||
"alertingRules": "Alert rules",
|
"alertingRules": "Alert rules",
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ import {
|
|||||||
} from "@app/components/ui/command";
|
} from "@app/components/ui/command";
|
||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
import { ListUserOrgsResponse } from "@server/routers/org";
|
import { ListUserOrgsResponse } from "@server/routers/org";
|
||||||
|
import {
|
||||||
|
ChevronRightIcon,
|
||||||
|
GlobeIcon,
|
||||||
|
GlobeLockIcon,
|
||||||
|
LaptopIcon,
|
||||||
|
PlugIcon,
|
||||||
|
ServerIcon,
|
||||||
|
UserIcon
|
||||||
|
} from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import React, {
|
import React, {
|
||||||
@@ -28,7 +37,7 @@ import React, {
|
|||||||
import { useCommandPaletteActions } from "./useCommandPaletteActions";
|
import { useCommandPaletteActions } from "./useCommandPaletteActions";
|
||||||
import { useCommandPaletteNavigation } from "./useCommandPaletteNavigation";
|
import { useCommandPaletteNavigation } from "./useCommandPaletteNavigation";
|
||||||
import { useCommandPaletteSearch } from "./useCommandPaletteSearch";
|
import { useCommandPaletteSearch } from "./useCommandPaletteSearch";
|
||||||
import { search } from "jmespath";
|
import { resources } from "@server/db";
|
||||||
|
|
||||||
type CommandPaletteProps = {
|
type CommandPaletteProps = {
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
@@ -54,12 +63,21 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
|||||||
const navigationGroups = useCommandPaletteNavigation(navItems);
|
const navigationGroups = useCommandPaletteNavigation(navItems);
|
||||||
// const organizations = useCommandPaletteOrganizations(orgs);
|
// const organizations = useCommandPaletteOrganizations(orgs);
|
||||||
const actions = useCommandPaletteActions(orgId, orgs);
|
const actions = useCommandPaletteActions(orgId, orgs);
|
||||||
const { shouldSearch, sites, resources, users, machineClients, isLoading } =
|
const {
|
||||||
useCommandPaletteSearch({
|
shouldSearch,
|
||||||
orgId,
|
sites,
|
||||||
query: search.substring(1),
|
publicResources,
|
||||||
enabled: open && isActionMode
|
privateResources,
|
||||||
});
|
users,
|
||||||
|
machineClients,
|
||||||
|
userDevices,
|
||||||
|
isLoading,
|
||||||
|
hasResults: hasEntityResults
|
||||||
|
} = useCommandPaletteSearch({
|
||||||
|
orgId,
|
||||||
|
query: search,
|
||||||
|
enabled: !isActionMode && open
|
||||||
|
});
|
||||||
|
|
||||||
const handleOpenChange = useCallback(
|
const handleOpenChange = useCallback(
|
||||||
(nextOpen: boolean) => {
|
(nextOpen: boolean) => {
|
||||||
@@ -80,15 +98,9 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
|||||||
[setOpen]
|
[setOpen]
|
||||||
);
|
);
|
||||||
|
|
||||||
// const hasEntityResults =
|
|
||||||
// sites.length > 0 ||
|
|
||||||
// resources.length > 0 ||
|
|
||||||
// users.length > 0 ||
|
|
||||||
// machineClients.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandDialog
|
<CommandDialog
|
||||||
open //={open} // TODO: flip back to normal
|
open={open} // TODO: flip back to normal
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
title={t("commandPaletteTitle")}
|
title={t("commandPaletteTitle")}
|
||||||
description={t("commandPaletteDescription")}
|
description={t("commandPaletteDescription")}
|
||||||
@@ -121,9 +133,9 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
|||||||
placeholder={t("commandPaletteSearchPlaceholder")}
|
placeholder={t("commandPaletteSearchPlaceholder")}
|
||||||
value={search}
|
value={search}
|
||||||
onValueChange={setSearch}
|
onValueChange={setSearch}
|
||||||
// isLoading
|
isLoading={!isActionMode && shouldSearch && isLoading}
|
||||||
/>
|
/>
|
||||||
<CommandList className="max-h-118 min-h-0 h-(--cmdk-list-height) scroll-pb-2.5 scroll-pt-2 transition-[height] duration-250 ease-in-out">
|
<CommandList className="max-h-118 min-h-0 h-(--cmdk-list-height) scroll-pb-4 scroll-pt-2 transition-[height] duration-250 ease-in-out">
|
||||||
<CommandEmpty>{t("commandPaletteNoResults")}</CommandEmpty>
|
<CommandEmpty>{t("commandPaletteNoResults")}</CommandEmpty>
|
||||||
|
|
||||||
<CommandGroup
|
<CommandGroup
|
||||||
@@ -132,41 +144,35 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{!isActionMode &&
|
{!isActionMode &&
|
||||||
navigationGroups.map((group, groupIndex) => {
|
navigationGroups.map((group, groupIndex) => (
|
||||||
console.log({
|
<React.Fragment key={group.heading}>
|
||||||
groupIndex,
|
{groupIndex > 0 && <CommandSeparator />}
|
||||||
groupHeading: group.heading
|
<CommandGroup
|
||||||
});
|
heading={group.heading}
|
||||||
return (
|
className={cn(
|
||||||
<React.Fragment key={group.heading}>
|
"[&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-sm pb-2.5",
|
||||||
{groupIndex > 0 && <CommandSeparator />}
|
groupIndex > 0 &&
|
||||||
<CommandGroup
|
"[&_[cmdk-group-heading]]:pt-3"
|
||||||
heading={group.heading}
|
)}
|
||||||
className={cn(
|
>
|
||||||
"[&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-sm pb-2.5",
|
{group.items.map((item) => (
|
||||||
groupIndex > 0 &&
|
<CommandItem
|
||||||
"[&_[cmdk-group-heading]]:pt-3"
|
key={item.id}
|
||||||
)}
|
value={`${item.title} ${group.heading}`}
|
||||||
>
|
onSelect={() =>
|
||||||
{group.items.map((item) => (
|
runCommand(() =>
|
||||||
<CommandItem
|
router.push(item.href)
|
||||||
key={item.id}
|
)
|
||||||
value={`${item.title} ${group.heading}`}
|
}
|
||||||
onSelect={() =>
|
className="h-9"
|
||||||
runCommand(() =>
|
>
|
||||||
router.push(item.href)
|
{item.icon}
|
||||||
)
|
<span>{item.title}</span>
|
||||||
}
|
</CommandItem>
|
||||||
className="h-9"
|
))}
|
||||||
>
|
</CommandGroup>
|
||||||
{item.icon}
|
</React.Fragment>
|
||||||
<span>{item.title}</span>
|
))}
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* {organizations.length > 1 && (
|
{/* {organizations.length > 1 && (
|
||||||
<>
|
<>
|
||||||
@@ -200,118 +206,157 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
|||||||
</>
|
</>
|
||||||
)} */}
|
)} */}
|
||||||
|
|
||||||
{/* {shouldSearch && orgId && (
|
{!isActionMode && shouldSearch && orgId && hasEntityResults && (
|
||||||
<>
|
<CommandGroup
|
||||||
<CommandSeparator />
|
heading={t("commandSearchResults")}
|
||||||
{isLoading && !hasEntityResults ? (
|
className={cn(
|
||||||
<div className="flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground">
|
"[&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-sm pb-2.5"
|
||||||
<Loader2 className="size-4 animate-spin" />
|
|
||||||
{t("commandPaletteSearching")}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{sites.length > 0 && (
|
|
||||||
<CommandGroup
|
|
||||||
heading={t("commandPaletteSites")}
|
|
||||||
>
|
|
||||||
{sites.map((site) => (
|
|
||||||
<CommandItem
|
|
||||||
key={site.id}
|
|
||||||
value={`${site.name} site`}
|
|
||||||
onSelect={() =>
|
|
||||||
runCommand(() =>
|
|
||||||
router.push(site.href)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="truncate">
|
|
||||||
{site.name}
|
|
||||||
</span>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
)}
|
|
||||||
{resources.length > 0 && (
|
|
||||||
<CommandGroup
|
|
||||||
heading={t("commandPaletteResources")}
|
|
||||||
>
|
|
||||||
{resources.map((resource) => (
|
|
||||||
<CommandItem
|
|
||||||
key={resource.id}
|
|
||||||
value={`${resource.name} resource`}
|
|
||||||
onSelect={() =>
|
|
||||||
runCommand(() =>
|
|
||||||
router.push(
|
|
||||||
resource.href
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="truncate">
|
|
||||||
{resource.name}
|
|
||||||
</span>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
)}
|
|
||||||
{users.length > 0 && (
|
|
||||||
<CommandGroup
|
|
||||||
heading={t("commandPaletteUsers")}
|
|
||||||
>
|
|
||||||
{users.map((user) => (
|
|
||||||
<CommandItem
|
|
||||||
key={user.id}
|
|
||||||
value={`${user.name} ${user.email}`}
|
|
||||||
onSelect={() =>
|
|
||||||
runCommand(() =>
|
|
||||||
router.push(user.href)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex min-w-0 flex-col">
|
|
||||||
<span className="truncate">
|
|
||||||
{user.name}
|
|
||||||
</span>
|
|
||||||
<span className="truncate text-xs text-muted-foreground">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
)}
|
|
||||||
{machineClients.length > 0 && (
|
|
||||||
<CommandGroup
|
|
||||||
heading={t("commandPaletteClients")}
|
|
||||||
>
|
|
||||||
{machineClients.map((client) => (
|
|
||||||
<CommandItem
|
|
||||||
key={client.id}
|
|
||||||
value={`${client.name} client`}
|
|
||||||
onSelect={() =>
|
|
||||||
runCommand(() =>
|
|
||||||
router.push(client.href)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="truncate">
|
|
||||||
{client.name}
|
|
||||||
</span>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</>
|
>
|
||||||
)} */}
|
{sites.map((site) => (
|
||||||
|
<CommandItem
|
||||||
|
key={site.id}
|
||||||
|
value={`${site.name} site`}
|
||||||
|
onSelect={() =>
|
||||||
|
runCommand(() => router.push(site.href))
|
||||||
|
}
|
||||||
|
className="h-9"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center gap-1">
|
||||||
|
<PlugIcon className="size-4 flex-none" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("commandSites")}
|
||||||
|
</span>
|
||||||
|
<ChevronRightIcon className="size-3.5! flex-none relative p-0! top-px" />
|
||||||
|
<span className="truncate">
|
||||||
|
{site.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
{publicResources.map((resource) => (
|
||||||
|
<CommandItem
|
||||||
|
key={resource.id}
|
||||||
|
value={`${resource.name} public resource`}
|
||||||
|
onSelect={() =>
|
||||||
|
runCommand(() => router.push(resource.href))
|
||||||
|
}
|
||||||
|
className="h-9"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center gap-1">
|
||||||
|
<GlobeIcon className="size-4 flex-none" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("commandProxyResources")}
|
||||||
|
</span>
|
||||||
|
<ChevronRightIcon className="size-3.5! flex-none relative p-0! top-px" />
|
||||||
|
<span className="truncate">
|
||||||
|
{resource.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
{privateResources.map((resource) => (
|
||||||
|
<CommandItem
|
||||||
|
key={resource.id}
|
||||||
|
value={`${resource.name} private resource`}
|
||||||
|
onSelect={() =>
|
||||||
|
runCommand(() => router.push(resource.href))
|
||||||
|
}
|
||||||
|
className="h-9"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center gap-1">
|
||||||
|
<GlobeLockIcon className="size-4 flex-none" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("commandClientResources")}
|
||||||
|
</span>
|
||||||
|
<ChevronRightIcon className="size-3.5! flex-none relative p-0! top-px" />
|
||||||
|
<span className="truncate">
|
||||||
|
{resource.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
{users.map((user) => (
|
||||||
|
<CommandItem
|
||||||
|
key={user.id}
|
||||||
|
value={`${user.name} ${user.email}`}
|
||||||
|
onSelect={() =>
|
||||||
|
runCommand(() => router.push(user.href))
|
||||||
|
}
|
||||||
|
className="h-9"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center gap-1">
|
||||||
|
<UserIcon className="size-4 flex-none" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("commandUsers")}
|
||||||
|
</span>
|
||||||
|
<ChevronRightIcon className="size-3.5! flex-none relative p-0! top-px" />
|
||||||
|
<div className="inline-flex min-w-0 items-center gap-1">
|
||||||
|
<span className="truncate">
|
||||||
|
{user.name}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
·
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs text-muted-foreground">
|
||||||
|
{user.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
{machineClients.map((client) => (
|
||||||
|
<CommandItem
|
||||||
|
key={client.id}
|
||||||
|
value={`${client.name} client`}
|
||||||
|
onSelect={() =>
|
||||||
|
runCommand(() => router.push(client.href))
|
||||||
|
}
|
||||||
|
className="h-9"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center gap-1">
|
||||||
|
<ServerIcon className="size-4 flex-none" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("commandMachineClients")}
|
||||||
|
</span>
|
||||||
|
<ChevronRightIcon className="size-3.5! flex-none relative p-0! top-px" />
|
||||||
|
<span className="truncate">
|
||||||
|
{client.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
{userDevices.map((device) => (
|
||||||
|
<CommandItem
|
||||||
|
key={device.id}
|
||||||
|
value={`${device.name} user device`}
|
||||||
|
onSelect={() =>
|
||||||
|
runCommand(() => router.push(device.href))
|
||||||
|
}
|
||||||
|
className="h-9"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center gap-1">
|
||||||
|
<LaptopIcon className="size-4 flex-none" />
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("commandUserDevices")}
|
||||||
|
</span>
|
||||||
|
<ChevronRightIcon className="size-3.5! flex-none relative p-0! top-px" />
|
||||||
|
<span className="truncate">
|
||||||
|
{device.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
{isActionMode && actions.length > 0 && (
|
{isActionMode && actions.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<CommandSeparator />
|
|
||||||
<CommandGroup
|
<CommandGroup
|
||||||
heading={t("commandPaletteActions")}
|
heading={t("commandPaletteActions")}
|
||||||
className="pb-2.5"
|
className={cn(
|
||||||
|
"[&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-sm pb-2.5"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{actions.map((action) => (
|
{actions.map((action) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
@@ -326,6 +371,7 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
className="h-9"
|
||||||
>
|
>
|
||||||
{action.icon}
|
{action.icon}
|
||||||
<span>{action.label}</span>
|
<span>{action.label}</span>
|
||||||
@@ -387,7 +433,7 @@ export function CommandPaletteProvider({
|
|||||||
orgs,
|
orgs,
|
||||||
navItems
|
navItems
|
||||||
}: CommandPaletteProviderProps) {
|
}: CommandPaletteProviderProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(true); // FIXME: should be set to `false` by default, this is temporary
|
||||||
|
|
||||||
const toggle = useCallback(() => {
|
const toggle = useCallback(() => {
|
||||||
setOpen((current) => !current);
|
setOpen((current) => !current);
|
||||||
|
|||||||
@@ -47,7 +47,14 @@ export function useCommandPaletteSearch({
|
|||||||
const shouldSearch =
|
const shouldSearch =
|
||||||
enabled && !!orgId && trimmedQuery.length >= MIN_QUERY_LENGTH;
|
enabled && !!orgId && trimmedQuery.length >= MIN_QUERY_LENGTH;
|
||||||
|
|
||||||
const [sitesQuery, resourcesQuery, usersQuery, clientsQuery] = useQueries({
|
const [
|
||||||
|
sitesQuery,
|
||||||
|
proxyResourcesQuery,
|
||||||
|
privateResourcesQuery,
|
||||||
|
usersQuery,
|
||||||
|
clientsQuery,
|
||||||
|
userDevicesQuery
|
||||||
|
] = useQueries({
|
||||||
queries: [
|
queries: [
|
||||||
{
|
{
|
||||||
...orgQueries.sites({
|
...orgQueries.sites({
|
||||||
@@ -58,7 +65,15 @@ export function useCommandPaletteSearch({
|
|||||||
enabled: shouldSearch
|
enabled: shouldSearch
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...orgQueries.resources({
|
...orgQueries.proxyResources({
|
||||||
|
orgId: orgId ?? "",
|
||||||
|
query: trimmedQuery,
|
||||||
|
perPage: SEARCH_PER_PAGE
|
||||||
|
}),
|
||||||
|
enabled: shouldSearch
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...orgQueries.privateResources({
|
||||||
orgId: orgId ?? "",
|
orgId: orgId ?? "",
|
||||||
query: trimmedQuery,
|
query: trimmedQuery,
|
||||||
perPage: SEARCH_PER_PAGE
|
perPage: SEARCH_PER_PAGE
|
||||||
@@ -80,6 +95,14 @@ export function useCommandPaletteSearch({
|
|||||||
perPage: SEARCH_PER_PAGE
|
perPage: SEARCH_PER_PAGE
|
||||||
}),
|
}),
|
||||||
enabled: shouldSearch
|
enabled: shouldSearch
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...orgQueries.userDevices({
|
||||||
|
orgId: orgId ?? "",
|
||||||
|
query: trimmedQuery,
|
||||||
|
perPage: SEARCH_PER_PAGE
|
||||||
|
}),
|
||||||
|
enabled: shouldSearch
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -93,14 +116,23 @@ export function useCommandPaletteSearch({
|
|||||||
}));
|
}));
|
||||||
}, [orgId, sitesQuery.data]);
|
}, [orgId, sitesQuery.data]);
|
||||||
|
|
||||||
const resources = useMemo((): ResourceSearchResult[] => {
|
const publicResources = useMemo((): ResourceSearchResult[] => {
|
||||||
if (!orgId || !resourcesQuery.data) return [];
|
if (!orgId || !proxyResourcesQuery.data) return [];
|
||||||
return resourcesQuery.data.map((resource) => ({
|
return proxyResourcesQuery.data.map((resource) => ({
|
||||||
id: `resource-${resource.resourceId}`,
|
id: `resource-${resource.resourceId}`,
|
||||||
name: resource.name,
|
name: resource.name,
|
||||||
href: `/${orgId}/settings/resources/proxy/${resource.niceId}`
|
href: `/${orgId}/settings/resources/proxy/${resource.niceId}`
|
||||||
}));
|
}));
|
||||||
}, [orgId, resourcesQuery.data]);
|
}, [orgId, proxyResourcesQuery.data]);
|
||||||
|
|
||||||
|
const privateResources = useMemo((): ResourceSearchResult[] => {
|
||||||
|
if (!orgId || !privateResourcesQuery.data) return [];
|
||||||
|
return privateResourcesQuery.data.map((resource) => ({
|
||||||
|
id: `site-resource-${resource.siteResourceId}`,
|
||||||
|
name: resource.name,
|
||||||
|
href: `/${orgId}/settings/resources/private?query=${resource.name}`
|
||||||
|
}));
|
||||||
|
}, [orgId, privateResourcesQuery.data]);
|
||||||
|
|
||||||
const users = useMemo((): UserSearchResult[] => {
|
const users = useMemo((): UserSearchResult[] => {
|
||||||
if (!orgId || !usersQuery.data) return [];
|
if (!orgId || !usersQuery.data) return [];
|
||||||
@@ -123,20 +155,44 @@ export function useCommandPaletteSearch({
|
|||||||
}));
|
}));
|
||||||
}, [orgId, clientsQuery.data]);
|
}, [orgId, clientsQuery.data]);
|
||||||
|
|
||||||
|
const userDevices = useMemo((): ClientSearchResult[] => {
|
||||||
|
if (!orgId || !userDevicesQuery.data) return [];
|
||||||
|
return userDevicesQuery.data
|
||||||
|
.filter((client) => !client.userId)
|
||||||
|
.map((client) => ({
|
||||||
|
id: `client-${client.clientId}`,
|
||||||
|
name: client.name,
|
||||||
|
href: `/${orgId}/settings/clients/user/${client.niceId}`
|
||||||
|
}));
|
||||||
|
}, [orgId, userDevicesQuery.data]);
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
shouldSearch &&
|
shouldSearch &&
|
||||||
(sitesQuery.isFetching ||
|
(sitesQuery.isFetching ||
|
||||||
resourcesQuery.isFetching ||
|
proxyResourcesQuery.isFetching ||
|
||||||
|
privateResourcesQuery.isFetching ||
|
||||||
usersQuery.isFetching ||
|
usersQuery.isFetching ||
|
||||||
|
userDevicesQuery.isFetching ||
|
||||||
clientsQuery.isFetching);
|
clientsQuery.isFetching);
|
||||||
|
|
||||||
|
const hasResults =
|
||||||
|
sites.length > 0 ||
|
||||||
|
publicResources.length > 0 ||
|
||||||
|
users.length > 0 ||
|
||||||
|
privateResources.length > 0 ||
|
||||||
|
userDevices.length > 0 ||
|
||||||
|
machineClients.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debouncedQuery: trimmedQuery,
|
debouncedQuery: trimmedQuery,
|
||||||
shouldSearch,
|
shouldSearch,
|
||||||
sites,
|
sites,
|
||||||
resources,
|
publicResources,
|
||||||
|
privateResources,
|
||||||
users,
|
users,
|
||||||
machineClients,
|
machineClients,
|
||||||
isLoading
|
userDevices,
|
||||||
|
isLoading,
|
||||||
|
hasResults
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user