mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
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:
parent
479c145b02
commit
9ad1e73666
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
};
|
@ -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>
|
||||
|
@ -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";
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from "./blocks";
|
||||
export * from "./layout";
|
||||
export * from "./root";
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
@ -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>
|
||||
)}
|
||||
|
@ -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">
|
||||
|
@ -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) => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./calendar";
|
||||
export * from "./gantt";
|
||||
export * from "./header";
|
||||
export * from "./kanban";
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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"],
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user