fix: selected filters for numeric and text filters (#19129)

* fix: selected filters for numeric and text filters

* little adjustment
This commit is contained in:
Eunjae Lee authored and GitHub committed 2025-02-07 13:43:25 +00:00
1 parent aff2c7cdb4
commit 6cad7ac1d1
5 files changed
+138 -56

No files matched your search

@@ -2,7 +2,7 @@
import startCase from "lodash/startCase";
import type { IconName } from "@calcom/ui";
import { Button, Icon, Popover, PopoverContent, PopoverTrigger } from "@calcom/ui";
import { Button, Icon, Popover, PopoverContent, PopoverTrigger, Badge } from "@calcom/ui";
import { useFilterValue } from "../../hooks";
import { type FilterableColumn, type FilterValue, ZFilterValue, ColumnFilterType } from "../../lib/types";
@@ -13,6 +13,7 @@ import {
isNumberFilterValue,
} from "../../lib/utils";
import { FilterOptions } from "./FilterOptions";
import { numberFilterOperatorOptions, useTextFilterOperatorOptions } from "./utils";
const FILTER_ICONS: Record<ColumnFilterType, IconName> = {
[ColumnFilterType.TEXT]: "file-text",
@@ -26,42 +27,117 @@ type FilterPopoverProps = {
column: FilterableColumn;
};
const useSelectedLabels = ({
type SelectedLabelsProps = {
column: FilterableColumn;
filterValue?: FilterValue;
};
function Operator({ children }: { children: React.ReactNode }) {
return <span className="ml-2 text-sm lowercase opacity-75">{children}</span>;
}
function AppliedTextFilterValue({ filterValue }: SelectedLabelsProps) {
const textFilterOperatorOptions = useTextFilterOperatorOptions();
if (!isTextFilterValue(filterValue)) return null;
const operator = filterValue.data.operator;
const operatorOption = textFilterOperatorOptions.find((o) => o.value === operator);
if (!operatorOption) return null;
if (!operatorOption.requiresOperand) {
return <Operator>{operatorOption.label}</Operator>;
}
const operand = filterValue.data.operand;
return (
<>
<Operator>{operatorOption.label}</Operator>
<Badge variant="gray" className="ml-2">
{operand}
</Badge>
</>
);
}
function AppliedNumberFilterValue({ filterValue }: SelectedLabelsProps) {
if (!isNumberFilterValue(filterValue)) return null;
const operator = filterValue.data.operator;
const operatorOption = numberFilterOperatorOptions.find((o) => o.value === operator);
if (!operatorOption) return null;
return (
<>
<Operator>{operatorOption.label}</Operator>
<Badge variant="gray" className="ml-2">
{filterValue.data.operand}
</Badge>
</>
);
}
function AppliedSelectFilterValue({ column, filterValue }: SelectedLabelsProps) {
let text: string | undefined;
let moreCount = 0;
if (isSingleSelectFilterValue(filterValue)) {
const options = (column as Extract<FilterableColumn, { type: ColumnFilterType.SINGLE_SELECT }>).options;
text = options.find((opt) => opt.value === filterValue.data)?.label;
moreCount = 0;
} else if (isMultiSelectFilterValue(filterValue)) {
const options = (column as Extract<FilterableColumn, { type: ColumnFilterType.MULTI_SELECT }>).options;
text = options.find((opt) => opt.value === filterValue.data[0])?.label;
moreCount = filterValue.data.length - 1;
}
if (!text) return null;
return (
<>
<Badge variant="gray" className="ml-2">
{text}
</Badge>
{moreCount > 0 && (
<Badge variant="gray" className="ml-2">
+{moreCount}
</Badge>
)}
</>
);
}
function AppliedFilterValue({
column,
filterValue,
}: {
column: FilterableColumn;
filterValue?: FilterValue;
}) => {
if (isTextFilterValue(filterValue)) {
return { text: filterValue.data.operand, moreCount: 0 };
} else if (isNumberFilterValue(filterValue)) {
return { text: filterValue.data.operand, moreCount: 0 };
} else if (isSingleSelectFilterValue(filterValue)) {
const options = (column as Extract<FilterableColumn, { type: ColumnFilterType.SINGLE_SELECT }>).options;
const text = options.find((opt) => opt.value === filterValue.data)?.label;
return { text, moreCount: 0 };
} else if (isMultiSelectFilterValue(filterValue)) {
const options = (column as Extract<FilterableColumn, { type: ColumnFilterType.MULTI_SELECT }>).options;
const text = options.find((opt) => opt.value === filterValue.data[0])?.label;
return { text, moreCount: filterValue.data.length - 1 };
}) {
if (column.type === ColumnFilterType.TEXT) {
return <AppliedTextFilterValue column={column} filterValue={filterValue} />;
} else if (column.type === ColumnFilterType.NUMBER) {
return <AppliedNumberFilterValue column={column} filterValue={filterValue} />;
} else if (
column.type === ColumnFilterType.SINGLE_SELECT ||
column.type === ColumnFilterType.MULTI_SELECT
) {
return <AppliedSelectFilterValue column={column} filterValue={filterValue} />;
} else {
return { text: "", moreCount: 0 };
return null;
}
};
}
export function FilterPopover({ column }: FilterPopoverProps) {
const icon = column.icon || FILTER_ICONS[column.type];
const filterValue = useFilterValue(column.id, ZFilterValue);
const { text, moreCount } = useSelectedLabels({ column, filterValue });
return (
<Popover>
<PopoverTrigger asChild>
<Button color="secondary" className="items-center">
<Icon name={icon} className="mr-2 h-4 w-4" />
<span>{startCase(column.title)}</span>
{text && <span className="bg-subtle ml-2 rounded-md px-2">{text}</span>}
{moreCount > 0 && <span className="bg-subtle ml-2 rounded-md px-2">+{moreCount}</span>}
<AppliedFilterValue column={column} filterValue={filterValue} />
<Icon name="chevron-down" className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
@@ -6,22 +6,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Form, Input, Select, Button } from "@calcom/ui";
import { useFilterValue, useDataTable } from "../../hooks";
import type { FilterableColumn, NumberFilterOperator } from "../../lib/types";
import type { FilterableColumn } from "../../lib/types";
import { ZNumberFilterValue, ColumnFilterType } from "../../lib/types";
export type NumberFilterOperatorOption = {
label: string;
value: NumberFilterOperator;
};
const numberFilterOperatorOptions: NumberFilterOperatorOption[] = [
{ value: "eq", label: "=" },
{ value: "neq", label: "≠" },
{ value: "gt", label: ">" },
{ value: "gte", label: "≥" },
{ value: "lt", label: "<" },
{ value: "lte", label: "≤" },
];
import { numberFilterOperatorOptions } from "./utils";
export type NumberFilterOptionsProps = {
column: Extract<FilterableColumn, { type: ColumnFilterType.NUMBER }>;
@@ -6,28 +6,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Form, Input, Select, Button } from "@calcom/ui";
import { useFilterValue, useDataTable } from "../../hooks";
import type { FilterableColumn, TextFilterOperator } from "../../lib/types";
import type { FilterableColumn } from "../../lib/types";
import { ZTextFilterValue, ColumnFilterType } from "../../lib/types";
export type TextFilterOperatorOption = {
label: string;
value: TextFilterOperator;
requiresOperand: boolean;
};
const useTextFilterOperatorOptions = (): TextFilterOperatorOption[] => {
const { t } = useLocale();
return [
{ value: "equals", label: t("filter_operator_is"), requiresOperand: true },
{ value: "notEquals", label: t("filter_operator_is_not"), requiresOperand: true },
{ value: "contains", label: t("filter_operator_contains"), requiresOperand: true },
{ value: "notContains", label: t("filter_operator_does_not_contain"), requiresOperand: true },
{ value: "startsWith", label: t("filter_operator_starts_with"), requiresOperand: true },
{ value: "endsWith", label: t("filter_operator_ends_with"), requiresOperand: true },
{ value: "isEmpty", label: t("filter_operator_is_empty"), requiresOperand: false },
{ value: "isNotEmpty", label: t("filter_operator_not_empty"), requiresOperand: false },
];
};
import { useTextFilterOperatorOptions } from "./utils";
export type TextFilterOptionsProps = {
column: Extract<FilterableColumn, { type: ColumnFilterType.TEXT }>;
@@ -0,0 +1,12 @@
import type { NumberFilterOperator, TextFilterOperator } from "../../lib/types";
export type NumberFilterOperatorOption = {
label: string;
value: NumberFilterOperator;
};
export type TextFilterOperatorOption = {
label: string;
value: TextFilterOperator;
requiresOperand: boolean;
};
@@ -0,0 +1,26 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { TextFilterOperatorOption, NumberFilterOperatorOption } from "./types";
export const numberFilterOperatorOptions: NumberFilterOperatorOption[] = [
{ value: "eq", label: "=" },
{ value: "neq", label: "≠" },
{ value: "gt", label: ">" },
{ value: "gte", label: "≥" },
{ value: "lt", label: "<" },
{ value: "lte", label: "≤" },
];
export const useTextFilterOperatorOptions = (): TextFilterOperatorOption[] => {
const { t } = useLocale();
return [
{ value: "equals", label: t("filter_operator_is"), requiresOperand: true },
{ value: "notEquals", label: t("filter_operator_is_not"), requiresOperand: true },
{ value: "contains", label: t("filter_operator_contains"), requiresOperand: true },
{ value: "notContains", label: t("filter_operator_does_not_contain"), requiresOperand: true },
{ value: "startsWith", label: t("filter_operator_starts_with"), requiresOperand: true },
{ value: "endsWith", label: t("filter_operator_ends_with"), requiresOperand: true },
{ value: "isEmpty", label: t("filter_operator_is_empty"), requiresOperand: false },
{ value: "isNotEmpty", label: t("filter_operator_not_empty"), requiresOperand: false },
];
};