♻️ make site selector popover its own component

This commit is contained in:
Fred KISSIE
2026-03-19 00:35:26 +01:00
parent c9be84a8a8
commit 722595c131
2 changed files with 104 additions and 67 deletions

View File

@@ -23,6 +23,7 @@ import {
import { Input } from "./ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
import { SitesSelector } from "./site-selector";
type SiteWithUpdateAvailable = ListSitesResponse["sites"][number];
@@ -54,16 +55,6 @@ export function ResourceTargetAddressItem({
}: ResourceTargetAddressItemProps) {
const t = useTranslations();
const [siteSearchQuery, setSiteSearchQuery] = useState("");
const { data: sites = [] } = useQuery(
orgQueries.sites({
orgId,
query: siteSearchQuery,
perPage: 10
})
);
const [selectedSite, setSelectedSite] = useState<Pick<
SiteWithUpdateAvailable,
"name" | "siteId" | "type"
@@ -82,22 +73,6 @@ export function ResourceTargetAddressItem({
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 = (
hostname: string,
port?: number
@@ -150,47 +125,18 @@ export function ResourceTargetAddressItem({
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-45">
<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={() => {
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>
<SitesSelector
orgId={orgId}
selectedSite={selectedSite}
onSelectSite={(site) => {
updateTarget(proxyTarget.targetId, {
siteId: site.siteId,
siteType: site.type,
siteName: site.name
});
setSelectedSite(site);
}}
/>
</PopoverContent>
</Popover>

View 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>
);
}