[WEB-4951] [WEB-4884] feat: work item filters revamp (#7810)
This commit is contained in:
parent
e6a7ca4c72
commit
9aef5d4aa9
160 changed files with 5879 additions and 4881 deletions
16
apps/web/ce/helpers/work-item-filters/project-level.ts
Normal file
16
apps/web/ce/helpers/work-item-filters/project-level.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { TWorkItemFiltersEntityProps } from "@/plane-web/hooks/work-item-filters/use-work-item-filters-config";
|
||||
|
||||
export type TGetAdditionalPropsForProjectLevelFiltersHOCParams = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TGetAdditionalPropsForProjectLevelFiltersHOC = (
|
||||
params: TGetAdditionalPropsForProjectLevelFiltersHOCParams
|
||||
) => TWorkItemFiltersEntityProps;
|
||||
|
||||
export const getAdditionalProjectLevelFiltersHOCProps: TGetAdditionalPropsForProjectLevelFiltersHOC = ({
|
||||
workspaceSlug,
|
||||
}) => ({
|
||||
workspaceSlug,
|
||||
});
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { CORE_OPERATORS, TSupportedOperators } from "@plane/types";
|
||||
|
||||
export type TFiltersOperatorConfigs = {
|
||||
allowedOperators: Set<TSupportedOperators>;
|
||||
allowNegative: boolean;
|
||||
};
|
||||
|
||||
export type TUseFiltersOperatorConfigsProps = {
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const useFiltersOperatorConfigs = (_props: TUseFiltersOperatorConfigsProps): TFiltersOperatorConfigs => ({
|
||||
allowedOperators: new Set(Object.values(CORE_OPERATORS)),
|
||||
allowNegative: false,
|
||||
});
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
import { useCallback, useMemo } from "react";
|
||||
import {
|
||||
AtSign,
|
||||
Briefcase,
|
||||
CalendarCheck2,
|
||||
CalendarClock,
|
||||
CircleUserRound,
|
||||
SignalHigh,
|
||||
Tag,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
// plane imports
|
||||
import {
|
||||
ContrastIcon,
|
||||
CycleGroupIcon,
|
||||
DiceIcon,
|
||||
DoubleCircleIcon,
|
||||
PriorityIcon,
|
||||
StateGroupIcon,
|
||||
} from "@plane/propel/icons";
|
||||
import {
|
||||
ICycle,
|
||||
IState,
|
||||
IUserLite,
|
||||
TFilterConfig,
|
||||
TFilterValue,
|
||||
IIssueLabel,
|
||||
IModule,
|
||||
IProject,
|
||||
TWorkItemFilterProperty,
|
||||
} from "@plane/types";
|
||||
import { Avatar, Logo } from "@plane/ui";
|
||||
import {
|
||||
getAssigneeFilterConfig,
|
||||
getCreatedByFilterConfig,
|
||||
getCycleFilterConfig,
|
||||
getFileURL,
|
||||
getLabelFilterConfig,
|
||||
getMentionFilterConfig,
|
||||
getModuleFilterConfig,
|
||||
getPriorityFilterConfig,
|
||||
getProjectFilterConfig,
|
||||
getStartDateFilterConfig,
|
||||
getStateFilterConfig,
|
||||
getStateGroupFilterConfig,
|
||||
getSubscriberFilterConfig,
|
||||
getTargetDateFilterConfig,
|
||||
} from "@plane/utils";
|
||||
// store hooks
|
||||
import { useCycle } from "@/hooks/store/use-cycle";
|
||||
import { useLabel } from "@/hooks/store/use-label";
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
import { useModule } from "@/hooks/store/use-module";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useProjectState } from "@/hooks/store/use-project-state";
|
||||
// plane web imports
|
||||
import { useFiltersOperatorConfigs } from "@/plane-web/hooks/rich-filters/use-filters-operator-configs";
|
||||
|
||||
export type TWorkItemFiltersEntityProps = {
|
||||
workspaceSlug: string;
|
||||
cycleIds?: string[];
|
||||
labelIds?: string[];
|
||||
memberIds?: string[];
|
||||
moduleIds?: string[];
|
||||
projectId?: string;
|
||||
projectIds?: string[];
|
||||
stateIds?: string[];
|
||||
};
|
||||
|
||||
export type TUseWorkItemFiltersConfigProps = {
|
||||
allowedFilters: TWorkItemFilterProperty[];
|
||||
} & TWorkItemFiltersEntityProps;
|
||||
|
||||
export type TWorkItemFiltersConfig = {
|
||||
configs: TFilterConfig<TWorkItemFilterProperty, TFilterValue>[];
|
||||
configMap: {
|
||||
[key in TWorkItemFilterProperty]?: TFilterConfig<TWorkItemFilterProperty, TFilterValue>;
|
||||
};
|
||||
isFilterEnabled: (key: TWorkItemFilterProperty) => boolean;
|
||||
};
|
||||
|
||||
export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps): TWorkItemFiltersConfig => {
|
||||
const { allowedFilters, cycleIds, labelIds, memberIds, moduleIds, projectId, projectIds, stateIds, workspaceSlug } =
|
||||
props;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { getCycleById } = useCycle();
|
||||
const { getLabelById } = useLabel();
|
||||
const { getModuleById } = useModule();
|
||||
const { getStateById } = useProjectState();
|
||||
const { getUserDetails } = useMember();
|
||||
// derived values
|
||||
const operatorConfigs = useFiltersOperatorConfigs({ workspaceSlug });
|
||||
const filtersToShow = useMemo(() => new Set(allowedFilters), [allowedFilters]);
|
||||
const project = useMemo(() => getProjectById(projectId), [projectId, getProjectById]);
|
||||
const members: IUserLite[] | undefined = useMemo(
|
||||
() =>
|
||||
memberIds
|
||||
? (memberIds.map((memberId) => getUserDetails(memberId)).filter((member) => member) as IUserLite[])
|
||||
: undefined,
|
||||
[memberIds, getUserDetails]
|
||||
);
|
||||
const workItemStates: IState[] | undefined = useMemo(
|
||||
() =>
|
||||
stateIds ? (stateIds.map((stateId) => getStateById(stateId)).filter((state) => state) as IState[]) : undefined,
|
||||
[stateIds, getStateById]
|
||||
);
|
||||
const workItemLabels: IIssueLabel[] | undefined = useMemo(
|
||||
() =>
|
||||
labelIds
|
||||
? (labelIds.map((labelId) => getLabelById(labelId)).filter((label) => label) as IIssueLabel[])
|
||||
: undefined,
|
||||
[labelIds, getLabelById]
|
||||
);
|
||||
const cycles = useMemo(
|
||||
() => (cycleIds ? (cycleIds.map((cycleId) => getCycleById(cycleId)).filter((cycle) => cycle) as ICycle[]) : []),
|
||||
[cycleIds, getCycleById]
|
||||
);
|
||||
const modules = useMemo(
|
||||
() =>
|
||||
moduleIds ? (moduleIds.map((moduleId) => getModuleById(moduleId)).filter((module) => module) as IModule[]) : [],
|
||||
[moduleIds, getModuleById]
|
||||
);
|
||||
const projects = useMemo(
|
||||
() =>
|
||||
projectIds
|
||||
? (projectIds.map((projectId) => getProjectById(projectId)).filter((project) => project) as IProject[])
|
||||
: [],
|
||||
[projectIds, getProjectById]
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks if a filter is enabled based on the filters to show.
|
||||
* @param key - The filter key.
|
||||
* @param level - The level of the filter.
|
||||
* @returns True if the filter is enabled, false otherwise.
|
||||
*/
|
||||
const isFilterEnabled = useCallback((key: TWorkItemFilterProperty) => filtersToShow.has(key), [filtersToShow]);
|
||||
|
||||
// state group filter config
|
||||
const stateGroupFilterConfig = useMemo(
|
||||
() =>
|
||||
getStateGroupFilterConfig<TWorkItemFilterProperty>("state_group")({
|
||||
isEnabled: isFilterEnabled("state_group"),
|
||||
filterIcon: DoubleCircleIcon,
|
||||
getOptionIcon: (stateGroupKey) => <StateGroupIcon stateGroup={stateGroupKey} />,
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, operatorConfigs]
|
||||
);
|
||||
|
||||
// state filter config
|
||||
const stateFilterConfig = useMemo(
|
||||
() =>
|
||||
getStateFilterConfig<TWorkItemFilterProperty>("state_id")({
|
||||
isEnabled: isFilterEnabled("state_id") && workItemStates !== undefined,
|
||||
filterIcon: DoubleCircleIcon,
|
||||
getOptionIcon: (state) => <StateGroupIcon stateGroup={state.group} color={state.color} />,
|
||||
states: workItemStates ?? [],
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, workItemStates, operatorConfigs]
|
||||
);
|
||||
|
||||
// label filter config
|
||||
const labelFilterConfig = useMemo(
|
||||
() =>
|
||||
getLabelFilterConfig<TWorkItemFilterProperty>("label_id")({
|
||||
isEnabled: isFilterEnabled("label_id") && workItemLabels !== undefined,
|
||||
filterIcon: Tag,
|
||||
labels: workItemLabels ?? [],
|
||||
getOptionIcon: (color) => (
|
||||
<span className="flex flex-shrink-0 size-2.5 rounded-full" style={{ backgroundColor: color }} />
|
||||
),
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, workItemLabels, operatorConfigs]
|
||||
);
|
||||
|
||||
// cycle filter config
|
||||
const cycleFilterConfig = useMemo(
|
||||
() =>
|
||||
getCycleFilterConfig<TWorkItemFilterProperty>("cycle_id")({
|
||||
isEnabled: isFilterEnabled("cycle_id") && project?.cycle_view === true && cycles !== undefined,
|
||||
filterIcon: ContrastIcon,
|
||||
getOptionIcon: (cycleGroup) => <CycleGroupIcon cycleGroup={cycleGroup} className="h-3.5 w-3.5 flex-shrink-0" />,
|
||||
cycles: cycles ?? [],
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, project?.cycle_view, cycles, operatorConfigs]
|
||||
);
|
||||
|
||||
// module filter config
|
||||
const moduleFilterConfig = useMemo(
|
||||
() =>
|
||||
getModuleFilterConfig<TWorkItemFilterProperty>("module_id")({
|
||||
isEnabled: isFilterEnabled("module_id") && project?.module_view === true && modules !== undefined,
|
||||
filterIcon: DiceIcon,
|
||||
getOptionIcon: () => <DiceIcon className="h-3 w-3 flex-shrink-0" />,
|
||||
modules: modules ?? [],
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, project?.module_view, modules, operatorConfigs]
|
||||
);
|
||||
|
||||
// assignee filter config
|
||||
const assigneeFilterConfig = useMemo(
|
||||
() =>
|
||||
getAssigneeFilterConfig<TWorkItemFilterProperty>("assignee_id")({
|
||||
isEnabled: isFilterEnabled("assignee_id") && members !== undefined,
|
||||
filterIcon: Users,
|
||||
members: members ?? [],
|
||||
getOptionIcon: (memberDetails) => (
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, members, operatorConfigs]
|
||||
);
|
||||
|
||||
// mention filter config
|
||||
const mentionFilterConfig = useMemo(
|
||||
() =>
|
||||
getMentionFilterConfig<TWorkItemFilterProperty>("mention_id")({
|
||||
isEnabled: isFilterEnabled("mention_id") && members !== undefined,
|
||||
filterIcon: AtSign,
|
||||
members: members ?? [],
|
||||
getOptionIcon: (memberDetails) => (
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, members, operatorConfigs]
|
||||
);
|
||||
|
||||
// created by filter config
|
||||
const createdByFilterConfig = useMemo(
|
||||
() =>
|
||||
getCreatedByFilterConfig<TWorkItemFilterProperty>("created_by_id")({
|
||||
isEnabled: isFilterEnabled("created_by_id") && members !== undefined,
|
||||
filterIcon: CircleUserRound,
|
||||
members: members ?? [],
|
||||
getOptionIcon: (memberDetails) => (
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, members, operatorConfigs]
|
||||
);
|
||||
|
||||
// subscriber filter config
|
||||
const subscriberFilterConfig = useMemo(
|
||||
() =>
|
||||
getSubscriberFilterConfig<TWorkItemFilterProperty>("subscriber_id")({
|
||||
isEnabled: isFilterEnabled("subscriber_id") && members !== undefined,
|
||||
filterIcon: Users,
|
||||
members: members ?? [],
|
||||
getOptionIcon: (memberDetails) => (
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, members, operatorConfigs]
|
||||
);
|
||||
|
||||
// priority filter config
|
||||
const priorityFilterConfig = useMemo(
|
||||
() =>
|
||||
getPriorityFilterConfig<TWorkItemFilterProperty>("priority")({
|
||||
isEnabled: isFilterEnabled("priority"),
|
||||
filterIcon: SignalHigh,
|
||||
getOptionIcon: (priority) => <PriorityIcon priority={priority} />,
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, operatorConfigs]
|
||||
);
|
||||
|
||||
// start date filter config
|
||||
const startDateFilterConfig = useMemo(
|
||||
() =>
|
||||
getStartDateFilterConfig<TWorkItemFilterProperty>("start_date")({
|
||||
isEnabled: true,
|
||||
filterIcon: CalendarClock,
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[operatorConfigs]
|
||||
);
|
||||
|
||||
// target date filter config
|
||||
const targetDateFilterConfig = useMemo(
|
||||
() =>
|
||||
getTargetDateFilterConfig<TWorkItemFilterProperty>("target_date")({
|
||||
isEnabled: true,
|
||||
filterIcon: CalendarCheck2,
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[operatorConfigs]
|
||||
);
|
||||
|
||||
// project filter config
|
||||
const projectFilterConfig = useMemo(
|
||||
() =>
|
||||
getProjectFilterConfig<TWorkItemFilterProperty>("project_id")({
|
||||
isEnabled: isFilterEnabled("project_id") && projects !== undefined,
|
||||
filterIcon: Briefcase,
|
||||
projects: projects,
|
||||
getOptionIcon: (project) => <Logo logo={project.logo_props} size={12} />,
|
||||
...operatorConfigs,
|
||||
}),
|
||||
[isFilterEnabled, projects, operatorConfigs]
|
||||
);
|
||||
|
||||
return {
|
||||
configs: [
|
||||
stateFilterConfig,
|
||||
stateGroupFilterConfig,
|
||||
assigneeFilterConfig,
|
||||
priorityFilterConfig,
|
||||
projectFilterConfig,
|
||||
mentionFilterConfig,
|
||||
labelFilterConfig,
|
||||
cycleFilterConfig,
|
||||
moduleFilterConfig,
|
||||
startDateFilterConfig,
|
||||
targetDateFilterConfig,
|
||||
createdByFilterConfig,
|
||||
subscriberFilterConfig,
|
||||
],
|
||||
configMap: {
|
||||
project_id: projectFilterConfig,
|
||||
state_group: stateGroupFilterConfig,
|
||||
state_id: stateFilterConfig,
|
||||
label_id: labelFilterConfig,
|
||||
cycle_id: cycleFilterConfig,
|
||||
module_id: moduleFilterConfig,
|
||||
assignee_id: assigneeFilterConfig,
|
||||
mention_id: mentionFilterConfig,
|
||||
created_by_id: createdByFilterConfig,
|
||||
subscriber_id: subscriberFilterConfig,
|
||||
priority: priorityFilterConfig,
|
||||
start_date: startDateFilterConfig,
|
||||
target_date: targetDateFilterConfig,
|
||||
},
|
||||
isFilterEnabled,
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue