forked from github/plane
[WEB-477] feat: enhanced project issue filtering by cycles and modules (#3830)
* feat: implemented cycle and module filter in project issues * feat: implemented cycle and module filter in draft and archived issues
This commit is contained in:
parent
7abfbac479
commit
51f795fbd7
4
packages/types/src/view-props.d.ts
vendored
4
packages/types/src/view-props.d.ts
vendored
@ -60,6 +60,8 @@ export type TIssueParams =
|
||||
| "created_by"
|
||||
| "subscriber"
|
||||
| "labels"
|
||||
| "cycle"
|
||||
| "module"
|
||||
| "start_date"
|
||||
| "target_date"
|
||||
| "project"
|
||||
@ -79,6 +81,8 @@ export interface IIssueFilterOptions {
|
||||
labels?: string[] | null;
|
||||
priority?: string[] | null;
|
||||
project?: string[] | null;
|
||||
cycle?: string[] | null;
|
||||
module?: string[] | null;
|
||||
start_date?: string[] | null;
|
||||
state?: string[] | null;
|
||||
state_group?: string[] | null;
|
||||
|
@ -0,0 +1,48 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
// hooks
|
||||
import { useCycle } from "hooks/store";
|
||||
// ui
|
||||
import { CycleGroupIcon } from "@plane/ui";
|
||||
// types
|
||||
import { TCycleGroups } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
values: string[];
|
||||
editable: boolean | undefined;
|
||||
};
|
||||
|
||||
export const AppliedCycleFilters: React.FC<Props> = observer((props) => {
|
||||
const { handleRemove, values, editable } = props;
|
||||
// store hooks
|
||||
const { getCycleById } = useCycle();
|
||||
|
||||
return (
|
||||
<>
|
||||
{values.map((cycleId) => {
|
||||
const cycleDetails = getCycleById(cycleId) ?? null;
|
||||
|
||||
if (!cycleDetails) return null;
|
||||
|
||||
const cycleStatus = (cycleDetails?.status ? cycleDetails?.status.toLocaleLowerCase() : "draft") as TCycleGroups;
|
||||
|
||||
return (
|
||||
<div key={cycleId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
||||
<CycleGroupIcon cycleGroup={cycleStatus} className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="normal-case">{cycleDetails.name}</span>
|
||||
{editable && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
|
||||
onClick={() => handleRemove(cycleId)}
|
||||
>
|
||||
<X size={10} strokeWidth={2} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,12 +1,15 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// components
|
||||
import {
|
||||
AppliedCycleFilters,
|
||||
AppliedDateFilters,
|
||||
AppliedLabelsFilters,
|
||||
AppliedMembersFilters,
|
||||
AppliedModuleFilters,
|
||||
AppliedPriorityFilters,
|
||||
AppliedProjectFilters,
|
||||
AppliedStateFilters,
|
||||
@ -34,6 +37,9 @@ const dateFilters = ["start_date", "target_date"];
|
||||
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states, alwaysAllowEditing } = props;
|
||||
// store hooks
|
||||
const {
|
||||
router: { moduleId, cycleId },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
@ -104,6 +110,20 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||
values={value}
|
||||
/>
|
||||
)}
|
||||
{filterKey === "cycle" && !cycleId && (
|
||||
<AppliedCycleFilters
|
||||
editable={isEditingAllowed}
|
||||
handleRemove={(val) => handleRemoveFilter("cycle", val)}
|
||||
values={value}
|
||||
/>
|
||||
)}
|
||||
{filterKey === "module" && !moduleId && (
|
||||
<AppliedModuleFilters
|
||||
editable={isEditingAllowed}
|
||||
handleRemove={(val) => handleRemoveFilter("module", val)}
|
||||
values={value}
|
||||
/>
|
||||
)}
|
||||
{isEditingAllowed && (
|
||||
<button
|
||||
type="button"
|
||||
|
@ -5,5 +5,7 @@ export * from "./label";
|
||||
export * from "./members";
|
||||
export * from "./priority";
|
||||
export * from "./project";
|
||||
export * from "./module";
|
||||
export * from "./cycle";
|
||||
export * from "./state";
|
||||
export * from "./state-group";
|
||||
|
@ -0,0 +1,44 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
// hooks
|
||||
import { useModule } from "hooks/store";
|
||||
// ui
|
||||
import { DiceIcon } from "@plane/ui";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
values: string[];
|
||||
editable: boolean | undefined;
|
||||
};
|
||||
|
||||
export const AppliedModuleFilters: React.FC<Props> = observer((props) => {
|
||||
const { handleRemove, values, editable } = props;
|
||||
// store hooks
|
||||
const { getModuleById } = useModule();
|
||||
|
||||
return (
|
||||
<>
|
||||
{values.map((moduleId) => {
|
||||
const moduleDetails = getModuleById(moduleId) ?? null;
|
||||
|
||||
if (!moduleDetails) return null;
|
||||
|
||||
return (
|
||||
<div key={moduleId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
||||
<DiceIcon className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="normal-case">{moduleDetails.name}</span>
|
||||
{editable && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
|
||||
onClick={() => handleRemove(moduleId)}
|
||||
>
|
||||
<X size={10} strokeWidth={2} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
});
|
@ -0,0 +1,96 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import sortBy from "lodash/sortBy";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
import { useApplication, useCycle } from "hooks/store";
|
||||
// ui
|
||||
import { Loader, CycleGroupIcon } from "@plane/ui";
|
||||
// types
|
||||
import { TCycleGroups } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
handleUpdate: (val: string) => void;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export const FilterCycle: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters, handleUpdate, searchQuery } = props;
|
||||
|
||||
// hooks
|
||||
const {
|
||||
router: { projectId },
|
||||
} = useApplication();
|
||||
const { getCycleById, getProjectCycleIds } = useCycle();
|
||||
|
||||
// states
|
||||
const [itemsToRender, setItemsToRender] = useState(5);
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
|
||||
const cycleIds = projectId ? getProjectCycleIds(projectId) : undefined;
|
||||
const cycles = cycleIds?.map((projectId) => getCycleById(projectId)!) ?? null;
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
const filteredOptions = sortBy(
|
||||
cycles?.filter((cycle) => cycle.name.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||
(cycle) => cycle.name.toLowerCase()
|
||||
);
|
||||
|
||||
const handleViewToggle = () => {
|
||||
if (!filteredOptions) return;
|
||||
|
||||
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
|
||||
else setItemsToRender(filteredOptions.length);
|
||||
};
|
||||
|
||||
const cycleStatus = (status: TCycleGroups) => (status ? status.toLocaleLowerCase() : "draft") as TCycleGroups;
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterHeader
|
||||
title={`Cycle ${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
|
||||
isPreviewEnabled={previewEnabled}
|
||||
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
|
||||
/>
|
||||
{previewEnabled && (
|
||||
<div>
|
||||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
<>
|
||||
{filteredOptions.slice(0, itemsToRender).map((cycle) => (
|
||||
<FilterOption
|
||||
key={cycle.id}
|
||||
isChecked={appliedFilters?.includes(cycle.id) ? true : false}
|
||||
onClick={() => handleUpdate(cycle.id)}
|
||||
icon={
|
||||
<CycleGroupIcon cycleGroup={cycleStatus(cycle?.status)} className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
}
|
||||
title={cycle.name}
|
||||
activePulse={cycleStatus(cycle?.status) === "current" ? true : false}
|
||||
/>
|
||||
))}
|
||||
{filteredOptions.length > 5 && (
|
||||
<button
|
||||
type="button"
|
||||
className="ml-8 text-xs font-medium text-custom-primary-100"
|
||||
onClick={handleViewToggle}
|
||||
>
|
||||
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs italic text-custom-text-400">No matches found</p>
|
||||
)
|
||||
) : (
|
||||
<Loader className="space-y-2">
|
||||
<Loader.Item height="20px" />
|
||||
<Loader.Item height="20px" />
|
||||
<Loader.Item height="20px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,6 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Search, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import {
|
||||
FilterAssignees,
|
||||
@ -13,6 +15,8 @@ import {
|
||||
FilterState,
|
||||
FilterStateGroup,
|
||||
FilterTargetDate,
|
||||
FilterCycle,
|
||||
FilterModule,
|
||||
} from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions, IIssueLabel, IState } from "@plane/types";
|
||||
@ -30,6 +34,10 @@ type Props = {
|
||||
|
||||
export const FilterSelection: React.FC<Props> = observer((props) => {
|
||||
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, memberIds, states } = props;
|
||||
// hooks
|
||||
const {
|
||||
router: { moduleId, cycleId },
|
||||
} = useApplication();
|
||||
// states
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
|
||||
@ -102,6 +110,28 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* cycle */}
|
||||
{isFilterEnabled("cycle") && !cycleId && (
|
||||
<div className="py-2">
|
||||
<FilterCycle
|
||||
appliedFilters={filters.cycle ?? null}
|
||||
handleUpdate={(val) => handleFiltersUpdate("cycle", val)}
|
||||
searchQuery={filtersSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* module */}
|
||||
{isFilterEnabled("module") && !moduleId && (
|
||||
<div className="py-2">
|
||||
<FilterModule
|
||||
appliedFilters={filters.module ?? null}
|
||||
handleUpdate={(val) => handleFiltersUpdate("module", val)}
|
||||
searchQuery={filtersSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* assignees */}
|
||||
{isFilterEnabled("mentions") && (
|
||||
<div className="py-2">
|
||||
|
@ -8,4 +8,6 @@ export * from "./project";
|
||||
export * from "./start-date";
|
||||
export * from "./state-group";
|
||||
export * from "./state";
|
||||
export * from "./cycle";
|
||||
export * from "./module";
|
||||
export * from "./target-date";
|
||||
|
@ -0,0 +1,89 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import sortBy from "lodash/sortBy";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
import { useApplication, useModule } from "hooks/store";
|
||||
// ui
|
||||
import { Loader, DiceIcon } from "@plane/ui";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
handleUpdate: (val: string) => void;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export const FilterModule: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters, handleUpdate, searchQuery } = props;
|
||||
|
||||
// hooks
|
||||
const {
|
||||
router: { projectId },
|
||||
} = useApplication();
|
||||
const { getModuleById, getProjectModuleIds } = useModule();
|
||||
|
||||
// states
|
||||
const [itemsToRender, setItemsToRender] = useState(5);
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
|
||||
const moduleIds = projectId ? getProjectModuleIds(projectId) : undefined;
|
||||
const modules = moduleIds?.map((projectId) => getModuleById(projectId)!) ?? null;
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
const filteredOptions = sortBy(
|
||||
modules?.filter((module) => module.name.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||
(module) => module.name.toLowerCase()
|
||||
);
|
||||
|
||||
const handleViewToggle = () => {
|
||||
if (!filteredOptions) return;
|
||||
|
||||
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
|
||||
else setItemsToRender(filteredOptions.length);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterHeader
|
||||
title={`Module ${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
|
||||
isPreviewEnabled={previewEnabled}
|
||||
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
|
||||
/>
|
||||
{previewEnabled && (
|
||||
<div>
|
||||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
<>
|
||||
{filteredOptions.slice(0, itemsToRender).map((cycle) => (
|
||||
<FilterOption
|
||||
key={cycle.id}
|
||||
isChecked={appliedFilters?.includes(cycle.id) ? true : false}
|
||||
onClick={() => handleUpdate(cycle.id)}
|
||||
icon={<DiceIcon className="h-3 w-3 flex-shrink-0" />}
|
||||
title={cycle.name}
|
||||
/>
|
||||
))}
|
||||
{filteredOptions.length > 5 && (
|
||||
<button
|
||||
type="button"
|
||||
className="ml-8 text-xs font-medium text-custom-primary-100"
|
||||
onClick={handleViewToggle}
|
||||
>
|
||||
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs italic text-custom-text-400">No matches found</p>
|
||||
)
|
||||
) : (
|
||||
<Loader className="space-y-2">
|
||||
<Loader.Item height="20px" />
|
||||
<Loader.Item height="20px" />
|
||||
<Loader.Item height="20px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
@ -8,10 +8,11 @@ type Props = {
|
||||
title: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
multiple?: boolean;
|
||||
activePulse?: boolean;
|
||||
};
|
||||
|
||||
export const FilterOption: React.FC<Props> = (props) => {
|
||||
const { icon, isChecked, multiple = true, onClick, title } = props;
|
||||
const { icon, isChecked, multiple = true, onClick, title, activePulse = false } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
@ -30,6 +31,9 @@ export const FilterOption: React.FC<Props> = (props) => {
|
||||
{icon && <div className="grid w-5 flex-shrink-0 place-items-center">{icon}</div>}
|
||||
<div className="flex-grow truncate text-xs text-custom-text-200">{title}</div>
|
||||
</div>
|
||||
{activePulse && (
|
||||
<div className="flex-shrink-0 text-xs w-2 h-2 rounded-full bg-custom-primary-100 animate-pulse ml-auto" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
@ -263,7 +263,17 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
archived_issues: {
|
||||
list: {
|
||||
filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"],
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state", "state_detail.group", "priority", "labels", "assignees", "created_by", null],
|
||||
@ -278,7 +288,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
draft_issues: {
|
||||
list: {
|
||||
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
|
||||
filters: ["priority", "state_group", "cycle", "module", "labels", "start_date", "target_date"],
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels", null],
|
||||
@ -291,7 +301,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
|
||||
filters: ["priority", "state_group", "cycle", "module", "labels", "start_date", "target_date"],
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels"],
|
||||
@ -350,7 +360,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
issues: {
|
||||
list: {
|
||||
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state", "priority", "labels", "assignees", "created_by", null],
|
||||
@ -363,7 +384,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state", "priority", "labels", "assignees", "created_by"],
|
||||
@ -377,7 +409,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
},
|
||||
calendar: {
|
||||
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date"],
|
||||
filters: ["priority", "state", "cycle", "module", "assignees", "mentions", "created_by", "labels", "start_date"],
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
type: [null, "active", "backlog"],
|
||||
@ -388,7 +420,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
},
|
||||
spreadsheet: {
|
||||
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
@ -400,7 +443,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
},
|
||||
},
|
||||
gantt_chart: {
|
||||
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: false,
|
||||
display_filters: {
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
|
@ -84,6 +84,8 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
|
||||
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||
if (!filteredParams) return undefined;
|
||||
|
||||
if (filteredParams.includes("cycle")) filteredParams.splice(filteredParams.indexOf("cycle"), 1);
|
||||
|
||||
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
|
||||
userFilters?.filters as IIssueFilterOptions,
|
||||
userFilters?.displayFilters as IIssueDisplayFilterOptions,
|
||||
|
@ -74,6 +74,8 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
||||
mentions: filters?.mentions || undefined,
|
||||
created_by: filters?.created_by || undefined,
|
||||
labels: filters?.labels || undefined,
|
||||
cycle: filters?.cycle || undefined,
|
||||
module: filters?.module || undefined,
|
||||
start_date: filters?.start_date || undefined,
|
||||
target_date: filters?.target_date || undefined,
|
||||
project: filters.project || undefined,
|
||||
@ -107,6 +109,8 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
||||
mentions: filters?.mentions || null,
|
||||
created_by: filters?.created_by || null,
|
||||
labels: filters?.labels || null,
|
||||
cycle: filters?.cycle || null,
|
||||
module: filters?.module || null,
|
||||
start_date: filters?.start_date || null,
|
||||
target_date: filters?.target_date || null,
|
||||
project: filters?.project || null,
|
||||
|
@ -84,6 +84,8 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
||||
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||
if (!filteredParams) return undefined;
|
||||
|
||||
if (filteredParams.includes("module")) filteredParams.splice(filteredParams.indexOf("module"), 1);
|
||||
|
||||
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
|
||||
userFilters?.filters as IIssueFilterOptions,
|
||||
userFilters?.displayFilters as IIssueDisplayFilterOptions,
|
||||
|
Loading…
Reference in New Issue
Block a user