diff --git a/src/components/LabelColumnFilterButton.tsx b/src/components/LabelColumnFilterButton.tsx index ed6a1f744..9cab6c72a 100644 --- a/src/components/LabelColumnFilterButton.tsx +++ b/src/components/LabelColumnFilterButton.tsx @@ -1,14 +1,6 @@ "use client"; import { Button } from "@app/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList -} from "@app/components/ui/command"; import { Popover, PopoverContent, @@ -16,16 +8,15 @@ import { } from "@app/components/ui/popover"; import { cn } from "@app/lib/cn"; import { dataTableFilterPopoverContentClassName } from "@app/lib/dataTableFilterPopover"; -import { CheckIcon, Funnel } from "lucide-react"; -import { useTranslations } from "next-intl"; -import { useMemo, useState } from "react"; import { orgQueries } from "@app/lib/queries"; import { useQuery } from "@tanstack/react-query"; -import { useDebounce } from "use-debounce"; +import { Funnel } from "lucide-react"; +import { useMemo, useState } from "react"; +import { useTranslations } from "next-intl"; import { LabelBadge } from "./label-badge"; import { LabelOverflowBadge } from "./label-overflow-badge"; +import { LabelsFilterSelector } from "./LabelsFilterSelector"; import { LABEL_COLORS } from "./labels-selector"; -import { Checkbox } from "./ui/checkbox"; function areSelectionsEqual(a: string[], b: string[]) { if (a.length !== b.length) { @@ -54,13 +45,9 @@ export function LabelColumnFilterButton({ const [draftValues, setDraftValues] = useState(selectedValues); const t = useTranslations(); - const [labelSearchQuery, setlabelsSearchQuery] = useState(""); - const [debouncedQuery] = useDebounce(labelSearchQuery, 150); - const { data: labels = [] } = useQuery( orgQueries.labels({ orgId, - query: debouncedQuery, perPage: 500 }) ); @@ -152,53 +139,17 @@ export function LabelColumnFilterButton({ className={dataTableFilterPopoverContentClassName} align="start" > - - - - {t("labelsNotFound")} - - {draftValues.length > 0 && ( - { - setDraftValues([]); - }} - className="text-muted-foreground" - > - {t("accessFilterClear")} - - )} - {labels.map((label) => ( - { - toggle(label.name); - }} - className="flex items-center gap-2" - > - -
- {label.name} - - ))} - - - + draftSet.has(label.name)} + onToggle={(label) => { + toggle(label.name); + }} + showClear={draftValues.length > 0} + onClear={() => { + setDraftValues([]); + }} + />
diff --git a/src/components/LabelsFilterSelector.tsx b/src/components/LabelsFilterSelector.tsx new file mode 100644 index 000000000..610bbd224 --- /dev/null +++ b/src/components/LabelsFilterSelector.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from "@app/components/ui/command"; +import { orgQueries } from "@app/lib/queries"; +import { useQuery } from "@tanstack/react-query"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; +import { useDebounce } from "use-debounce"; +import { Checkbox } from "./ui/checkbox"; + +export type LabelFilterOption = { + labelId: number; + name: string; + color: string; +}; + +type LabelsFilterSelectorProps = { + orgId: string; + isSelected: (label: LabelFilterOption) => boolean; + onToggle: (label: LabelFilterOption) => void; + onClear?: () => void; + showClear?: boolean; +}; + +export function LabelsFilterSelector({ + orgId, + isSelected, + onToggle, + onClear, + showClear = false +}: LabelsFilterSelectorProps) { + const t = useTranslations(); + const [labelSearchQuery, setlabelsSearchQuery] = useState(""); + const [debouncedQuery] = useDebounce(labelSearchQuery, 150); + + const { data: labels = [] } = useQuery( + orgQueries.labels({ + orgId, + query: debouncedQuery, + perPage: 500 + }) + ); + + return ( + + + + {t("labelsNotFound")} + + {showClear && onClear && ( + + {t("accessFilterClear")} + + )} + {labels.map((label) => ( + { + onToggle(label); + }} + className="flex items-center gap-2" + > + +
+ {label.name} + + ))} + + + + ); +} diff --git a/src/components/resource-launcher/LauncherFilterPopover.tsx b/src/components/resource-launcher/LauncherFilterPopover.tsx index 36d6a3baa..bcf072086 100644 --- a/src/components/resource-launcher/LauncherFilterPopover.tsx +++ b/src/components/resource-launcher/LauncherFilterPopover.tsx @@ -6,9 +6,10 @@ import { } from "@app/components/multi-site-selector"; import { formatLabelsSelectorLabel, - LabelsSelector, + LABEL_COLORS, type SelectedLabel } from "@app/components/labels-selector"; +import { LabelsFilterSelector } from "@app/components/LabelsFilterSelector"; import { Button } from "@app/components/ui/button"; import { Popover, @@ -16,9 +17,11 @@ import { PopoverTrigger } from "@app/components/ui/popover"; import { cn } from "@app/lib/cn"; +import { orgQueries } from "@app/lib/queries"; +import { useQuery } from "@tanstack/react-query"; import { useTranslations } from "next-intl"; import { ChevronsUpDown, Funnel } from "lucide-react"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import type { Selectedsite } from "@app/components/site-selector"; type LauncherFilterPopoverProps = { @@ -40,6 +43,34 @@ export function LauncherFilterPopover({ const [sitesOpen, setSitesOpen] = useState(false); const [labelsOpen, setLabelsOpen] = useState(false); + const { data: labels = [] } = useQuery( + orgQueries.labels({ + orgId, + perPage: 500 + }) + ); + + const selectedLabelIds = useMemo( + () => new Set(selectedLabels.map((label) => label.labelId)), + [selectedLabels] + ); + + const resolvedSelectedLabels: SelectedLabel[] = useMemo( + () => + selectedLabels.map((selected) => { + const found = labels.find( + (label) => label.labelId === selected.labelId + ); + return ( + found ?? { + ...selected, + color: selected.color || LABEL_COLORS.gray + } + ); + }), + [labels, selectedLabels] + ); + return ( @@ -101,7 +132,7 @@ export function LauncherFilterPopover({ > {formatLabelsSelectorLabel( - selectedLabels, + resolvedSelectedLabels, t )} @@ -112,16 +143,15 @@ export function LauncherFilterPopover({ className="w-[var(--radix-popover-trigger-width)] p-0" align="start" > - { - if (action === "attach") { - onLabelsChange([ - ...selectedLabels, - label - ]); - } else { + isSelected={(label) => + selectedLabelIds.has(label.labelId) + } + onToggle={(label) => { + if ( + selectedLabelIds.has(label.labelId) + ) { onLabelsChange( selectedLabels.filter( (item) => @@ -129,8 +159,17 @@ export function LauncherFilterPopover({ label.labelId ) ); + } else { + onLabelsChange([ + ...selectedLabels, + label + ]); } }} + showClear={selectedLabels.length > 0} + onClear={() => { + onLabelsChange([]); + }} />