forked from github/plane
[WEB-601] feat: enhanced display filters grouping by cycles and modules in project issues (#3834)
* feat: implemented cycle and module for display filters groupBy and sunGroupBy in project issues list and kanban layouts * chore: Enabled drag ability for cycle and handled prepopulated data for quick add * chore: disbaled drag ability for cycle * chore: updated preloaded data * chore: updated module and cycle store router dependancy to prop dependancy
This commit is contained in:
parent
56805203f1
commit
9326fb0762
2
packages/types/src/issues.d.ts
vendored
2
packages/types/src/issues.d.ts
vendored
@ -203,6 +203,8 @@ export interface ViewFlags {
|
|||||||
|
|
||||||
export type GroupByColumnTypes =
|
export type GroupByColumnTypes =
|
||||||
| "project"
|
| "project"
|
||||||
|
| "cycle"
|
||||||
|
| "module"
|
||||||
| "state"
|
| "state"
|
||||||
| "state_detail.group"
|
| "state_detail.group"
|
||||||
| "priority"
|
| "priority"
|
||||||
|
2
packages/types/src/view-props.d.ts
vendored
2
packages/types/src/view-props.d.ts
vendored
@ -14,6 +14,8 @@ export type TIssueGroupByOptions =
|
|||||||
| "project"
|
| "project"
|
||||||
| "assignees"
|
| "assignees"
|
||||||
| "mentions"
|
| "mentions"
|
||||||
|
| "cycle"
|
||||||
|
| "module"
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export type TIssueOrderByOptions =
|
export type TIssueOrderByOptions =
|
||||||
|
@ -152,6 +152,7 @@ export const CycleMobileHeader = () => {
|
|||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
|
ignoreGroupedFilters={["cycle"]}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -244,6 +244,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
|
ignoreGroupedFilters={["cycle"]}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
|
|
||||||
|
@ -248,6 +248,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
|
ignoreGroupedFilters={["module"]}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
FilterSubGroupBy,
|
FilterSubGroupBy,
|
||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssueGroupByOptions } from "@plane/types";
|
||||||
import { ILayoutDisplayFiltersOptions } from "constants/issue";
|
import { ILayoutDisplayFiltersOptions } from "constants/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -20,6 +20,7 @@ type Props = {
|
|||||||
handleDisplayFiltersUpdate: (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => void;
|
handleDisplayFiltersUpdate: (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => void;
|
||||||
handleDisplayPropertiesUpdate: (updatedDisplayProperties: Partial<IIssueDisplayProperties>) => void;
|
handleDisplayPropertiesUpdate: (updatedDisplayProperties: Partial<IIssueDisplayProperties>) => void;
|
||||||
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
|
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
|
||||||
|
ignoreGroupedFilters?: Partial<TIssueGroupByOptions>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
||||||
@ -29,6 +30,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
handleDisplayFiltersUpdate,
|
handleDisplayFiltersUpdate,
|
||||||
handleDisplayPropertiesUpdate,
|
handleDisplayPropertiesUpdate,
|
||||||
layoutDisplayFiltersOptions,
|
layoutDisplayFiltersOptions,
|
||||||
|
ignoreGroupedFilters = [],
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) =>
|
const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) =>
|
||||||
@ -54,6 +56,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
group_by: val,
|
group_by: val,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
ignoreGroupedFilters={ignoreGroupedFilters}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -71,6 +74,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
subGroupByOptions={layoutDisplayFiltersOptions?.display_filters.sub_group_by ?? []}
|
subGroupByOptions={layoutDisplayFiltersOptions?.display_filters.sub_group_by ?? []}
|
||||||
|
ignoreGroupedFilters={ignoreGroupedFilters}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "components/issues";
|
import { FilterHeader, FilterOption } from "components/issues";
|
||||||
// types
|
// types
|
||||||
@ -12,10 +11,11 @@ type Props = {
|
|||||||
displayFilters: IIssueDisplayFilterOptions;
|
displayFilters: IIssueDisplayFilterOptions;
|
||||||
groupByOptions: TIssueGroupByOptions[];
|
groupByOptions: TIssueGroupByOptions[];
|
||||||
handleUpdate: (val: TIssueGroupByOptions) => void;
|
handleUpdate: (val: TIssueGroupByOptions) => void;
|
||||||
|
ignoreGroupedFilters: Partial<TIssueGroupByOptions>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterGroupBy: React.FC<Props> = observer((props) => {
|
export const FilterGroupBy: React.FC<Props> = observer((props) => {
|
||||||
const { displayFilters, groupByOptions, handleUpdate } = props;
|
const { displayFilters, groupByOptions, handleUpdate, ignoreGroupedFilters } = props;
|
||||||
|
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
|
|
||||||
@ -34,6 +34,7 @@ export const FilterGroupBy: React.FC<Props> = observer((props) => {
|
|||||||
{ISSUE_GROUP_BY_OPTIONS.filter((option) => groupByOptions.includes(option.key)).map((groupBy) => {
|
{ISSUE_GROUP_BY_OPTIONS.filter((option) => groupByOptions.includes(option.key)).map((groupBy) => {
|
||||||
if (displayFilters.layout === "kanban" && selectedSubGroupBy !== null && groupBy.key === selectedSubGroupBy)
|
if (displayFilters.layout === "kanban" && selectedSubGroupBy !== null && groupBy.key === selectedSubGroupBy)
|
||||||
return null;
|
return null;
|
||||||
|
if (ignoreGroupedFilters.includes(groupBy?.key)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterOption
|
<FilterOption
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "components/issues";
|
import { FilterHeader, FilterOption } from "components/issues";
|
||||||
// types
|
// types
|
||||||
@ -12,10 +11,11 @@ type Props = {
|
|||||||
displayFilters: IIssueDisplayFilterOptions;
|
displayFilters: IIssueDisplayFilterOptions;
|
||||||
handleUpdate: (val: TIssueGroupByOptions) => void;
|
handleUpdate: (val: TIssueGroupByOptions) => void;
|
||||||
subGroupByOptions: TIssueGroupByOptions[];
|
subGroupByOptions: TIssueGroupByOptions[];
|
||||||
|
ignoreGroupedFilters: Partial<TIssueGroupByOptions>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterSubGroupBy: React.FC<Props> = observer((props) => {
|
export const FilterSubGroupBy: React.FC<Props> = observer((props) => {
|
||||||
const { displayFilters, handleUpdate, subGroupByOptions } = props;
|
const { displayFilters, handleUpdate, subGroupByOptions, ignoreGroupedFilters } = props;
|
||||||
|
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
|
|
||||||
@ -33,6 +33,7 @@ export const FilterSubGroupBy: React.FC<Props> = observer((props) => {
|
|||||||
<div>
|
<div>
|
||||||
{ISSUE_GROUP_BY_OPTIONS.filter((option) => subGroupByOptions.includes(option.key)).map((subGroupBy) => {
|
{ISSUE_GROUP_BY_OPTIONS.filter((option) => subGroupByOptions.includes(option.key)).map((subGroupBy) => {
|
||||||
if (selectedGroupBy !== null && subGroupBy.key === selectedGroupBy) return null;
|
if (selectedGroupBy !== null && subGroupBy.key === selectedGroupBy) return null;
|
||||||
|
if (ignoreGroupedFilters.includes(subGroupBy?.key)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterOption
|
<FilterOption
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail, useKanbanView, useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
import {
|
||||||
|
useCycle,
|
||||||
|
useIssueDetail,
|
||||||
|
useKanbanView,
|
||||||
|
useLabel,
|
||||||
|
useMember,
|
||||||
|
useModule,
|
||||||
|
useProject,
|
||||||
|
useProjectState,
|
||||||
|
} from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||||
import { KanbanGroup } from "./kanban-group";
|
import { KanbanGroup } from "./kanban-group";
|
||||||
@ -79,14 +88,16 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
const member = useMember();
|
const member = useMember();
|
||||||
const project = useProject();
|
const project = useProject();
|
||||||
const label = useLabel();
|
const label = useLabel();
|
||||||
|
const cycle = useCycle();
|
||||||
|
const _module = useModule();
|
||||||
const projectState = useProjectState();
|
const projectState = useProjectState();
|
||||||
const { peekIssue } = useIssueDetail();
|
const { peekIssue } = useIssueDetail();
|
||||||
|
|
||||||
const list = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member);
|
const list = getGroupByColumns(group_by as GroupByColumnTypes, project, cycle, _module, label, projectState, member);
|
||||||
|
|
||||||
if (!list) return null;
|
if (!list) return null;
|
||||||
|
|
||||||
const groupWithIssues = list.filter((_list) => (issueIds as TGroupedIssues)[_list.id]?.length > 0);
|
const groupWithIssues = list.filter((_list) => (issueIds as TGroupedIssues)?.[_list.id]?.length > 0);
|
||||||
|
|
||||||
const groupList = showEmptyGroup ? list : groupWithIssues;
|
const groupList = showEmptyGroup ? list : groupWithIssues;
|
||||||
|
|
||||||
|
@ -80,6 +80,10 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
preloadedData = { ...preloadedData, state_id: groupValue };
|
preloadedData = { ...preloadedData, state_id: groupValue };
|
||||||
} else if (groupByKey === "priority") {
|
} else if (groupByKey === "priority") {
|
||||||
preloadedData = { ...preloadedData, priority: groupValue };
|
preloadedData = { ...preloadedData, priority: groupValue };
|
||||||
|
} else if (groupByKey === "cycle") {
|
||||||
|
preloadedData = { ...preloadedData, cycle_id: groupValue };
|
||||||
|
} else if (groupByKey === "module") {
|
||||||
|
preloadedData = { ...preloadedData, module_ids: [groupValue] };
|
||||||
} else if (groupByKey === "labels" && groupValue != "None") {
|
} else if (groupByKey === "labels" && groupValue != "None") {
|
||||||
preloadedData = { ...preloadedData, label_ids: [groupValue] };
|
preloadedData = { ...preloadedData, label_ids: [groupValue] };
|
||||||
} else if (groupByKey === "assignees" && groupValue != "None") {
|
} else if (groupByKey === "assignees" && groupValue != "None") {
|
||||||
@ -96,6 +100,10 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
preloadedData = { ...preloadedData, state_id: subGroupValue };
|
preloadedData = { ...preloadedData, state_id: subGroupValue };
|
||||||
} else if (subGroupByKey === "priority") {
|
} else if (subGroupByKey === "priority") {
|
||||||
preloadedData = { ...preloadedData, priority: subGroupValue };
|
preloadedData = { ...preloadedData, priority: subGroupValue };
|
||||||
|
} else if (groupByKey === "cycle") {
|
||||||
|
preloadedData = { ...preloadedData, cycle_id: subGroupValue };
|
||||||
|
} else if (groupByKey === "module") {
|
||||||
|
preloadedData = { ...preloadedData, module_ids: [subGroupValue] };
|
||||||
} else if (subGroupByKey === "labels" && subGroupValue != "None") {
|
} else if (subGroupByKey === "labels" && subGroupValue != "None") {
|
||||||
preloadedData = { ...preloadedData, label_ids: [subGroupValue] };
|
preloadedData = { ...preloadedData, label_ids: [subGroupValue] };
|
||||||
} else if (subGroupByKey === "assignees" && subGroupValue != "None") {
|
} else if (subGroupByKey === "assignees" && subGroupValue != "None") {
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EIssueActions } from "../types";
|
import { EIssueActions } from "../types";
|
||||||
import { useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "hooks/store";
|
||||||
import { getGroupByColumns } from "../utils";
|
import { getGroupByColumns } from "../utils";
|
||||||
import { TCreateModalStoreTypes } from "constants/issue";
|
import { TCreateModalStoreTypes } from "constants/issue";
|
||||||
|
|
||||||
@ -217,10 +217,28 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
const member = useMember();
|
const member = useMember();
|
||||||
const project = useProject();
|
const project = useProject();
|
||||||
const label = useLabel();
|
const label = useLabel();
|
||||||
|
const cycle = useCycle();
|
||||||
|
const _module = useModule();
|
||||||
const projectState = useProjectState();
|
const projectState = useProjectState();
|
||||||
|
|
||||||
const groupByList = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member);
|
const groupByList = getGroupByColumns(
|
||||||
const subGroupByList = getGroupByColumns(sub_group_by as GroupByColumnTypes, project, label, projectState, member);
|
group_by as GroupByColumnTypes,
|
||||||
|
project,
|
||||||
|
cycle,
|
||||||
|
_module,
|
||||||
|
label,
|
||||||
|
projectState,
|
||||||
|
member
|
||||||
|
);
|
||||||
|
const subGroupByList = getGroupByColumns(
|
||||||
|
sub_group_by as GroupByColumnTypes,
|
||||||
|
project,
|
||||||
|
cycle,
|
||||||
|
_module,
|
||||||
|
label,
|
||||||
|
projectState,
|
||||||
|
member
|
||||||
|
);
|
||||||
|
|
||||||
if (!groupByList || !subGroupByList) return null;
|
if (!groupByList || !subGroupByList) return null;
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { useRef } from "react";
|
|||||||
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
|
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
|
||||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||||
// hooks
|
// hooks
|
||||||
import { useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "hooks/store";
|
||||||
// types
|
// types
|
||||||
import {
|
import {
|
||||||
GroupByColumnTypes,
|
GroupByColumnTypes,
|
||||||
@ -65,10 +65,21 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
const project = useProject();
|
const project = useProject();
|
||||||
const label = useLabel();
|
const label = useLabel();
|
||||||
const projectState = useProjectState();
|
const projectState = useProjectState();
|
||||||
|
const cycle = useCycle();
|
||||||
|
const _module = useModule();
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const groups = getGroupByColumns(group_by as GroupByColumnTypes, project, label, projectState, member, true);
|
const groups = getGroupByColumns(
|
||||||
|
group_by as GroupByColumnTypes,
|
||||||
|
project,
|
||||||
|
cycle,
|
||||||
|
_module,
|
||||||
|
label,
|
||||||
|
projectState,
|
||||||
|
member,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
if (!groups) return null;
|
if (!groups) return null;
|
||||||
|
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
||||||
import { EIssueListRow, ISSUE_PRIORITIES } from "constants/issue";
|
// stores
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { IMemberRootStore } from "store/member";
|
import { IMemberRootStore } from "store/member";
|
||||||
import { IProjectStore } from "store/project/project.store";
|
import { IProjectStore } from "store/project/project.store";
|
||||||
import { IStateStore } from "store/state.store";
|
import { IStateStore } from "store/state.store";
|
||||||
import { GroupByColumnTypes, IGroupByColumn, IIssueListRow, TGroupedIssues, TUnGroupedIssues } from "@plane/types";
|
|
||||||
import { STATE_GROUPS } from "constants/state";
|
|
||||||
import { ILabelStore } from "store/label.store";
|
import { ILabelStore } from "store/label.store";
|
||||||
|
import { ICycleStore } from "store/cycle.store";
|
||||||
|
import { IModuleStore } from "store/module.store";
|
||||||
|
// helpers
|
||||||
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
|
// constants
|
||||||
|
import { STATE_GROUPS } from "constants/state";
|
||||||
|
import { ISSUE_PRIORITIES } from "constants/issue";
|
||||||
|
// types
|
||||||
|
import { GroupByColumnTypes, IGroupByColumn, TCycleGroups } from "@plane/types";
|
||||||
|
import { ContrastIcon } from "lucide-react";
|
||||||
|
|
||||||
export const getGroupByColumns = (
|
export const getGroupByColumns = (
|
||||||
groupBy: GroupByColumnTypes | null,
|
groupBy: GroupByColumnTypes | null,
|
||||||
project: IProjectStore,
|
project: IProjectStore,
|
||||||
|
cycle: ICycleStore,
|
||||||
|
module: IModuleStore,
|
||||||
label: ILabelStore,
|
label: ILabelStore,
|
||||||
projectState: IStateStore,
|
projectState: IStateStore,
|
||||||
member: IMemberRootStore,
|
member: IMemberRootStore,
|
||||||
@ -19,6 +28,10 @@ export const getGroupByColumns = (
|
|||||||
switch (groupBy) {
|
switch (groupBy) {
|
||||||
case "project":
|
case "project":
|
||||||
return getProjectColumns(project);
|
return getProjectColumns(project);
|
||||||
|
case "cycle":
|
||||||
|
return getCycleColumns(project, cycle);
|
||||||
|
case "module":
|
||||||
|
return getModuleColumns(project, module);
|
||||||
case "state":
|
case "state":
|
||||||
return getStateColumns(projectState);
|
return getStateColumns(projectState);
|
||||||
case "state_detail.group":
|
case "state_detail.group":
|
||||||
@ -55,6 +68,68 @@ const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined
|
|||||||
}) as any;
|
}) as any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCycleColumns = (projectStore: IProjectStore, cycleStore: ICycleStore): IGroupByColumn[] | undefined => {
|
||||||
|
const { currentProjectDetails } = projectStore;
|
||||||
|
const { getProjectCycleIds, getCycleById } = cycleStore;
|
||||||
|
|
||||||
|
if (!currentProjectDetails || !currentProjectDetails?.id) return;
|
||||||
|
|
||||||
|
const cycleIds = currentProjectDetails?.id ? getProjectCycleIds(currentProjectDetails?.id) : undefined;
|
||||||
|
if (!cycleIds) return;
|
||||||
|
|
||||||
|
const cycles = [];
|
||||||
|
|
||||||
|
cycleIds.map((cycleId) => {
|
||||||
|
const cycle = getCycleById(cycleId);
|
||||||
|
if (cycle) {
|
||||||
|
const cycleStatus = cycle.status ? (cycle.status.toLocaleLowerCase() as TCycleGroups) : "draft";
|
||||||
|
cycles.push({
|
||||||
|
id: cycle.id,
|
||||||
|
name: cycle.name,
|
||||||
|
icon: <CycleGroupIcon cycleGroup={cycleStatus as TCycleGroups} className="h-3.5 w-3.5" />,
|
||||||
|
payload: { cycle_id: cycle.id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cycles.push({
|
||||||
|
id: "None",
|
||||||
|
name: "None",
|
||||||
|
icon: <ContrastIcon className="h-3.5 w-3.5" />,
|
||||||
|
});
|
||||||
|
|
||||||
|
return cycles as any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getModuleColumns = (projectStore: IProjectStore, moduleStore: IModuleStore): IGroupByColumn[] | undefined => {
|
||||||
|
const { currentProjectDetails } = projectStore;
|
||||||
|
const { getProjectModuleIds, getModuleById } = moduleStore;
|
||||||
|
|
||||||
|
if (!currentProjectDetails || !currentProjectDetails?.id) return;
|
||||||
|
|
||||||
|
const moduleIds = currentProjectDetails?.id ? getProjectModuleIds(currentProjectDetails?.id) : undefined;
|
||||||
|
if (!moduleIds) return;
|
||||||
|
|
||||||
|
const modules = [];
|
||||||
|
|
||||||
|
moduleIds.map((moduleId) => {
|
||||||
|
const _module = getModuleById(moduleId);
|
||||||
|
if (_module)
|
||||||
|
modules.push({
|
||||||
|
id: _module.id,
|
||||||
|
name: _module.name,
|
||||||
|
icon: <DiceIcon className="w-3.5 h-3.5" />,
|
||||||
|
payload: { module_ids: [_module.id] },
|
||||||
|
});
|
||||||
|
}) as any;
|
||||||
|
modules.push({
|
||||||
|
id: "None",
|
||||||
|
name: "None",
|
||||||
|
icon: <DiceIcon className="w-3.5 h-3.5" />,
|
||||||
|
});
|
||||||
|
|
||||||
|
return modules as any;
|
||||||
|
};
|
||||||
|
|
||||||
const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefined => {
|
const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefined => {
|
||||||
const { projectStates } = projectState;
|
const { projectStates } = projectState;
|
||||||
if (!projectStates) return;
|
if (!projectStates) return;
|
||||||
|
@ -14,153 +14,153 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
import { ProjectAnalyticsModal } from "components/analytics";
|
import { ProjectAnalyticsModal } from "components/analytics";
|
||||||
|
|
||||||
export const IssuesMobileHeader = () => {
|
export const IssuesMobileHeader = () => {
|
||||||
const layouts = [
|
const layouts = [
|
||||||
{ key: "list", title: "List", icon: List },
|
{ key: "list", title: "List", icon: List },
|
||||||
{ key: "kanban", title: "Kanban", icon: Kanban },
|
{ key: "kanban", title: "Kanban", icon: Kanban },
|
||||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||||
];
|
];
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
const { workspaceSlug, projectId } = router.query as {
|
const { workspaceSlug, projectId } = router.query as {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
const { projectStates } = useProjectState();
|
const { projectStates } = useProjectState();
|
||||||
const { projectLabels } = useLabel();
|
const { projectLabels } = useLabel();
|
||||||
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
issuesFilter: { issueFilters, updateFilters },
|
issuesFilter: { issueFilters, updateFilters },
|
||||||
} = useIssues(EIssuesStoreType.PROJECT);
|
} = useIssues(EIssuesStoreType.PROJECT);
|
||||||
const {
|
const {
|
||||||
project: { projectMemberIds },
|
project: { projectMemberIds },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
const activeLayout = issueFilters?.displayFilters?.layout;
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
|
|
||||||
const handleLayoutChange = useCallback(
|
const handleLayoutChange = useCallback(
|
||||||
(layout: TIssueLayouts) => {
|
(layout: TIssueLayouts) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, updateFilters]
|
[workspaceSlug, projectId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFiltersUpdate = useCallback(
|
const handleFiltersUpdate = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value.forEach((val) => {
|
value.forEach((val) => {
|
||||||
if (!newValues.includes(val)) newValues.push(val);
|
if (!newValues.includes(val)) newValues.push(val);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||||
else newValues.push(value);
|
else newValues.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues });
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, issueFilters, updateFilters]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDisplayFilters = useCallback(
|
||||||
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, updateFilters]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDisplayProperties = useCallback(
|
||||||
|
(property: Partial<IIssueDisplayProperties>) => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, updateFilters]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProjectAnalyticsModal
|
||||||
|
isOpen={analyticsModal}
|
||||||
|
onClose={() => setAnalyticsModal(false)}
|
||||||
|
projectDetails={currentProjectDetails ?? undefined}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-evenly py-2 border-b border-custom-border-200">
|
||||||
|
<CustomMenu
|
||||||
|
maxHeight={"md"}
|
||||||
|
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||||
|
placement="bottom-start"
|
||||||
|
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
|
||||||
|
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||||
|
closeOnSelect
|
||||||
|
>
|
||||||
|
{layouts.map((layout, index) => (
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleLayoutChange(ISSUE_LAYOUTS[index].key);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<layout.icon className="w-3 h-3" />
|
||||||
|
<div className="text-custom-text-300">{layout.title}</div>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
))}
|
||||||
|
</CustomMenu>
|
||||||
|
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||||
|
<FiltersDropdown
|
||||||
|
title="Filters"
|
||||||
|
placement="bottom-end"
|
||||||
|
menuButton={
|
||||||
|
<span className="flex items-center text-custom-text-200 text-sm">
|
||||||
|
Filters
|
||||||
|
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
|
>
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues });
|
<FilterSelection
|
||||||
},
|
filters={issueFilters?.filters ?? {}}
|
||||||
[workspaceSlug, projectId, issueFilters, updateFilters]
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
);
|
layoutDisplayFiltersOptions={
|
||||||
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
const handleDisplayFilters = useCallback(
|
}
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
labels={projectLabels}
|
||||||
if (!workspaceSlug || !projectId) return;
|
memberIds={projectMemberIds ?? undefined}
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
|
states={projectStates}
|
||||||
},
|
|
||||||
[workspaceSlug, projectId, updateFilters]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDisplayProperties = useCallback(
|
|
||||||
(property: Partial<IIssueDisplayProperties>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
|
|
||||||
},
|
|
||||||
[workspaceSlug, projectId, updateFilters]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ProjectAnalyticsModal
|
|
||||||
isOpen={analyticsModal}
|
|
||||||
onClose={() => setAnalyticsModal(false)}
|
|
||||||
projectDetails={currentProjectDetails ?? undefined}
|
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-evenly py-2 border-b border-custom-border-200">
|
</FiltersDropdown>
|
||||||
<CustomMenu
|
</div>
|
||||||
maxHeight={"md"}
|
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||||
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
<FiltersDropdown
|
||||||
placement="bottom-start"
|
title="Display"
|
||||||
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
|
placement="bottom-end"
|
||||||
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
|
menuButton={
|
||||||
closeOnSelect
|
<span className="flex items-center text-custom-text-200 text-sm">
|
||||||
>
|
Display
|
||||||
{layouts.map((layout, index) => (
|
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
||||||
<CustomMenu.MenuItem
|
</span>
|
||||||
onClick={() => {
|
}
|
||||||
handleLayoutChange(ISSUE_LAYOUTS[index].key);
|
>
|
||||||
}}
|
<DisplayFiltersSelection
|
||||||
className="flex items-center gap-2"
|
layoutDisplayFiltersOptions={
|
||||||
>
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
<layout.icon className="w-3 h-3" />
|
}
|
||||||
<div className="text-custom-text-300">{layout.title}</div>
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
</CustomMenu.MenuItem>
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
))}
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
</CustomMenu>
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
/>
|
||||||
<FiltersDropdown
|
</FiltersDropdown>
|
||||||
title="Filters"
|
</div>
|
||||||
placement="bottom-end"
|
|
||||||
menuButton={
|
|
||||||
<span className="flex items-center text-custom-text-200 text-sm">
|
|
||||||
Filters
|
|
||||||
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FilterSelection
|
|
||||||
filters={issueFilters?.filters ?? {}}
|
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
|
||||||
layoutDisplayFiltersOptions={
|
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
|
||||||
}
|
|
||||||
labels={projectLabels}
|
|
||||||
memberIds={projectMemberIds ?? undefined}
|
|
||||||
states={projectStates}
|
|
||||||
/>
|
|
||||||
</FiltersDropdown>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
|
||||||
<FiltersDropdown
|
|
||||||
title="Display"
|
|
||||||
placement="bottom-end"
|
|
||||||
menuButton={
|
|
||||||
<span className="flex items-center text-custom-text-200 text-sm">
|
|
||||||
Display
|
|
||||||
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DisplayFiltersSelection
|
|
||||||
layoutDisplayFiltersOptions={
|
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
|
||||||
}
|
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
|
||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
|
||||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
|
||||||
/>
|
|
||||||
</FiltersDropdown>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setAnalyticsModal(true)}
|
onClick={() => setAnalyticsModal(true)}
|
||||||
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
|
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
|
||||||
>
|
>
|
||||||
Analytics
|
Analytics
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,154 +9,155 @@ import router from "next/router";
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
export const ModuleMobileHeader = () => {
|
export const ModuleMobileHeader = () => {
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
const { getModuleById } = useModule();
|
const { getModuleById } = useModule();
|
||||||
const layouts = [
|
const layouts = [
|
||||||
{ key: "list", title: "List", icon: List },
|
{ key: "list", title: "List", icon: List },
|
||||||
{ key: "kanban", title: "Kanban", icon: Kanban },
|
{ key: "kanban", title: "Kanban", icon: Kanban },
|
||||||
{ key: "calendar", title: "Calendar", icon: Calendar },
|
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||||
];
|
];
|
||||||
const { workspaceSlug, projectId, moduleId } = router.query as {
|
const { workspaceSlug, projectId, moduleId } = router.query as {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
};
|
};
|
||||||
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issuesFilter: { issueFilters, updateFilters },
|
issuesFilter: { issueFilters, updateFilters },
|
||||||
} = useIssues(EIssuesStoreType.MODULE);
|
} = useIssues(EIssuesStoreType.MODULE);
|
||||||
const activeLayout = issueFilters?.displayFilters?.layout;
|
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||||
const { projectStates } = useProjectState();
|
const { projectStates } = useProjectState();
|
||||||
const { projectLabels } = useLabel();
|
const { projectLabels } = useLabel();
|
||||||
const {
|
const {
|
||||||
project: { projectMemberIds },
|
project: { projectMemberIds },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
|
|
||||||
const handleLayoutChange = useCallback(
|
const handleLayoutChange = useCallback(
|
||||||
(layout: TIssueLayouts) => {
|
(layout: TIssueLayouts) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId);
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId);
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFiltersUpdate = useCallback(
|
const handleFiltersUpdate = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value.forEach((val) => {
|
value.forEach((val) => {
|
||||||
if (!newValues.includes(val)) newValues.push(val);
|
if (!newValues.includes(val)) newValues.push(val);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||||
else newValues.push(value);
|
else newValues.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, moduleId);
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDisplayFilters = useCallback(
|
||||||
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId);
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDisplayProperties = useCallback(
|
||||||
|
(property: Partial<IIssueDisplayProperties>) => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId);
|
||||||
|
},
|
||||||
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="block md:hidden">
|
||||||
|
<ProjectAnalyticsModal
|
||||||
|
isOpen={analyticsModal}
|
||||||
|
onClose={() => setAnalyticsModal(false)}
|
||||||
|
moduleDetails={moduleDetails ?? undefined}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-evenly py-2 border-b border-custom-border-200">
|
||||||
|
<CustomMenu
|
||||||
|
maxHeight={"md"}
|
||||||
|
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||||
|
placement="bottom-start"
|
||||||
|
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
|
||||||
|
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
|
||||||
|
closeOnSelect
|
||||||
|
>
|
||||||
|
{layouts.map((layout, index) => (
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleLayoutChange(ISSUE_LAYOUTS[index].key);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<layout.icon className="w-3 h-3" />
|
||||||
|
<div className="text-custom-text-300">{layout.title}</div>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
))}
|
||||||
|
</CustomMenu>
|
||||||
|
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||||
|
<FiltersDropdown
|
||||||
|
title="Filters"
|
||||||
|
placement="bottom-end"
|
||||||
|
menuButton={
|
||||||
|
<span className="flex items-center text-custom-text-200 text-sm">
|
||||||
|
Filters
|
||||||
|
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
|
>
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, moduleId);
|
<FilterSelection
|
||||||
},
|
filters={issueFilters?.filters ?? {}}
|
||||||
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
);
|
layoutDisplayFiltersOptions={
|
||||||
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
const handleDisplayFilters = useCallback(
|
}
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
labels={projectLabels}
|
||||||
if (!workspaceSlug || !projectId) return;
|
memberIds={projectMemberIds ?? undefined}
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId);
|
states={projectStates}
|
||||||
},
|
|
||||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDisplayProperties = useCallback(
|
|
||||||
(property: Partial<IIssueDisplayProperties>) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId);
|
|
||||||
},
|
|
||||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="block md:hidden">
|
|
||||||
<ProjectAnalyticsModal
|
|
||||||
isOpen={analyticsModal}
|
|
||||||
onClose={() => setAnalyticsModal(false)}
|
|
||||||
moduleDetails={moduleDetails ?? undefined}
|
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-evenly py-2 border-b border-custom-border-200">
|
</FiltersDropdown>
|
||||||
<CustomMenu
|
|
||||||
maxHeight={"md"}
|
|
||||||
className="flex flex-grow justify-center text-custom-text-200 text-sm"
|
|
||||||
placement="bottom-start"
|
|
||||||
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
|
|
||||||
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
|
|
||||||
closeOnSelect
|
|
||||||
>
|
|
||||||
{layouts.map((layout, index) => (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
handleLayoutChange(ISSUE_LAYOUTS[index].key);
|
|
||||||
}}
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<layout.icon className="w-3 h-3" />
|
|
||||||
<div className="text-custom-text-300">{layout.title}</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
))}
|
|
||||||
</CustomMenu>
|
|
||||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
|
||||||
<FiltersDropdown
|
|
||||||
title="Filters"
|
|
||||||
placement="bottom-end"
|
|
||||||
menuButton={
|
|
||||||
<span className="flex items-center text-custom-text-200 text-sm">
|
|
||||||
Filters
|
|
||||||
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FilterSelection
|
|
||||||
filters={issueFilters?.filters ?? {}}
|
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
|
||||||
layoutDisplayFiltersOptions={
|
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
|
||||||
}
|
|
||||||
labels={projectLabels}
|
|
||||||
memberIds={projectMemberIds ?? undefined}
|
|
||||||
states={projectStates}
|
|
||||||
/>
|
|
||||||
</FiltersDropdown>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
|
||||||
<FiltersDropdown
|
|
||||||
title="Display"
|
|
||||||
placement="bottom-end"
|
|
||||||
menuButton={
|
|
||||||
<span className="flex items-center text-custom-text-200 text-sm">
|
|
||||||
Display
|
|
||||||
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DisplayFiltersSelection
|
|
||||||
layoutDisplayFiltersOptions={
|
|
||||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
|
||||||
}
|
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
|
||||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
|
||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
|
||||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
|
||||||
/>
|
|
||||||
</FiltersDropdown>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setAnalyticsModal(true)}
|
|
||||||
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
|
|
||||||
>
|
|
||||||
Analytics
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
|
||||||
|
<FiltersDropdown
|
||||||
|
title="Display"
|
||||||
|
placement="bottom-end"
|
||||||
|
menuButton={
|
||||||
|
<span className="flex items-center text-custom-text-200 text-sm">
|
||||||
|
Display
|
||||||
|
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DisplayFiltersSelection
|
||||||
|
layoutDisplayFiltersOptions={
|
||||||
|
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
|
||||||
|
}
|
||||||
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
|
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||||
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
|
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||||
|
ignoreGroupedFilters={["module"]}
|
||||||
|
/>
|
||||||
|
</FiltersDropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setAnalyticsModal(true)}
|
||||||
|
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
|
||||||
|
>
|
||||||
|
Analytics
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -49,22 +49,6 @@ export const ISSUE_PRIORITIES: {
|
|||||||
{ key: "none", title: "None" },
|
{ key: "none", title: "None" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ISSUE_START_DATE_OPTIONS = [
|
|
||||||
{ key: "last_week", title: "Last Week" },
|
|
||||||
{ key: "2_weeks_from_now", title: "2 weeks from now" },
|
|
||||||
{ key: "1_month_from_now", title: "1 month from now" },
|
|
||||||
{ key: "2_months_from_now", title: "2 months from now" },
|
|
||||||
{ key: "custom", title: "Custom" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_DUE_DATE_OPTIONS = [
|
|
||||||
{ key: "last_week", title: "Last Week" },
|
|
||||||
{ key: "2_weeks_from_now", title: "2 weeks from now" },
|
|
||||||
{ key: "1_month_from_now", title: "1 month from now" },
|
|
||||||
{ key: "2_months_from_now", title: "2 months from now" },
|
|
||||||
{ key: "custom", title: "Custom" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_GROUP_BY_OPTIONS: {
|
export const ISSUE_GROUP_BY_OPTIONS: {
|
||||||
key: TIssueGroupByOptions;
|
key: TIssueGroupByOptions;
|
||||||
title: string;
|
title: string;
|
||||||
@ -73,6 +57,8 @@ export const ISSUE_GROUP_BY_OPTIONS: {
|
|||||||
{ key: "state_detail.group", title: "State Groups" },
|
{ key: "state_detail.group", title: "State Groups" },
|
||||||
{ key: "priority", title: "Priority" },
|
{ key: "priority", title: "Priority" },
|
||||||
{ key: "project", title: "Project" }, // required this on my issues
|
{ key: "project", title: "Project" }, // required this on my issues
|
||||||
|
{ key: "cycle", title: "Cycle" }, // required this on my issues
|
||||||
|
{ key: "module", title: "Module" }, // required this on my issues
|
||||||
{ key: "labels", title: "Labels" },
|
{ key: "labels", title: "Labels" },
|
||||||
{ key: "assignees", title: "Assignees" },
|
{ key: "assignees", title: "Assignees" },
|
||||||
{ key: "created_by", title: "Created By" },
|
{ key: "created_by", title: "Created By" },
|
||||||
@ -140,81 +126,6 @@ export const ISSUE_LAYOUTS: {
|
|||||||
{ key: "gantt_chart", title: "Gantt Chart Layout", icon: GanttChartSquare },
|
{ key: "gantt_chart", title: "Gantt Chart Layout", icon: GanttChartSquare },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ISSUE_LIST_FILTERS = [
|
|
||||||
{ key: "mentions", title: "Mentions" },
|
|
||||||
{ key: "priority", title: "Priority" },
|
|
||||||
{ key: "state", title: "State" },
|
|
||||||
{ key: "assignees", title: "Assignees" },
|
|
||||||
{ key: "created_by", title: "Created By" },
|
|
||||||
{ key: "labels", title: "Labels" },
|
|
||||||
{ key: "start_date", title: "Start Date" },
|
|
||||||
{ key: "due_date", title: "Due Date" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_KANBAN_FILTERS = [
|
|
||||||
{ key: "priority", title: "Priority" },
|
|
||||||
{ key: "state", title: "State" },
|
|
||||||
{ key: "assignees", title: "Assignees" },
|
|
||||||
{ key: "created_by", title: "Created By" },
|
|
||||||
{ key: "labels", title: "Labels" },
|
|
||||||
{ key: "start_date", title: "Start Date" },
|
|
||||||
{ key: "due_date", title: "Due Date" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_CALENDER_FILTERS = [
|
|
||||||
{ key: "priority", title: "Priority" },
|
|
||||||
{ key: "state", title: "State" },
|
|
||||||
{ key: "assignees", title: "Assignees" },
|
|
||||||
{ key: "created_by", title: "Created By" },
|
|
||||||
{ key: "labels", title: "Labels" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_SPREADSHEET_FILTERS = [
|
|
||||||
{ key: "priority", title: "Priority" },
|
|
||||||
{ key: "state", title: "State" },
|
|
||||||
{ key: "assignees", title: "Assignees" },
|
|
||||||
{ key: "created_by", title: "Created By" },
|
|
||||||
{ key: "labels", title: "Labels" },
|
|
||||||
{ key: "start_date", title: "Start Date" },
|
|
||||||
{ key: "due_date", title: "Due Date" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_GANTT_FILTERS = [
|
|
||||||
{ key: "priority", title: "Priority" },
|
|
||||||
{ key: "state", title: "State" },
|
|
||||||
{ key: "assignees", title: "Assignees" },
|
|
||||||
{ key: "created_by", title: "Created By" },
|
|
||||||
{ key: "labels", title: "Labels" },
|
|
||||||
{ key: "start_date", title: "Start Date" },
|
|
||||||
{ key: "due_date", title: "Due Date" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_LIST_DISPLAY_FILTERS = [
|
|
||||||
{ key: "group_by", title: "Group By" },
|
|
||||||
{ key: "order_by", title: "Order By" },
|
|
||||||
{ key: "issue_type", title: "Issue Type" },
|
|
||||||
{ key: "sub_issue", title: "Sub Issue" },
|
|
||||||
{ key: "show_empty_groups", title: "Show Empty Groups" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_KANBAN_DISPLAY_FILTERS = [
|
|
||||||
{ key: "group_by", title: "Group By" },
|
|
||||||
{ key: "order_by", title: "Order By" },
|
|
||||||
{ key: "issue_type", title: "Issue Type" },
|
|
||||||
{ key: "sub_issue", title: "Sub Issue" },
|
|
||||||
{ key: "show_empty_groups", title: "Show Empty Groups" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ISSUE_CALENDER_DISPLAY_FILTERS = [{ key: "issue_type", title: "Issue Type" }];
|
|
||||||
|
|
||||||
export const ISSUE_SPREADSHEET_DISPLAY_FILTERS = [{ key: "issue_type", title: "Issue Type" }];
|
|
||||||
|
|
||||||
export const ISSUE_GANTT_DISPLAY_FILTERS = [
|
|
||||||
{ key: "order_by", title: "Order By" },
|
|
||||||
{ key: "issue_type", title: "Issue Type" },
|
|
||||||
{ key: "sub_issue", title: "Sub Issue" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export interface ILayoutDisplayFiltersOptions {
|
export interface ILayoutDisplayFiltersOptions {
|
||||||
filters: (keyof IIssueFilterOptions)[];
|
filters: (keyof IIssueFilterOptions)[];
|
||||||
display_properties: boolean;
|
display_properties: boolean;
|
||||||
@ -276,7 +187,17 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
],
|
],
|
||||||
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",
|
||||||
|
"cycle",
|
||||||
|
"module",
|
||||||
|
"state_detail.group",
|
||||||
|
"priority",
|
||||||
|
"labels",
|
||||||
|
"assignees",
|
||||||
|
"created_by",
|
||||||
|
null,
|
||||||
|
],
|
||||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||||
type: [null, "active", "backlog"],
|
type: [null, "active", "backlog"],
|
||||||
},
|
},
|
||||||
@ -291,7 +212,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
filters: ["priority", "state_group", "cycle", "module", "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", "cycle", "module", "priority", "project", "labels", null],
|
||||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||||
type: [null, "active", "backlog"],
|
type: [null, "active", "backlog"],
|
||||||
},
|
},
|
||||||
@ -304,7 +225,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
filters: ["priority", "state_group", "cycle", "module", "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", "cycle", "module", "priority", "project", "labels"],
|
||||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||||
type: [null, "active", "backlog"],
|
type: [null, "active", "backlog"],
|
||||||
},
|
},
|
||||||
@ -374,7 +295,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
],
|
],
|
||||||
display_properties: true,
|
display_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
group_by: ["state", "priority", "labels", "assignees", "created_by", null],
|
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
|
||||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||||
type: [null, "active", "backlog"],
|
type: [null, "active", "backlog"],
|
||||||
},
|
},
|
||||||
@ -398,8 +319,8 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
],
|
],
|
||||||
display_properties: true,
|
display_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
group_by: ["state", "priority", "labels", "assignees", "created_by"],
|
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by"],
|
||||||
sub_group_by: ["state", "priority", "labels", "assignees", "created_by", null],
|
sub_group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
|
||||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
|
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
|
||||||
type: [null, "active", "backlog"],
|
type: [null, "active", "backlog"],
|
||||||
},
|
},
|
||||||
|
@ -36,6 +36,8 @@ export type TIssueHelperStore = {
|
|||||||
|
|
||||||
const ISSUE_FILTER_DEFAULT_DATA: Record<TIssueDisplayFilterOptions, keyof TIssue> = {
|
const ISSUE_FILTER_DEFAULT_DATA: Record<TIssueDisplayFilterOptions, keyof TIssue> = {
|
||||||
project: "project_id",
|
project: "project_id",
|
||||||
|
cycle: "cycle_id",
|
||||||
|
module: "module_ids",
|
||||||
state: "state_id",
|
state: "state_id",
|
||||||
"state_detail.group": "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display,
|
"state_detail.group": "state_group" as keyof TIssue, // state_detail.group is only being used for state_group display,
|
||||||
priority: "priority",
|
priority: "priority",
|
||||||
@ -157,6 +159,10 @@ export class IssueHelperStore implements TIssueHelperStore {
|
|||||||
return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {});
|
return Object.keys(this.rootStore?.workSpaceMemberRolesMap || {});
|
||||||
case "project":
|
case "project":
|
||||||
return Object.keys(this.rootStore?.projectMap || {});
|
return Object.keys(this.rootStore?.projectMap || {});
|
||||||
|
case "cycle":
|
||||||
|
return Object.keys(this.rootStore?.cycleMap || {});
|
||||||
|
case "module":
|
||||||
|
return Object.keys(this.rootStore?.moduleMap || {});
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user