[WEB-5099] improvement: enhance rich filters with new components and configurations (#7916)

* feat: enhance rich filters with new components and configurations

- Added `AdditionalFilterValueInput` for unsupported filter types.
- Introduced `FilterItem` and related components for better filter item management.
- Updated filter configurations to include new properties and support for multiple values.
- Improved loading states and error handling in filter components.
- Refactored existing filter logic to streamline operations and enhance performance.

* Refactor rich filters component structure and enhance filter item functionality

- Moved AddFilterButton and AddFilterDropdown to a new directory structure for better organization.
- Updated FilterItemProperty to handle filter selection and condition updates more effectively.
- Enhanced the FilterInstance class with methods to update condition properties and operators, improving filter management.
- Added new functionality to handle invalid filter states and improve user feedback.

* [WEB-5111] feat: add 'created_at' and 'updated_at' filters to work item configuration

- Introduced new filter configurations for 'created_at' and 'updated_at' in the work item filters.
- Updated relevant components to utilize these new filters, enhancing filtering capabilities.
- Added corresponding filter configuration functions in the utils for better date handling.

* fix: build
This commit is contained in:
Prateek Shourya 2025-10-14 01:39:24 +05:30 committed by GitHub
parent 9f41e92d21
commit cfb4a8212c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 854 additions and 247 deletions

View file

@ -24,6 +24,7 @@ export interface IFilterConfigManager<P extends TFilterProperty> {
// observables
filterConfigs: Map<P, IFilterConfig<P, TFilterValue>>; // filter property -> config
configOptions: TConfigOptions;
areConfigsReady: boolean;
// computed
allAvailableConfigs: IFilterConfig<P, TFilterValue>[];
// computed functions
@ -32,6 +33,7 @@ export interface IFilterConfigManager<P extends TFilterProperty> {
register: <C extends TFilterConfig<P, TFilterValue>>(config: C) => void;
registerAll: (configs: TFilterConfig<P, TFilterValue>[]) => void;
updateConfigByProperty: (property: P, configUpdates: Partial<TFilterConfig<P, TFilterValue>>) => void;
setAreConfigsReady: (value: boolean) => void;
}
/**
@ -57,6 +59,7 @@ export class FilterConfigManager<P extends TFilterProperty, E extends TExternalF
// observables
filterConfigs: IFilterConfigManager<P>["filterConfigs"];
configOptions: IFilterConfigManager<P>["configOptions"];
areConfigsReady: IFilterConfigManager<P>["areConfigsReady"];
// parent filter instance
private _filterInstance: IFilterInstance<P, E>;
@ -69,18 +72,21 @@ export class FilterConfigManager<P extends TFilterProperty, E extends TExternalF
constructor(filterInstance: IFilterInstance<P, E>, params: TConfigManagerParams) {
this.filterConfigs = new Map<P, IFilterConfig<P>>();
this.configOptions = this._initializeConfigOptions(params.options);
this.areConfigsReady = true;
// parent filter instance
this._filterInstance = filterInstance;
makeObservable(this, {
filterConfigs: observable,
configOptions: observable,
areConfigsReady: observable,
// computed
allAvailableConfigs: computed,
// helpers
register: action,
registerAll: action,
updateConfigByProperty: action,
setAreConfigsReady: action,
});
}
@ -146,6 +152,14 @@ export class FilterConfigManager<P extends TFilterProperty, E extends TExternalF
prevConfig?.mutate(configUpdates);
});
/**
* Updates the configs ready state.
* @param value - The new configs ready state.
*/
setAreConfigsReady: IFilterConfigManager<P>["setAreConfigsReady"] = action((value) => {
this.areConfigsReady = value;
});
// ------------ private computed ------------
private get _allConfigs(): IFilterConfig<P, TFilterValue>[] {

View file

@ -6,6 +6,7 @@ import {
IFilterAdapter,
LOGICAL_OPERATOR,
TSupportedOperators,
TFilterConditionNode,
TFilterExpression,
TFilterValue,
TFilterProperty,
@ -43,9 +44,16 @@ export interface IFilterInstanceHelper<P extends TFilterProperty, E extends TExt
condition: TFilterConditionPayload<P, V>,
isNegation: boolean
) => TFilterExpression<P> | null;
handleConditionPropertyUpdate: (
expression: TFilterExpression<P>,
conditionId: string,
property: P,
operator: TSupportedOperators,
isNegation: boolean
) => TFilterExpression<P> | null;
// group operations
restructureExpressionForOperatorChange: (
expression: TFilterExpression<P> | null,
expression: TFilterExpression<P>,
conditionId: string,
newOperator: TSupportedOperators,
isNegation: boolean,
@ -162,6 +170,28 @@ export class FilterInstanceHelper<P extends TFilterProperty, E extends TExternal
isNegation
) => this._addConditionByOperator(expression, groupOperator, this._getConditionPayloadToAdd(condition, isNegation));
/**
* Updates the property and operator of a condition in the filter expression.
* This method updates the property, operator, resets the value, and handles negation properly.
* @param expression - The filter expression to operate on
* @param conditionId - The ID of the condition being updated
* @param property - The new property for the condition
* @param operator - The new operator for the condition
* @param isNegation - Whether the condition should be negated
* @returns The updated expression
*/
handleConditionPropertyUpdate: IFilterInstanceHelper<P, E>["handleConditionPropertyUpdate"] = (
expression,
conditionId,
property,
operator,
isNegation
) => {
const payload = { property, operator, value: undefined };
return this._updateCondition(expression, conditionId, payload, isNegation);
};
// ------------ group operations ------------
/**
@ -177,17 +207,12 @@ export class FilterInstanceHelper<P extends TFilterProperty, E extends TExternal
expression,
conditionId,
newOperator,
_isNegation,
isNegation,
shouldResetValue
) => {
if (!expression) return null;
const payload = shouldResetValue ? { operator: newOperator, value: undefined } : { operator: newOperator };
// Update the condition with the new operator
updateNodeInExpression(expression, conditionId, payload);
return expression;
return this._updateCondition(expression, conditionId, payload, isNegation);
};
// ------------ private helpers ------------
@ -227,4 +252,24 @@ export class FilterInstanceHelper<P extends TFilterProperty, E extends TExternal
return expression;
}
}
/**
* Updates a condition with the given payload and handles negation wrapping/unwrapping.
* @param expression - The filter expression to operate on
* @param conditionId - The ID of the condition being updated
* @param payload - The payload to update the condition with
* @param isNegation - Whether the condition should be negated
* @returns The updated expression with proper negation handling
*/
private _updateCondition = (
expression: TFilterExpression<P>,
conditionId: string,
payload: Partial<TFilterConditionNode<P, TFilterValue>>,
_isNegation: boolean
): TFilterExpression<P> | null => {
// Update the condition with the payload
updateNodeInExpression(expression, conditionId, payload);
return expression;
};
}

View file

@ -1,4 +1,4 @@
import { cloneDeep } from "lodash-es";
import { cloneDeep, isEqual } from "lodash-es";
import { action, computed, makeObservable, observable, toJS } from "mobx";
import { computedFn } from "mobx-utils";
import { v4 as uuidv4 } from "uuid";
@ -101,6 +101,12 @@ export interface IFilterInstance<P extends TFilterProperty, E extends TExternalF
condition: TFilterConditionPayload<P, V>,
isNegation: boolean
) => void;
updateConditionProperty: (
conditionId: string,
property: P,
operator: TSupportedOperators,
isNegation: boolean
) => void;
updateConditionOperator: (conditionId: string, operator: TSupportedOperators, isNegation: boolean) => void;
updateConditionValue: <V extends TFilterValue>(conditionId: string, value: SingleOrArray<V>) => void;
removeCondition: (conditionId: string) => void;
@ -360,6 +366,33 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
}
});
/**
* Updates the property of a condition in the filter expression.
* @param conditionId - The id of the condition to update.
* @param property - The new property for the condition.
*/
updateConditionProperty: IFilterInstance<P, E>["updateConditionProperty"] = action(
(conditionId: string, property: P, operator: TSupportedOperators, isNegation: boolean) => {
if (!this.expression) return;
const conditionBeforeUpdate = cloneDeep(findNodeById(this.expression, conditionId));
if (!conditionBeforeUpdate || conditionBeforeUpdate.type !== FILTER_NODE_TYPE.CONDITION) return;
// Update the condition property
const updatedExpression = this.helper.handleConditionPropertyUpdate(
this.expression,
conditionId,
property,
operator,
isNegation
);
if (updatedExpression) {
this.expression = updatedExpression;
this._notifyExpressionChange();
}
}
);
/**
* Updates the operator of a condition in the filter expression.
* @param conditionId - The id of the condition to update.
@ -410,12 +443,23 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
// If the expression is not valid, return
if (!this.expression) return;
// Get the condition before update
const conditionBeforeUpdate = cloneDeep(findNodeById(this.expression, conditionId));
// If the condition is not valid, return
if (!conditionBeforeUpdate || conditionBeforeUpdate.type !== FILTER_NODE_TYPE.CONDITION) return;
// If the value is not valid, remove the condition
if (!hasValidValue(value)) {
this.removeCondition(conditionId);
return;
}
// If the value is the same as the condition before update, return
if (isEqual(conditionBeforeUpdate.value, value)) {
return;
}
// Update the condition value
updateNodeInExpression(this.expression, conditionId, {
value,

View file

@ -2,6 +2,7 @@
import { isEmpty } from "lodash-es";
import {
LOGICAL_OPERATOR,
MULTI_VALUE_OPERATORS,
SingleOrArray,
TFilterExpression,
TFilterValue,
@ -161,7 +162,8 @@ class WorkItemFiltersAdapter extends FilterAdapter<TWorkItemFilterProperty, TWor
const operator = key.substring(lastDoubleUnderscoreIndex + 2);
// Validate property is in allowed list
if (!WORK_ITEM_FILTER_PROPERTY_KEYS.includes(property as TWorkItemFilterProperty)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (!WORK_ITEM_FILTER_PROPERTY_KEYS.includes(property as any) && !property.startsWith("customproperty_")) {
return false;
}
@ -192,17 +194,12 @@ class WorkItemFiltersAdapter extends FilterAdapter<TWorkItemFilterProperty, TWor
// Find the last occurrence of '__' to separate property from operator
const lastDoubleUnderscoreIndex = key.lastIndexOf("__");
const property = key.substring(0, lastDoubleUnderscoreIndex);
const operator = key.substring(lastDoubleUnderscoreIndex + 2);
const operator = key.substring(lastDoubleUnderscoreIndex + 2) as TSupportedOperators;
const rawValue = data[key as TWorkItemFilterConditionKey];
if (typeof rawValue !== "string") {
console.error(`Filter value must be a string, got: ${typeof rawValue}`);
return null;
}
// Parse comma-separated values
const parsedValue = this._parseFilterValue(rawValue);
const parsedValue = MULTI_VALUE_OPERATORS.includes(operator) ? this._parseFilterValue(rawValue) : rawValue;
return [property as TWorkItemFilterProperty, operator as TSupportedOperators, parsedValue];
};
@ -212,7 +209,9 @@ class WorkItemFiltersAdapter extends FilterAdapter<TWorkItemFilterProperty, TWor
* @param value - The string value to parse
* @returns Parsed value as string or array of strings
*/
private _parseFilterValue = (value: string): SingleOrArray<TFilterValue> => {
private _parseFilterValue = (value: TFilterValue): SingleOrArray<TFilterValue> => {
if (!value) return value;
if (typeof value !== "string") return value;
// Handle empty string

View file

@ -15,4 +15,6 @@ export type TFilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
isEnabled: boolean;
allowMultipleFilters?: boolean;
supportedOperatorConfigsMap: TOperatorConfigMap<V>;
rightContent?: React.ReactNode; // content to display on the right side of the filter option in the dropdown
tooltipContent?: React.ReactNode; // content to display when hovering over the applied filter item in the filter list
};

View file

@ -26,6 +26,11 @@ export const CORE_COMPARISON_OPERATOR = {
RANGE: "range",
} as const;
/**
* Core operators that support multiple values
*/
export const CORE_MULTI_VALUE_OPERATORS = [CORE_COLLECTION_OPERATOR.IN, CORE_COMPARISON_OPERATOR.RANGE] as const;
/**
* All core operators
*/

View file

@ -18,6 +18,11 @@ export const EXTENDED_COLLECTION_OPERATOR = {} as const;
*/
export const EXTENDED_COMPARISON_OPERATOR = {} as const;
/**
* Extended operators that support multiple values
*/
export const EXTENDED_MULTI_VALUE_OPERATORS = [] as const;
/**
* All extended operators
*/

View file

@ -4,6 +4,7 @@ import {
CORE_COLLECTION_OPERATOR,
CORE_COMPARISON_OPERATOR,
TCoreSupportedOperators,
CORE_MULTI_VALUE_OPERATORS,
} from "./core";
import {
EXTENDED_LOGICAL_OPERATOR,
@ -11,6 +12,7 @@ import {
EXTENDED_COLLECTION_OPERATOR,
EXTENDED_COMPARISON_OPERATOR,
TExtendedSupportedOperators,
EXTENDED_MULTI_VALUE_OPERATORS,
} from "./extended";
// -------- COMPOSED OPERATORS --------
@ -35,6 +37,11 @@ export const COMPARISON_OPERATOR = {
...EXTENDED_COMPARISON_OPERATOR,
} as const;
export const MULTI_VALUE_OPERATORS: ReadonlyArray<TSupportedOperators> = [
...CORE_MULTI_VALUE_OPERATORS,
...EXTENDED_MULTI_VALUE_OPERATORS,
] as const;
// -------- COMPOSED TYPES --------
export type TLogicalOperator = (typeof LOGICAL_OPERATOR)[keyof typeof LOGICAL_OPERATOR];

View file

@ -100,13 +100,15 @@ export const WORK_ITEM_FILTER_PROPERTY_KEYS = [
"cycle_id",
"module_id",
"project_id",
"created_at",
"updated_at",
] as const;
export type TWorkItemFilterProperty = (typeof WORK_ITEM_FILTER_PROPERTY_KEYS)[number];
export type TWorkItemFilterConditionKey = `${TWorkItemFilterProperty}__${TSupportedOperators}`;
export type TWorkItemFilterConditionData = Partial<{
[K in TWorkItemFilterConditionKey]: string;
[K in TWorkItemFilterConditionKey]: string | boolean | number;
}>;
export type TWorkItemFilterAndGroup = {

View file

@ -139,14 +139,14 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
<Combobox.Options data-prevent-outside-click static>
<div
className={cn(
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-48 whitespace-nowrap z-30",
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-48 whitespace-nowrap z-30",
optionsClassName
)}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div className="flex items-center gap-1.5 rounded border border-custom-border-100 bg-custom-background-90 px-2">
<div className="flex items-center gap-1.5 rounded border border-custom-border-100 bg-custom-background-90 px-2 mx-2">
<Search className="h-3.5 w-3.5 text-custom-text-400" strokeWidth={1.5} />
<Combobox.Input
className="w-full bg-transparent py-1 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
@ -157,12 +157,13 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
/>
</div>
<div
className={cn("mt-2 space-y-1 overflow-y-scroll", {
className={cn("mt-2 px-2 space-y-1 overflow-y-scroll vertical-scrollbar scrollbar-xs", {
"max-h-96": maxHeight === "2xl",
"max-h-80": maxHeight === "xl",
"max-h-60": maxHeight === "lg",
"max-h-48": maxHeight === "md",
"max-h-36": maxHeight === "rg",
"max-h-28": maxHeight === "sm",
"max-h-full": maxHeight === "full",
})}
>
{filteredOptions ? (

View file

@ -24,7 +24,7 @@ export interface IDropdownProps {
disabled?: boolean;
input?: boolean;
label?: string | React.ReactNode;
maxHeight?: "sm" | "rg" | "md" | "lg" | "full";
maxHeight?: "sm" | "rg" | "md" | "lg" | "xl" | "2xl";
noChevron?: boolean;
chevronClassName?: string;
onOpen?: () => void;

View file

@ -14,6 +14,7 @@ export * from "./file";
export * from "./filter";
export * from "./get-icon-for-link";
export * from "./intake";
export * from "./loader";
export * from "./math";
export * from "./module";
export * from "./notification";

View file

@ -0,0 +1,4 @@
import { TLoader } from "@plane/types";
// checks if a loader has finished initialization
export const isLoaderReady = (loader: TLoader | undefined) => loader !== "init-loader";

View file

@ -1,2 +1,3 @@
export * from "./core";
export * from "./shared";
export * from "./properties";

View file

@ -0,0 +1,27 @@
// plane imports
import { TFilterProperty } from "@plane/types";
// local imports
import { createFilterConfig, TCreateDateFilterParams, TCreateFilterConfig } from "../shared";
import { getSupportedDateOperators, TCustomPropertyFilterParams } from "./shared";
/**
* Date property filter specific params
*/
export type TCreateDatePropertyFilterParams = TCustomPropertyFilterParams<Date> & TCreateDateFilterParams;
/**
* Get the date property filter config
* @param params - The filter params
* @returns The date property filter config
*/
export const getDatePropertyFilterConfig =
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateDatePropertyFilterParams> =>
(params: TCreateDatePropertyFilterParams) =>
createFilterConfig({
id: key,
...params,
label: params.propertyDisplayName,
icon: params.filterIcon,
allowMultipleFilters: true,
supportedOperatorConfigsMap: getSupportedDateOperators(params),
});

View file

@ -0,0 +1,3 @@
export * from "./date";
export * from "./member-picker";
export * from "./shared";

View file

@ -0,0 +1,30 @@
// plane imports
import { EQUALITY_OPERATOR, IUserLite, TFilterProperty } from "@plane/types";
// local imports
import { createFilterConfig, createOperatorConfigEntry, TCreateFilterConfig } from "../shared";
import { getMemberMultiSelectConfig, TCreateUserFilterParams, TCustomPropertyFilterParams } from "./shared";
/**
* Member picker property filter specific params
*/
type TCreateMemberPickerPropertyFilterParams = TCustomPropertyFilterParams<IUserLite> & TCreateUserFilterParams;
/**
* Get the member picker property filter config
* @param params - The filter params
* @returns The member picker property filter config
*/
export const getMemberPickerPropertyFilterConfig =
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateMemberPickerPropertyFilterParams> =>
(params: TCreateMemberPickerPropertyFilterParams) =>
createFilterConfig({
id: key,
...params,
label: params.propertyDisplayName,
icon: params.filterIcon,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(EQUALITY_OPERATOR.EXACT, params, (updatedParams) =>
getMemberMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
),
]),
});

View file

@ -0,0 +1,96 @@
// plane imports
import {
COMPARISON_OPERATOR,
EQUALITY_OPERATOR,
IProject,
IUserLite,
TOperatorConfigMap,
TSupportedOperators,
} from "@plane/types";
// local imports
import { getDatePickerConfig, getDateRangePickerConfig, getMultiSelectConfig } from "../core";
import {
createOperatorConfigEntry,
IFilterIconConfig,
TCreateDateFilterParams,
TCreateFilterConfigParams,
} from "../shared";
// ------------ Base User Filter Types ------------
/**
* User filter specific params
*/
export type TCreateUserFilterParams = TCreateFilterConfigParams &
IFilterIconConfig<IUserLite> & {
members: IUserLite[];
};
/**
* Helper to get the member multi select config
* @param params - The filter params
* @returns The member multi select config
*/
export const getMemberMultiSelectConfig = (params: TCreateUserFilterParams, singleValueOperator: TSupportedOperators) =>
getMultiSelectConfig<IUserLite, string, IUserLite>(
{
items: params.members,
getId: (member) => member.id,
getLabel: (member) => member.display_name,
getValue: (member) => member.id,
getIconData: (member) => member,
},
{
singleValueOperator,
...params,
},
{
...params,
}
);
// ------------ Date Operators ------------
export const getSupportedDateOperators = (params: TCreateDateFilterParams): TOperatorConfigMap<Date> =>
new Map([
createOperatorConfigEntry(EQUALITY_OPERATOR.EXACT, params, (updatedParams) => getDatePickerConfig(updatedParams)),
createOperatorConfigEntry(COMPARISON_OPERATOR.RANGE, params, (updatedParams) =>
getDateRangePickerConfig(updatedParams)
),
]);
// ------------ Project filter ------------
/**
* Project filter specific params
*/
export type TCreateProjectFilterParams = TCreateFilterConfigParams &
IFilterIconConfig<IProject> & {
projects: IProject[];
};
/**
* Helper to get the project multi select config
* @param params - The filter params
* @returns The member multi select config
*/
export const getProjectMultiSelectConfig = (
params: TCreateProjectFilterParams,
singleValueOperator: TSupportedOperators
) =>
getMultiSelectConfig<IProject, string, IProject>(
{
items: params.projects,
getId: (project) => project.id,
getLabel: (project) => project.name,
getValue: (project) => project.id,
getIconData: (project) => project,
},
{
singleValueOperator,
...params,
},
{
...params,
}
);

View file

@ -29,14 +29,21 @@ export const createFilterConfig = <P extends TFilterProperty, V extends TFilterV
export type TCreateFilterConfigParams = Omit<TBaseFilterFieldConfig, "isOperatorEnabled"> & {
isEnabled: boolean;
allowedOperators: Set<TSupportedOperators>;
rightContent?: React.ReactNode; // content to display on the right side of the filter option in the dropdown
tooltipContent?: React.ReactNode; // content to display when hovering over the applied filter item in the filter list
};
/**
* Type for filter icon type
*/
export type TFilterIconType = string | number | boolean | object | undefined;
/**
* Icon configuration for filters and their options.
* - filterIcon: Optional icon for the filter
* - getOptionIcon: Function to get icon for specific option values
*/
export interface IFilterIconConfig<T extends string | number | boolean | object | undefined = undefined> {
export interface IFilterIconConfig<T extends TFilterIconType = undefined> {
filterIcon?: React.FC<React.SVGAttributes<SVGElement>>;
getOptionIcon?: (value: T) => React.ReactNode;
}

View file

@ -1,3 +1,4 @@
export * from "./configs/core";
export * from "./configs/shared";
export * from "./configs/properties";
export * from "./nodes/core";

View file

@ -60,8 +60,8 @@ export const getCycleFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "Cycle",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getCycleMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)

View file

@ -1,8 +1,12 @@
// plane imports
import { TFilterProperty } from "@plane/types";
// local imports
import { createFilterConfig, TCreateFilterConfig, TCreateDateFilterParams } from "../../../rich-filters";
import { getSupportedDateOperators } from "./shared";
import {
createFilterConfig,
TCreateFilterConfig,
TCreateDateFilterParams,
getSupportedDateOperators,
} from "../../../rich-filters";
// ------------ Date filters ------------
@ -18,8 +22,8 @@ export const getStartDateFilterConfig =
createFilterConfig<P, Date>({
id: key,
label: "Start date",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
allowMultipleFilters: true,
supportedOperatorConfigsMap: getSupportedDateOperators(params),
});
@ -36,8 +40,44 @@ export const getTargetDateFilterConfig =
createFilterConfig<P, Date>({
id: key,
label: "Target date",
...params,
icon: params.filterIcon,
allowMultipleFilters: true,
supportedOperatorConfigsMap: getSupportedDateOperators(params),
});
/**
* Get the created at filter config
* @template K - The filter key
* @param key - The filter key to use
* @returns A function that takes parameters and returns the created at filter config
*/
export const getCreatedAtFilterConfig =
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateDateFilterParams> =>
(params: TCreateDateFilterParams) =>
createFilterConfig<P, Date>({
id: key,
label: "Created at",
...params,
icon: params.filterIcon,
allowMultipleFilters: true,
supportedOperatorConfigsMap: getSupportedDateOperators(params),
});
/**
* Get the updated at filter config
* @template K - The filter key
* @param key - The filter key to use
* @returns A function that takes parameters and returns the updated at filter config
*/
export const getUpdatedAtFilterConfig =
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateDateFilterParams> =>
(params: TCreateDateFilterParams) =>
createFilterConfig<P, Date>({
id: key,
label: "Updated at",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
allowMultipleFilters: true,
supportedOperatorConfigsMap: getSupportedDateOperators(params),
});

View file

@ -59,8 +59,8 @@ export const getLabelFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "Label",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getLabelMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)

View file

@ -53,8 +53,8 @@ export const getModuleFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "Module",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getModuleMultiSelectConfig(updatedParams)

View file

@ -56,8 +56,8 @@ export const getPriorityFilterConfig =
createFilterConfig<P, TIssuePriorities>({
id: key,
label: "Priority",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getPriorityMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)

View file

@ -1,8 +1,13 @@
// plane imports
import { EQUALITY_OPERATOR, TFilterProperty, COLLECTION_OPERATOR } from "@plane/types";
// local imports
import { createFilterConfig, createOperatorConfigEntry, TCreateFilterConfig } from "../../../rich-filters";
import { getProjectMultiSelectConfig, TCreateProjectFilterParams } from "./shared";
import {
createFilterConfig,
createOperatorConfigEntry,
getProjectMultiSelectConfig,
TCreateFilterConfig,
TCreateProjectFilterParams,
} from "../../../rich-filters";
// ------------ Project filter ------------
@ -18,8 +23,8 @@ export const getProjectFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "Projects",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getProjectMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)

View file

@ -63,8 +63,8 @@ export const getStateGroupFilterConfig =
createFilterConfig<P, TStateGroups>({
id: key,
label: "State Group",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getStateGroupMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
@ -87,7 +87,7 @@ export type TCreateStateFilterParams = TCreateFilterConfigParams &
* @param params - The filter params
* @returns The state multi select config
*/
export const getStateMultiSelectConfig = (params: TCreateStateFilterParams) =>
export const getStateMultiSelectConfig = (params: TCreateStateFilterParams, singleValueOperator: TSupportedOperators) =>
getMultiSelectConfig<IState, string, IState>(
{
items: params.states,
@ -97,7 +97,7 @@ export const getStateMultiSelectConfig = (params: TCreateStateFilterParams) =>
getIconData: (state) => state,
},
{
singleValueOperator: EQUALITY_OPERATOR.EXACT,
singleValueOperator,
...params,
},
{
@ -117,11 +117,11 @@ export const getStateFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "State",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getStateMultiSelectConfig(updatedParams)
getStateMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
),
]),
});

View file

@ -1,48 +1,14 @@
// plane imports
import { EQUALITY_OPERATOR, IUserLite, TFilterProperty, COLLECTION_OPERATOR } from "@plane/types";
import { EQUALITY_OPERATOR, TFilterProperty, COLLECTION_OPERATOR } from "@plane/types";
// local imports
import {
createFilterConfig,
TCreateFilterConfigParams,
IFilterIconConfig,
TCreateFilterConfig,
getMultiSelectConfig,
createOperatorConfigEntry,
getMemberMultiSelectConfig,
TCreateUserFilterParams,
} from "../../../rich-filters";
// ------------ Base User Filter Types ------------
/**
* User filter specific params
*/
export type TCreateUserFilterParams = TCreateFilterConfigParams &
IFilterIconConfig<IUserLite> & {
members: IUserLite[];
};
/**
* Helper to get the member multi select config
* @param params - The filter params
* @returns The member multi select config
*/
export const getMemberMultiSelectConfig = (params: TCreateUserFilterParams) =>
getMultiSelectConfig<IUserLite, string, IUserLite>(
{
items: params.members,
getId: (member) => member.id,
getLabel: (member) => member.display_name,
getValue: (member) => member.id,
getIconData: (member) => member,
},
{
singleValueOperator: EQUALITY_OPERATOR.EXACT,
...params,
},
{
...params,
}
);
// ------------ Assignee filter ------------
/**
@ -62,11 +28,11 @@ export const getAssigneeFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "Assignees",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getMemberMultiSelectConfig(updatedParams)
getMemberMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
),
]),
});
@ -90,11 +56,11 @@ export const getMentionFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "Mentions",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getMemberMultiSelectConfig(updatedParams)
getMemberMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
),
]),
});
@ -118,11 +84,11 @@ export const getCreatedByFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "Created by",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getMemberMultiSelectConfig(updatedParams)
getMemberMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
),
]),
});
@ -146,11 +112,11 @@ export const getSubscriberFilterConfig =
createFilterConfig<P, string>({
id: key,
label: "Subscriber",
...params,
icon: params.filterIcon,
isEnabled: params.isEnabled,
supportedOperatorConfigsMap: new Map([
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
getMemberMultiSelectConfig(updatedParams)
getMemberMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
),
]),
});