mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-01 23:46:38 +00:00
♻️ make site selector popover its own component
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
|||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
|
||||||
|
import { SitesSelector } from "./site-selector";
|
||||||
|
|
||||||
type SiteWithUpdateAvailable = ListSitesResponse["sites"][number];
|
type SiteWithUpdateAvailable = ListSitesResponse["sites"][number];
|
||||||
|
|
||||||
@@ -54,16 +55,6 @@ export function ResourceTargetAddressItem({
|
|||||||
}: ResourceTargetAddressItemProps) {
|
}: ResourceTargetAddressItemProps) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
const [siteSearchQuery, setSiteSearchQuery] = useState("");
|
|
||||||
|
|
||||||
const { data: sites = [] } = useQuery(
|
|
||||||
orgQueries.sites({
|
|
||||||
orgId,
|
|
||||||
query: siteSearchQuery,
|
|
||||||
perPage: 10
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const [selectedSite, setSelectedSite] = useState<Pick<
|
const [selectedSite, setSelectedSite] = useState<Pick<
|
||||||
SiteWithUpdateAvailable,
|
SiteWithUpdateAvailable,
|
||||||
"name" | "siteId" | "type"
|
"name" | "siteId" | "type"
|
||||||
@@ -82,22 +73,6 @@ export function ResourceTargetAddressItem({
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const sitesShown = useMemo(() => {
|
|
||||||
const allSites: Array<
|
|
||||||
Pick<SiteWithUpdateAvailable, "name" | "siteId" | "type">
|
|
||||||
> = [...sites];
|
|
||||||
if (
|
|
||||||
selectedSite !== null &&
|
|
||||||
!(
|
|
||||||
allSites.find((site) => site.siteId)?.siteId ===
|
|
||||||
selectedSite?.siteId
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
allSites.unshift(selectedSite);
|
|
||||||
}
|
|
||||||
return allSites;
|
|
||||||
}, [sites, selectedSite]);
|
|
||||||
|
|
||||||
const handleContainerSelectForTarget = (
|
const handleContainerSelectForTarget = (
|
||||||
hostname: string,
|
hostname: string,
|
||||||
port?: number
|
port?: number
|
||||||
@@ -150,47 +125,18 @@ export function ResourceTargetAddressItem({
|
|||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="p-0 w-45">
|
<PopoverContent className="p-0 w-45">
|
||||||
<Command shouldFilter={false}>
|
<SitesSelector
|
||||||
<CommandInput
|
orgId={orgId}
|
||||||
placeholder={t("siteSearch")}
|
selectedSite={selectedSite}
|
||||||
value={siteSearchQuery}
|
onSelectSite={(site) => {
|
||||||
onValueChange={(v) => setSiteSearchQuery(v)}
|
updateTarget(proxyTarget.targetId, {
|
||||||
/>
|
siteId: site.siteId,
|
||||||
<CommandList>
|
siteType: site.type,
|
||||||
<CommandEmpty>{t("siteNotFound")}</CommandEmpty>
|
siteName: site.name
|
||||||
<CommandGroup>
|
});
|
||||||
{sitesShown.map((site) => (
|
setSelectedSite(site);
|
||||||
<CommandItem
|
}}
|
||||||
key={site.siteId}
|
/>
|
||||||
value={`${site.siteId}:${site.name}`}
|
|
||||||
onSelect={() => {
|
|
||||||
updateTarget(
|
|
||||||
proxyTarget.targetId,
|
|
||||||
{
|
|
||||||
siteId: site.siteId,
|
|
||||||
siteType: site.type,
|
|
||||||
siteName: site.name
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
setSelectedSite(site);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CheckIcon
|
|
||||||
className={cn(
|
|
||||||
"mr-2 h-4 w-4",
|
|
||||||
site.siteId ===
|
|
||||||
proxyTarget.siteId
|
|
||||||
? "opacity-100"
|
|
||||||
: "opacity-0"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{site.name}
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
|||||||
91
src/components/site-selector.tsx
Normal file
91
src/components/site-selector.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { orgQueries } from "@app/lib/queries";
|
||||||
|
import type { ListSitesResponse } from "@server/routers/site";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList
|
||||||
|
} from "./ui/command";
|
||||||
|
import { cn } from "@app/lib/cn";
|
||||||
|
import { CheckIcon } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useDebounce } from "use-debounce";
|
||||||
|
|
||||||
|
type Selectedsite = Pick<
|
||||||
|
ListSitesResponse["sites"][number],
|
||||||
|
"name" | "siteId" | "type"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SitesSelectorProps = {
|
||||||
|
orgId: string;
|
||||||
|
selectedSite?: Selectedsite | null;
|
||||||
|
onSelectSite: (selected: Selectedsite) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SitesSelector({
|
||||||
|
orgId,
|
||||||
|
selectedSite,
|
||||||
|
onSelectSite
|
||||||
|
}: SitesSelectorProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
const [siteSearchQuery, setSiteSearchQuery] = useState("");
|
||||||
|
const [debouncedQuery] = useDebounce(siteSearchQuery, 150);
|
||||||
|
|
||||||
|
const { data: sites = [] } = useQuery(
|
||||||
|
orgQueries.sites({
|
||||||
|
orgId,
|
||||||
|
query: debouncedQuery,
|
||||||
|
perPage: 10
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// always include the selected site in the list of sites shown
|
||||||
|
const sitesShown = useMemo(() => {
|
||||||
|
const allSites: Array<Selectedsite> = [...sites];
|
||||||
|
if (
|
||||||
|
selectedSite &&
|
||||||
|
!allSites.find((site) => site.siteId === selectedSite?.siteId)
|
||||||
|
) {
|
||||||
|
allSites.unshift(selectedSite);
|
||||||
|
}
|
||||||
|
return allSites;
|
||||||
|
}, [sites, selectedSite]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Command shouldFilter={false}>
|
||||||
|
<CommandInput
|
||||||
|
placeholder={t("siteSearch")}
|
||||||
|
value={siteSearchQuery}
|
||||||
|
onValueChange={(v) => setSiteSearchQuery(v)}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>{t("siteNotFound")}</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{sitesShown.map((site) => (
|
||||||
|
<CommandItem
|
||||||
|
key={site.siteId}
|
||||||
|
value={`${site.siteId}:${site.name}`}
|
||||||
|
onSelect={() => {
|
||||||
|
onSelectSite(site);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"mr-2 h-4 w-4",
|
||||||
|
site.siteId === selectedSite?.siteId
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{site.name}
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user