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 { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// react-beautiful-dnd
|
// mobx store
|
||||||
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
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import { RootStore } from "store/root";
|
// components
|
||||||
import { observer } from "mobx-react-lite";
|
import { CalendarLayout, GanttLayout, KanBanLayout } from "components/issues";
|
||||||
|
|
||||||
type Props = {
|
export const AllViews: React.FC = observer(() => {
|
||||||
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 }) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query as {
|
const { workspaceSlug, projectId } = router.query as {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
cycleId: string;
|
cycleId: string;
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [myIssueProjectId, setMyIssueProjectId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const { issue: issueStore, project: projectStore, issueFilter: issueFilterStore } = useMobxStore();
|
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 () => {
|
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES` : null, async () => {
|
||||||
if (workspaceSlug && projectId) {
|
if (workspaceSlug && projectId) {
|
||||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug, projectId);
|
await issueFilterStore.fetchUserProjectFilters(workspaceSlug, projectId);
|
||||||
@ -105,7 +35,13 @@ export const AllViews: React.FC<Props> = observer(({ trashBox, setTrashBox }) =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full overflow-auto">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,6 @@ import { useRouter } from "next/router";
|
|||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useGanttChartCycleIssues from "hooks/gantt-chart/cycle-issues-view";
|
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";
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
||||||
@ -47,9 +46,7 @@ export const CycleIssuesGanttChartView: React.FC<Props> = ({ disableUserActions
|
|||||||
title="Issues"
|
title="Issues"
|
||||||
loaderTitle="Issues"
|
loaderTitle="Issues"
|
||||||
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
||||||
blockUpdateHandler={(block, payload) =>
|
blockUpdateHandler={(block, payload) => {}}
|
||||||
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
|
||||||
}
|
|
||||||
SidebarBlockRender={IssueGanttSidebarBlock}
|
SidebarBlockRender={IssueGanttSidebarBlock}
|
||||||
BlockRender={IssueGanttBlock}
|
BlockRender={IssueGanttBlock}
|
||||||
enableBlockLeftResize={isAllowed}
|
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 { issueFilter: issueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
|
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||||
|
|
||||||
const handleLayoutChange = useCallback(
|
const handleLayoutChange = useCallback(
|
||||||
(layout: TIssueLayouts) => {
|
(layout: TIssueLayouts) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
@ -72,15 +74,13 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
|||||||
<LayoutSelection
|
<LayoutSelection
|
||||||
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
|
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
|
||||||
onChange={(layout) => handleLayoutChange(layout)}
|
onChange={(layout) => handleLayoutChange(layout)}
|
||||||
selectedLayout={issueFilterStore.userDisplayFilters.layout ?? "list"}
|
selectedLayout={activeLayout}
|
||||||
/>
|
/>
|
||||||
<FiltersDropdown title="Filters">
|
<FiltersDropdown title="Filters">
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
filters={issueFilterStore.userFilters}
|
filters={issueFilterStore.userFilters}
|
||||||
handleFiltersUpdate={handleFiltersUpdate}
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||||
ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[issueFilterStore.userDisplayFilters.layout ?? "list"]
|
|
||||||
}
|
|
||||||
projectId={projectId?.toString() ?? ""}
|
projectId={projectId?.toString() ?? ""}
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
@ -88,9 +88,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
|||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
displayFilters={issueFilterStore.userDisplayFilters}
|
displayFilters={issueFilterStore.userDisplayFilters}
|
||||||
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||||
ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[issueFilterStore.userDisplayFilters.layout ?? "list"]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,6 @@ export * from "./activity";
|
|||||||
export * from "./delete-issue-modal";
|
export * from "./delete-issue-modal";
|
||||||
export * from "./description-form";
|
export * from "./description-form";
|
||||||
export * from "./form";
|
export * from "./form";
|
||||||
export * from "./gantt-chart";
|
|
||||||
export * from "./issue-layouts";
|
export * from "./issue-layouts";
|
||||||
export * from "./main-content";
|
export * from "./main-content";
|
||||||
export * from "./modal";
|
export * from "./modal";
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
export * from "./blocks";
|
export * from "./blocks";
|
||||||
export * from "./layout";
|
export * from "./root";
|
@ -1,63 +1,59 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// hooks
|
// 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";
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
||||||
import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues";
|
import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssueUnGroupedStructure } from "store/issue";
|
||||||
|
|
||||||
type Props = {
|
export const GanttLayout: React.FC = observer(() => {
|
||||||
disableUserActions: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const IssueGanttChartView: React.FC<Props> = ({ disableUserActions }) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { displayFilters } = useIssuesView();
|
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
const { projectDetails } = useProjectDetails();
|
const { projectDetails } = useProjectDetails();
|
||||||
|
|
||||||
const { ganttIssues, mutateGanttIssues } = useGanttChartIssues(
|
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string
|
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
|
||||||
);
|
|
||||||
|
const issues = issueStore.getIssues;
|
||||||
|
|
||||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
||||||
|
|
||||||
|
console.log("issues", issues);
|
||||||
|
console.log("appliedFilters", issueFilterStore.appliedFilters);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IssuePeekOverview
|
<IssuePeekOverview
|
||||||
handleMutation={() => mutateGanttIssues()}
|
|
||||||
projectId={projectId?.toString() ?? ""}
|
projectId={projectId?.toString() ?? ""}
|
||||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||||
readOnly={disableUserActions}
|
readOnly={!isAllowed}
|
||||||
/>
|
/>
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
border={false}
|
border={false}
|
||||||
title="Issues"
|
title="Issues"
|
||||||
loaderTitle="Issues"
|
loaderTitle="Issues"
|
||||||
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
|
||||||
blockUpdateHandler={(block, payload) =>
|
blockUpdateHandler={(block, payload) => {
|
||||||
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
// TODO: update mutation logic
|
||||||
}
|
// updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
||||||
|
}}
|
||||||
BlockRender={IssueGanttBlock}
|
BlockRender={IssueGanttBlock}
|
||||||
SidebarBlockRender={IssueGanttSidebarBlock}
|
SidebarBlockRender={IssueGanttSidebarBlock}
|
||||||
enableBlockLeftResize={isAllowed}
|
enableBlockLeftResize={isAllowed}
|
||||||
enableBlockRightResize={isAllowed}
|
enableBlockRightResize={isAllowed}
|
||||||
enableBlockMove={isAllowed}
|
enableBlockMove={isAllowed}
|
||||||
enableReorder={displayFilters.order_by === "sort_order" && isAllowed}
|
enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
|
||||||
bottomSpacing
|
bottomSpacing
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
@ -17,19 +17,19 @@ import { ILayoutDisplayFiltersOptions } from "constants/issue";
|
|||||||
type Props = {
|
type Props = {
|
||||||
displayFilters: IIssueDisplayFilterOptions;
|
displayFilters: IIssueDisplayFilterOptions;
|
||||||
handleDisplayFiltersUpdate: (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => void;
|
handleDisplayFiltersUpdate: (updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => void;
|
||||||
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions;
|
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
||||||
const { displayFilters, handleDisplayFiltersUpdate, layoutDisplayFiltersOptions } = props;
|
const { displayFilters, handleDisplayFiltersUpdate, layoutDisplayFiltersOptions } = props;
|
||||||
|
|
||||||
const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) =>
|
const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) =>
|
||||||
Object.keys(layoutDisplayFiltersOptions.display_filters).includes(displayFilter);
|
Object.keys(layoutDisplayFiltersOptions?.display_filters ?? {}).includes(displayFilter);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full overflow-hidden overflow-y-auto relative px-2.5 divide-y divide-custom-border-200">
|
<div className="w-full h-full overflow-hidden overflow-y-auto relative px-2.5 divide-y divide-custom-border-200">
|
||||||
{/* display properties */}
|
{/* display properties */}
|
||||||
{layoutDisplayFiltersOptions.display_properties && (
|
{layoutDisplayFiltersOptions?.display_properties && (
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterDisplayProperties />
|
<FilterDisplayProperties />
|
||||||
</div>
|
</div>
|
||||||
@ -41,7 +41,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
<FilterGroupBy
|
<FilterGroupBy
|
||||||
selectedGroupBy={displayFilters.group_by}
|
selectedGroupBy={displayFilters.group_by}
|
||||||
selectedSubGroupBy={displayFilters.sub_group_by}
|
selectedSubGroupBy={displayFilters.sub_group_by}
|
||||||
groupByOptions={layoutDisplayFiltersOptions.display_filters.group_by ?? []}
|
groupByOptions={layoutDisplayFiltersOptions?.display_filters.group_by ?? []}
|
||||||
handleUpdate={(val) =>
|
handleUpdate={(val) =>
|
||||||
handleDisplayFiltersUpdate({
|
handleDisplayFiltersUpdate({
|
||||||
group_by: val,
|
group_by: val,
|
||||||
@ -62,7 +62,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
sub_group_by: val,
|
sub_group_by: val,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
subGroupByOptions={layoutDisplayFiltersOptions.display_filters.sub_group_by ?? []}
|
subGroupByOptions={layoutDisplayFiltersOptions?.display_filters.sub_group_by ?? []}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -96,7 +96,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Options */}
|
{/* Options */}
|
||||||
{layoutDisplayFiltersOptions.extra_options.access && (
|
{layoutDisplayFiltersOptions?.extra_options.access && (
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterExtraOptions
|
<FilterExtraOptions
|
||||||
selectedExtraOptions={{
|
selectedExtraOptions={{
|
||||||
@ -108,7 +108,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
[key]: val,
|
[key]: val,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
enabledExtraOptions={layoutDisplayFiltersOptions.extra_options.values}
|
enabledExtraOptions={layoutDisplayFiltersOptions?.extra_options.values}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -27,7 +27,7 @@ import { DATE_FILTER_OPTIONS } from "constants/filters";
|
|||||||
type Props = {
|
type Props = {
|
||||||
filters: IIssueFilterOptions;
|
filters: IIssueFilterOptions;
|
||||||
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
||||||
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions;
|
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||||||
return filterDetails.currentLength > 5;
|
return filterDetails.currentLength > 5;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions.filters.includes(filter);
|
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions?.filters.includes(filter);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex flex-col overflow-hidden">
|
<div className="w-full h-full flex flex-col overflow-hidden">
|
||||||
|
@ -10,7 +10,7 @@ import { ISSUE_LAYOUTS } from "constants/issue";
|
|||||||
type Props = {
|
type Props = {
|
||||||
layouts: TIssueLayouts[];
|
layouts: TIssueLayouts[];
|
||||||
onChange: (layout: TIssueLayouts) => void;
|
onChange: (layout: TIssueLayouts) => void;
|
||||||
selectedLayout: TIssueLayouts;
|
selectedLayout: TIssueLayouts | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayoutSelection: React.FC<Props> = (props) => {
|
export const LayoutSelection: React.FC<Props> = (props) => {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export * from "./calendar";
|
export * from "./calendar";
|
||||||
|
export * from "./gantt";
|
||||||
export * from "./header";
|
export * from "./header";
|
||||||
export * from "./kanban";
|
export * from "./kanban";
|
||||||
|
@ -18,7 +18,7 @@ import { IIssue } from "types";
|
|||||||
import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleMutation: () => void;
|
handleMutation?: () => void;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -57,14 +57,14 @@ export const IssuePeekOverview: React.FC<Props> = observer(({ handleMutation, pr
|
|||||||
|
|
||||||
await updateIssue(workspaceSlug, projectId, issue.id, formData, user);
|
await updateIssue(workspaceSlug, projectId, issue.id, formData, user);
|
||||||
mutate(PROJECT_ISSUES_ACTIVITY(issue.id));
|
mutate(PROJECT_ISSUES_ACTIVITY(issue.id));
|
||||||
handleMutation();
|
if (handleMutation) handleMutation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteIssue = async () => {
|
const handleDeleteIssue = async () => {
|
||||||
if (!issue || !user) return;
|
if (!issue || !user) return;
|
||||||
|
|
||||||
await deleteIssue(workspaceSlug, projectId, issue.id, user);
|
await deleteIssue(workspaceSlug, projectId, issue.id, user);
|
||||||
handleMutation();
|
if (handleMutation) handleMutation();
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,6 @@ import { useRouter } from "next/router";
|
|||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useGanttChartModuleIssues from "hooks/gantt-chart/module-issues-view";
|
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";
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
||||||
@ -45,9 +44,7 @@ export const ModuleIssuesGanttChartView: React.FC<Props> = ({ disableUserActions
|
|||||||
title="Issues"
|
title="Issues"
|
||||||
loaderTitle="Issues"
|
loaderTitle="Issues"
|
||||||
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
||||||
blockUpdateHandler={(block, payload) =>
|
blockUpdateHandler={(block, payload) => {}}
|
||||||
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
|
||||||
}
|
|
||||||
SidebarBlockRender={IssueGanttSidebarBlock}
|
SidebarBlockRender={IssueGanttSidebarBlock}
|
||||||
BlockRender={IssueGanttBlock}
|
BlockRender={IssueGanttBlock}
|
||||||
enableBlockLeftResize={isAllowed}
|
enableBlockLeftResize={isAllowed}
|
||||||
|
@ -3,7 +3,6 @@ import { useRouter } from "next/router";
|
|||||||
// hooks
|
// hooks
|
||||||
import useGanttChartViewIssues from "hooks/gantt-chart/view-issues-view";
|
import useGanttChartViewIssues from "hooks/gantt-chart/view-issues-view";
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import { updateGanttIssue } from "components/gantt-chart/hooks/block-update";
|
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
||||||
@ -42,9 +41,7 @@ export const ViewIssuesGanttChartView: React.FC<Props> = ({ disableUserActions }
|
|||||||
title="Issues"
|
title="Issues"
|
||||||
loaderTitle="Issues"
|
loaderTitle="Issues"
|
||||||
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
||||||
blockUpdateHandler={(block, payload) =>
|
blockUpdateHandler={(block, payload) => {}}
|
||||||
updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString())
|
|
||||||
}
|
|
||||||
SidebarBlockRender={IssueGanttSidebarBlock}
|
SidebarBlockRender={IssueGanttSidebarBlock}
|
||||||
BlockRender={IssueGanttBlock}
|
BlockRender={IssueGanttBlock}
|
||||||
enableBlockLeftResize={isAllowed}
|
enableBlockLeftResize={isAllowed}
|
||||||
|
@ -238,7 +238,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
|
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
|
||||||
display_properties: true,
|
display_properties: true,
|
||||||
display_filters: {
|
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],
|
sub_group_by: ["state_detail.group", "project", "priority", "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"],
|
||||||
@ -268,7 +268,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
|||||||
filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"],
|
filters: ["priority", "state", "assignees", "created_by", "labels", "start_date", "target_date"],
|
||||||
display_properties: true,
|
display_properties: true,
|
||||||
display_filters: {
|
display_filters: {
|
||||||
group_by: ["state", "priority", "labels", "assignees", "created_by", null],
|
group_by: ["state", "priority", "labels", "assignees", "created_by"],
|
||||||
sub_group_by: ["state", "priority", "labels", "assignees", "created_by", null],
|
sub_group_by: ["state", "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"],
|
||||||
|
@ -11,30 +11,20 @@ import projectService from "services/project.service";
|
|||||||
import inboxService from "services/inbox.service";
|
import inboxService from "services/inbox.service";
|
||||||
// layouts
|
// layouts
|
||||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// contexts
|
|
||||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
|
||||||
// helper
|
// helper
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// components
|
// components
|
||||||
import { IssuesView } from "components/core";
|
import { AllViews } from "components/core";
|
||||||
import { AnalyticsProjectModal } from "components/analytics";
|
import { AnalyticsProjectModal } from "components/analytics";
|
||||||
import { ProjectIssuesHeader } from "components/headers";
|
import { ProjectIssuesHeader } from "components/headers";
|
||||||
// ui
|
// ui
|
||||||
import { PrimaryButton, SecondaryButton } from "components/ui";
|
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import { PROJECT_DETAILS, INBOX_LIST } from "constants/fetch-keys";
|
||||||
PROJECT_DETAILS,
|
|
||||||
INBOX_LIST,
|
|
||||||
STATES_LIST,
|
|
||||||
PROJECT_ISSUE_LABELS,
|
|
||||||
PROJECT_MEMBERS,
|
|
||||||
USER_PROJECT_VIEW,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
const ProjectIssues: NextPage = () => {
|
const ProjectIssues: NextPage = () => {
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
@ -84,7 +74,6 @@ const ProjectIssues: NextPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueViewContextProvider>
|
|
||||||
<ProjectAuthorizationWrapper
|
<ProjectAuthorizationWrapper
|
||||||
breadcrumbs={
|
breadcrumbs={
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
@ -135,10 +124,9 @@ const ProjectIssues: NextPage = () => {
|
|||||||
>
|
>
|
||||||
<AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
|
<AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
|
||||||
<div className="h-full w-full flex flex-col">
|
<div className="h-full w-full flex flex-col">
|
||||||
<IssuesView />
|
<AllViews />
|
||||||
</div>
|
</div>
|
||||||
</ProjectAuthorizationWrapper>
|
</ProjectAuthorizationWrapper>
|
||||||
</IssueViewContextProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -156,6 +156,7 @@ class IssueFilterStore implements IIssueFilterStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.userDisplayFilters.layout === "calendar") filteredRouteParams.target_date = this.calendarLayoutDateRange();
|
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");
|
const filteredParams = handleIssueQueryParamsByLayout(this.userDisplayFilters.layout, "issues");
|
||||||
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
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
|
// 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;
|
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 {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.userFilters = newViewProps.filters;
|
this.userFilters = newViewProps.filters;
|
||||||
|
Loading…
Reference in New Issue
Block a user