[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

@ -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)
),
]),
});