[WEB-2213] fix: group by persistence for list view (#5590)
* fix kanban view localStorage * add functionality for list view and add type for kanban function * add comment in issue-filter-helper store * improved code quality * add comment for clarity * use better variable names * use useCallback hook and change variable name * made suggested changes
This commit is contained in:
parent
8291043704
commit
b7ee7e19fc
17 changed files with 115 additions and 74 deletions
|
|
@ -199,21 +199,24 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKanbanFilters = (toggle: "group_by" | "sub_group_by", value: string) => {
|
const handleCollapsedGroups = useCallback(
|
||||||
if (workspaceSlug) {
|
(toggle: "group_by" | "sub_group_by", value: string) => {
|
||||||
let kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || [];
|
if (workspaceSlug) {
|
||||||
if (kanbanFilters.includes(value)) {
|
let collapsedGroups = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || [];
|
||||||
kanbanFilters = kanbanFilters.filter((_value) => _value != value);
|
if (collapsedGroups.includes(value)) {
|
||||||
} else {
|
collapsedGroups = collapsedGroups.filter((_value) => _value != value);
|
||||||
kanbanFilters.push(value);
|
} else {
|
||||||
|
collapsedGroups.push(value);
|
||||||
|
}
|
||||||
|
updateFilters(projectId?.toString() ?? "", EIssueFilterType.KANBAN_FILTERS, {
|
||||||
|
[toggle]: collapsedGroups,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
updateFilters(projectId?.toString() ?? "", EIssueFilterType.KANBAN_FILTERS, {
|
},
|
||||||
[toggle]: kanbanFilters,
|
[workspaceSlug, issuesFilter, projectId, updateFilters]
|
||||||
});
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters || { group_by: [], sub_group_by: [] };
|
const collapsedGroups = issuesFilter?.issueFilters?.kanbanFilters || { group_by: [], sub_group_by: [] };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueLayoutHOC layout={EIssueLayoutTypes.KANBAN}>
|
<IssueLayoutHOC layout={EIssueLayoutTypes.KANBAN}>
|
||||||
|
|
@ -258,8 +261,8 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||||
orderBy={orderBy}
|
orderBy={orderBy}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
quickActions={renderQuickActions}
|
quickActions={renderQuickActions}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
kanbanFilters={kanbanFilters}
|
collapsedGroups={collapsedGroups}
|
||||||
enableQuickIssueCreate={enableQuickAdd}
|
enableQuickIssueCreate={enableQuickAdd}
|
||||||
showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true}
|
showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true}
|
||||||
quickAddCallback={quickAddIssue}
|
quickAddCallback={quickAddIssue}
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ export interface IKanBan {
|
||||||
sub_group_index?: number;
|
sub_group_index?: number;
|
||||||
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
collapsedGroups: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: any;
|
handleCollapsedGroups: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||||
|
|
@ -71,8 +71,8 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||||
sub_group_id = "null",
|
sub_group_id = "null",
|
||||||
updateIssue,
|
updateIssue,
|
||||||
quickActions,
|
quickActions,
|
||||||
kanbanFilters,
|
collapsedGroups,
|
||||||
handleKanbanFilters,
|
handleCollapsedGroups,
|
||||||
enableQuickIssueCreate,
|
enableQuickIssueCreate,
|
||||||
quickAddCallback,
|
quickAddCallback,
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
|
|
@ -133,7 +133,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||||
if ((getGroupIssueCount(_list.id, undefined, false) ?? 0) > 0) groupVisibility.showGroup = true;
|
if ((getGroupIssueCount(_list.id, undefined, false) ?? 0) > 0) groupVisibility.showGroup = true;
|
||||||
else groupVisibility.showGroup = false;
|
else groupVisibility.showGroup = false;
|
||||||
}
|
}
|
||||||
if (kanbanFilters?.group_by.includes(_list.id)) groupVisibility.showIssues = false;
|
if (collapsedGroups?.group_by.includes(_list.id)) groupVisibility.showIssues = false;
|
||||||
return groupVisibility;
|
return groupVisibility;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -176,8 +176,8 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||||
issuePayload={subList.payload}
|
issuePayload={subList.payload}
|
||||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
|
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
kanbanFilters={kanbanFilters}
|
collapsedGroups={collapsedGroups}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ interface IHeaderGroupByCard {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
count: number;
|
count: number;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
collapsedGroups: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: any;
|
handleCollapsedGroups: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
issuePayload: Partial<TIssue>;
|
issuePayload: Partial<TIssue>;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||||
|
|
@ -38,13 +38,13 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
count,
|
count,
|
||||||
kanbanFilters,
|
collapsedGroups,
|
||||||
handleKanbanFilters,
|
handleCollapsedGroups,
|
||||||
issuePayload,
|
issuePayload,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
addIssuesToView,
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
const verticalAlignPosition = sub_group_by ? false : kanbanFilters?.group_by.includes(column_id);
|
const verticalAlignPosition = sub_group_by ? false : collapsedGroups?.group_by.includes(column_id);
|
||||||
// states
|
// states
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
||||||
|
|
@ -133,7 +133,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||||
{sub_group_by === null && (
|
{sub_group_by === null && (
|
||||||
<div
|
<div
|
||||||
className="flex h-[20px] w-[20px] flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm transition-all hover:bg-custom-background-80"
|
className="flex h-[20px] w-[20px] flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm transition-all hover:bg-custom-background-80"
|
||||||
onClick={() => handleKanbanFilters("group_by", column_id)}
|
onClick={() => handleCollapsedGroups("group_by", column_id)}
|
||||||
>
|
>
|
||||||
{verticalAlignPosition ? (
|
{verticalAlignPosition ? (
|
||||||
<Maximize2 width={14} strokeWidth={2} />
|
<Maximize2 width={14} strokeWidth={2} />
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,19 @@ interface IHeaderSubGroupByCard {
|
||||||
title: string;
|
title: string;
|
||||||
count: number;
|
count: number;
|
||||||
column_id: string;
|
column_id: string;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
collapsedGroups: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
handleCollapsedGroups: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderSubGroupByCard: FC<IHeaderSubGroupByCard> = observer((props) => {
|
export const HeaderSubGroupByCard: FC<IHeaderSubGroupByCard> = observer((props) => {
|
||||||
const { icon, title, count, column_id, kanbanFilters, handleKanbanFilters } = props;
|
const { icon, title, count, column_id, collapsedGroups, handleCollapsedGroups } = props;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative flex w-full flex-shrink-0 flex-row items-center gap-1 rounded-sm py-1.5 cursor-pointer`}
|
className={`relative flex w-full flex-shrink-0 flex-row items-center gap-1 rounded-sm py-1.5 cursor-pointer`}
|
||||||
onClick={() => handleKanbanFilters("sub_group_by", column_id)}
|
onClick={() => handleCollapsedGroups("sub_group_by", column_id)}
|
||||||
>
|
>
|
||||||
<div className="flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm transition-all hover:bg-custom-background-80">
|
<div className="flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm transition-all hover:bg-custom-background-80">
|
||||||
{kanbanFilters?.sub_group_by.includes(column_id) ? (
|
{collapsedGroups?.sub_group_by.includes(column_id) ? (
|
||||||
<ChevronDown width={14} strokeWidth={2} />
|
<ChevronDown width={14} strokeWidth={2} />
|
||||||
) : (
|
) : (
|
||||||
<ChevronUp width={14} strokeWidth={2} />
|
<ChevronUp width={14} strokeWidth={2} />
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ interface ISubGroupSwimlaneHeader {
|
||||||
sub_group_by: TIssueGroupByOptions | undefined;
|
sub_group_by: TIssueGroupByOptions | undefined;
|
||||||
group_by: TIssueGroupByOptions | undefined;
|
group_by: TIssueGroupByOptions | undefined;
|
||||||
list: IGroupByColumn[];
|
list: IGroupByColumn[];
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
collapsedGroups: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
handleCollapsedGroups: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ const visibilitySubGroupByGroupCount = (subGroupIssueCount: number, showEmptyGro
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||||
({ getGroupIssueCount, sub_group_by, group_by, list, kanbanFilters, handleKanbanFilters, showEmptyGroup }) => (
|
({ getGroupIssueCount, sub_group_by, group_by, list, collapsedGroups, handleCollapsedGroups, showEmptyGroup }) => (
|
||||||
<div className="relative flex h-max min-h-full w-full items-center gap-2">
|
<div className="relative flex h-max min-h-full w-full items-center gap-2">
|
||||||
{list &&
|
{list &&
|
||||||
list.length > 0 &&
|
list.length > 0 &&
|
||||||
|
|
@ -73,8 +73,8 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||||
icon={_list.icon}
|
icon={_list.icon}
|
||||||
title={_list.name}
|
title={_list.name}
|
||||||
count={groupCount}
|
count={groupCount}
|
||||||
kanbanFilters={kanbanFilters}
|
collapsedGroups={collapsedGroups}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
issuePayload={_list.payload}
|
issuePayload={_list.payload}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -96,8 +96,8 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
collapsedGroups: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
handleCollapsedGroups: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
enableQuickIssueCreate: boolean;
|
enableQuickIssueCreate: boolean;
|
||||||
|
|
@ -120,8 +120,8 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||||
updateIssue,
|
updateIssue,
|
||||||
quickActions,
|
quickActions,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
kanbanFilters,
|
collapsedGroups,
|
||||||
handleKanbanFilters,
|
handleCollapsedGroups,
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
showEmptyGroup,
|
showEmptyGroup,
|
||||||
enableQuickIssueCreate,
|
enableQuickIssueCreate,
|
||||||
|
|
@ -147,7 +147,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||||
if (subGroupCount > 0) subGroupVisibility.showGroup = true;
|
if (subGroupCount > 0) subGroupVisibility.showGroup = true;
|
||||||
else subGroupVisibility.showGroup = false;
|
else subGroupVisibility.showGroup = false;
|
||||||
}
|
}
|
||||||
if (kanbanFilters?.sub_group_by.includes(_list.id)) subGroupVisibility.showIssues = false;
|
if (collapsedGroups?.sub_group_by.includes(_list.id)) subGroupVisibility.showIssues = false;
|
||||||
return subGroupVisibility;
|
return subGroupVisibility;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -168,8 +168,8 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||||
icon={_list.icon}
|
icon={_list.icon}
|
||||||
title={_list.name || ""}
|
title={_list.name || ""}
|
||||||
count={issueCount}
|
count={issueCount}
|
||||||
kanbanFilters={kanbanFilters}
|
collapsedGroups={collapsedGroups}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -187,8 +187,8 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||||
subGroupIndex={subGroupIndex}
|
subGroupIndex={subGroupIndex}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
kanbanFilters={kanbanFilters}
|
collapsedGroups={collapsedGroups}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
|
|
@ -224,8 +224,8 @@ export interface IKanBanSwimLanes {
|
||||||
group_by: TIssueGroupByOptions | undefined;
|
group_by: TIssueGroupByOptions | undefined;
|
||||||
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
collapsedGroups: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
handleCollapsedGroups: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||||
|
|
@ -249,8 +249,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||||
orderBy,
|
orderBy,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
quickActions,
|
quickActions,
|
||||||
kanbanFilters,
|
collapsedGroups,
|
||||||
handleKanbanFilters,
|
handleCollapsedGroups,
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
showEmptyGroup,
|
showEmptyGroup,
|
||||||
handleOnDrop,
|
handleOnDrop,
|
||||||
|
|
@ -303,8 +303,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||||
getGroupIssueCount={getGroupIssueCount}
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
kanbanFilters={kanbanFilters}
|
collapsedGroups={collapsedGroups}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
list={groupByList}
|
list={groupByList}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
/>
|
/>
|
||||||
|
|
@ -322,8 +322,8 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||||
orderBy={orderBy}
|
orderBy={orderBy}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
kanbanFilters={kanbanFilters}
|
collapsedGroups={collapsedGroups}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
handleOnDrop={handleOnDrop}
|
handleOnDrop={handleOnDrop}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { FC, useCallback, useEffect } from "react";
|
import { FC, useCallback, useEffect } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// types
|
// types
|
||||||
import { GroupByColumnTypes, TGroupedIssues } from "@plane/types";
|
import { useParams } from "next/navigation";
|
||||||
|
import { GroupByColumnTypes, TGroupedIssues, TIssueKanbanFilters } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue";
|
import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues, useUserPermissions } from "@/hooks/store";
|
import { useIssues, useUserPermissions } from "@/hooks/store";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -59,6 +60,10 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||||
const group_by = (displayFilters?.group_by || null) as GroupByColumnTypes | null;
|
const group_by = (displayFilters?.group_by || null) as GroupByColumnTypes | null;
|
||||||
const showEmptyGroup = displayFilters?.show_empty_groups ?? false;
|
const showEmptyGroup = displayFilters?.show_empty_groups ?? false;
|
||||||
|
|
||||||
|
const { workspaceSlug, projectId } = useParams();
|
||||||
|
const {updateFilters} = useIssuesActions(storeType);
|
||||||
|
const collapsedGroups = issuesFilter?.issueFilters?.kanbanFilters || { group_by: [], sub_group_by: [] } as TIssueKanbanFilters;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchIssues("init-loader", { canGroup: true, perPageCount: group_by ? 50 : 100 }, viewId);
|
fetchIssues("init-loader", { canGroup: true, perPageCount: group_by ? 50 : 100 }, viewId);
|
||||||
}, [fetchIssues, storeType, group_by, viewId]);
|
}, [fetchIssues, storeType, group_by, viewId]);
|
||||||
|
|
@ -107,6 +112,25 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||||
[fetchNextIssues]
|
[fetchNextIssues]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// kanbanFilters and EIssueFilterType.KANBAN_FILTERS are used becuase the state is shared between kanban view and list view
|
||||||
|
const handleCollapsedGroups = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
if (workspaceSlug) {
|
||||||
|
let collapsedGroups = issuesFilter?.issueFilters?.kanbanFilters?.group_by || [];
|
||||||
|
if (collapsedGroups.includes(value)) {
|
||||||
|
collapsedGroups = collapsedGroups.filter((_value) => _value != value);
|
||||||
|
} else {
|
||||||
|
collapsedGroups.push(value);
|
||||||
|
}
|
||||||
|
updateFilters(projectId?.toString() ?? "", EIssueFilterType.KANBAN_FILTERS,
|
||||||
|
{ group_by: collapsedGroups } as TIssueKanbanFilters
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[workspaceSlug, issuesFilter, projectId, updateFilters]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueLayoutHOC layout={EIssueLayoutTypes.LIST}>
|
<IssueLayoutHOC layout={EIssueLayoutTypes.LIST}>
|
||||||
<div className={`relative size-full bg-custom-background-90`}>
|
<div className={`relative size-full bg-custom-background-90`}>
|
||||||
|
|
@ -127,6 +151,8 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
isCompletedCycle={isCompletedCycle}
|
isCompletedCycle={isCompletedCycle}
|
||||||
handleOnDrop={handleOnDrop}
|
handleOnDrop={handleOnDrop}
|
||||||
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
|
collapsedGroups={collapsedGroups}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</IssueLayoutHOC>
|
</IssueLayoutHOC>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
TIssueGroupByOptions,
|
TIssueGroupByOptions,
|
||||||
TIssueOrderByOptions,
|
TIssueOrderByOptions,
|
||||||
IGroupByColumn,
|
IGroupByColumn,
|
||||||
|
TIssueKanbanFilters,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { MultipleSelectGroup } from "@/components/core";
|
import { MultipleSelectGroup } from "@/components/core";
|
||||||
|
|
@ -47,6 +48,8 @@ export interface IList {
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||||
isCompletedCycle?: boolean;
|
isCompletedCycle?: boolean;
|
||||||
loadMoreIssues: (groupId?: string) => void;
|
loadMoreIssues: (groupId?: string) => void;
|
||||||
|
handleCollapsedGroups: (value: string) => void;
|
||||||
|
collapsedGroups : TIssueKanbanFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const List: React.FC<IList> = observer((props) => {
|
export const List: React.FC<IList> = observer((props) => {
|
||||||
|
|
@ -67,6 +70,8 @@ export const List: React.FC<IList> = observer((props) => {
|
||||||
addIssuesToView,
|
addIssuesToView,
|
||||||
isCompletedCycle = false,
|
isCompletedCycle = false,
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
|
handleCollapsedGroups,
|
||||||
|
collapsedGroups
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const storeType = useIssueStoreType();
|
const storeType = useIssueStoreType();
|
||||||
|
|
@ -162,6 +167,8 @@ export const List: React.FC<IList> = observer((props) => {
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
selectionHelpers={helpers}
|
selectionHelpers={helpers}
|
||||||
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
|
collapsedGroups={collapsedGroups}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ interface IHeaderGroupByCard {
|
||||||
count: number;
|
count: number;
|
||||||
issuePayload: Partial<TIssue>;
|
issuePayload: Partial<TIssue>;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
toggleListGroup: () => void;
|
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||||
selectionHelpers: TSelectionHelper;
|
selectionHelpers: TSelectionHelper;
|
||||||
|
handleCollapsedGroups: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
||||||
|
|
@ -43,7 +43,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
addIssuesToView,
|
addIssuesToView,
|
||||||
selectionHelpers,
|
selectionHelpers,
|
||||||
toggleListGroup,
|
handleCollapsedGroups
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
@ -108,7 +108,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="relative flex w-full flex-row items-center gap-1 overflow-hidden cursor-pointer"
|
className="relative flex w-full flex-row items-center gap-1 overflow-hidden cursor-pointer"
|
||||||
onClick={toggleListGroup}
|
onClick={() => handleCollapsedGroups(groupID)}
|
||||||
>
|
>
|
||||||
<div className="inline-block line-clamp-1 truncate font-medium text-custom-text-100">{title}</div>
|
<div className="inline-block line-clamp-1 truncate font-medium text-custom-text-100">{title}</div>
|
||||||
<div className="pl-2 text-sm font-medium text-custom-text-300">{count || 0}</div>
|
<div className="pl-2 text-sm font-medium text-custom-text-300">{count || 0}</div>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
TIssueOrderByOptions,
|
TIssueOrderByOptions,
|
||||||
TIssue,
|
TIssue,
|
||||||
IIssueDisplayProperties,
|
IIssueDisplayProperties,
|
||||||
|
TIssueKanbanFilters,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
import { Row, setToast, TOAST_TYPE } from "@plane/ui";
|
import { Row, setToast, TOAST_TYPE } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
|
|
@ -60,6 +61,8 @@ interface Props {
|
||||||
showEmptyGroup?: boolean;
|
showEmptyGroup?: boolean;
|
||||||
loadMoreIssues: (groupId?: string) => void;
|
loadMoreIssues: (groupId?: string) => void;
|
||||||
selectionHelpers: TSelectionHelper;
|
selectionHelpers: TSelectionHelper;
|
||||||
|
handleCollapsedGroups: (value: string) => void;
|
||||||
|
collapsedGroups: TIssueKanbanFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ListGroup = observer((props: Props) => {
|
export const ListGroup = observer((props: Props) => {
|
||||||
|
|
@ -84,11 +87,13 @@ export const ListGroup = observer((props: Props) => {
|
||||||
showEmptyGroup,
|
showEmptyGroup,
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
selectionHelpers,
|
selectionHelpers,
|
||||||
|
handleCollapsedGroups,
|
||||||
|
collapsedGroups,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false);
|
const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false);
|
||||||
const [dragColumnOrientation, setDragColumnOrientation] = useState<"justify-start" | "justify-end">("justify-start");
|
const [dragColumnOrientation, setDragColumnOrientation] = useState<"justify-start" | "justify-end">("justify-start");
|
||||||
const [isExpanded, setIsExpanded] = useState(true);
|
const isExpanded = !(collapsedGroups?.group_by.includes(group.id))
|
||||||
const groupRef = useRef<HTMLDivElement | null>(null);
|
const groupRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
|
|
@ -129,10 +134,6 @@ export const ListGroup = observer((props: Props) => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleListGroup = () => {
|
|
||||||
setIsExpanded((prevState) => !prevState);
|
|
||||||
};
|
|
||||||
|
|
||||||
const prePopulateQuickAddData = (groupByKey: string | null, value: any) => {
|
const prePopulateQuickAddData = (groupByKey: string | null, value: any) => {
|
||||||
const defaultState = projectState.projectStates?.find((state) => state.default);
|
const defaultState = projectState.projectStates?.find((state) => state.default);
|
||||||
let preloadedData: object = { state_id: defaultState?.id };
|
let preloadedData: object = { state_id: defaultState?.id };
|
||||||
|
|
@ -213,6 +214,10 @@ export const ListGroup = observer((props: Props) => {
|
||||||
handleOnDrop(source, destination);
|
handleOnDrop(source, destination);
|
||||||
|
|
||||||
highlightIssueOnDrop(getIssueBlockId(source.id, destination?.groupId), orderBy !== "sort_order");
|
highlightIssueOnDrop(getIssueBlockId(source.id, destination?.groupId), orderBy !== "sort_order");
|
||||||
|
|
||||||
|
if(!isExpanded){
|
||||||
|
handleCollapsedGroups(group.id)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -248,7 +253,7 @@ export const ListGroup = observer((props: Props) => {
|
||||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy || isCompletedCycle}
|
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy || isCompletedCycle}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
selectionHelpers={selectionHelpers}
|
selectionHelpers={selectionHelpers}
|
||||||
toggleListGroup={toggleListGroup}
|
handleCollapsedGroups={handleCollapsedGroups}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
{shouldExpand && (
|
{shouldExpand && (
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,7 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
|
||||||
|
|
||||||
const currentUserId = this.rootIssueStore.currentUserId;
|
const currentUserId = this.rootIssueStore.currentUserId;
|
||||||
if (currentUserId)
|
if (currentUserId)
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT, type, workspaceSlug, projectId, undefined, {
|
this.handleIssuesLocalFilters.set(EIssuesStoreType.ARCHIVED, type, workspaceSlug, projectId, undefined, {
|
||||||
kanban_filters: _filters.kanbanFilters,
|
kanban_filters: _filters.kanbanFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
|
||||||
|
|
||||||
const currentUserId = this.rootIssueStore.currentUserId;
|
const currentUserId = this.rootIssueStore.currentUserId;
|
||||||
if (currentUserId)
|
if (currentUserId)
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT, type, workspaceSlug, cycleId, currentUserId, {
|
this.handleIssuesLocalFilters.set(EIssuesStoreType.CYCLE, type, workspaceSlug, cycleId, currentUserId, {
|
||||||
kanban_filters: _filters.kanbanFilters,
|
kanban_filters: _filters.kanbanFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,7 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI
|
||||||
|
|
||||||
const currentUserId = this.rootIssueStore.currentUserId;
|
const currentUserId = this.rootIssueStore.currentUserId;
|
||||||
if (currentUserId)
|
if (currentUserId)
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT, type, workspaceSlug, projectId, undefined, {
|
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
|
||||||
kanban_filters: _filters.kanbanFilters,
|
kanban_filters: _filters.kanbanFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
||||||
[filterType]: filters[filterType],
|
[filterType]: filters[filterType],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
// All group_by "filters" are stored in a single array, will cause inconsistency in case of duplicated values
|
||||||
storage.set("issue_local_filters", JSON.stringify(storageFilters));
|
storage.set("issue_local_filters", JSON.stringify(storageFilters));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
||||||
|
|
||||||
const currentUserId = this.rootIssueStore.currentUserId;
|
const currentUserId = this.rootIssueStore.currentUserId;
|
||||||
if (currentUserId)
|
if (currentUserId)
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT, type, workspaceSlug, moduleId, currentUserId, {
|
this.handleIssuesLocalFilters.set(EIssuesStoreType.MODULE, type, workspaceSlug, moduleId, currentUserId, {
|
||||||
kanban_filters: _filters.kanbanFilters,
|
kanban_filters: _filters.kanbanFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
|
||||||
|
|
||||||
const currentUserId = this.rootIssueStore.currentUserId;
|
const currentUserId = this.rootIssueStore.currentUserId;
|
||||||
if (currentUserId)
|
if (currentUserId)
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT, type, workspaceSlug, userId, undefined, {
|
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROFILE, type, workspaceSlug, userId, undefined, {
|
||||||
kanban_filters: _filters.kanbanFilters,
|
kanban_filters: _filters.kanbanFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,7 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
|
||||||
|
|
||||||
const currentUserId = this.rootIssueStore.currentUserId;
|
const currentUserId = this.rootIssueStore.currentUserId;
|
||||||
if (currentUserId)
|
if (currentUserId)
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT, type, workspaceSlug, viewId, currentUserId, {
|
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT_VIEW, type, workspaceSlug, viewId, currentUserId, {
|
||||||
kanban_filters: _filters.kanbanFilters,
|
kanban_filters: _filters.kanbanFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
||||||
|
|
||||||
const currentUserId = this.rootIssueStore.currentUserId;
|
const currentUserId = this.rootIssueStore.currentUserId;
|
||||||
if (currentUserId)
|
if (currentUserId)
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT, type, workspaceSlug, undefined, viewId, {
|
this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, {
|
||||||
kanban_filters: _filters.kanbanFilters,
|
kanban_filters: _filters.kanbanFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue