forked from github/plane
dev: add remaining layouts to cycle (#2413)
This commit is contained in:
parent
265e60a536
commit
fcfdd74d4f
@ -2,4 +2,3 @@ export * from "./date-filter-modal";
|
||||
export * from "./date-filter-select";
|
||||
export * from "./filters-list";
|
||||
export * from "./workspace-filters-list";
|
||||
export * from "./issues-view-filter";
|
||||
|
111
web/components/headers/cycle-issues.tsx
Normal file
111
web/components/headers/cycle-issues.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
import { useCallback } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
|
||||
export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||
|
||||
const {
|
||||
issueFilter: issueFilterStore,
|
||||
cycleIssueFilter: cycleIssueFilterStore,
|
||||
project: projectStore,
|
||||
} = useMobxStore();
|
||||
|
||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: TIssueLayouts) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
||||
display_filters: {
|
||||
layout,
|
||||
},
|
||||
});
|
||||
},
|
||||
[issueFilterStore, projectId, workspaceSlug]
|
||||
);
|
||||
|
||||
const handleFiltersUpdate = useCallback(
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||
|
||||
const newValues = cycleIssueFilterStore.cycleFilters?.[key] ?? [];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
});
|
||||
} else {
|
||||
if (cycleIssueFilterStore.cycleFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
}
|
||||
|
||||
cycleIssueFilterStore.updateCycleFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), {
|
||||
[key]: newValues,
|
||||
});
|
||||
},
|
||||
[cycleId, cycleIssueFilterStore, projectId, workspaceSlug]
|
||||
);
|
||||
|
||||
const handleDisplayFiltersUpdate = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
||||
display_filters: {
|
||||
...updatedDisplayFilter,
|
||||
},
|
||||
});
|
||||
},
|
||||
[issueFilterStore, projectId, workspaceSlug]
|
||||
);
|
||||
|
||||
const handleDisplayPropertiesUpdate = useCallback(
|
||||
(property: Partial<IIssueDisplayProperties>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
issueFilterStore.updateDisplayProperties(workspaceSlug.toString(), projectId.toString(), property);
|
||||
},
|
||||
[issueFilterStore, projectId, workspaceSlug]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutSelection
|
||||
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
|
||||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
selectedLayout={activeLayout}
|
||||
/>
|
||||
<FiltersDropdown title="Filters">
|
||||
<FilterSelection
|
||||
filters={cycleIssueFilterStore.cycleFilters}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
|
||||
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
|
||||
states={projectStore.states?.[projectId?.toString() ?? ""] ?? undefined}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<FiltersDropdown title="View">
|
||||
<DisplayFiltersSelection
|
||||
displayFilters={issueFilterStore.userDisplayFilters}
|
||||
displayProperties={issueFilterStore.userDisplayProperties}
|
||||
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
||||
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
|
||||
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
export * from "./cycle-issues";
|
||||
export * from "./global-issues";
|
||||
export * from "./module-issues";
|
||||
export * from "./project-issues";
|
||||
|
39
web/components/issues/issue-layouts/calendar/cycle-root.tsx
Normal file
39
web/components/issues/issue-layouts/calendar/cycle-root.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { DragDropContext, DropResult } from "@hello-pangea/dnd";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CalendarChart } from "components/issues";
|
||||
// types
|
||||
import { IIssueGroupedStructure } from "store/issue";
|
||||
|
||||
export const CycleCalendarLayout: React.FC = observer(() => {
|
||||
const { cycleIssue: cycleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
|
||||
// TODO: add drag and drop functionality
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
if (!result) return;
|
||||
|
||||
// return if not dropped on the correct place
|
||||
if (!result.destination) return;
|
||||
|
||||
// return if dropped on the same date
|
||||
if (result.destination.droppableId === result.source.droppableId) return;
|
||||
|
||||
// issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
|
||||
};
|
||||
|
||||
const issues = cycleIssueStore.getIssues;
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<CalendarChart
|
||||
issues={issues as IIssueGroupedStructure | null}
|
||||
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
|
||||
/>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
export * from "./dropdowns";
|
||||
export * from "./calendar";
|
||||
export * from "./cycle-root";
|
||||
export * from "./types.d";
|
||||
export * from "./day-tile";
|
||||
export * from "./header";
|
||||
|
@ -9,7 +9,7 @@ import { CalendarChart } from "components/issues";
|
||||
import { IIssueGroupedStructure } from "store/issue";
|
||||
|
||||
export const ModuleCalendarLayout: React.FC = observer(() => {
|
||||
const { module: moduleStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
const { moduleIssue: moduleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
|
||||
// TODO: add drag and drop functionality
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
@ -24,7 +24,7 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
|
||||
// issueKanBanViewStore?.handleDragDrop(result.source, result.destination);
|
||||
};
|
||||
|
||||
const issues = moduleStore.getIssues;
|
||||
const issues = moduleIssueStore.getIssues;
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-4 bg-custom-background-100 overflow-hidden">
|
||||
|
@ -6,7 +6,13 @@ import useSWR from "swr";
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { CycleKanBanLayout, CycleListLayout } from "components/issues";
|
||||
import {
|
||||
CycleCalendarLayout,
|
||||
CycleGanttLayout,
|
||||
CycleKanBanLayout,
|
||||
CycleListLayout,
|
||||
CycleSpreadsheetLayout,
|
||||
} from "components/issues";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
@ -46,7 +52,17 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
{activeLayout === "list" ? <CycleListLayout /> : activeLayout === "kanban" ? <CycleKanBanLayout /> : null}
|
||||
{activeLayout === "list" ? (
|
||||
<CycleListLayout />
|
||||
) : activeLayout === "kanban" ? (
|
||||
<CycleKanBanLayout />
|
||||
) : activeLayout === "calendar" ? (
|
||||
<CycleCalendarLayout />
|
||||
) : activeLayout === "gantt_chart" ? (
|
||||
<CycleGanttLayout />
|
||||
) : activeLayout === "spreadsheet" ? (
|
||||
<CycleSpreadsheetLayout />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -25,6 +25,17 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
|
||||
} = useMobxStore();
|
||||
|
||||
const viewDetails = globalViewId ? globalViewsStore.globalViewDetails[globalViewId.toString()] : undefined;
|
||||
const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined;
|
||||
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
Object.entries(storedFilters ?? {}).forEach(([key, value]) => {
|
||||
if (!value) return;
|
||||
|
||||
if (Array.isArray(value) && value.length === 0) return;
|
||||
|
||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
||||
});
|
||||
|
||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!globalViewId) return;
|
||||
@ -72,15 +83,16 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const storedFilters = globalViewId ? globalViewFiltersStore.storedFilters[globalViewId.toString()] : undefined;
|
||||
|
||||
// update stored filters when view details are fetched
|
||||
useEffect(() => {
|
||||
if (!globalViewId || !viewDetails) return;
|
||||
|
||||
if (!globalViewFiltersStore.storedFilters[globalViewId.toString()])
|
||||
globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {});
|
||||
}, [globalViewId, globalViewFiltersStore, storedFilters, viewDetails]);
|
||||
}, [globalViewId, globalViewFiltersStore, viewDetails]);
|
||||
|
||||
// return if no filters are applied
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-start justify-between gap-4 p-4">
|
||||
|
@ -24,11 +24,11 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
||||
} = useMobxStore();
|
||||
|
||||
const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
|
||||
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] ?? {} : {};
|
||||
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
|
||||
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
Object.entries(storedFilters).forEach(([key, value]) => {
|
||||
Object.entries(storedFilters ?? {}).forEach(([key, value]) => {
|
||||
if (!value) return;
|
||||
|
||||
if (Array.isArray(value) && value.length === 0) return;
|
||||
@ -60,7 +60,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
||||
if (!workspaceSlug || !projectId || !viewId) return;
|
||||
|
||||
const newFilters: IIssueFilterOptions = {};
|
||||
Object.keys(storedFilters).forEach((key) => {
|
||||
Object.keys(storedFilters ?? {}).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
|
||||
|
55
web/components/issues/issue-layouts/gantt/cycle-root.tsx
Normal file
55
web/components/issues/issue-layouts/gantt/cycle-root.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
// components
|
||||
import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart";
|
||||
import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues";
|
||||
// types
|
||||
import { IIssueUnGroupedStructure } from "store/issue";
|
||||
|
||||
export const CycleGanttLayout: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { projectDetails } = useProjectDetails();
|
||||
|
||||
const { cycleIssue: cycleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
|
||||
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
|
||||
|
||||
const issues = cycleIssueStore.getIssues;
|
||||
|
||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
||||
|
||||
return (
|
||||
<>
|
||||
<IssuePeekOverview
|
||||
projectId={projectId?.toString() ?? ""}
|
||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||
readOnly={!isAllowed}
|
||||
/>
|
||||
<div className="w-full h-full">
|
||||
<GanttChartRoot
|
||||
border={false}
|
||||
title="Issues"
|
||||
loaderTitle="Issues"
|
||||
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={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,4 +1,5 @@
|
||||
export * from "./blocks";
|
||||
export * from "./cycle-root";
|
||||
export * from "./module-root";
|
||||
export * from "./project-view-root";
|
||||
export * from "./root";
|
||||
|
@ -17,11 +17,11 @@ export const ModuleGanttLayout: React.FC = observer(() => {
|
||||
|
||||
const { projectDetails } = useProjectDetails();
|
||||
|
||||
const { module: moduleStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
const { moduleIssue: moduleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
|
||||
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
|
||||
|
||||
const issues = moduleStore.getIssues;
|
||||
const issues = moduleIssueStore.getIssues;
|
||||
|
||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
||||
|
||||
|
146
web/components/issues/issue-layouts/spreadsheet/cycle-root.tsx
Normal file
146
web/components/issues/issue-layouts/spreadsheet/cycle-root.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
// components
|
||||
import { SpreadsheetColumns, SpreadsheetIssues } from "components/core";
|
||||
import { IssuePeekOverview } from "components/issues";
|
||||
// ui
|
||||
import { CustomMenu } from "components/ui";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// icon
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "types";
|
||||
import { IIssueUnGroupedStructure } from "store/issue";
|
||||
// constants
|
||||
import { SPREADSHEET_COLUMN } from "constants/spreadsheet";
|
||||
|
||||
export const CycleSpreadsheetLayout: React.FC = observer(() => {
|
||||
const [expandedIssues, setExpandedIssues] = useState<string[]>([]);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { user } = useUser();
|
||||
const { projectDetails } = useProjectDetails();
|
||||
|
||||
const { cycleIssue: cycleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
|
||||
const issues = cycleIssueStore.getIssues;
|
||||
const issueDisplayProperties = issueFilterStore.userDisplayProperties;
|
||||
|
||||
const handleDisplayFiltersUpdate = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
||||
display_filters: {
|
||||
...updatedDisplayFilter,
|
||||
},
|
||||
});
|
||||
},
|
||||
[issueFilterStore, projectId, workspaceSlug]
|
||||
);
|
||||
|
||||
const columnData = SPREADSHEET_COLUMN.map((column) => ({
|
||||
...column,
|
||||
isActive: issueDisplayProperties
|
||||
? column.propertyName === "labels"
|
||||
? issueDisplayProperties[column.propertyName as keyof IIssueDisplayProperties]
|
||||
: column.propertyName === "title"
|
||||
? true
|
||||
: issueDisplayProperties[column.propertyName as keyof IIssueDisplayProperties]
|
||||
: false,
|
||||
}));
|
||||
|
||||
const gridTemplateColumns = columnData
|
||||
.filter((column) => column.isActive)
|
||||
.map((column) => column.colSize)
|
||||
.join(" ");
|
||||
|
||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
||||
|
||||
return (
|
||||
<>
|
||||
<IssuePeekOverview
|
||||
projectId={projectId?.toString() ?? ""}
|
||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||
readOnly={!isAllowed}
|
||||
/>
|
||||
<div className="h-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-100">
|
||||
<div className="sticky z-[2] top-0 border-b border-custom-border-200 bg-custom-background-90 w-full min-w-max">
|
||||
<SpreadsheetColumns
|
||||
columnData={columnData}
|
||||
displayFilters={issueFilterStore.userDisplayFilters}
|
||||
gridTemplateColumns={gridTemplateColumns}
|
||||
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
||||
/>
|
||||
</div>
|
||||
{issues ? (
|
||||
<div className="flex flex-col h-full w-full bg-custom-background-100 rounded-sm ">
|
||||
{(issues as IIssueUnGroupedStructure).map((issue: IIssue, index) => (
|
||||
<SpreadsheetIssues
|
||||
key={`${issue.id}_${index}`}
|
||||
index={index}
|
||||
issue={issue}
|
||||
expandedIssues={expandedIssues}
|
||||
setExpandedIssues={setExpandedIssues}
|
||||
gridTemplateColumns={gridTemplateColumns}
|
||||
properties={issueDisplayProperties}
|
||||
handleIssueAction={() => {}}
|
||||
disableUserActions={!isAllowed}
|
||||
user={user}
|
||||
userAuth={{
|
||||
isViewer: projectDetails?.member_role === 5,
|
||||
isGuest: projectDetails?.member_role === 10,
|
||||
isMember: projectDetails?.member_role === 15,
|
||||
isOwner: projectDetails?.member_role === 20,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-custom-background-80 border-b border-custom-border-200 w-full min-w-max"
|
||||
style={{ gridTemplateColumns }}
|
||||
>
|
||||
{isAllowed && (
|
||||
<CustomMenu
|
||||
className="sticky left-0 z-[1]"
|
||||
customButton={
|
||||
<button
|
||||
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 bg-custom-background-100 group-hover:text-custom-text-100 group-hover:bg-custom-background-80 border-custom-border-200 w-full"
|
||||
type="button"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
}
|
||||
position="left"
|
||||
optionsClassName="left-5 !w-36"
|
||||
noBorder
|
||||
>
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "c" });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
Create new
|
||||
</CustomMenu.MenuItem>
|
||||
{true && <CustomMenu.MenuItem onClick={() => {}}>Add an existing issue</CustomMenu.MenuItem>}
|
||||
</CustomMenu>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
export * from "./cycle-root";
|
||||
export * from "./module-root";
|
||||
export * from "./project-view-root";
|
||||
export * from "./root";
|
||||
|
@ -30,9 +30,9 @@ export const ModuleSpreadsheetLayout: React.FC = observer(() => {
|
||||
const { user } = useUser();
|
||||
const { projectDetails } = useProjectDetails();
|
||||
|
||||
const { module: moduleStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
const { moduleIssue: moduleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
|
||||
const issues = moduleStore.getIssues;
|
||||
const issues = moduleIssueStore.getIssues;
|
||||
const issueDisplayProperties = issueFilterStore.userDisplayProperties;
|
||||
|
||||
const handleDisplayFiltersUpdate = useCallback(
|
||||
|
@ -11,7 +11,7 @@ import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
|
||||
// contexts
|
||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
||||
// components
|
||||
import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "components/core";
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
import { CycleDetailsSidebar, TransferIssues, TransferIssuesModal } from "components/cycles";
|
||||
import { CycleLayoutRoot } from "components/issues/issue-layouts";
|
||||
// services
|
||||
@ -35,6 +35,7 @@ import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||
import { ISearchIssueResponse } from "types";
|
||||
// fetch-keys
|
||||
import { CYCLES_LIST, CYCLE_DETAILS } from "constants/fetch-keys";
|
||||
import { CycleIssuesHeader } from "components/headers";
|
||||
|
||||
const SingleCycle: React.FC = () => {
|
||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||
@ -68,10 +69,6 @@ const SingleCycle: React.FC = () => {
|
||||
? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date)
|
||||
: "draft";
|
||||
|
||||
const openIssuesListModal = () => {
|
||||
setCycleIssuesListModal(true);
|
||||
};
|
||||
|
||||
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
@ -132,7 +129,7 @@ const SingleCycle: React.FC = () => {
|
||||
}
|
||||
right={
|
||||
<div className={`flex flex-shrink-0 items-center gap-2 duration-300`}>
|
||||
<IssuesFilterView />
|
||||
<CycleIssuesHeader />
|
||||
<Button variant="neutral-primary" onClick={() => setAnalyticsModal(true)}>
|
||||
Analytics
|
||||
</Button>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { action, computed, observable, makeObservable, runInAction } from "mobx";
|
||||
import { action, observable, makeObservable, runInAction } from "mobx";
|
||||
// services
|
||||
import { ProjectService } from "services/project.service";
|
||||
import { ModuleService } from "services/modules.service";
|
||||
@ -37,13 +37,6 @@ export interface IModuleStore {
|
||||
deleteModule: (workspaceSlug: string, projectId: string, moduleId: string) => void;
|
||||
addModuleToFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => void;
|
||||
removeModuleFromFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => void;
|
||||
|
||||
// issue related operations
|
||||
fetchModuleIssues: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<any>;
|
||||
updateIssueStructure: (group_id: string | null, sub_group_id: string | null, moduleId: string, issue: IIssue) => void;
|
||||
|
||||
// computed
|
||||
getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null;
|
||||
}
|
||||
|
||||
class ModuleStore implements IModuleStore {
|
||||
@ -90,9 +83,6 @@ class ModuleStore implements IModuleStore {
|
||||
moduleDetails: observable.ref,
|
||||
issues: observable.ref,
|
||||
|
||||
// computed
|
||||
getIssues: computed,
|
||||
|
||||
// actions
|
||||
setModuleId: action,
|
||||
|
||||
@ -104,9 +94,6 @@ class ModuleStore implements IModuleStore {
|
||||
deleteModule: action,
|
||||
addModuleToFavorites: action,
|
||||
removeModuleFromFavorites: action,
|
||||
|
||||
fetchModuleIssues: action,
|
||||
updateIssueStructure: action,
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
@ -120,16 +107,6 @@ class ModuleStore implements IModuleStore {
|
||||
return this.modules[this.rootStore.project.projectId] || null;
|
||||
}
|
||||
|
||||
get getIssues() {
|
||||
const moduleId = this.moduleId;
|
||||
|
||||
const issueType = this.rootStore.issue.getIssueType;
|
||||
|
||||
if (!moduleId || !issueType) return null;
|
||||
|
||||
return this.issues?.[moduleId]?.[issueType] || null;
|
||||
}
|
||||
|
||||
// actions
|
||||
setModuleId = (moduleSlug: string) => {
|
||||
this.moduleId = moduleSlug ?? null;
|
||||
@ -332,88 +309,6 @@ class ModuleStore implements IModuleStore {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fetchModuleIssues = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
||||
try {
|
||||
this.loader = true;
|
||||
this.error = null;
|
||||
|
||||
this.rootStore.workspace.setWorkspaceSlug(workspaceSlug);
|
||||
this.rootStore.project.setProjectId(projectId);
|
||||
|
||||
const params = this.rootStore?.issueFilter?.appliedFilters;
|
||||
const issueResponse = await this.moduleService.getModuleIssuesWithParams(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
moduleId,
|
||||
params
|
||||
);
|
||||
|
||||
const issueType = this.rootStore.issue.getIssueType;
|
||||
if (issueType != null) {
|
||||
const _issues = {
|
||||
...this.issues,
|
||||
[moduleId]: {
|
||||
...this.issues[moduleId],
|
||||
[issueType]: issueResponse,
|
||||
},
|
||||
};
|
||||
runInAction(() => {
|
||||
this.issues = _issues;
|
||||
this.loader = false;
|
||||
this.error = null;
|
||||
});
|
||||
}
|
||||
|
||||
return issueResponse;
|
||||
} catch (error) {
|
||||
console.error("Error: Fetching error module issues in module store", error);
|
||||
this.loader = false;
|
||||
this.error = error;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
updateIssueStructure = async (
|
||||
group_id: string | null,
|
||||
sub_group_id: string | null,
|
||||
moduleId: string,
|
||||
issue: IIssue
|
||||
) => {
|
||||
const issueType = this.rootStore.issue.getIssueType;
|
||||
|
||||
if (!issueType) return null;
|
||||
|
||||
let issues = this.getIssues;
|
||||
|
||||
if (!issues) return null;
|
||||
|
||||
if (issueType === "grouped" && group_id) {
|
||||
issues = issues as IIssueGroupedStructure;
|
||||
issues = {
|
||||
...issues,
|
||||
[group_id]: issues[group_id].map((i: IIssue) => (i?.id === issue?.id ? issue : i)),
|
||||
};
|
||||
}
|
||||
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
|
||||
issues = issues as IIssueGroupWithSubGroupsStructure;
|
||||
issues = {
|
||||
...issues,
|
||||
[sub_group_id]: {
|
||||
...issues[sub_group_id],
|
||||
[group_id]: issues[sub_group_id][group_id].map((i: IIssue) => (i?.id === issue?.id ? issue : i)),
|
||||
},
|
||||
};
|
||||
}
|
||||
if (issueType === "ungrouped") {
|
||||
issues = issues as IIssueUnGroupedStructure;
|
||||
issues = issues.map((i: IIssue) => (i?.id === issue?.id ? issue : i));
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.issues = { ...this.issues, [moduleId]: { ...this.issues[moduleId], [issueType]: issues } };
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ModuleStore;
|
||||
|
Loading…
Reference in New Issue
Block a user