diff --git a/apps/api/plane/utils/filters/filterset.py b/apps/api/plane/utils/filters/filterset.py index 721bf4c7afd..1b7b8481ae6 100644 --- a/apps/api/plane/utils/filters/filterset.py +++ b/apps/api/plane/utils/filters/filterset.py @@ -164,6 +164,7 @@ class Meta: "target_date": ["exact", "range"], "created_at": ["exact", "range"], "updated_at": ["exact", "range"], + "completed_at": ["exact", "range"], "is_draft": ["exact"], "priority": ["exact", "in"], } diff --git a/apps/web/ce/hooks/work-item-filters/use-work-item-filters-config.tsx b/apps/web/ce/hooks/work-item-filters/use-work-item-filters-config.tsx index c4b6f3f0b20..ff9c1691fc9 100644 --- a/apps/web/ce/hooks/work-item-filters/use-work-item-filters-config.tsx +++ b/apps/web/ce/hooks/work-item-filters/use-work-item-filters-config.tsx @@ -36,6 +36,7 @@ import type { import { Avatar } from "@plane/ui"; import { getAssigneeFilterConfig, + getCompletedAtFilterConfig, getCreatedAtFilterConfig, getCreatedByFilterConfig, getCycleFilterConfig, @@ -349,6 +350,17 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps): [operatorConfigs] ); + // completed at filter config + const completedAtFilterConfig = useMemo( + () => + getCompletedAtFilterConfig("completed_at")({ + isEnabled: true, + filterIcon: CalendarLayoutIcon, + ...operatorConfigs, + }), + [operatorConfigs] + ); + // project filter config const projectFilterConfig = useMemo( () => @@ -378,6 +390,7 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps): targetDateFilterConfig, createdAtFilterConfig, updatedAtFilterConfig, + completedAtFilterConfig, createdByFilterConfig, subscriberFilterConfig, ], @@ -397,6 +410,7 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps): target_date: targetDateFilterConfig, created_at: createdAtFilterConfig, updated_at: updatedAtFilterConfig, + completed_at: completedAtFilterConfig, }, isFilterEnabled, members: members ?? [], diff --git a/apps/web/core/store/issue/helpers/base-issues-utils.ts b/apps/web/core/store/issue/helpers/base-issues-utils.ts index b5b0e6ecf46..d57bbc39f2d 100644 --- a/apps/web/core/store/issue/helpers/base-issues-utils.ts +++ b/apps/web/core/store/issue/helpers/base-issues-utils.ts @@ -192,13 +192,13 @@ export const getIssueIds = (issues: TIssue[]) => issues.map((issue) => issue?.id /** * Checks if an issue meets the date filter criteria * @param issue The issue to check - * @param filterKey The date field to check ('start_date' or 'target_date') + * @param filterKey The date field to check ('start_date', 'target_date', or 'completed_at') * @param dateFilters Array of date filter strings * @returns boolean indicating if the issue meets the date criteria */ export const checkIssueDateFilter = ( issue: TIssue, - filterKey: "start_date" | "target_date", + filterKey: "start_date" | "target_date" | "completed_at", dateFilters: string[] ): boolean => { if (!dateFilters || dateFilters.length === 0) return true; @@ -254,7 +254,7 @@ export const getFilteredWorkItems = (workItems: TIssue[], filters: IIssueFilterO // Check all filter conditions (AND operation between different filters) activeFilters.every(([filterKey, filterValues]) => { // Handle date filters separately - if (filterKey === "start_date" || filterKey === "target_date") { + if (filterKey === "start_date" || filterKey === "target_date" || filterKey === "completed_at") { return checkIssueDateFilter(workItem, filterKey, filterValues as string[]); } // Handle regular filters diff --git a/packages/constants/src/issue/filter.ts b/packages/constants/src/issue/filter.ts index 115b8856f41..bfca99d166e 100644 --- a/packages/constants/src/issue/filter.ts +++ b/packages/constants/src/issue/filter.ts @@ -111,7 +111,7 @@ export type TIssueFiltersToDisplayByPageType = { export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = { profile_issues: { - filters: ["priority", "state_group", "label_id", "start_date", "target_date"], + filters: ["priority", "state_group", "label_id", "start_date", "target_date", "completed_at"], layoutOptions: { list: { display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, @@ -151,6 +151,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = { "label_id", "start_date", "target_date", + "completed_at", ], layoutOptions: { list: { @@ -178,6 +179,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = { "project_id", "start_date", "target_date", + "completed_at", ], layoutOptions: { spreadsheet: { @@ -216,6 +218,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = { "label_id", "start_date", "target_date", + "completed_at", ], layoutOptions: { list: { diff --git a/packages/types/src/view-props.ts b/packages/types/src/view-props.ts index 638afa0fd13..d83a99390e3 100644 --- a/packages/types/src/view-props.ts +++ b/packages/types/src/view-props.ts @@ -109,6 +109,7 @@ export const WORK_ITEM_FILTER_PROPERTY_KEYS = [ "project_id", "created_at", "updated_at", + "completed_at", ] as const; export type TWorkItemFilterProperty = (typeof WORK_ITEM_FILTER_PROPERTY_KEYS)[number]; @@ -143,6 +144,7 @@ export interface IIssueFilterOptions { state_group?: string[] | null; subscriber?: string[] | null; target_date?: string[] | null; + completed_at?: string[] | null; issue_type?: string[] | null; } diff --git a/packages/utils/src/work-item-filters/configs/filters/date.ts b/packages/utils/src/work-item-filters/configs/filters/date.ts index c1f5df6a897..80bb4759e0d 100644 --- a/packages/utils/src/work-item-filters/configs/filters/date.ts +++ b/packages/utils/src/work-item-filters/configs/filters/date.ts @@ -83,3 +83,21 @@ export const getUpdatedAtFilterConfig = allowMultipleFilters: true, supportedOperatorConfigsMap: getSupportedDateOperators(params), }); + +/** + * Get the completed at filter config + * @template K - The filter key + * @param key - The filter key to use + * @returns A function that takes parameters and returns the completed at filter config + */ +export const getCompletedAtFilterConfig = +

(key: P): TCreateFilterConfig => + (params: TCreateDateFilterParams) => + createFilterConfig

({ + id: key, + label: "Completed at", + ...params, + icon: params.filterIcon, + allowMultipleFilters: true, + supportedOperatorConfigsMap: getSupportedDateOperators(params), + });