dev: gantt chart implementation using MobX (#2302)

* dev: fetch project gantt issues using mobx

* chore: handle group by options in the kanban layout
This commit is contained in:
Aaryan Khandelwal 2023-09-29 15:00:51 +05:30 committed by GitHub
parent 479c145b02
commit 9ad1e73666
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 120 additions and 246 deletions

View File

@ -1,94 +1,24 @@
import React, { useCallback, useState } from "react";
import React from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// react-beautiful-dnd
import { DragDropContext, DropResult } from "react-beautiful-dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
// services
import stateService from "services/project_state.service";
// hooks
import useUser from "hooks/use-user";
import { useProjectMyMembership } from "contexts/project-member.context";
// components
import { AllLists, AllBoards, CalendarView, SpreadsheetView, GanttChartView } from "components/core";
import { CalendarLayout, KanBanLayout } from "components/issues";
// ui
import { EmptyState, Spinner } from "components/ui";
// icons
import { TrashIcon } from "@heroicons/react/24/outline";
// images
import emptyIssue from "public/empty-state/issue.svg";
import emptyIssueArchive from "public/empty-state/issue-archive.svg";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
import { IIssue, IIssueViewProps } from "types";
// fetch-keys
import { STATES_LIST } from "constants/fetch-keys";
// store
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
import { observer } from "mobx-react-lite";
// components
import { CalendarLayout, GanttLayout, KanBanLayout } from "components/issues";
type Props = {
addIssueToDate: (date: string) => void;
addIssueToGroup: (groupTitle: string) => void;
disableUserActions: boolean;
dragDisabled?: boolean;
emptyState: {
title: string;
description?: string;
primaryButton?: {
icon: any;
text: string;
onClick: () => void;
};
secondaryButton?: React.ReactNode;
};
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
handleDraftIssueAction?: (issue: IIssue, action: "edit" | "delete") => void;
handleOnDragEnd: (result: DropResult) => Promise<void>;
openIssuesListModal: (() => void) | null;
removeIssue: ((bridgeId: string, issueId: string) => void) | null;
disableAddIssueOption?: boolean;
trashBox: boolean;
setTrashBox: React.Dispatch<React.SetStateAction<boolean>>;
viewProps: IIssueViewProps;
};
export const AllViews: React.FC<Props> = observer(({ trashBox, setTrashBox }) => {
export const AllViews: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query as {
const { workspaceSlug, projectId } = router.query as {
workspaceSlug: string;
projectId: string;
cycleId: string;
moduleId: string;
};
const [myIssueProjectId, setMyIssueProjectId] = useState<string | null>(null);
const { issue: issueStore, project: projectStore, issueFilter: issueFilterStore } = useMobxStore();
const { data: stateGroups } = useSWR(
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
workspaceSlug ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
);
const states = getStatesList(stateGroups);
const handleMyIssueOpen = (issue: IIssue) => {
setMyIssueProjectId(issue.project);
};
const handleTrashBox = useCallback(
(isDragging: boolean) => {
if (isDragging && !trashBox) setTrashBox(true);
},
[trashBox, setTrashBox]
);
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES` : null, async () => {
if (workspaceSlug && projectId) {
await issueFilterStore.fetchUserProjectFilters(workspaceSlug, projectId);
@ -105,7 +35,13 @@ export const AllViews: React.FC<Props> = observer(({ trashBox, setTrashBox }) =>
return (
<div className="relative w-full h-full overflow-auto">
{activeLayout === "kanban" ? <KanBanLayout /> : activeLayout === "calendar" ? <CalendarLayout /> : null}
{activeLayout === "kanban" ? (
<KanBanLayout />
) : activeLayout === "calendar" ? (
<CalendarLayout />
) : activeLayout === "gantt_chart" ? (
<GanttLayout />
) : null}
</div>
);
});

View File

@ -4,7 +4,6 @@ import { useRouter } from "next/router";
import useIssuesView from "hooks/use-issues-view";
import useUser from "hooks/use-user";
import useGanttChartCycleIssues from "hooks/gantt-chart/cycle-issues-view";
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
import useProjectDetails from "hooks/use-project-details";
// components
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
@ -47,9 +46,7 @@ export const CycleIssuesGanttChartView: React.FC<Props> = ({ disableUserActions
title="Issues"
loaderTitle="Issues"
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
blockUpdateHandler={(block, payload) =>
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
}
blockUpdateHandler={(block, payload) => {}}
SidebarBlockRender={IssueGanttSidebarBlock}
BlockRender={IssueGanttBlock}
enableBlockLeftResize={isAllowed}

View File

@ -1,40 +0,0 @@
import { KeyedMutator } from "swr";
// services
import issuesService from "services/issue.service";
// types
import { ICurrentUserResponse, IIssue } from "types";
import { IBlockUpdateData } from "../types";
export const updateGanttIssue = (
issue: IIssue,
payload: IBlockUpdateData,
mutate: KeyedMutator<any>,
user: ICurrentUserResponse | undefined,
workspaceSlug: string | undefined
) => {
if (!issue || !workspaceSlug || !user) return;
mutate((prevData: any) => {
if (!prevData) return prevData;
const newList = prevData.map((p: any) => ({
...p,
...(p.id === issue.id ? payload : {}),
}));
if (payload.sort_order) {
const removedElement = newList.splice(payload.sort_order.sourceIndex, 1)[0];
removedElement.sort_order = payload.sort_order.newSortOrder;
newList.splice(payload.sort_order.destinationIndex, 0, removedElement);
}
return newList;
}, false);
const newPayload: any = { ...payload };
if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder;
issuesService.patchIssue(workspaceSlug, issue.project, issue.id, newPayload, user);
};

View File

@ -17,6 +17,8 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
const { issueFilter: issueFilterStore } = useMobxStore();
const activeLayout = issueFilterStore.userDisplayFilters.layout;
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
@ -72,15 +74,13 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={issueFilterStore.userDisplayFilters.layout ?? "list"}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters">
<FilterSelection
filters={issueFilterStore.userFilters}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[issueFilterStore.userDisplayFilters.layout ?? "list"]
}
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
projectId={projectId?.toString() ?? ""}
/>
</FiltersDropdown>
@ -88,9 +88,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
<DisplayFiltersSelection
displayFilters={issueFilterStore.userDisplayFilters}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
layoutDisplayFiltersOptions={
ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[issueFilterStore.userDisplayFilters.layout ?? "list"]
}
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
/>
</FiltersDropdown>
</div>

View File

@ -7,7 +7,6 @@ export * from "./activity";
export * from "./delete-issue-modal";
export * from "./description-form";
export * from "./form";
export * from "./gantt-chart";
export * from "./issue-layouts";
export * from "./main-content";
export * from "./modal";

View File

@ -1,2 +1,2 @@
export * from "./blocks";
export * from "./layout";
export * from "./root";

View File

@ -1,63 +1,59 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useIssuesView from "hooks/use-issues-view";
import useUser from "hooks/use-user";
import useGanttChartIssues from "hooks/gantt-chart/issue-view";
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
import useProjectDetails from "hooks/use-project-details";
// components
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues";
// types
import { IIssue } from "types";
import { IIssueUnGroupedStructure } from "store/issue";
type Props = {
disableUserActions: boolean;
};
export const IssueGanttChartView: React.FC<Props> = ({ disableUserActions }) => {
export const GanttLayout: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { displayFilters } = useIssuesView();
const { user } = useUser();
const { projectDetails } = useProjectDetails();
const { ganttIssues, mutateGanttIssues } = useGanttChartIssues(
workspaceSlug as string,
projectId as string
);
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
const issues = issueStore.getIssues;
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
console.log("issues", issues);
console.log("appliedFilters", issueFilterStore.appliedFilters);
return (
<>
<IssuePeekOverview
handleMutation={() => mutateGanttIssues()}
projectId={projectId?.toString() ?? ""}
workspaceSlug={workspaceSlug?.toString() ?? ""}
readOnly={disableUserActions}
readOnly={!isAllowed}
/>
<div className="w-full h-full">
<GanttChartRoot
border={false}
title="Issues"
loaderTitle="Issues"
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
blockUpdateHandler={(block, payload) =>
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
}
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
blockUpdateHandler={(block, payload) => {
// TODO: update mutation logic
// updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
}}
BlockRender={IssueGanttBlock}
SidebarBlockRender={IssueGanttSidebarBlock}
enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}
enableReorder={displayFilters.order_by === "sort_order" && isAllowed}
enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
bottomSpacing
/>
</div>
</>
);
};
});

View File

@ -17,19 +17,19 @@ import { ILayoutDisplayFiltersOptions } from "constants/issue";
type Props = {
displayFilters: IIssueDisplayFilterOptions;
handleDisplayFiltersUpdate: (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => void;
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions;
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
};
export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
const { displayFilters, handleDisplayFiltersUpdate, layoutDisplayFiltersOptions } = props;
const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) =>
Object.keys(layoutDisplayFiltersOptions.display_filters).includes(displayFilter);
Object.keys(layoutDisplayFiltersOptions?.display_filters ?? {}).includes(displayFilter);
return (
<div className="w-full h-full overflow-hidden overflow-y-auto relative px-2.5 divide-y divide-custom-border-200">
{/* display properties */}
{layoutDisplayFiltersOptions.display_properties && (
{layoutDisplayFiltersOptions?.display_properties && (
<div className="py-2">
<FilterDisplayProperties />
</div>
@ -41,7 +41,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
<FilterGroupBy
selectedGroupBy={displayFilters.group_by}
selectedSubGroupBy={displayFilters.sub_group_by}
groupByOptions={layoutDisplayFiltersOptions.display_filters.group_by ?? []}
groupByOptions={layoutDisplayFiltersOptions?.display_filters.group_by ?? []}
handleUpdate={(val) =>
handleDisplayFiltersUpdate({
group_by: val,
@ -62,7 +62,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
sub_group_by: val,
})
}
subGroupByOptions={layoutDisplayFiltersOptions.display_filters.sub_group_by ?? []}
subGroupByOptions={layoutDisplayFiltersOptions?.display_filters.sub_group_by ?? []}
/>
</div>
)}
@ -96,7 +96,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
)}
{/* Options */}
{layoutDisplayFiltersOptions.extra_options.access && (
{layoutDisplayFiltersOptions?.extra_options.access && (
<div className="py-2">
<FilterExtraOptions
selectedExtraOptions={{
@ -108,7 +108,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
[key]: val,
})
}
enabledExtraOptions={layoutDisplayFiltersOptions.extra_options.values}
enabledExtraOptions={layoutDisplayFiltersOptions?.extra_options.values}
/>
</div>
)}

View File

@ -27,7 +27,7 @@ import { DATE_FILTER_OPTIONS } from "constants/filters";
type Props = {
filters: IIssueFilterOptions;
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions;
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
projectId: string;
};
@ -125,7 +125,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
return filterDetails.currentLength > 5;
};
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions.filters.includes(filter);
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions?.filters.includes(filter);
return (
<div className="w-full h-full flex flex-col overflow-hidden">

View File

@ -10,7 +10,7 @@ import { ISSUE_LAYOUTS } from "constants/issue";
type Props = {
layouts: TIssueLayouts[];
onChange: (layout: TIssueLayouts) => void;
selectedLayout: TIssueLayouts;
selectedLayout: TIssueLayouts | undefined;
};
export const LayoutSelection: React.FC<Props> = (props) => {

View File

@ -1,3 +1,4 @@
export * from "./calendar";
export * from "./gantt";
export * from "./header";
export * from "./kanban";

View File

@ -18,7 +18,7 @@ import { IIssue } from "types";
import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
type Props = {
handleMutation: () => void;
handleMutation?: () => void;
projectId: string;
readOnly: boolean;
workspaceSlug: string;
@ -57,14 +57,14 @@ export const IssuePeekOverview: React.FC<Props> = observer(({ handleMutation, pr
await updateIssue(workspaceSlug, projectId, issue.id, formData, user);
mutate(PROJECT_ISSUES_ACTIVITY(issue.id));
handleMutation();
if (handleMutation) handleMutation();
};
const handleDeleteIssue = async () => {
if (!issue || !user) return;
await deleteIssue(workspaceSlug, projectId, issue.id, user);
handleMutation();
if (handleMutation) handleMutation();
handleClose();
};

View File

@ -4,7 +4,6 @@ import { useRouter } from "next/router";
import useIssuesView from "hooks/use-issues-view";
import useUser from "hooks/use-user";
import useGanttChartModuleIssues from "hooks/gantt-chart/module-issues-view";
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
import useProjectDetails from "hooks/use-project-details";
// components
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
@ -45,9 +44,7 @@ export const ModuleIssuesGanttChartView: React.FC<Props> = ({ disableUserActions
title="Issues"
loaderTitle="Issues"
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
blockUpdateHandler={(block, payload) =>
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
}
blockUpdateHandler={(block, payload) => {}}
SidebarBlockRender={IssueGanttSidebarBlock}
BlockRender={IssueGanttBlock}
enableBlockLeftResize={isAllowed}

View File

@ -3,7 +3,6 @@ import { useRouter } from "next/router";
// hooks
import useGanttChartViewIssues from "hooks/gantt-chart/view-issues-view";
import useUser from "hooks/use-user";
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
import useProjectDetails from "hooks/use-project-details";
// components
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
@ -42,9 +41,7 @@ export const ViewIssuesGanttChartView: React.FC<Props> = ({ disableUserActions }
title="Issues"
loaderTitle="Issues"
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
blockUpdateHandler={(block, payload) =>
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
}
blockUpdateHandler={(block, payload) => {}}
SidebarBlockRender={IssueGanttSidebarBlock}
BlockRender={IssueGanttBlock}
enableBlockLeftResize={isAllowed}

View File

@ -238,7 +238,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
display_properties: true,
display_filters: {
group_by: ["state_detail.group", "project", "priority", "labels", null],
group_by: ["state_detail.group", "project", "priority", "labels"],
sub_group_by: ["state_detail.group", "project", "priority", "labels", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
type: [null, "active", "backlog"],
@ -268,7 +268,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"],
display_properties: true,
display_filters: {
group_by: ["state", "priority", "labels", "assignees", "created_by", null],
group_by: ["state", "priority", "labels", "assignees", "created_by"],
sub_group_by: ["state", "priority", "labels", "assignees", "created_by", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
type: [null, "active", "backlog"],

View File

@ -11,30 +11,20 @@ import projectService from "services/project.service";
import inboxService from "services/inbox.service";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts
import { IssueViewContextProvider } from "contexts/issue-view.context";
// helper
import { truncateText } from "helpers/string.helper";
// components
import { IssuesView } from "components/core";
import { AllViews } from "components/core";
import { AnalyticsProjectModal } from "components/analytics";
import { ProjectIssuesHeader } from "components/headers";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// types
import type { NextPage } from "next";
// fetch-keys
import {
PROJECT_DETAILS,
INBOX_LIST,
STATES_LIST,
PROJECT_ISSUE_LABELS,
PROJECT_MEMBERS,
USER_PROJECT_VIEW,
} from "constants/fetch-keys";
import { PROJECT_DETAILS, INBOX_LIST } from "constants/fetch-keys";
const ProjectIssues: NextPage = () => {
const [analyticsModal, setAnalyticsModal] = useState(false);
@ -84,61 +74,59 @@ const ProjectIssues: NextPage = () => {
);
return (
<IssueViewContextProvider>
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Issues`} />
</Breadcrumbs>
}
right={
<ProjectIssuesHeader />
// <div className="flex items-center gap-2">
// <SecondaryButton
// onClick={() => setAnalyticsModal(true)}
// className="!py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-border-200 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90"
// outline
// >
// Analytics
// </SecondaryButton>
// {projectDetails && projectDetails.inbox_view && (
// <Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxList?.[0]?.id}`}>
// <a>
// <SecondaryButton
// className="relative !py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-border-200 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90"
// outline
// >
// <span>Inbox</span>
// {inboxList && inboxList?.[0]?.pending_issue_count !== 0 && (
// <span className="absolute -top-1 -right-1 h-4 w-4 rounded-full text-custom-text-100 bg-custom-sidebar-background-80 border border-custom-sidebar-border-200">
// {inboxList?.[0]?.pending_issue_count}
// </span>
// )}
// </SecondaryButton>
// </a>
// </Link>
// )}
// <PrimaryButton
// className="flex items-center gap-2"
// onClick={() => {
// const e = new KeyboardEvent("keydown", { key: "c" });
// document.dispatchEvent(e);
// }}
// >
// <PlusIcon className="h-4 w-4" />
// Add Issue
// </PrimaryButton>
// </div>
}
bg="secondary"
>
<AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
<div className="h-full w-full flex flex-col">
<IssuesView />
</div>
</ProjectAuthorizationWrapper>
</IssueViewContextProvider>
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Issues`} />
</Breadcrumbs>
}
right={
<ProjectIssuesHeader />
// <div className="flex items-center gap-2">
// <SecondaryButton
// onClick={() => setAnalyticsModal(true)}
// className="!py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-border-200 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90"
// outline
// >
// Analytics
// </SecondaryButton>
// {projectDetails && projectDetails.inbox_view && (
// <Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxList?.[0]?.id}`}>
// <a>
// <SecondaryButton
// className="relative !py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-border-200 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90"
// outline
// >
// <span>Inbox</span>
// {inboxList && inboxList?.[0]?.pending_issue_count !== 0 && (
// <span className="absolute -top-1 -right-1 h-4 w-4 rounded-full text-custom-text-100 bg-custom-sidebar-background-80 border border-custom-sidebar-border-200">
// {inboxList?.[0]?.pending_issue_count}
// </span>
// )}
// </SecondaryButton>
// </a>
// </Link>
// )}
// <PrimaryButton
// className="flex items-center gap-2"
// onClick={() => {
// const e = new KeyboardEvent("keydown", { key: "c" });
// document.dispatchEvent(e);
// }}
// >
// <PlusIcon className="h-4 w-4" />
// Add Issue
// </PrimaryButton>
// </div>
}
bg="secondary"
>
<AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
<div className="h-full w-full flex flex-col">
<AllViews />
</div>
</ProjectAuthorizationWrapper>
);
};

View File

@ -156,6 +156,7 @@ class IssueFilterStore implements IIssueFilterStore {
};
if (this.userDisplayFilters.layout === "calendar") filteredRouteParams.target_date = this.calendarLayoutDateRange();
if (this.userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
const filteredParams = handleIssueQueryParamsByLayout(this.userDisplayFilters.layout, "issues");
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
@ -200,6 +201,10 @@ class IssueFilterStore implements IIssueFilterStore {
// set sub_group_by to null if group_by is set to null
if (newViewProps.display_filters.group_by === null) newViewProps.display_filters.sub_group_by = null;
// set group_by to state if layout is switched to kanban and group_by is null
if (newViewProps.display_filters.layout === "kanban" && newViewProps.display_filters.group_by === null)
newViewProps.display_filters.group_by = "state";
try {
runInAction(() => {
this.userFilters = newViewProps.filters;