forked from github/plane
dev: setup module and module filter store (#2364)
* dev: implement module issues using mobx store * dev: module filter store setup * chore: module store crud operations
This commit is contained in:
parent
844a3e4b42
commit
0f47762e6d
@ -13,7 +13,6 @@ import issuesServices from "services/issue.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
|
||||||
// ui
|
// ui
|
||||||
import { DangerButton, SecondaryButton } from "components/ui";
|
import { DangerButton, SecondaryButton } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -55,7 +54,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
const { displayFilters, params } = useIssuesView();
|
const { displayFilters, params } = useIssuesView();
|
||||||
const { params: calendarParams } = useCalendarIssuesView();
|
|
||||||
const { order_by, group_by, ...viewGanttParams } = params;
|
const { order_by, group_by, ...viewGanttParams } = params;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -90,14 +88,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
|
|||||||
|
|
||||||
if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids];
|
if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids];
|
||||||
|
|
||||||
const calendarFetchKey = cycleId
|
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
|
|
||||||
: moduleId
|
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams)
|
|
||||||
: viewId
|
|
||||||
? VIEW_ISSUES(viewId.toString(), calendarParams)
|
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", calendarParams);
|
|
||||||
|
|
||||||
const ganttFetchKey = cycleId
|
const ganttFetchKey = cycleId
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
||||||
: moduleId
|
: moduleId
|
||||||
@ -122,8 +112,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
|
|||||||
message: "Issues deleted successfully!",
|
message: "Issues deleted successfully!",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey);
|
||||||
else if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey);
|
|
||||||
else {
|
else {
|
||||||
if (cycleId) {
|
if (cycleId) {
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params));
|
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params));
|
||||||
|
@ -8,8 +8,6 @@ import { mutate } from "swr";
|
|||||||
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issue.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
|
||||||
// components
|
// components
|
||||||
import { SingleCalendarDate, CalendarHeader } from "components/core";
|
import { SingleCalendarDate, CalendarHeader } from "components/core";
|
||||||
import { IssuePeekOverview } from "components/issues";
|
import { IssuePeekOverview } from "components/issues";
|
||||||
|
@ -10,7 +10,6 @@ import { DraggableProvided, DraggableStateSnapshot } from "react-beautiful-dnd";
|
|||||||
import issuesService from "services/issue.service";
|
import issuesService from "services/issue.service";
|
||||||
import trackEventServices from "services/track_event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
|
||||||
import useIssuesProperties from "hooks/use-issue-properties";
|
import useIssuesProperties from "hooks/use-issue-properties";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
@ -60,7 +59,7 @@ export const SingleCalendarIssue: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { params } = useCalendarIssuesView();
|
const params = {};
|
||||||
|
|
||||||
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
|
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
|
||||||
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
export * from "./module-issues";
|
||||||
export * from "./project-issues";
|
export * from "./project-issues";
|
||||||
|
94
web/components/headers/module-issues.tsx
Normal file
94
web/components/headers/module-issues.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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, IIssueFilterOptions, TIssueLayouts } from "types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
|
|
||||||
|
export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||||
|
|
||||||
|
const { issueFilter: issueFilterStore, moduleFilter: moduleFilterStore } = 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 || !moduleId) return;
|
||||||
|
|
||||||
|
const newValues = moduleFilterStore.userModuleFilters?.[key] ?? [];
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((val) => {
|
||||||
|
if (!newValues.includes(val)) newValues.push(val);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (moduleFilterStore.userModuleFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||||
|
else newValues.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleFilterStore.updateUserModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), {
|
||||||
|
[key]: newValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[moduleId, moduleFilterStore, projectId, workspaceSlug]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDisplayFiltersUpdate = useCallback(
|
||||||
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
|
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
|
||||||
|
display_filters: {
|
||||||
|
...updatedDisplayFilter,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[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={moduleFilterStore.userModuleFilters}
|
||||||
|
handleFiltersUpdate={handleFiltersUpdate}
|
||||||
|
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||||
|
projectId={projectId?.toString() ?? ""}
|
||||||
|
/>
|
||||||
|
</FiltersDropdown>
|
||||||
|
<FiltersDropdown title="View">
|
||||||
|
<DisplayFiltersSelection
|
||||||
|
displayFilters={issueFilterStore.userDisplayFilters}
|
||||||
|
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
|
||||||
|
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||||
|
/>
|
||||||
|
</FiltersDropdown>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -10,7 +10,6 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
import issueServices from "services/issue.service";
|
import issueServices from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// icons
|
// icons
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||||
@ -52,7 +51,6 @@ export const DeleteIssueModal: React.FC<Props> = ({
|
|||||||
const isArchivedIssues = router.pathname.includes("archived-issues");
|
const isArchivedIssues = router.pathname.includes("archived-issues");
|
||||||
|
|
||||||
const { displayFilters, params } = useIssuesView();
|
const { displayFilters, params } = useIssuesView();
|
||||||
const { params: calendarParams } = useCalendarIssuesView();
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -73,17 +71,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
|
|||||||
await issueServices
|
await issueServices
|
||||||
.deleteIssue(workspaceSlug as string, data.project, data.id, user)
|
.deleteIssue(workspaceSlug as string, data.project, data.id, user)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (displayFilters.layout === "calendar") {
|
if (displayFilters.layout === "spreadsheet") {
|
||||||
const calendarFetchKey = cycleId
|
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
|
|
||||||
: moduleId
|
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams)
|
|
||||||
: viewId
|
|
||||||
? VIEW_ISSUES(viewId.toString(), calendarParams)
|
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(data.project, calendarParams);
|
|
||||||
|
|
||||||
mutate<IIssue[]>(calendarFetchKey, (prevData) => (prevData ?? []).filter((p) => p.id !== data.id), false);
|
|
||||||
} else if (displayFilters.layout === "spreadsheet") {
|
|
||||||
if (data.parent) {
|
if (data.parent) {
|
||||||
mutate<ISubIssueResponse>(
|
mutate<ISubIssueResponse>(
|
||||||
SUB_ISSUES(data.parent.toString()),
|
SUB_ISSUES(data.parent.toString()),
|
||||||
|
@ -11,7 +11,6 @@ import issuesService from "services/issue.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
@ -78,7 +77,6 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
|
|||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||||
|
|
||||||
const { displayFilters, params } = useIssuesView();
|
const { displayFilters, params } = useIssuesView();
|
||||||
const { params: calendarParams } = useCalendarIssuesView();
|
|
||||||
const { ...viewGanttParams } = params;
|
const { ...viewGanttParams } = params;
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
@ -146,14 +144,6 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
|
|||||||
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
||||||
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
|
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
|
||||||
|
|
||||||
const calendarFetchKey = cycleId
|
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
|
|
||||||
: moduleId
|
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams)
|
|
||||||
: viewId
|
|
||||||
? VIEW_ISSUES(viewId.toString(), calendarParams)
|
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "", calendarParams);
|
|
||||||
|
|
||||||
const ganttFetchKey = cycleId
|
const ganttFetchKey = cycleId
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
||||||
: moduleId
|
: moduleId
|
||||||
@ -171,7 +161,6 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
|
|||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
||||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
||||||
|
|
||||||
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
|
||||||
if (displayFilters.layout === "gantt_chart")
|
if (displayFilters.layout === "gantt_chart")
|
||||||
mutate(ganttFetchKey, {
|
mutate(ganttFetchKey, {
|
||||||
start_target_date: true,
|
start_target_date: true,
|
||||||
@ -210,7 +199,6 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
|
|||||||
if (isUpdatingSingleIssue) {
|
if (isUpdatingSingleIssue) {
|
||||||
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
||||||
} else {
|
} else {
|
||||||
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
|
||||||
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
|
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
||||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
||||||
@ -290,7 +278,6 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
|
|||||||
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
||||||
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
||||||
|
|
||||||
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
|
||||||
if (displayFilters.layout === "gantt_chart")
|
if (displayFilters.layout === "gantt_chart")
|
||||||
mutate(ganttFetchKey, {
|
mutate(ganttFetchKey, {
|
||||||
start_target_date: true,
|
start_target_date: true,
|
||||||
|
@ -4,6 +4,7 @@ export * from "./types.d";
|
|||||||
export * from "./day-tile";
|
export * from "./day-tile";
|
||||||
export * from "./header";
|
export * from "./header";
|
||||||
export * from "./issue-blocks";
|
export * from "./issue-blocks";
|
||||||
|
export * from "./module-root";
|
||||||
export * from "./root";
|
export * from "./root";
|
||||||
export * from "./week-days";
|
export * from "./week-days";
|
||||||
export * from "./week-header";
|
export * from "./week-header";
|
||||||
|
36
web/components/issues/issue-layouts/calendar/module-root.tsx
Normal file
36
web/components/issues/issue-layouts/calendar/module-root.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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 ModuleCalendarLayout: React.FC = observer(() => {
|
||||||
|
const { module: moduleStore } = 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 = moduleStore.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} />
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -2,6 +2,7 @@ export * from "./date";
|
|||||||
export * from "./filters-list";
|
export * from "./filters-list";
|
||||||
export * from "./label";
|
export * from "./label";
|
||||||
export * from "./members";
|
export * from "./members";
|
||||||
|
export * from "./module-root";
|
||||||
export * from "./priority";
|
export * from "./priority";
|
||||||
export * from "./root";
|
export * from "./root";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// components
|
||||||
|
import { AppliedFiltersList } from "components/issues";
|
||||||
|
// types
|
||||||
|
import { IIssueFilterOptions } from "types";
|
||||||
|
|
||||||
|
export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||||
|
|
||||||
|
const { project: projectStore, moduleFilter: moduleFilterStore } = useMobxStore();
|
||||||
|
|
||||||
|
const userFilters = moduleFilterStore.userModuleFilters;
|
||||||
|
|
||||||
|
// filters whose value not null or empty array
|
||||||
|
const appliedFilters: IIssueFilterOptions = {};
|
||||||
|
Object.entries(userFilters).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 (!workspaceSlug || !projectId || !moduleId) return;
|
||||||
|
|
||||||
|
// remove all values of the key if value is null
|
||||||
|
if (!value) {
|
||||||
|
moduleFilterStore.updateUserModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), {
|
||||||
|
[key]: null,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the passed value from the key
|
||||||
|
let newValues = moduleFilterStore.userModuleFilters?.[key] ?? [];
|
||||||
|
newValues = newValues.filter((val) => val !== value);
|
||||||
|
|
||||||
|
moduleFilterStore.updateUserModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), {
|
||||||
|
[key]: newValues,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearAllFilters = () => {
|
||||||
|
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||||
|
|
||||||
|
const newFilters: IIssueFilterOptions = {};
|
||||||
|
Object.keys(userFilters).forEach((key) => {
|
||||||
|
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
moduleFilterStore.updateUserModuleFilters(workspaceSlug.toString(), projectId.toString(), moduleId?.toString(), {
|
||||||
|
...newFilters,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// return if no filters are applied
|
||||||
|
if (Object.keys(appliedFilters).length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppliedFiltersList
|
||||||
|
appliedFilters={appliedFilters}
|
||||||
|
handleClearAllFilters={handleClearAllFilters}
|
||||||
|
handleRemoveFilter={handleRemoveFilter}
|
||||||
|
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
|
||||||
|
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
|
||||||
|
states={projectStore.states?.[projectId?.toString() ?? ""]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
@ -12,7 +12,7 @@ export const AppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore();
|
const { issueFilter: issueFilterStore, project: projectStore, moduleFilter: moduleFilterStore } = useMobxStore();
|
||||||
|
|
||||||
const userFilters = issueFilterStore.userFilters;
|
const userFilters = issueFilterStore.userFilters;
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from "./blocks";
|
export * from "./blocks";
|
||||||
|
export * from "./module-root";
|
||||||
export * from "./root";
|
export * from "./root";
|
||||||
|
55
web/components/issues/issue-layouts/gantt/module-root.tsx
Normal file
55
web/components/issues/issue-layouts/gantt/module-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 ModuleGanttLayout: React.FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const { projectDetails } = useProjectDetails();
|
||||||
|
|
||||||
|
const { module: moduleStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
|
const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
|
||||||
|
|
||||||
|
const issues = moduleStore.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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -3,3 +3,4 @@ export * from "./filters";
|
|||||||
export * from "./gantt";
|
export * from "./gantt";
|
||||||
export * from "./kanban";
|
export * from "./kanban";
|
||||||
export * from "./spreadsheet";
|
export * from "./spreadsheet";
|
||||||
|
export * from "./module-all-layouts";
|
||||||
|
54
web/components/issues/issue-layouts/module-all-layouts.tsx
Normal file
54
web/components/issues/issue-layouts/module-all-layouts.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// components
|
||||||
|
import {
|
||||||
|
KanBanLayout,
|
||||||
|
ModuleAppliedFiltersRoot,
|
||||||
|
ModuleCalendarLayout,
|
||||||
|
ModuleGanttLayout,
|
||||||
|
ModuleSpreadsheetLayout,
|
||||||
|
} from "components/issues";
|
||||||
|
|
||||||
|
export const ModuleAllLayouts: React.FC = observer(() => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||||
|
|
||||||
|
const { module: moduleStore, project: projectStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
|
useSWR(workspaceSlug && projectId ? `MODULE_ISSUES` : null, async () => {
|
||||||
|
if (workspaceSlug && projectId && moduleId) {
|
||||||
|
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
||||||
|
|
||||||
|
await projectStore.fetchProjectStates(workspaceSlug.toString(), projectId.toString());
|
||||||
|
await projectStore.fetchProjectLabels(workspaceSlug.toString(), projectId.toString());
|
||||||
|
await projectStore.fetchProjectMembers(workspaceSlug.toString(), projectId.toString());
|
||||||
|
|
||||||
|
await moduleStore.fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||||
|
await moduleStore.fetchModuleIssues(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full h-full flex flex-col overflow-auto">
|
||||||
|
<ModuleAppliedFiltersRoot />
|
||||||
|
<div className="h-full w-full">
|
||||||
|
{activeLayout === "kanban" ? (
|
||||||
|
<KanBanLayout />
|
||||||
|
) : activeLayout === "calendar" ? (
|
||||||
|
<ModuleCalendarLayout />
|
||||||
|
) : activeLayout === "gantt_chart" ? (
|
||||||
|
<ModuleGanttLayout />
|
||||||
|
) : activeLayout === "spreadsheet" ? (
|
||||||
|
<ModuleSpreadsheetLayout />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -1 +1,2 @@
|
|||||||
|
export * from "./module-root";
|
||||||
export * from "./root";
|
export * from "./root";
|
||||||
|
160
web/components/issues/issue-layouts/spreadsheet/module-root.tsx
Normal file
160
web/components/issues/issue-layouts/spreadsheet/module-root.tsx
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
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, Spinner } from "components/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 ModuleSpreadsheetLayout: React.FC = observer(() => {
|
||||||
|
const [expandedIssues, setExpandedIssues] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
const { projectDetails } = useProjectDetails();
|
||||||
|
|
||||||
|
const { module: moduleStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||||
|
|
||||||
|
const issues = moduleStore.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 type = cycleId ? "cycle" : moduleId ? "module" : "issue";
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
>
|
||||||
|
{type === "issue" ? (
|
||||||
|
<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"
|
||||||
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", { key: "c" });
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-4 w-4" />
|
||||||
|
Add Issue
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
@ -13,7 +13,6 @@ import inboxServices from "services/inbox.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useInboxView from "hooks/use-inbox-view";
|
import useInboxView from "hooks/use-inbox-view";
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
@ -83,7 +82,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId, inboxId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId, inboxId } = router.query;
|
||||||
|
|
||||||
const { displayFilters, params } = useIssuesView();
|
const { displayFilters, params } = useIssuesView();
|
||||||
const { params: calendarParams } = useCalendarIssuesView();
|
|
||||||
const { ...viewGanttParams } = params;
|
const { ...viewGanttParams } = params;
|
||||||
const { params: inboxParams } = useInboxView();
|
const { params: inboxParams } = useInboxView();
|
||||||
|
|
||||||
@ -270,14 +268,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const calendarFetchKey = cycleId
|
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
|
|
||||||
: moduleId
|
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams)
|
|
||||||
: viewId
|
|
||||||
? VIEW_ISSUES(viewId.toString(), calendarParams)
|
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "", calendarParams);
|
|
||||||
|
|
||||||
const ganttFetchKey = cycleId
|
const ganttFetchKey = cycleId
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
||||||
: moduleId
|
: moduleId
|
||||||
@ -298,7 +288,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
||||||
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
||||||
|
|
||||||
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
|
||||||
if (displayFilters.layout === "gantt_chart")
|
if (displayFilters.layout === "gantt_chart")
|
||||||
mutate(ganttFetchKey, {
|
mutate(ganttFetchKey, {
|
||||||
start_target_date: true,
|
start_target_date: true,
|
||||||
@ -375,7 +364,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
if (isUpdatingSingleIssue) {
|
if (isUpdatingSingleIssue) {
|
||||||
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
||||||
} else {
|
} else {
|
||||||
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
|
||||||
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
|
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import { useRouter } from "next/router";
|
|||||||
// hooks
|
// hooks
|
||||||
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 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";
|
||||||
@ -22,18 +21,11 @@ export const ModuleIssuesGanttChartView: React.FC<Props> = ({ disableUserActions
|
|||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { projectDetails } = useProjectDetails();
|
const { projectDetails } = useProjectDetails();
|
||||||
|
|
||||||
const { ganttIssues, mutateGanttIssues } = useGanttChartModuleIssues(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
moduleId as string
|
|
||||||
);
|
|
||||||
|
|
||||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IssuePeekOverview
|
<IssuePeekOverview
|
||||||
handleMutation={() => mutateGanttIssues()}
|
|
||||||
projectId={projectId?.toString() ?? ""}
|
projectId={projectId?.toString() ?? ""}
|
||||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||||
readOnly={disableUserActions}
|
readOnly={disableUserActions}
|
||||||
@ -43,7 +35,7 @@ export const ModuleIssuesGanttChartView: React.FC<Props> = ({ disableUserActions
|
|||||||
border={false}
|
border={false}
|
||||||
title="Issues"
|
title="Issues"
|
||||||
loaderTitle="Issues"
|
loaderTitle="Issues"
|
||||||
blocks={ganttIssues ? renderIssueBlocksStructure(ganttIssues as IIssue[]) : null}
|
blocks={null}
|
||||||
blockUpdateHandler={(block, payload) => {}}
|
blockUpdateHandler={(block, payload) => {}}
|
||||||
SidebarBlockRender={IssueGanttSidebarBlock}
|
SidebarBlockRender={IssueGanttSidebarBlock}
|
||||||
BlockRender={IssueGanttBlock}
|
BlockRender={IssueGanttBlock}
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
// services
|
|
||||||
import modulesService from "services/modules.service";
|
|
||||||
// hooks
|
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
// fetch-keys
|
|
||||||
import { MODULE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
const useGanttChartModuleIssues = (
|
|
||||||
workspaceSlug: string | undefined,
|
|
||||||
projectId: string | undefined,
|
|
||||||
moduleId: string | undefined
|
|
||||||
) => {
|
|
||||||
const { displayFilters, filters } = useIssuesView();
|
|
||||||
|
|
||||||
const params: any = {
|
|
||||||
order_by: displayFilters.order_by,
|
|
||||||
type: displayFilters?.type ? displayFilters?.type : undefined,
|
|
||||||
sub_issue: displayFilters.sub_issue,
|
|
||||||
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
|
||||||
state: filters?.state ? filters?.state.join(",") : undefined,
|
|
||||||
priority: filters?.priority ? filters?.priority.join(",") : undefined,
|
|
||||||
labels: filters?.labels ? filters?.labels.join(",") : undefined,
|
|
||||||
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
|
||||||
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
|
|
||||||
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
|
|
||||||
start_target_date: true, // to fetch only issues with a start and target date
|
|
||||||
};
|
|
||||||
|
|
||||||
// all issues under the workspace and project
|
|
||||||
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && moduleId
|
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && moduleId
|
|
||||||
? () =>
|
|
||||||
modulesService.getModuleIssuesWithParams(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
moduleId.toString(),
|
|
||||||
params
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ganttIssues,
|
|
||||||
mutateGanttIssues,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useGanttChartModuleIssues;
|
|
@ -1,113 +0,0 @@
|
|||||||
import { useContext } from "react";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
// contexts
|
|
||||||
import { issueViewContext } from "contexts/issue-view.context";
|
|
||||||
// services
|
|
||||||
import issuesService from "services/issue.service";
|
|
||||||
import cyclesService from "services/cycles.service";
|
|
||||||
import modulesService from "services/modules.service";
|
|
||||||
// types
|
|
||||||
import { IIssue } from "types";
|
|
||||||
// fetch-keys
|
|
||||||
import {
|
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
VIEW_ISSUES,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
const useCalendarIssuesView = () => {
|
|
||||||
const {
|
|
||||||
display_filters: displayFilters,
|
|
||||||
setDisplayFilters,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
resetFilterToDefault,
|
|
||||||
setNewFilterDefaultView,
|
|
||||||
} = useContext(issueViewContext);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
|
||||||
|
|
||||||
const params: any = {
|
|
||||||
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
|
||||||
state: filters?.state ? filters?.state.join(",") : undefined,
|
|
||||||
priority: filters?.priority ? filters?.priority.join(",") : undefined,
|
|
||||||
type: displayFilters?.type ? displayFilters?.type : undefined,
|
|
||||||
labels: filters?.labels ? filters?.labels.join(",") : undefined,
|
|
||||||
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
|
||||||
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: projectCalendarIssues, mutate: mutateProjectCalendarIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params) : null,
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? () => issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: cycleCalendarIssues, mutate: mutateCycleCalendarIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) : null,
|
|
||||||
workspaceSlug && projectId && cycleId
|
|
||||||
? () =>
|
|
||||||
cyclesService.getCycleIssuesWithParams(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
cycleId.toString(),
|
|
||||||
params
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: moduleCalendarIssues, mutate: mutateModuleCalendarIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && moduleId ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) : null,
|
|
||||||
workspaceSlug && projectId && moduleId
|
|
||||||
? () =>
|
|
||||||
modulesService.getModuleIssuesWithParams(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
moduleId.toString(),
|
|
||||||
params
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: viewCalendarIssues, mutate: mutateViewCalendarIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null,
|
|
||||||
workspaceSlug && projectId && viewId && params
|
|
||||||
? () => issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const calendarIssues = cycleId
|
|
||||||
? (cycleCalendarIssues as IIssue[])
|
|
||||||
: moduleId
|
|
||||||
? (moduleCalendarIssues as IIssue[])
|
|
||||||
: viewId
|
|
||||||
? (viewCalendarIssues as IIssue[])
|
|
||||||
: (projectCalendarIssues as IIssue[]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
displayFilters,
|
|
||||||
setDisplayFilters,
|
|
||||||
calendarIssues: calendarIssues ?? [],
|
|
||||||
mutateIssues: cycleId
|
|
||||||
? mutateCycleCalendarIssues
|
|
||||||
: moduleId
|
|
||||||
? mutateModuleCalendarIssues
|
|
||||||
: viewId
|
|
||||||
? mutateViewCalendarIssues
|
|
||||||
: mutateProjectCalendarIssues,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
params,
|
|
||||||
resetFilterToDefault,
|
|
||||||
setNewFilterDefaultView,
|
|
||||||
} as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useCalendarIssuesView;
|
|
@ -6,11 +6,17 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
const MobxStoreInit = () => {
|
const MobxStoreInit = () => {
|
||||||
const { theme: themeStore, user: userStore, workspace: workspaceStore, project: projectStore } = useMobxStore();
|
const {
|
||||||
|
theme: themeStore,
|
||||||
|
user: userStore,
|
||||||
|
workspace: workspaceStore,
|
||||||
|
project: projectStore,
|
||||||
|
module: moduleStore,
|
||||||
|
} = useMobxStore();
|
||||||
const { setTheme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// sidebar collapsed toggle
|
// sidebar collapsed toggle
|
||||||
@ -47,7 +53,8 @@ const MobxStoreInit = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString());
|
if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString());
|
||||||
if (projectId) projectStore.setProjectId(projectId.toString());
|
if (projectId) projectStore.setProjectId(projectId.toString());
|
||||||
}, [workspaceSlug, projectId, workspaceStore, projectStore]);
|
if (moduleId) moduleStore.setModuleId(moduleId.toString());
|
||||||
|
}, [workspaceSlug, projectId, moduleId, workspaceStore, projectStore, moduleStore]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
@ -13,10 +13,8 @@ import useToast from "hooks/use-toast";
|
|||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
// layouts
|
// layouts
|
||||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
|
||||||
// contexts
|
|
||||||
import { IssueViewContextProvider } from "contexts/issue-view.context";
|
|
||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "components/core";
|
import { ExistingIssuesListModal, IssuesFilterView } from "components/core";
|
||||||
import { ModuleDetailsSidebar } from "components/modules";
|
import { ModuleDetailsSidebar } from "components/modules";
|
||||||
import { AnalyticsProjectModal } from "components/analytics";
|
import { AnalyticsProjectModal } from "components/analytics";
|
||||||
// ui
|
// ui
|
||||||
@ -30,10 +28,12 @@ import { truncateText } from "helpers/string.helper";
|
|||||||
import { ISearchIssueResponse } from "types";
|
import { ISearchIssueResponse } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { MODULE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys";
|
import { MODULE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys";
|
||||||
|
import { ModuleAllLayouts } from "components/issues";
|
||||||
|
import { ModuleIssuesHeader } from "components/headers";
|
||||||
|
|
||||||
const SingleModule: React.FC = () => {
|
const SingleModule: React.FC = () => {
|
||||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||||
const [moduleSidebar, setModuleSidebar] = useState(true);
|
const [moduleSidebar, setModuleSidebar] = useState(false);
|
||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -85,7 +85,7 @@ const SingleModule: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueViewContextProvider>
|
<>
|
||||||
<ExistingIssuesListModal
|
<ExistingIssuesListModal
|
||||||
isOpen={moduleIssuesListModal}
|
isOpen={moduleIssuesListModal}
|
||||||
handleClose={() => setModuleIssuesListModal(false)}
|
handleClose={() => setModuleIssuesListModal(false)}
|
||||||
@ -124,27 +124,7 @@ const SingleModule: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
}
|
}
|
||||||
right={
|
right={<ModuleIssuesHeader />}
|
||||||
<div className={`flex items-center gap-2 duration-300`}>
|
|
||||||
<IssuesFilterView />
|
|
||||||
<SecondaryButton
|
|
||||||
onClick={() => setAnalyticsModal(true)}
|
|
||||||
className="!py-1.5 font-normal rounded-md text-custom-text-200 hover:text-custom-text-100"
|
|
||||||
outline
|
|
||||||
>
|
|
||||||
Analytics
|
|
||||||
</SecondaryButton>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-background-90 ${
|
|
||||||
moduleSidebar ? "rotate-180" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setModuleSidebar((prevData) => !prevData)}
|
|
||||||
>
|
|
||||||
<ArrowLeftIcon className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{error ? (
|
{error ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@ -164,7 +144,7 @@ const SingleModule: React.FC = () => {
|
|||||||
analyticsModal ? "mr-[50%]" : ""
|
analyticsModal ? "mr-[50%]" : ""
|
||||||
} duration-300`}
|
} duration-300`}
|
||||||
>
|
>
|
||||||
<IssuesView openIssuesListModal={openIssuesListModal} />
|
<ModuleAllLayouts />
|
||||||
</div>
|
</div>
|
||||||
<ModuleDetailsSidebar
|
<ModuleDetailsSidebar
|
||||||
module={moduleDetails}
|
module={moduleDetails}
|
||||||
@ -175,7 +155,7 @@ const SingleModule: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ProjectAuthorizationWrapper>
|
</ProjectAuthorizationWrapper>
|
||||||
</IssueViewContextProvider>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,12 +18,7 @@ export class ModuleService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createModule(
|
async createModule(workspaceSlug: string, projectId: string, data: any, user: any): Promise<IModule> {
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
data: any,
|
|
||||||
user: ICurrentUserResponse | undefined
|
|
||||||
): Promise<any> {
|
|
||||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/`, data)
|
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/`, data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
trackEventServices.trackModuleEvent(response?.data, "MODULE_CREATE", user);
|
trackEventServices.trackModuleEvent(response?.data, "MODULE_CREATE", user);
|
||||||
@ -64,7 +59,7 @@ export class ModuleService extends APIService {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
moduleId: string,
|
moduleId: string,
|
||||||
data: Partial<IModule>,
|
data: Partial<IModule>,
|
||||||
user: ICurrentUserResponse | undefined
|
user: any
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data)
|
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`, data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -76,12 +71,7 @@ export class ModuleService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteModule(
|
async deleteModule(workspaceSlug: string, projectId: string, moduleId: string, user: any): Promise<any> {
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
moduleId: string,
|
|
||||||
user: ICurrentUserResponse | undefined
|
|
||||||
): Promise<any> {
|
|
||||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`)
|
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}/`)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
trackEventServices.trackModuleEvent(response?.data, "MODULE_DELETE", user);
|
trackEventServices.trackModuleEvent(response?.data, "MODULE_DELETE", user);
|
||||||
|
@ -100,7 +100,7 @@ class IssueStore implements IIssueStore {
|
|||||||
|
|
||||||
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
|
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
|
||||||
const projectId: string | null = issue?.project;
|
const projectId: string | null = issue?.project;
|
||||||
const issueType: IIssueType | null = this.getIssueType;
|
const issueType = this.getIssueType;
|
||||||
if (!projectId || !issueType) return null;
|
if (!projectId || !issueType) return null;
|
||||||
|
|
||||||
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
|
||||||
|
@ -4,7 +4,6 @@ import { ProjectService } from "services/project.service";
|
|||||||
import { IssueService } from "services/issue.service";
|
import { IssueService } from "services/issue.service";
|
||||||
// helpers
|
// helpers
|
||||||
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||||
import { renderDateFormat } from "helpers/date-time.helper";
|
|
||||||
// types
|
// types
|
||||||
import { RootStore } from "./root";
|
import { RootStore } from "./root";
|
||||||
import {
|
import {
|
||||||
@ -113,13 +112,7 @@ class IssueFilterStore implements IIssueFilterStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
get appliedFilters(): TIssueParams[] | null {
|
get appliedFilters(): TIssueParams[] | null {
|
||||||
if (
|
if (!this.userFilters || !this.userDisplayFilters) return null;
|
||||||
!this.userFilters ||
|
|
||||||
Object.keys(this.userFilters).length === 0 ||
|
|
||||||
!this.userDisplayFilters ||
|
|
||||||
Object.keys(this.userDisplayFilters).length === 0
|
|
||||||
)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
let filteredRouteParams: any = {
|
let filteredRouteParams: any = {
|
||||||
priority: this.userFilters?.priority || undefined,
|
priority: this.userFilters?.priority || undefined,
|
||||||
|
148
web/store/module_filters.ts
Normal file
148
web/store/module_filters.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||||
|
// services
|
||||||
|
import { ProjectService } from "services/project.service";
|
||||||
|
import { ModuleService } from "services/modules.service";
|
||||||
|
// helpers
|
||||||
|
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||||
|
// types
|
||||||
|
import { RootStore } from "./root";
|
||||||
|
import { IIssueFilterOptions, TIssueParams } from "types";
|
||||||
|
|
||||||
|
export interface IModuleFilterStore {
|
||||||
|
loader: boolean;
|
||||||
|
error: any | null;
|
||||||
|
userModuleFilters: IIssueFilterOptions;
|
||||||
|
defaultFilters: IIssueFilterOptions;
|
||||||
|
|
||||||
|
// action
|
||||||
|
updateUserModuleFilters: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
moduleId: string,
|
||||||
|
filterToUpdate: Partial<IIssueFilterOptions>
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
// computed
|
||||||
|
appliedFilters: TIssueParams[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleFilterStore implements IModuleFilterStore {
|
||||||
|
loader: boolean = false;
|
||||||
|
error: any | null = null;
|
||||||
|
|
||||||
|
// observables
|
||||||
|
userModuleFilters: IIssueFilterOptions = {};
|
||||||
|
defaultFilters: IIssueFilterOptions = {};
|
||||||
|
|
||||||
|
// root store
|
||||||
|
rootStore;
|
||||||
|
|
||||||
|
// services
|
||||||
|
projectService;
|
||||||
|
moduleService;
|
||||||
|
|
||||||
|
constructor(_rootStore: RootStore) {
|
||||||
|
makeObservable(this, {
|
||||||
|
loader: observable.ref,
|
||||||
|
error: observable.ref,
|
||||||
|
|
||||||
|
// observables
|
||||||
|
defaultFilters: observable.ref,
|
||||||
|
userModuleFilters: observable.ref,
|
||||||
|
|
||||||
|
// actions
|
||||||
|
updateUserModuleFilters: action,
|
||||||
|
|
||||||
|
// computed
|
||||||
|
appliedFilters: computed,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
|
||||||
|
this.projectService = new ProjectService();
|
||||||
|
this.moduleService = new ModuleService();
|
||||||
|
}
|
||||||
|
|
||||||
|
computedFilter = (filters: any, filteredParams: any) => {
|
||||||
|
const computedFilters: any = {};
|
||||||
|
Object.keys(filters).map((key) => {
|
||||||
|
if (filters[key] != undefined && filteredParams.includes(key))
|
||||||
|
computedFilters[key] =
|
||||||
|
typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(",");
|
||||||
|
});
|
||||||
|
|
||||||
|
return computedFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
get appliedFilters(): TIssueParams[] | null {
|
||||||
|
const userDisplayFilters = this.rootStore.issueFilter.userDisplayFilters;
|
||||||
|
|
||||||
|
if (!this.userModuleFilters || !userDisplayFilters) return null;
|
||||||
|
|
||||||
|
let filteredRouteParams: any = {
|
||||||
|
priority: this.userModuleFilters?.priority || undefined,
|
||||||
|
state_group: this.userModuleFilters?.state_group || undefined,
|
||||||
|
state: this.userModuleFilters?.state || undefined,
|
||||||
|
assignees: this.userModuleFilters?.assignees || undefined,
|
||||||
|
created_by: this.userModuleFilters?.created_by || undefined,
|
||||||
|
labels: this.userModuleFilters?.labels || undefined,
|
||||||
|
start_date: this.userModuleFilters?.start_date || undefined,
|
||||||
|
target_date: this.userModuleFilters?.target_date || undefined,
|
||||||
|
group_by: userDisplayFilters?.group_by || "state",
|
||||||
|
order_by: userDisplayFilters?.order_by || "-created_at",
|
||||||
|
sub_group_by: userDisplayFilters?.sub_group_by || undefined,
|
||||||
|
type: userDisplayFilters?.type || undefined,
|
||||||
|
sub_issue: userDisplayFilters?.sub_issue || true,
|
||||||
|
show_empty_groups: userDisplayFilters?.show_empty_groups || true,
|
||||||
|
start_target_date: userDisplayFilters?.start_target_date || true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredParams = handleIssueQueryParamsByLayout(userDisplayFilters.layout, "issues");
|
||||||
|
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
||||||
|
|
||||||
|
if (userDisplayFilters.layout === "calendar") filteredRouteParams.group_by = "target_date";
|
||||||
|
if (userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||||
|
|
||||||
|
return filteredRouteParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserModuleFilters = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
moduleId: string,
|
||||||
|
filterToUpdate: Partial<IIssueFilterOptions>
|
||||||
|
) => {
|
||||||
|
const newFilters = {
|
||||||
|
...this.userModuleFilters,
|
||||||
|
...filterToUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.userModuleFilters = newFilters;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.moduleService.patchModule(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
moduleId,
|
||||||
|
{
|
||||||
|
view_props: {
|
||||||
|
filters: newFilters,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
this.rootStore.user.currentUser
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Failed to update user filters in issue filter store", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModuleFilterStore;
|
@ -1,10 +1,11 @@
|
|||||||
import { action, computed, observable, makeObservable, runInAction } from "mobx";
|
import { action, computed, observable, makeObservable, runInAction } from "mobx";
|
||||||
// types
|
|
||||||
import { RootStore } from "./root";
|
|
||||||
// services
|
// services
|
||||||
import { ProjectService } from "services/project.service";
|
import { ProjectService } from "services/project.service";
|
||||||
import { ModuleService } from "services/modules.service";
|
import { ModuleService } from "services/modules.service";
|
||||||
import { IModule } from "@/types";
|
// types
|
||||||
|
import { RootStore } from "./root";
|
||||||
|
import { IIssue, IModule } from "types";
|
||||||
|
import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "./issue";
|
||||||
|
|
||||||
export interface IModuleStore {
|
export interface IModuleStore {
|
||||||
loader: boolean;
|
loader: boolean;
|
||||||
@ -14,13 +15,35 @@ export interface IModuleStore {
|
|||||||
modules: {
|
modules: {
|
||||||
[project_id: string]: IModule[];
|
[project_id: string]: IModule[];
|
||||||
};
|
};
|
||||||
module_details: {
|
moduleDetails: {
|
||||||
[module_id: string]: IModule;
|
[module_id: string]: IModule;
|
||||||
};
|
};
|
||||||
|
issues: {
|
||||||
|
[module_id: string]: {
|
||||||
|
grouped: IIssueGroupedStructure;
|
||||||
|
groupWithSubGroups: IIssueGroupWithSubGroupsStructure;
|
||||||
|
ungrouped: IIssueUnGroupedStructure;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
setModuleId: (moduleSlug: string) => void;
|
setModuleId: (moduleSlug: string) => void;
|
||||||
|
|
||||||
fetchModules: (workspaceSlug: string, projectSlug: string) => void;
|
fetchModules: (workspaceSlug: string, projectId: string) => void;
|
||||||
|
fetchModuleDetails: (workspaceSlug: string, projectId: string, moduleId: string) => void;
|
||||||
|
|
||||||
|
// crud operations
|
||||||
|
createModule: (workspaceSlug: string, projectId: string, data: Partial<IModule>) => Promise<IModule>;
|
||||||
|
updateModuleDetails: (workspaceSlug: string, projectId: string, moduleId: string, data: Partial<IModule>) => void;
|
||||||
|
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 {
|
class ModuleStore implements IModuleStore {
|
||||||
@ -33,10 +56,24 @@ class ModuleStore implements IModuleStore {
|
|||||||
[project_id: string]: IModule[];
|
[project_id: string]: IModule[];
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
module_details: {
|
moduleDetails: {
|
||||||
[module_id: string]: IModule;
|
[module_id: string]: IModule;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
issues: {
|
||||||
|
[module_id: string]: {
|
||||||
|
grouped: {
|
||||||
|
[group_id: string]: IIssue[];
|
||||||
|
};
|
||||||
|
groupWithSubGroups: {
|
||||||
|
[group_id: string]: {
|
||||||
|
[sub_group_id: string]: IIssue[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
ungrouped: IIssue[];
|
||||||
|
};
|
||||||
|
} = {};
|
||||||
|
|
||||||
// root store
|
// root store
|
||||||
rootStore;
|
rootStore;
|
||||||
// services
|
// services
|
||||||
@ -49,11 +86,27 @@ class ModuleStore implements IModuleStore {
|
|||||||
error: observable.ref,
|
error: observable.ref,
|
||||||
|
|
||||||
moduleId: observable.ref,
|
moduleId: observable.ref,
|
||||||
|
modules: observable.ref,
|
||||||
|
moduleDetails: observable.ref,
|
||||||
|
issues: observable.ref,
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
|
getIssues: computed,
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
setModuleId: action,
|
setModuleId: action,
|
||||||
|
|
||||||
|
fetchModules: action,
|
||||||
|
fetchModuleDetails: action,
|
||||||
|
|
||||||
|
createModule: action,
|
||||||
|
updateModuleDetails: action,
|
||||||
|
deleteModule: action,
|
||||||
|
addModuleToFavorites: action,
|
||||||
|
removeModuleFromFavorites: action,
|
||||||
|
|
||||||
|
fetchModuleIssues: action,
|
||||||
|
updateIssueStructure: action,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore = _rootStore;
|
this.rootStore = _rootStore;
|
||||||
@ -67,32 +120,302 @@ class ModuleStore implements IModuleStore {
|
|||||||
return this.modules[this.rootStore.project.projectId] || null;
|
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
|
// actions
|
||||||
setModuleId = (moduleSlug: string) => {
|
setModuleId = (moduleSlug: string) => {
|
||||||
this.moduleId = moduleSlug ?? null;
|
this.moduleId = moduleSlug ?? null;
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchModules = async (workspaceSlug: string, projectSlug: string) => {
|
fetchModules = async (workspaceSlug: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
this.loader = true;
|
runInAction(() => {
|
||||||
this.error = null;
|
this.loader = true;
|
||||||
|
this.error = null;
|
||||||
|
});
|
||||||
|
|
||||||
const modulesResponse = await this.moduleService.getModules(workspaceSlug, projectSlug);
|
const modulesResponse = await this.moduleService.getModules(workspaceSlug, projectId);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.modules = {
|
this.modules = {
|
||||||
...this.modules,
|
...this.modules,
|
||||||
[projectSlug]: modulesResponse,
|
[projectId]: modulesResponse,
|
||||||
};
|
};
|
||||||
this.loader = false;
|
this.loader = false;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch modules list in project store", error);
|
console.error("Failed to fetch modules list in module store", error);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.loader = false;
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchModuleDetails = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.loader = true;
|
||||||
|
this.error = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.moduleService.getModuleDetails(workspaceSlug, projectId, moduleId);
|
||||||
|
|
||||||
|
if (!response) return null;
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.moduleDetails = {
|
||||||
|
...this.moduleDetails,
|
||||||
|
[moduleId]: response,
|
||||||
|
};
|
||||||
|
this.rootStore.moduleFilter.userModuleFilters = response.view_props?.filters ?? {};
|
||||||
|
this.loader = false;
|
||||||
|
this.error = null;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch module details in module store", error);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.loader = false;
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
createModule = async (workspaceSlug: string, projectId: string, data: Partial<IModule>) => {
|
||||||
|
try {
|
||||||
|
const response = await this.moduleService.createModule(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
data,
|
||||||
|
this.rootStore.user.currentUser
|
||||||
|
);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.modules = {
|
||||||
|
...this.modules,
|
||||||
|
[projectId]: [...this.modules[projectId], response],
|
||||||
|
};
|
||||||
|
this.loader = false;
|
||||||
|
this.error = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create module in module store", error);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.loader = false;
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateModuleDetails = async (workspaceSlug: string, projectId: string, moduleId: string, data: Partial<IModule>) => {
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
(this.modules = {
|
||||||
|
...this.modules,
|
||||||
|
[projectId]: this.modules[projectId].map((module) =>
|
||||||
|
module.id === moduleId ? { ...module, ...data } : module
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
(this.moduleDetails = {
|
||||||
|
...this.moduleDetails,
|
||||||
|
[moduleId]: {
|
||||||
|
...this.moduleDetails[moduleId],
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data, this.rootStore.user.currentUser);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update module in module store", error);
|
||||||
|
|
||||||
|
this.fetchModules(workspaceSlug, projectId);
|
||||||
|
this.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteModule = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.modules = {
|
||||||
|
...this.modules,
|
||||||
|
[projectId]: this.modules[projectId].filter((module) => module.id !== moduleId),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.moduleService.deleteModule(workspaceSlug, projectId, moduleId, this.rootStore.user.currentUser);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to delete module in module store", error);
|
||||||
|
|
||||||
|
this.fetchModules(workspaceSlug, projectId);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addModuleToFavorites = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.modules = {
|
||||||
|
...this.modules,
|
||||||
|
[projectId]: this.modules[projectId].map((module) => ({
|
||||||
|
...module,
|
||||||
|
is_favorite: module.id === moduleId ? true : module.is_favorite,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.moduleService.addModuleToFavorites(workspaceSlug, projectId, {
|
||||||
|
module: moduleId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to add module to favorites in module store", error);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.modules = {
|
||||||
|
...this.modules,
|
||||||
|
[projectId]: this.modules[projectId].map((module) => ({
|
||||||
|
...module,
|
||||||
|
is_favorite: module.id === moduleId ? false : module.is_favorite,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
this.error = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
removeModuleFromFavorites = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.modules = {
|
||||||
|
...this.modules,
|
||||||
|
[projectId]: this.modules[projectId].map((module) => ({
|
||||||
|
...module,
|
||||||
|
is_favorite: module.id === moduleId ? false : module.is_favorite,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.moduleService.removeModuleFromFavorites(workspaceSlug, projectId, moduleId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to remove module from favorites in module store", error);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.modules = {
|
||||||
|
...this.modules,
|
||||||
|
[projectId]: this.modules[projectId].map((module) => ({
|
||||||
|
...module,
|
||||||
|
is_favorite: module.id === moduleId ? true : module.is_favorite,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
console.log("params", params);
|
||||||
|
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.loader = false;
|
||||||
this.error = error;
|
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;
|
export default ModuleStore;
|
||||||
|
@ -15,6 +15,7 @@ import IssueFilterStore, { IIssueFilterStore } from "./issue_filters";
|
|||||||
import IssueViewDetailStore from "./issue_detail";
|
import IssueViewDetailStore from "./issue_detail";
|
||||||
import IssueKanBanViewStore from "./kanban_view";
|
import IssueKanBanViewStore from "./kanban_view";
|
||||||
import CalendarStore, { ICalendarStore } from "./calendar";
|
import CalendarStore, { ICalendarStore } from "./calendar";
|
||||||
|
import ModuleFilterStore, { IModuleFilterStore } from "./module_filters";
|
||||||
|
|
||||||
enableStaticRendering(typeof window === "undefined");
|
enableStaticRendering(typeof window === "undefined");
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ export class RootStore {
|
|||||||
project: IProjectStore;
|
project: IProjectStore;
|
||||||
issue: IIssueStore;
|
issue: IIssueStore;
|
||||||
module: IModuleStore;
|
module: IModuleStore;
|
||||||
|
moduleFilter: IModuleFilterStore;
|
||||||
cycle: ICycleStore;
|
cycle: ICycleStore;
|
||||||
view: IViewStore;
|
view: IViewStore;
|
||||||
issueFilter: IIssueFilterStore;
|
issueFilter: IIssueFilterStore;
|
||||||
@ -41,6 +43,7 @@ export class RootStore {
|
|||||||
this.project = new ProjectStore(this);
|
this.project = new ProjectStore(this);
|
||||||
this.projectPublish = new ProjectPublishStore(this);
|
this.projectPublish = new ProjectPublishStore(this);
|
||||||
this.module = new ModuleStore(this);
|
this.module = new ModuleStore(this);
|
||||||
|
this.moduleFilter = new ModuleFilterStore(this);
|
||||||
this.cycle = new CycleStore(this);
|
this.cycle = new CycleStore(this);
|
||||||
this.view = new ViewStore(this);
|
this.view = new ViewStore(this);
|
||||||
this.issue = new IssueStore(this);
|
this.issue = new IssueStore(this);
|
||||||
|
Loading…
Reference in New Issue
Block a user