mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[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"
|
| "created_by"
|
||||||
| "subscriber"
|
| "subscriber"
|
||||||
| "labels"
|
| "labels"
|
||||||
|
| "cycle"
|
||||||
|
| "module"
|
||||||
| "start_date"
|
| "start_date"
|
||||||
| "target_date"
|
| "target_date"
|
||||||
| "project"
|
| "project"
|
||||||
@ -79,6 +81,8 @@ export interface IIssueFilterOptions {
|
|||||||
labels?: string[] | null;
|
labels?: string[] | null;
|
||||||
priority?: string[] | null;
|
priority?: string[] | null;
|
||||||
project?: string[] | null;
|
project?: string[] | null;
|
||||||
|
cycle?: string[] | null;
|
||||||
|
module?: string[] | null;
|
||||||
start_date?: string[] | null;
|
start_date?: string[] | null;
|
||||||
state?: string[] | null;
|
state?: string[] | null;
|
||||||
state_group?: 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 { observer } from "mobx-react-lite";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "hooks/store";
|
import { useApplication, useUser } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
|
AppliedCycleFilters,
|
||||||
AppliedDateFilters,
|
AppliedDateFilters,
|
||||||
AppliedLabelsFilters,
|
AppliedLabelsFilters,
|
||||||
AppliedMembersFilters,
|
AppliedMembersFilters,
|
||||||
|
AppliedModuleFilters,
|
||||||
AppliedPriorityFilters,
|
AppliedPriorityFilters,
|
||||||
AppliedProjectFilters,
|
AppliedProjectFilters,
|
||||||
AppliedStateFilters,
|
AppliedStateFilters,
|
||||||
@ -34,6 +37,9 @@ const dateFilters = ["start_date", "target_date"];
|
|||||||
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||||
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states, alwaysAllowEditing } = props;
|
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states, alwaysAllowEditing } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
|
const {
|
||||||
|
router: { moduleId, cycleId },
|
||||||
|
} = useApplication();
|
||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
@ -104,6 +110,20 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
|||||||
values={value}
|
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 && (
|
{isEditingAllowed && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -5,5 +5,7 @@ export * from "./label";
|
|||||||
export * from "./members";
|
export * from "./members";
|
||||||
export * from "./priority";
|
export * from "./priority";
|
||||||
export * from "./project";
|
export * from "./project";
|
||||||
|
export * from "./module";
|
||||||
|
export * from "./cycle";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
export * from "./state-group";
|
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 { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Search, X } from "lucide-react";
|
import { Search, X } from "lucide-react";
|
||||||
|
// hooks
|
||||||
|
import { useApplication } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
FilterAssignees,
|
FilterAssignees,
|
||||||
@ -13,6 +15,8 @@ import {
|
|||||||
FilterState,
|
FilterState,
|
||||||
FilterStateGroup,
|
FilterStateGroup,
|
||||||
FilterTargetDate,
|
FilterTargetDate,
|
||||||
|
FilterCycle,
|
||||||
|
FilterModule,
|
||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueFilterOptions, IIssueLabel, IState } from "@plane/types";
|
import { IIssueFilterOptions, IIssueLabel, IState } from "@plane/types";
|
||||||
@ -30,6 +34,10 @@ type Props = {
|
|||||||
|
|
||||||
export const FilterSelection: React.FC<Props> = observer((props) => {
|
export const FilterSelection: React.FC<Props> = observer((props) => {
|
||||||
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, memberIds, states } = props;
|
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, memberIds, states } = props;
|
||||||
|
// hooks
|
||||||
|
const {
|
||||||
|
router: { moduleId, cycleId },
|
||||||
|
} = useApplication();
|
||||||
// states
|
// states
|
||||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||||
|
|
||||||
@ -102,6 +110,28 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</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 */}
|
{/* assignees */}
|
||||||
{isFilterEnabled("mentions") && (
|
{isFilterEnabled("mentions") && (
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
|
@ -8,4 +8,6 @@ export * from "./project";
|
|||||||
export * from "./start-date";
|
export * from "./start-date";
|
||||||
export * from "./state-group";
|
export * from "./state-group";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
|
export * from "./cycle";
|
||||||
|
export * from "./module";
|
||||||
export * from "./target-date";
|
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;
|
title: React.ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
|
activePulse?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterOption: React.FC<Props> = (props) => {
|
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 (
|
return (
|
||||||
<button
|
<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>}
|
{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 className="flex-grow truncate text-xs text-custom-text-200">{title}</div>
|
||||||
</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>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -263,7 +263,17 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
},
|
},
|
||||||
archived_issues: {
|
archived_issues: {
|
||||||
list: {
|
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_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
group_by: ["state", "state_detail.group", "priority", "labels", "assignees", "created_by", null],
|
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: {
|
draft_issues: {
|
||||||
list: {
|
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_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
group_by: ["state_detail.group", "priority", "project", "labels", null],
|
group_by: ["state_detail.group", "priority", "project", "labels", null],
|
||||||
@ -291,7 +301,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
kanban: {
|
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_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
group_by: ["state_detail.group", "priority", "project", "labels"],
|
group_by: ["state_detail.group", "priority", "project", "labels"],
|
||||||
@ -350,7 +360,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
},
|
},
|
||||||
issues: {
|
issues: {
|
||||||
list: {
|
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_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
group_by: ["state", "priority", "labels", "assignees", "created_by", null],
|
group_by: ["state", "priority", "labels", "assignees", "created_by", null],
|
||||||
@ -363,7 +384,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
kanban: {
|
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_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
group_by: ["state", "priority", "labels", "assignees", "created_by"],
|
group_by: ["state", "priority", "labels", "assignees", "created_by"],
|
||||||
@ -377,7 +409,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
calendar: {
|
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_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
type: [null, "active", "backlog"],
|
type: [null, "active", "backlog"],
|
||||||
@ -388,7 +420,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
spreadsheet: {
|
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_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||||
@ -400,7 +443,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
gantt_chart: {
|
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_properties: false,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
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");
|
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||||
if (!filteredParams) return undefined;
|
if (!filteredParams) return undefined;
|
||||||
|
|
||||||
|
if (filteredParams.includes("cycle")) filteredParams.splice(filteredParams.indexOf("cycle"), 1);
|
||||||
|
|
||||||
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
|
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
|
||||||
userFilters?.filters as IIssueFilterOptions,
|
userFilters?.filters as IIssueFilterOptions,
|
||||||
userFilters?.displayFilters as IIssueDisplayFilterOptions,
|
userFilters?.displayFilters as IIssueDisplayFilterOptions,
|
||||||
|
@ -74,6 +74,8 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
|||||||
mentions: filters?.mentions || undefined,
|
mentions: filters?.mentions || undefined,
|
||||||
created_by: filters?.created_by || undefined,
|
created_by: filters?.created_by || undefined,
|
||||||
labels: filters?.labels || undefined,
|
labels: filters?.labels || undefined,
|
||||||
|
cycle: filters?.cycle || undefined,
|
||||||
|
module: filters?.module || undefined,
|
||||||
start_date: filters?.start_date || undefined,
|
start_date: filters?.start_date || undefined,
|
||||||
target_date: filters?.target_date || undefined,
|
target_date: filters?.target_date || undefined,
|
||||||
project: filters.project || undefined,
|
project: filters.project || undefined,
|
||||||
@ -107,6 +109,8 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
|||||||
mentions: filters?.mentions || null,
|
mentions: filters?.mentions || null,
|
||||||
created_by: filters?.created_by || null,
|
created_by: filters?.created_by || null,
|
||||||
labels: filters?.labels || null,
|
labels: filters?.labels || null,
|
||||||
|
cycle: filters?.cycle || null,
|
||||||
|
module: filters?.module || null,
|
||||||
start_date: filters?.start_date || null,
|
start_date: filters?.start_date || null,
|
||||||
target_date: filters?.target_date || null,
|
target_date: filters?.target_date || null,
|
||||||
project: filters?.project || null,
|
project: filters?.project || null,
|
||||||
|
@ -84,6 +84,8 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
|||||||
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||||
if (!filteredParams) return undefined;
|
if (!filteredParams) return undefined;
|
||||||
|
|
||||||
|
if (filteredParams.includes("module")) filteredParams.splice(filteredParams.indexOf("module"), 1);
|
||||||
|
|
||||||
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
|
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
|
||||||
userFilters?.filters as IIssueFilterOptions,
|
userFilters?.filters as IIssueFilterOptions,
|
||||||
userFilters?.displayFilters as IIssueDisplayFilterOptions,
|
userFilters?.displayFilters as IIssueDisplayFilterOptions,
|
||||||
|
Loading…
Reference in New Issue
Block a user