[WEB-4969] feat: add toggle button for work item filters row visibility (#7865)
* [WEB-4969] feat: add toggle button for work item filters row visibility * fix: improve error handling in filter update and refine visibility condition check * chore: correct visibility toggle parameter in filter store
This commit is contained in:
parent
992457efd2
commit
7ce21a6488
31 changed files with 394 additions and 141 deletions
|
|
@ -58,7 +58,7 @@ export class FilterConfigManager<P extends TFilterProperty, E extends TExternalF
|
|||
filterConfigs: IFilterConfigManager<P>["filterConfigs"];
|
||||
configOptions: IFilterConfigManager<P>["configOptions"];
|
||||
// parent filter instance
|
||||
_filterInstance: IFilterInstance<P, E>;
|
||||
private _filterInstance: IFilterInstance<P, E>;
|
||||
|
||||
/**
|
||||
* Creates a new FilterConfigManager instance.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { cloneDeep } from "lodash-es";
|
||||
import { toJS } from "mobx";
|
||||
import { action, makeObservable, observable, toJS } from "mobx";
|
||||
// plane imports
|
||||
import { DEFAULT_FILTER_EXPRESSION_OPTIONS, TExpressionOptions } from "@plane/constants";
|
||||
import { DEFAULT_FILTER_EXPRESSION_OPTIONS, TAutoVisibilityOptions, TExpressionOptions } from "@plane/constants";
|
||||
import {
|
||||
IFilterAdapter,
|
||||
LOGICAL_OPERATOR,
|
||||
|
|
@ -14,6 +14,12 @@ import {
|
|||
TFilterConditionPayload,
|
||||
} from "@plane/types";
|
||||
import { addAndCondition, createConditionNode, updateNodeInExpression } from "@plane/utils";
|
||||
// local imports
|
||||
import { type IFilterInstance } from "./filter";
|
||||
|
||||
type TFilterInstanceHelperParams<P extends TFilterProperty, E extends TExternalFilter> = {
|
||||
adapter: IFilterAdapter<P, E>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for filter instance helper utilities.
|
||||
|
|
@ -23,9 +29,13 @@ import { addAndCondition, createConditionNode, updateNodeInExpression } from "@p
|
|||
* @template E - The external filter type extending TExternalFilter
|
||||
*/
|
||||
export interface IFilterInstanceHelper<P extends TFilterProperty, E extends TExternalFilter> {
|
||||
isVisible: boolean;
|
||||
// initialization
|
||||
initializeExpression: (initialExpression?: E) => TFilterExpression<P> | null;
|
||||
initializeExpressionOptions: (expressionOptions?: Partial<TExpressionOptions<E>>) => TExpressionOptions<E>;
|
||||
// visibility
|
||||
setInitialVisibility: (visibilityOption: TAutoVisibilityOptions) => void;
|
||||
toggleVisibility: (isVisible?: boolean) => void;
|
||||
// condition operations
|
||||
addConditionToExpression: <V extends TFilterValue>(
|
||||
expression: TFilterExpression<P> | null,
|
||||
|
|
@ -54,15 +64,28 @@ export interface IFilterInstanceHelper<P extends TFilterProperty, E extends TExt
|
|||
export class FilterInstanceHelper<P extends TFilterProperty, E extends TExternalFilter>
|
||||
implements IFilterInstanceHelper<P, E>
|
||||
{
|
||||
// parent filter instance
|
||||
private _filterInstance: IFilterInstance<P, E>;
|
||||
// adapter
|
||||
private adapter: IFilterAdapter<P, E>;
|
||||
// visibility
|
||||
isVisible: boolean;
|
||||
|
||||
/**
|
||||
* Creates a new FilterInstanceHelper instance.
|
||||
*
|
||||
* @param adapter - The filter adapter for converting between internal and external formats
|
||||
*/
|
||||
constructor(adapter: IFilterAdapter<P, E>) {
|
||||
this.adapter = adapter;
|
||||
constructor(filterInstance: IFilterInstance<P, E>, params: TFilterInstanceHelperParams<P, E>) {
|
||||
this._filterInstance = filterInstance;
|
||||
this.adapter = params.adapter;
|
||||
this.isVisible = false;
|
||||
|
||||
makeObservable(this, {
|
||||
isVisible: observable,
|
||||
setInitialVisibility: action,
|
||||
toggleVisibility: action,
|
||||
});
|
||||
}
|
||||
|
||||
// ------------ initialization ------------
|
||||
|
|
@ -87,6 +110,41 @@ export class FilterInstanceHelper<P extends TFilterProperty, E extends TExternal
|
|||
...expressionOptions,
|
||||
});
|
||||
|
||||
/**
|
||||
* Sets the initial visibility state for the filter based on options and active filters.
|
||||
* @param visibilityOption - The visibility options for the filter instance.
|
||||
* @returns The initial visibility state
|
||||
*/
|
||||
setInitialVisibility: IFilterInstanceHelper<P, E>["setInitialVisibility"] = action((visibilityOption) => {
|
||||
// If explicit initial visibility is provided, use it
|
||||
if (visibilityOption.autoSetVisibility === false) {
|
||||
this.isVisible = visibilityOption.isVisibleOnMount;
|
||||
return;
|
||||
}
|
||||
|
||||
// If filter has active filters, make it visible
|
||||
if (this._filterInstance.hasActiveFilters) {
|
||||
this.isVisible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Default to hidden if no active filters
|
||||
this.isVisible = false;
|
||||
return;
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the filter.
|
||||
* @param isVisible - The visibility to set.
|
||||
*/
|
||||
toggleVisibility: IFilterInstanceHelper<P, E>["toggleVisibility"] = action((isVisible) => {
|
||||
if (isVisible !== undefined) {
|
||||
this.isVisible = isVisible;
|
||||
return;
|
||||
}
|
||||
this.isVisible = !this.isVisible;
|
||||
});
|
||||
|
||||
// ------------ condition operations ------------
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { computedFn } from "mobx-utils";
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
// plane imports
|
||||
import {
|
||||
DEFAULT_FILTER_VISIBILITY_OPTIONS,
|
||||
TClearFilterOptions,
|
||||
TExpressionOptions,
|
||||
TFilterOptions,
|
||||
|
|
@ -71,6 +72,7 @@ export interface IFilterInstance<P extends TFilterProperty, E extends TExternalF
|
|||
// computed
|
||||
hasActiveFilters: boolean;
|
||||
hasChanges: boolean;
|
||||
isVisible: boolean;
|
||||
allConditions: TFilterConditionNode<P, TFilterValue>[];
|
||||
allConditionsForDisplay: TFilterConditionNodeForDisplay<P, TFilterValue>[];
|
||||
// computed option helpers
|
||||
|
|
@ -81,6 +83,8 @@ export interface IFilterInstance<P extends TFilterProperty, E extends TExternalF
|
|||
canClearFilters: boolean;
|
||||
canSaveView: boolean;
|
||||
canUpdateView: boolean;
|
||||
// visibility
|
||||
toggleVisibility: (isVisible?: boolean) => void;
|
||||
// filter expression actions
|
||||
resetExpression: (externalExpression: E, shouldResetInitialExpression?: boolean) => void;
|
||||
// filter condition
|
||||
|
|
@ -108,7 +112,7 @@ export interface IFilterInstance<P extends TFilterProperty, E extends TExternalF
|
|||
updateExpressionOptions: (newOptions: Partial<TExpressionOptions<E>>) => void;
|
||||
}
|
||||
|
||||
export type TFilterParams<P extends TFilterProperty, E extends TExternalFilter> = {
|
||||
type TFilterParams<P extends TFilterProperty, E extends TExternalFilter> = {
|
||||
adapter: IFilterAdapter<P, E>;
|
||||
options?: Partial<TFilterOptions<E>>;
|
||||
initialExpression?: E;
|
||||
|
|
@ -131,7 +135,9 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
|
|||
constructor(params: TFilterParams<P, E>) {
|
||||
this.id = uuidv4();
|
||||
this.adapter = params.adapter;
|
||||
this.helper = new FilterInstanceHelper<P, E>(this.adapter);
|
||||
this.helper = new FilterInstanceHelper<P, E>(this, {
|
||||
adapter: this.adapter,
|
||||
});
|
||||
this.configManager = new FilterConfigManager<P, E>(this, {
|
||||
options: params.options?.config,
|
||||
});
|
||||
|
|
@ -141,6 +147,7 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
|
|||
this.expression = cloneDeep(initialExpression);
|
||||
this.expressionOptions = this.helper.initializeExpressionOptions(params.options?.expression);
|
||||
this.onExpressionChange = params.onExpressionChange;
|
||||
this.helper.setInitialVisibility(params.options?.visibility ?? DEFAULT_FILTER_VISIBILITY_OPTIONS);
|
||||
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
|
|
@ -153,6 +160,7 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
|
|||
// computed
|
||||
hasActiveFilters: computed,
|
||||
hasChanges: computed,
|
||||
isVisible: computed,
|
||||
allConditions: computed,
|
||||
allConditionsForDisplay: computed,
|
||||
// computed option helpers
|
||||
|
|
@ -201,6 +209,14 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
|
|||
return !deepCompareFilterExpressions(this.initialFilterExpression, this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visibility of the filter instance.
|
||||
* @returns The visibility of the filter instance.
|
||||
*/
|
||||
get isVisible(): IFilterInstance<P, E>["isVisible"] {
|
||||
return this.helper.isVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all conditions from the filter expression.
|
||||
* @returns An array of filter conditions.
|
||||
|
|
@ -279,6 +295,14 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
|
|||
|
||||
// ------------ actions ------------
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the filter instance.
|
||||
* @param isVisible - The visibility to set.
|
||||
*/
|
||||
toggleVisibility: IFilterInstance<P, E>["toggleVisibility"] = action((isVisible) => {
|
||||
this.helper.toggleVisibility(isVisible);
|
||||
});
|
||||
|
||||
/**
|
||||
* Resets the filter expression to the initial expression.
|
||||
* @param externalExpression - The external expression to reset to.
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import { EIssuesStoreType, LOGICAL_OPERATOR, TWorkItemFilterExpression, TWorkIte
|
|||
import { getOperatorForPayload } from "@plane/utils";
|
||||
// local imports
|
||||
import { buildWorkItemFilterExpressionFromConditions, TWorkItemFilterCondition } from "../../utils";
|
||||
import { FilterInstance, IFilterInstance } from "../rich-filters/filter";
|
||||
import { FilterInstance } from "../rich-filters/filter";
|
||||
import { workItemFiltersAdapter } from "./adapter";
|
||||
import { IWorkItemFilterInstance, TWorkItemFilterKey } from "./shared";
|
||||
|
||||
type TGetOrCreateFilterParams = {
|
||||
showOnMount?: boolean;
|
||||
entityId: string;
|
||||
entityType: EIssuesStoreType;
|
||||
expressionOptions?: TExpressionOptions<TWorkItemFilterExpression>;
|
||||
|
|
@ -17,17 +19,10 @@ type TGetOrCreateFilterParams = {
|
|||
onExpressionChange?: (expression: TWorkItemFilterExpression) => void;
|
||||
};
|
||||
|
||||
type TWorkItemFilterKey = `${EIssuesStoreType}-${string}`;
|
||||
|
||||
export interface IWorkItemFilterStore {
|
||||
filters: Map<TWorkItemFilterKey, IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression>>; // key is the entity id (project, cycle, workspace, teamspace, etc)
|
||||
getFilter: (
|
||||
entityType: EIssuesStoreType,
|
||||
entityId: string
|
||||
) => IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression> | undefined;
|
||||
getOrCreateFilter: (
|
||||
params: TGetOrCreateFilterParams
|
||||
) => IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression>;
|
||||
filters: Map<TWorkItemFilterKey, IWorkItemFilterInstance>; // key is the entity id (project, cycle, workspace, teamspace, etc)
|
||||
getFilter: (entityType: EIssuesStoreType, entityId: string) => IWorkItemFilterInstance | undefined;
|
||||
getOrCreateFilter: (params: TGetOrCreateFilterParams) => IWorkItemFilterInstance;
|
||||
resetExpression: (entityType: EIssuesStoreType, entityId: string, expression: TWorkItemFilterExpression) => void;
|
||||
updateFilterExpressionFromConditions: (
|
||||
entityType: EIssuesStoreType,
|
||||
|
|
@ -48,7 +43,7 @@ export class WorkItemFilterStore implements IWorkItemFilterStore {
|
|||
filters: IWorkItemFilterStore["filters"];
|
||||
|
||||
constructor() {
|
||||
this.filters = new Map<TWorkItemFilterKey, IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression>>();
|
||||
this.filters = new Map<TWorkItemFilterKey, IWorkItemFilterInstance>();
|
||||
makeObservable(this, {
|
||||
filters: observable,
|
||||
getOrCreateFilter: action,
|
||||
|
|
@ -87,12 +82,17 @@ export class WorkItemFilterStore implements IWorkItemFilterStore {
|
|||
if (params.onExpressionChange) {
|
||||
existingFilter.onExpressionChange = params.onExpressionChange;
|
||||
}
|
||||
// Update visibility if provided
|
||||
if (params.showOnMount !== undefined) {
|
||||
existingFilter.toggleVisibility(params.showOnMount);
|
||||
}
|
||||
return existingFilter;
|
||||
}
|
||||
|
||||
// create new filter instance
|
||||
const newFilter = this._initializeFilterInstance(params);
|
||||
this.filters.set(this._getFilterKey(params.entityType, params.entityId), newFilter);
|
||||
const filterKey = this._getFilterKey(params.entityType, params.entityId);
|
||||
this.filters.set(filterKey, newFilter);
|
||||
|
||||
return newFilter;
|
||||
});
|
||||
|
|
@ -210,6 +210,9 @@ export class WorkItemFilterStore implements IWorkItemFilterStore {
|
|||
onExpressionChange: params.onExpressionChange,
|
||||
options: {
|
||||
expression: params.expressionOptions,
|
||||
visibility: params.showOnMount
|
||||
? { autoSetVisibility: false, isVisibleOnMount: true }
|
||||
: { autoSetVisibility: true },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./adapter";
|
||||
export * from "./filter.store";
|
||||
export * from "./shared";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
// plane imports
|
||||
import { EIssuesStoreType, TWorkItemFilterExpression, TWorkItemFilterProperty } from "@plane/types";
|
||||
// local imports
|
||||
import { IFilterInstance } from "../rich-filters";
|
||||
|
||||
export type TWorkItemFilterKey = `${EIssuesStoreType}-${string}`;
|
||||
|
||||
export type IWorkItemFilterInstance = IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue