From 2bf7e63625bcb3c233262efdedb570e8f99765cf Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Mon, 27 Nov 2023 14:15:33 +0530 Subject: [PATCH] issues rendering in all issue layouts fir profile and project issues and global issues store implementation (#2886) * dev: draft and archived issue store * connect draft and archived issues * kanban for draft issues * fix filter store for calendar and kanban * dev: profile issues store and draft issues filters in header * disble issue creation for draft issues * dev: profile issues store filters * disable kanban properties in draft issues * dev: profile issues store filters * dev: seperated adding issues to the cycle and module as seperate methds in cycle and module store * dev: workspace profile issues store * dev: sub group issues in the swimlanes * profile issues and create issue connection * fix profile issues * fix spreadsheet issues * fix dissapearing project from create issue modal * page level modifications * fix additional bugs * dev: issues profile and global iisues and filters update * fix issue related bugs * fix project views for list and kanban * fix build errors --------- Co-authored-by: rahulramesha --- .../command-palette/command-pallette.tsx | 3 + web/components/headers/cycle-issues.tsx | 15 +- web/components/headers/module-issues.tsx | 15 +- .../headers/project-draft-issues.tsx | 93 ++++- web/components/headers/project-issues.tsx | 14 +- web/components/issues/delete-issue-modal.tsx | 2 +- web/components/issues/form.tsx | 2 +- .../calendar/base-calendar-root.tsx | 25 +- .../calendar/roots/cycle-root.tsx | 7 +- .../calendar/roots/module-root.tsx | 7 +- .../calendar/roots/project-root.tsx | 7 +- .../calendar/roots/project-view-root.tsx | 11 +- .../issue-layouts/empty-states/cycle.tsx | 13 +- .../empty-states/project-view.tsx | 14 +- .../issue-layouts/empty-states/project.tsx | 10 +- .../applied-filters/roots/archived-issue.tsx | 27 +- .../applied-filters/roots/draft-issue.tsx | 80 +++++ .../filters/applied-filters/roots/index.ts | 1 + .../roots/profile-issues-root.tsx | 72 ++++ .../issue-layouts/gantt/base-gantt-root.tsx | 14 +- .../issue-layouts/kanban/base-kanban-root.tsx | 61 +++- .../issues/issue-layouts/kanban/block.tsx | 3 + .../issue-layouts/kanban/blocks-list.tsx | 3 + .../issues/issue-layouts/kanban/default.tsx | 46 ++- .../issue-layouts/kanban/headers/assignee.tsx | 7 + .../kanban/headers/created_by.tsx | 7 + .../kanban/headers/group-by-card.tsx | 72 ++-- .../kanban/headers/group-by-root.tsx | 29 +- .../issue-layouts/kanban/headers/label.tsx | 7 + .../issue-layouts/kanban/headers/priority.tsx | 7 + .../issue-layouts/kanban/headers/project.tsx | 7 + .../kanban/headers/state-group.tsx | 7 + .../issue-layouts/kanban/headers/state.tsx | 7 + .../kanban/headers/sub-group-by-root.tsx | 27 +- .../issue-layouts/kanban/properties.tsx | 17 +- .../issue-layouts/kanban/roots/cycle-root.tsx | 9 +- .../kanban/roots/draft-issue-root.tsx | 48 +++ .../kanban/roots/module-root.tsx | 7 +- .../kanban/roots/profile-issues-root.tsx | 168 ++------- .../kanban/roots/project-root.tsx | 9 +- .../kanban/roots/project-view-root.tsx | 9 +- .../issues/issue-layouts/kanban/swimlanes.tsx | 64 +++- .../issue-layouts/list/base-list-root.tsx | 36 +- .../issues/issue-layouts/list/default.tsx | 30 +- .../issue-layouts/list/headers/assignee.tsx | 7 +- .../issue-layouts/list/headers/created-by.tsx | 7 +- .../list/headers/empty-group.tsx | 15 +- .../list/headers/group-by-card.tsx | 187 +++++----- .../list/headers/group-by-root.tsx | 69 +++- .../issue-layouts/list/headers/label.tsx | 7 +- .../issue-layouts/list/headers/priority.tsx | 7 +- .../issue-layouts/list/headers/project.tsx | 7 +- .../list/headers/state-group.tsx | 7 +- .../issue-layouts/list/headers/state.tsx | 7 +- .../list/roots/archived-issue-root.tsx | 29 +- .../issue-layouts/list/roots/cycle-root.tsx | 2 + .../list/roots/draft-issue-root.tsx | 48 +++ .../issue-layouts/list/roots/module-root.tsx | 2 + .../list/roots/profile-issues-root.tsx | 55 ++- .../issue-layouts/list/roots/project-root.tsx | 2 + .../list/roots/project-view-root.tsx | 2 + .../issues/issue-layouts/properties/date.tsx | 4 +- .../issue-layouts/properties/labels.tsx | 2 + .../quick-action-dropdowns/cycle-issue.tsx | 2 + .../quick-action-dropdowns/module-issue.tsx | 2 + .../quick-action-dropdowns/project-issue.tsx | 2 + .../roots/archived-issue-layout-root.tsx | 11 +- .../roots/draft-issue-layout-root.tsx | 51 +++ .../roots/global-view-layout-root.tsx | 6 +- .../roots/project-layout-root.tsx | 18 +- .../roots/project-view-layout-root.tsx | 12 +- .../spreadsheet/base-spreadsheet-root.tsx | 65 ++-- .../spreadsheet/columns/assignee-column.tsx | 4 +- .../spreadsheet/columns/attachment-column.tsx | 2 +- .../spreadsheet/columns/due-date-column.tsx | 2 +- .../spreadsheet/columns/estimate-column.tsx | 4 +- .../columns/issue/issue-column.tsx | 67 +--- .../issue/spreadsheet-issue-column.tsx | 12 +- .../spreadsheet/columns/label-column.tsx | 4 +- .../spreadsheet/columns/link-column.tsx | 2 +- .../spreadsheet/columns/priority-column.tsx | 2 +- .../spreadsheet/columns/start-date-column.tsx | 2 +- .../spreadsheet/columns/state-column.tsx | 4 +- .../spreadsheet/columns/sub-issue-column.tsx | 2 +- .../spreadsheet/columns/updated-on-column.tsx | 2 +- .../spreadsheet/roots/cycle-root.tsx | 29 +- .../spreadsheet/roots/module-root.tsx | 30 +- .../spreadsheet/roots/project-root.tsx | 30 +- .../spreadsheet/roots/project-view-root.tsx | 30 +- .../spreadsheet/spreadsheet-view.tsx | 41 ++- web/components/issues/modal.tsx | 126 ++++--- .../profile/profile-issues-filter.tsx | 92 +++-- web/components/profile/profile-issues.tsx | 63 ++++ .../workspace/sidebar-quick-action.tsx | 50 +-- .../profile/[userId]/assigned.tsx | 56 +-- .../profile/[userId]/created.tsx | 51 +-- .../profile/[userId]/subscribed.tsx | 51 +-- .../[projectId]/draft-issues/index.tsx | 7 +- web/services/issue/issue_archive.service.ts | 4 +- web/services/issue/issue_draft.service.tsx | 8 +- web/services/user.service.ts | 11 + web/store/command-palette.store.ts | 23 +- web/store/issues/global/filter.store.ts | 327 ++++++++++++++++++ web/store/issues/global/issue.store.ts | 327 ++++++++++++++++++ web/store/issues/index.ts | 4 + web/store/issues/profile/filter.store.ts | 327 ++++++++++++++++++ web/store/issues/profile/issue.store.ts | 327 ++++++++++++++++++ .../project-issues/archived/issue.store.ts | 16 +- .../issues/project-issues/base-issue.store.ts | 5 +- .../project-issues/cycle/issue.store.ts | 50 ++- .../project-issues/draft/issue.store.ts | 30 +- .../project-issues/module/issue.store.ts | 50 ++- .../project-view/issue.store.ts | 13 +- .../project-issues/project/issue.store.ts | 28 +- web/store/issues/types.ts | 6 + web/store/root.ts | 24 ++ 116 files changed, 3187 insertions(+), 912 deletions(-) create mode 100644 web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx create mode 100644 web/components/issues/issue-layouts/filters/applied-filters/roots/profile-issues-root.tsx create mode 100644 web/components/issues/issue-layouts/kanban/roots/draft-issue-root.tsx create mode 100644 web/components/issues/issue-layouts/list/roots/draft-issue-root.tsx create mode 100644 web/components/issues/issue-layouts/roots/draft-issue-layout-root.tsx create mode 100644 web/components/profile/profile-issues.tsx create mode 100644 web/store/issues/global/filter.store.ts create mode 100644 web/store/issues/global/issue.store.ts create mode 100644 web/store/issues/profile/filter.store.ts create mode 100644 web/store/issues/profile/issue.store.ts diff --git a/web/components/command-palette/command-pallette.tsx b/web/components/command-palette/command-pallette.tsx index e926c7474..a4f5279a2 100644 --- a/web/components/command-palette/command-pallette.tsx +++ b/web/components/command-palette/command-pallette.tsx @@ -55,6 +55,8 @@ export const CommandPalette: FC = observer(() => { toggleBulkDeleteIssueModal, isDeleteIssueModalOpen, toggleDeleteIssueModal, + + createIssueStoreType, } = commandPalette; const isAnyModalOpen = Boolean( @@ -224,6 +226,7 @@ export const CommandPalette: FC = observer(() => { prePopulateData={ cycleId ? { cycle: cycleId.toString() } : moduleId ? { module: moduleId.toString() } : undefined } + currentStore={createIssueStoreType} /> {issueId && issueDetails && ( diff --git a/web/components/headers/cycle-issues.tsx b/web/components/headers/cycle-issues.tsx index 5353adbbe..5ee80d68c 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/components/headers/cycle-issues.tsx @@ -20,6 +20,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption // constants import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { EFilterType } from "store/issues/types"; +import { EProjectStore } from "store/command-palette.store"; export const CycleIssuesHeader: React.FC = observer(() => { const [analyticsModal, setAnalyticsModal] = useState(false); @@ -33,7 +34,6 @@ export const CycleIssuesHeader: React.FC = observer(() => { const { cycle: cycleStore, - cycleIssueFilters: cycleIssueFiltersStore, projectIssuesFilter: projectIssueFiltersStore, project: { currentProjectDetails }, projectMember: { projectMembers }, @@ -190,11 +190,14 @@ export const CycleIssuesHeader: React.FC = observer(() => { - - - diff --git a/web/components/issues/delete-issue-modal.tsx b/web/components/issues/delete-issue-modal.tsx index 3cf0105f3..86d31d5eb 100644 --- a/web/components/issues/delete-issue-modal.tsx +++ b/web/components/issues/delete-issue-modal.tsx @@ -72,7 +72,7 @@ export const DeleteIssueModal: React.FC = (props) => {

Are you sure you want to delete issue{" "} - {data?.project_detail.identifier}-{data?.sequence_id} + {data?.project_detail?.identifier}-{data?.sequence_id} {""}? All of the data related to the issue will be permanently removed. This action cannot be undone. diff --git a/web/components/issues/form.tsx b/web/components/issues/form.tsx index 86972a024..eff3b6193 100644 --- a/web/components/issues/form.tsx +++ b/web/components/issues/form.tsx @@ -228,7 +228,7 @@ export const IssueForm: FC = observer((props) => { ...defaultValues, ...initialData, }); - }, [setFocus, initialData, reset]); + }, [setFocus, reset]); // update projectId in form when projectId changes useEffect(() => { diff --git a/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx b/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx index 756a8489b..d2b93910c 100644 --- a/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx +++ b/web/components/issues/issue-layouts/calendar/base-calendar-root.tsx @@ -7,14 +7,28 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { CalendarChart } from "components/issues"; // types import { IIssue } from "types"; -import { ICycleIssuesStore, IModuleIssuesStore, IProjectIssuesStore, IViewIssuesStore } from "store/issues"; -import { IIssueCalendarViewStore, IssueStore } from "store/issue"; +import { + ICycleIssuesFilterStore, + ICycleIssuesStore, + IModuleIssuesFilterStore, + IModuleIssuesStore, + IProjectIssuesFilterStore, + IProjectIssuesStore, + IViewIssuesFilterStore, + IViewIssuesStore, +} from "store/issues"; +import { IIssueCalendarViewStore } from "store/issue"; import { IQuickActionProps } from "../list/list-view-types"; import { EIssueActions } from "../types"; import { IGroupedIssues } from "store/issues/types"; interface IBaseCalendarRoot { issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore; + issuesFilterStore: + | IProjectIssuesFilterStore + | IModuleIssuesFilterStore + | ICycleIssuesFilterStore + | IViewIssuesFilterStore; calendarViewStore: IIssueCalendarViewStore; QuickActions: FC; issueActions: { @@ -26,10 +40,9 @@ interface IBaseCalendarRoot { } export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => { - const { issueStore, calendarViewStore, QuickActions, issueActions, viewId } = props; - const { projectIssuesFilter: issueFilterStore } = useMobxStore(); + const { issueStore, issuesFilterStore, calendarViewStore, QuickActions, issueActions, viewId } = props; - const displayFilters = issueFilterStore.issueFilters?.displayFilters; + const displayFilters = issuesFilterStore.issueFilters?.displayFilters; const issues = issueStore.getIssues; const groupedIssueIds = (issueStore.getIssuesIds ?? {}) as IGroupedIssues; @@ -75,7 +88,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => { } handleRemoveFromView={ issueActions[EIssueActions.REMOVE] - ? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.UPDATE) + ? async () => handleIssues(issue.target_date ?? "", issue, EIssueActions.REMOVE) : undefined } /> diff --git a/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx b/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx index 7dff4533e..60339acf8 100644 --- a/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/calendar/roots/cycle-root.tsx @@ -10,7 +10,11 @@ import { EIssueActions } from "../../types"; import { BaseCalendarRoot } from "../base-calendar-root"; export const CycleCalendarLayout: React.FC = observer(() => { - const { cycleIssues: cycleIssueStore, cycleIssueCalendarView: cycleIssueCalendarViewStore } = useMobxStore(); + const { + cycleIssues: cycleIssueStore, + cycleIssuesFilter: cycleIssueFilterStore, + cycleIssueCalendarView: cycleIssueCalendarViewStore, + } = useMobxStore(); const router = useRouter(); const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string }; @@ -34,6 +38,7 @@ export const CycleCalendarLayout: React.FC = observer(() => { return ( { - const { moduleIssues: moduleIssueStore, moduleIssueCalendarView: moduleIssueCalendarViewStore } = useMobxStore(); + const { + moduleIssues: moduleIssueStore, + moduleIssuesFilter: moduleIssueFilterStore, + moduleIssueCalendarView: moduleIssueCalendarViewStore, + } = useMobxStore(); const router = useRouter(); const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string }; @@ -33,6 +37,7 @@ export const ModuleCalendarLayout: React.FC = observer(() => { return ( { const { projectIssues: issueStore, issueCalendarView: issueCalendarViewStore, - issueDetail: issueDetailStore, + projectIssuesFilter: projectIssueFiltersStore, } = useMobxStore(); const issueActions = { [EIssueActions.UPDATE]: async (issue: IIssue) => { if (!workspaceSlug) return; - issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue); + issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue); }, [EIssueActions.DELETE]: async (issue: IIssue) => { if (!workspaceSlug) return; - issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id); + issueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id); }, }; return ( { const { viewIssues: projectViewIssuesStore, - issueDetail: issueDetailStore, + viewIssuesFilter: projectIssueViewFiltersStore, projectViewIssueCalendarView: projectViewIssueCalendarViewStore, } = useMobxStore(); @@ -25,18 +23,19 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => { [EIssueActions.UPDATE]: async (issue: IIssue) => { if (!workspaceSlug) return; - issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue); + projectViewIssuesStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue); }, [EIssueActions.DELETE]: async (issue: IIssue) => { if (!workspaceSlug) return; - issueDetailStore.deleteIssue(workspaceSlug.toString(), issue.project, issue.id); + projectViewIssuesStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id); }, }; return ( = observer((props) => { // states const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); - const { cycleIssue: cycleIssueStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore(); + const { + cycleIssue: cycleIssueStore, + commandPalette: commandPaletteStore, + trackEvent: { setTrackElement }, + } = useMobxStore(); const { setToastAlert } = useToast(); @@ -63,9 +68,9 @@ export const CycleEmptyState: React.FC = observer((props) => { text: "New issue", icon: , onClick: () => { - setTrackElement("CYCLE_EMPTY_STATE") - commandPaletteStore.toggleCreateIssueModal(true) - } + setTrackElement("CYCLE_EMPTY_STATE"); + commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.CYCLE); + }, }} secondaryButton={ - - - - - - } - placement="bottom-start" - > - - - +

{quickActions(issue)}
)} diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx index 703174de5..e401d2850 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx @@ -6,13 +6,14 @@ import { IssueColumn } from "components/issues"; import useSubIssue from "hooks/use-sub-issue"; // types import { IIssue, IIssueDisplayProperties } from "types"; +import { EIssueActions } from "components/issues/issue-layouts/types"; type Props = { issue: IIssue; expandedIssues: string[]; setExpandedIssues: React.Dispatch>; properties: IIssueDisplayProperties; - handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; + quickActions: (issue: IIssue) => React.ReactNode; setIssuePeekOverView: React.Dispatch< React.SetStateAction<{ workspaceSlug: string; @@ -30,7 +31,7 @@ export const SpreadsheetIssuesColumn: React.FC = ({ setExpandedIssues, setIssuePeekOverView, properties, - handleIssueAction, + quickActions, disableUserActions, nestingLevel = 0, }) => { @@ -48,7 +49,7 @@ export const SpreadsheetIssuesColumn: React.FC = ({ const isExpanded = expandedIssues.indexOf(issue.id) > -1; - const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded); return ( <> @@ -57,11 +58,10 @@ export const SpreadsheetIssuesColumn: React.FC = ({ expanded={isExpanded} handleToggleExpand={handleToggleExpand} properties={properties} - handleEditIssue={() => handleIssueAction(issue, "edit")} - handleDeleteIssue={() => handleIssueAction(issue, "delete")} setIssuePeekOverView={setIssuePeekOverView} disableUserActions={disableUserActions} nestingLevel={nestingLevel} + quickActions={quickActions} /> {isExpanded && @@ -75,7 +75,7 @@ export const SpreadsheetIssuesColumn: React.FC = ({ expandedIssues={expandedIssues} setExpandedIssues={setExpandedIssues} properties={properties} - handleIssueAction={handleIssueAction} + quickActions={quickActions} setIssuePeekOverView={setIssuePeekOverView} disableUserActions={disableUserActions} nestingLevel={nestingLevel + 1} diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx index 1c3443f74..274890620 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx @@ -20,12 +20,12 @@ export const SpreadsheetLabelColumn: React.FC = (props) => { const isExpanded = expandedIssues.indexOf(issue.id) > -1; - const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded); return ( <> onChange({ labels: data })} className="h-full w-full" diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx index e0dc67a10..ebaa915b2 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx @@ -14,7 +14,7 @@ export const SpreadsheetLinkColumn: React.FC = (props) => { const isExpanded = expandedIssues.indexOf(issue.id) > -1; - const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded); return ( <> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx index 388755cd3..f69e6cfae 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx @@ -17,7 +17,7 @@ type Props = { export const SpreadsheetPriorityColumn: React.FC = ({ issue, onChange, expandedIssues, disabled }) => { const isExpanded = expandedIssues.indexOf(issue.id) > -1; - const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded); return ( <> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx index 01e38c474..aaa8e7379 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx @@ -17,7 +17,7 @@ type Props = { export const SpreadsheetStartDateColumn: React.FC = ({ issue, onChange, expandedIssues, disabled }) => { const isExpanded = expandedIssues.indexOf(issue.id) > -1; - const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded); return ( <> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx index 44b9a217f..c2824646b 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx @@ -20,12 +20,12 @@ export const SpreadsheetStateColumn: React.FC = (props) => { const isExpanded = expandedIssues.indexOf(issue.id) > -1; - const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded); return ( <> onChange({ state: data.id, state_detail: data })} className="h-full w-full" diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx index 53a3973ad..8950f6919 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx @@ -14,7 +14,7 @@ export const SpreadsheetSubIssueColumn: React.FC = (props) => { const isExpanded = expandedIssues.indexOf(issue.id) > -1; - const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded); return ( <> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx index dba684c84..3f44828c9 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx @@ -17,7 +17,7 @@ export const SpreadsheetUpdatedOnColumn: React.FC = (props) => { const isExpanded = expandedIssues.indexOf(issue.id) > -1; - const { subIssues, isLoading } = useSubIssue(issue.project_detail.id, issue.id, isExpanded); + const { subIssues, isLoading } = useSubIssue(issue.project_detail?.id, issue.id, isExpanded); return ( <> diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx index fffc89552..0edefc4e7 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/cycle-root.tsx @@ -5,14 +5,39 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { useRouter } from "next/router"; +import { EIssueActions } from "../../types"; +import { IIssue } from "types"; +import { CycleIssueQuickActions } from "../../quick-action-dropdowns"; export const CycleSpreadsheetLayout: React.FC = observer(() => { const router = useRouter(); - const { cycleId } = router.query as { cycleId: string }; + const { workspaceSlug, cycleId } = router.query as { workspaceSlug: string; cycleId: string }; const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore(); + const issueActions = { + [EIssueActions.UPDATE]: async (issue: IIssue) => { + if (!workspaceSlug || !cycleId) return; + + cycleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, cycleId); + }, + [EIssueActions.DELETE]: async (issue: IIssue) => { + if (!workspaceSlug || !cycleId) return; + cycleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, cycleId); + }, + [EIssueActions.REMOVE]: async (issue: IIssue) => { + if (!workspaceSlug || !cycleId || !issue.bridge_id) return; + cycleIssueStore.removeIssueFromCycle(workspaceSlug, issue.project, cycleId, issue.id, issue.bridge_id); + }, + }; + return ( - + ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/module-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/module-root.tsx index 4135e3112..eb118a0cc 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/module-root.tsx @@ -6,13 +6,39 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; import { useRouter } from "next/router"; +import { EIssueActions } from "../../types"; +import { IIssue } from "types"; +import { ModuleIssueQuickActions } from "../../quick-action-dropdowns"; export const ModuleSpreadsheetLayout: React.FC = observer(() => { const router = useRouter(); - const { moduleId } = router.query as { moduleId: string }; + const { workspaceSlug, moduleId } = router.query as { workspaceSlug: string; moduleId: string }; const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore(); + + const issueActions = { + [EIssueActions.UPDATE]: async (issue: IIssue) => { + if (!workspaceSlug || !moduleId) return; + + moduleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, moduleId); + }, + [EIssueActions.DELETE]: async (issue: IIssue) => { + if (!workspaceSlug || !moduleId) return; + moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId); + }, + [EIssueActions.REMOVE]: async (issue: IIssue) => { + if (!workspaceSlug || !moduleId || !issue.bridge_id) return; + moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id); + }, + }; + return ( - + ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx index a3a67f5f9..abcd56821 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/project-root.tsx @@ -4,8 +4,36 @@ import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; +import { EIssueActions } from "../../types"; +import { IIssue } from "types"; +import { useRouter } from "next/router"; +import { ProjectIssueQuickActions } from "../../quick-action-dropdowns"; export const ProjectSpreadsheetLayout: React.FC = observer(() => { + const router = useRouter(); + const { workspaceSlug } = router.query as { workspaceSlug: string }; + const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore(); - return ; + + const issueActions = { + [EIssueActions.UPDATE]: async (issue: IIssue) => { + if (!workspaceSlug) return; + + await projectIssuesStore.updateIssue(workspaceSlug, issue.project, issue.id, issue); + }, + [EIssueActions.DELETE]: async (issue: IIssue) => { + if (!workspaceSlug) return; + + await projectIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id); + }, + }; + + return ( + + ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/roots/project-view-root.tsx b/web/components/issues/issue-layouts/spreadsheet/roots/project-view-root.tsx index 92975f7ff..c3fe9f0b7 100644 --- a/web/components/issues/issue-layouts/spreadsheet/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/roots/project-view-root.tsx @@ -4,8 +4,36 @@ import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; // components import { BaseSpreadsheetRoot } from "../base-spreadsheet-root"; +import { EIssueActions } from "../../types"; +import { IIssue } from "types"; +import { useRouter } from "next/router"; +import { ProjectIssueQuickActions } from "../../quick-action-dropdowns"; export const ProjectViewSpreadsheetLayout: React.FC = observer(() => { + const router = useRouter(); + const { workspaceSlug } = router.query as { workspaceSlug: string }; + const { viewIssues: projectViewIssuesStore, viewIssuesFilter: projectViewIssueFiltersStore } = useMobxStore(); - return ; + + const issueActions = { + [EIssueActions.UPDATE]: async (issue: IIssue) => { + if (!workspaceSlug) return; + + await projectViewIssuesStore.updateIssue(workspaceSlug, issue.project, issue.id, issue); + }, + [EIssueActions.DELETE]: async (issue: IIssue) => { + if (!workspaceSlug) return; + + await projectViewIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id); + }, + }; + + return ( + + ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 6f603967a..4b2a13a3a 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -7,6 +7,7 @@ import { IssuePeekOverview } from "components/issues/issue-peek-overview"; import { Spinner } from "@plane/ui"; // types import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabel, IState, IUserLite } from "types"; +import { EIssueActions } from "../types"; type Props = { displayProperties: IIssueDisplayProperties; @@ -16,8 +17,8 @@ type Props = { members?: IUserLite[] | undefined; labels?: IIssueLabel[] | undefined; states?: IState[] | undefined; - handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; - handleUpdateIssue: (issue: IIssue, data: Partial) => void; + quickActions: (issue: IIssue) => React.ReactNode; + handleIssues: (issue: IIssue, action: EIssueActions) => void; openIssuesListModal?: (() => void) | null; quickAddCallback?: ( workspaceSlug: string, @@ -39,8 +40,8 @@ export const SpreadsheetView: React.FC = observer((props) => { members, labels, states, - handleIssueAction, - handleUpdateIssue, + quickActions, + handleIssues, quickAddCallback, viewId, disableUserActions, @@ -80,6 +81,8 @@ export const SpreadsheetView: React.FC = observer((props) => { }; }, []); + console.log("spreadsheet issues", issues); + return (
@@ -103,18 +106,20 @@ export const SpreadsheetView: React.FC = observer((props) => { Issue
- {issues.map((issue, index) => ( - - ))} + {issues.map((issue, index) => + issue ? ( + + ) : null + )}
@@ -124,7 +129,7 @@ export const SpreadsheetView: React.FC = observer((props) => { disableUserActions={disableUserActions} expandedIssues={expandedIssues} handleDisplayFilterUpdate={handleDisplayFilterUpdate} - handleUpdateIssue={handleUpdateIssue} + handleUpdateIssue={(issue, data) => handleIssues({ ...issue, ...data }, EIssueActions.UPDATE)} issues={issues} members={members} labels={labels} @@ -185,7 +190,7 @@ export const SpreadsheetView: React.FC = observer((props) => { workspaceSlug={issuePeekOverview?.workspaceSlug} projectId={issuePeekOverview?.projectId} issueId={issuePeekOverview?.issueId} - handleIssue={(issueToUpdate: any) => handleUpdateIssue(issueToUpdate as IIssue, issueToUpdate)} + handleIssue={(issueToUpdate: any) => handleIssues(issueToUpdate, EIssueActions.UPDATE)} /> )} diff --git a/web/components/issues/modal.tsx b/web/components/issues/modal.tsx index 0ad008153..6bb6b81a1 100644 --- a/web/components/issues/modal.tsx +++ b/web/components/issues/modal.tsx @@ -16,6 +16,7 @@ import { IssueForm, ConfirmIssueDiscard } from "components/issues"; import type { IIssue } from "types"; // fetch-keys import { USER_ISSUE, SUB_ISSUES } from "constants/fetch-keys"; +import { EProjectStore } from "store/command-palette.store"; export interface IssuesModalProps { data?: IIssue | null; @@ -40,6 +41,7 @@ export interface IssuesModalProps { )[]; onSubmit?: (data: Partial) => Promise; handleSubmit?: (data: Partial) => Promise; + currentStore?: EProjectStore; } const issueDraftService = new IssueDraftService(); @@ -53,6 +55,7 @@ export const CreateUpdateIssueModal: React.FC = observer((prop fieldsToShow = ["all"], onSubmit, handleSubmit, + currentStore = EProjectStore.PROJECT, } = props; // states @@ -63,20 +66,56 @@ export const CreateUpdateIssueModal: React.FC = observer((prop const [prePopulateData, setPreloadedData] = useState>({}); const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId } = router.query as { + workspaceSlug: string; + projectId: string | undefined; + cycleId: string | undefined; + moduleId: string | undefined; + }; const { project: projectStore, - issue: issueStore, - issueDetail: issueDetailStore, - cycleIssue: cycleIssueStore, - moduleIssue: moduleIssueStore, + projectIssues: projectIssueStore, + viewIssues: projectViewIssueStore, + workspaceProfileIssues: profileIssueStore, + cycleIssues: cycleIssueStore, + moduleIssues: moduleIssueStore, user: userStore, - trackEvent: { postHogEventTracker } + trackEvent: { postHogEventTracker }, } = useMobxStore(); const user = userStore.currentUser; + const issueStores = { + [EProjectStore.PROJECT]: { + store: projectIssueStore, + dataIdToUpdate: activeProject, + viewId: undefined, + }, + [EProjectStore.PROJECT_VIEW]: { + store: projectViewIssueStore, + dataIdToUpdate: activeProject, + viewId: undefined, + }, + [EProjectStore.PROFILE]: { + store: profileIssueStore, + dataIdToUpdate: user?.id || undefined, + viewId: undefined, + }, + [EProjectStore.CYCLE]: { + store: cycleIssueStore, + dataIdToUpdate: activeProject, + viewId: cycleId, + }, + [EProjectStore.MODULE]: { + store: moduleIssueStore, + dataIdToUpdate: activeProject, + viewId: moduleId, + }, + }; + + const { store: currentIssueStore, viewId, dataIdToUpdate } = issueStores[currentStore]; + const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined; const { setValue: setValueInLocalStorage, clearValue: clearLocalStorageValue } = useLocalStorage( @@ -176,60 +215,57 @@ export const CreateUpdateIssueModal: React.FC = observer((prop // in the url. This has the least priority. if (projects && projects.length > 0 && !activeProject) setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null); - }, [activeProject, data, projectId, projects, isOpen]); + }, [data, projectId, projects, isOpen]); - const addIssueToCycle = async (issueId: string, cycleId: string) => { + const addIssueToCycle = async (issue: IIssue, cycleId: string) => { if (!workspaceSlug || !activeProject) return; - cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), activeProject, cycleId, [issueId]); + cycleIssueStore.addIssueToCycle(workspaceSlug, activeProject, cycleId, issue); }; - const addIssueToModule = async (issueId: string, moduleId: string) => { + const addIssueToModule = async (issue: IIssue, moduleId: string) => { if (!workspaceSlug || !activeProject) return; - moduleIssueStore.addIssueToModule(workspaceSlug.toString(), activeProject, moduleId, [issueId]); + moduleIssueStore.addIssueToModule(workspaceSlug, activeProject, moduleId, issue); }; const createIssue = async (payload: Partial) => { - if (!workspaceSlug || !activeProject) return; + if (!workspaceSlug || !dataIdToUpdate) return; - await issueDetailStore - .createIssue(workspaceSlug.toString(), activeProject, payload) + await currentIssueStore + .createIssue(workspaceSlug, dataIdToUpdate, payload, viewId) .then(async (res) => { + if (!res) throw new Error(); + if (handleSubmit) { await handleSubmit(res); } else { - issueStore.fetchIssues(workspaceSlug.toString(), activeProject); + currentIssueStore.fetchIssues(workspaceSlug, dataIdToUpdate, "mutation"); - if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle); - if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module); + if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res, payload.cycle); + if (payload.module && payload.module !== "") await addIssueToModule(res, payload.module); setToastAlert({ type: "success", title: "Success!", message: "Issue created successfully.", }); - postHogEventTracker( - "ISSUE_CREATE", - { - ...res, - state: "SUCCESS" - } - ); + postHogEventTracker("ISSUE_CREATE", { + ...res, + state: "SUCCESS", + }); if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent)); } - }).catch(() => { + }) + .catch(() => { setToastAlert({ type: "error", title: "Error!", message: "Issue could not be created. Please try again.", }); - postHogEventTracker( - "ISSUE_CREATE", - { - state: "FAILED" - } - ); + postHogEventTracker("ISSUE_CREATE", { + state: "FAILED", + }); }); if (!createMore) onFormSubmitClose(); @@ -269,10 +305,10 @@ export const CreateUpdateIssueModal: React.FC = observer((prop }; const updateIssue = async (payload: Partial) => { - if (!workspaceSlug || !activeProject || !data) return; + if (!workspaceSlug || !dataIdToUpdate || !data) return; - await issueDetailStore - .updateIssue(workspaceSlug.toString(), activeProject, data.id, payload) + await currentIssueStore + .updateIssue(workspaceSlug, dataIdToUpdate, data.id, payload, viewId) .then((res) => { if (!createMore) onFormSubmitClose(); @@ -281,13 +317,10 @@ export const CreateUpdateIssueModal: React.FC = observer((prop title: "Success!", message: "Issue updated successfully.", }); - postHogEventTracker( - "ISSUE_UPDATE", - { - ...res, - state: "SUCCESS" - } - ); + postHogEventTracker("ISSUE_UPDATE", { + ...res, + state: "SUCCESS", + }); }) .catch(() => { setToastAlert({ @@ -295,17 +328,14 @@ export const CreateUpdateIssueModal: React.FC = observer((prop title: "Error!", message: "Issue could not be updated. Please try again.", }); - postHogEventTracker( - "ISSUE_UPDATE", - { - state: "FAILED" - } - ); + postHogEventTracker("ISSUE_UPDATE", { + state: "FAILED", + }); }); }; const handleFormSubmit = async (formData: Partial) => { - if (!workspaceSlug || !activeProject) return; + if (!workspaceSlug || !dataIdToUpdate || !currentStore) return; const payload: Partial = { ...formData, diff --git a/web/components/profile/profile-issues-filter.tsx b/web/components/profile/profile-issues-filter.tsx index 28bc2259d..0478cc94f 100644 --- a/web/components/profile/profile-issues-filter.tsx +++ b/web/components/profile/profile-issues-filter.tsx @@ -1,4 +1,6 @@ +import { useCallback } from "react"; import { observer } from "mobx-react-lite"; +import { useRouter } from "next/router"; // components import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, LayoutSelection } from "components/issues"; // hooks @@ -6,40 +8,68 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; // constants import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; +import { EFilterType } from "store/issues/types"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types"; export const ProfileIssuesFilter = observer(() => { - const { workspace: workspaceStore, profileIssueFilters: profileIssueFiltersStore }: RootStore = useMobxStore(); - - const handleLayoutChange = (_layout: string) => { - const payload = { - layout: _layout, - group_by: profileIssueFiltersStore.userDisplayFilters.group_by - ? profileIssueFiltersStore.userDisplayFilters.group_by - : "state_detail.group", - }; - - profileIssueFiltersStore.handleIssueFilters("userDisplayFilters", payload); + const router = useRouter(); + const { workspaceSlug } = router.query as { + workspaceSlug: string; }; - const handleFilters = (key: any, value: any) => { - let updatesFilters: any = profileIssueFiltersStore?.userFilters; - updatesFilters = updatesFilters[key] || []; - if (updatesFilters && updatesFilters.length > 0 && updatesFilters.includes(value)) - updatesFilters = updatesFilters.filter((item: any) => item !== value); - else updatesFilters.push(value); - profileIssueFiltersStore.handleIssueFilters("userFilters", { [key]: updatesFilters }); - }; - - const handleDisplayFilters = (value: any) => profileIssueFiltersStore.handleIssueFilters("userDisplayFilters", value); - - const handleDisplayProperties = (value: any) => - profileIssueFiltersStore.handleIssueFilters("userDisplayProperties", value); + const { + workspace: workspaceStore, + workspaceProfileIssuesFilter: { issueFilters, updateFilters }, + }: RootStore = useMobxStore(); const states = undefined; const labels = workspaceStore.workspaceLabels || undefined; const members = undefined; - const activeLayout = profileIssueFiltersStore?.userDisplayFilters?.layout; + const activeLayout = issueFilters?.displayFilters?.layout; + + const handleLayoutChange = useCallback( + (layout: TIssueLayouts) => { + if (!workspaceSlug) return; + updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, { layout: layout }); + }, + [workspaceSlug, updateFilters] + ); + + const handleFiltersUpdate = useCallback( + (key: keyof IIssueFilterOptions, value: string | string[]) => { + if (!workspaceSlug) return; + const newValues = issueFilters?.filters?.[key] ?? []; + + if (Array.isArray(value)) { + value.forEach((val) => { + if (!newValues.includes(val)) newValues.push(val); + }); + } else { + if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + } + + updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues }); + }, + [workspaceSlug, issueFilters, updateFilters] + ); + + const handleDisplayFilters = useCallback( + (updatedDisplayFilter: Partial) => { + if (!workspaceSlug) return; + updateFilters(workspaceSlug, EFilterType.DISPLAY_FILTERS, updatedDisplayFilter); + }, + [workspaceSlug, updateFilters] + ); + + const handleDisplayProperties = useCallback( + (property: Partial) => { + if (!workspaceSlug) return; + updateFilters(workspaceSlug, EFilterType.DISPLAY_PROPERTIES, property); + }, + [workspaceSlug, updateFilters] + ); return (
@@ -51,11 +81,11 @@ export const ProfileIssuesFilter = observer(() => { {
diff --git a/web/components/profile/profile-issues.tsx b/web/components/profile/profile-issues.tsx new file mode 100644 index 000000000..e63f5714d --- /dev/null +++ b/web/components/profile/profile-issues.tsx @@ -0,0 +1,63 @@ +import React, { ReactElement } from "react"; +import { useRouter } from "next/router"; +import useSWR from "swr"; +import { observer } from "mobx-react-lite"; +// layouts +import { AppLayout } from "layouts/app-layout"; +import { ProfileAuthWrapper } from "layouts/user-profile-layout"; +// components +import { UserProfileHeader } from "components/headers"; +import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root"; +import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root"; +import { ProfileIssuesAppliedFiltersRoot } from "components/issues"; +import { Spinner } from "@plane/ui"; +// hooks +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +interface IProfileIssuesPage { + type: "assigned" | "subscribed" | "created"; +} + +export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => { + const router = useRouter(); + const { workspaceSlug, userId } = router.query as { + workspaceSlug: string; + userId: string; + }; + + const { + workspaceProfileIssues: { loader, getIssues, fetchIssues }, + workspaceProfileIssuesFilter: { issueFilters, fetchFilters }, + }: RootStore = useMobxStore(); + + useSWR(workspaceSlug && userId ? `CURRENT_WORKSPACE_PROFILE_ISSUES_${workspaceSlug}_${userId}` : null, async () => { + if (workspaceSlug && userId) { + await fetchFilters(workspaceSlug); + await fetchIssues(workspaceSlug, userId, getIssues ? "mutation" : "init-loader", props.type); + } + }); + + const activeLayout = issueFilters?.displayFilters?.layout || undefined; + + return ( + <> + {loader === "init-loader" ? ( +
+ +
+ ) : ( + <> + +
+ {activeLayout === "list" ? ( + + ) : activeLayout === "kanban" ? ( + + ) : null} +
+ + )} + + ); +}); diff --git a/web/components/workspace/sidebar-quick-action.tsx b/web/components/workspace/sidebar-quick-action.tsx index 1f16ad4c5..37bce9c3d 100644 --- a/web/components/workspace/sidebar-quick-action.tsx +++ b/web/components/workspace/sidebar-quick-action.tsx @@ -9,12 +9,17 @@ import { CreateUpdateDraftIssueModal } from "components/issues"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; import { observer } from "mobx-react-lite"; +import { EProjectStore } from "store/command-palette.store"; export const WorkspaceSidebarQuickAction = observer(() => { // states const [isDraftIssueModalOpen, setIsDraftIssueModalOpen] = useState(false); - const { theme: themeStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore(); + const { + theme: themeStore, + commandPalette: commandPaletteStore, + trackEvent: { setTrackElement }, + } = useMobxStore(); const { storedValue, clearValue } = useLocalStorage("draftedIssue", JSON.stringify({})); @@ -34,24 +39,26 @@ export const WorkspaceSidebarQuickAction = observer(() => { />
+
); }; diff --git a/web/services/issue/issue_archive.service.ts b/web/services/issue/issue_archive.service.ts index 7adc045ec..a21a98fbc 100644 --- a/web/services/issue/issue_archive.service.ts +++ b/web/services/issue/issue_archive.service.ts @@ -18,8 +18,8 @@ export class IssueArchiveService extends APIService { } async getV3ArchivedIssues(workspaceSlug: string, projectId: string, queries?: any): Promise { - return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/archived-issues/`, { - params: queries, + return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, { + params: { ...queries, archived: true }, }) .then((response) => response?.data) .catch((error) => { diff --git a/web/services/issue/issue_draft.service.tsx b/web/services/issue/issue_draft.service.tsx index 8d8ddecb3..5634e3bfe 100644 --- a/web/services/issue/issue_draft.service.tsx +++ b/web/services/issue/issue_draft.service.tsx @@ -17,13 +17,13 @@ export class IssueDraftService extends APIService { }); } - async getV3DraftIssues(workspaceSlug: string, projectId: string, params?: any): Promise { - return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/`, { - params, + async getV3DraftIssues(workspaceSlug: string, projectId: string, query?: any): Promise { + return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/issues/?draft=true`, { + params: { ...query }, }) .then((response) => response?.data) .catch((error) => { - throw error?.response; + throw error?.response?.data; }); } diff --git a/web/services/user.service.ts b/web/services/user.service.ts index 7d7175153..c3dbdfd9c 100644 --- a/web/services/user.service.ts +++ b/web/services/user.service.ts @@ -13,6 +13,7 @@ import type { } from "types"; // helpers import { API_BASE_URL } from "helpers/common.helper"; +import { IIssueResponse } from "store/issues/types"; export class UserService extends APIService { constructor() { @@ -193,6 +194,16 @@ export class UserService extends APIService { }); } + async getV3UserProfileIssues(workspaceSlug: string, userId: string, params: any): Promise { + return this.get(`/api/v3/workspaces/${workspaceSlug}/user-issues/${userId}/`, { + params, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + async deactivateAccount() { return this.delete(`/api/users/me/`) .then((response) => response?.data) diff --git a/web/store/command-palette.store.ts b/web/store/command-palette.store.ts index 7bed73bfe..1c9fc785d 100644 --- a/web/store/command-palette.store.ts +++ b/web/store/command-palette.store.ts @@ -5,6 +5,19 @@ import { RootStore } from "./root"; import { ProjectService } from "services/project"; import { PageService } from "services/page.service"; +export enum EProjectStore { + PROJECT = "ProjectStore", + PROJECT_VIEW = "ProjectViewStore", + PROFILE = "ProfileStore", + MODULE = "ModuleStore", + CYCLE = "CycleStore", +} + +export interface ModalData { + store: EProjectStore; + viewId: string; +} + export interface ICommandPaletteStore { isCommandPaletteOpen: boolean; isShortcutModalOpen: boolean; @@ -23,10 +36,12 @@ export interface ICommandPaletteStore { toggleCreateCycleModal: (value?: boolean) => void; toggleCreateViewModal: (value?: boolean) => void; toggleCreatePageModal: (value?: boolean) => void; - toggleCreateIssueModal: (value?: boolean) => void; + toggleCreateIssueModal: (value?: boolean, storeType?: EProjectStore) => void; toggleCreateModuleModal: (value?: boolean) => void; toggleDeleteIssueModal: (value?: boolean) => void; toggleBulkDeleteIssueModal: (value?: boolean) => void; + + createIssueStoreType: EProjectStore; } class CommandPaletteStore implements ICommandPaletteStore { @@ -46,6 +61,8 @@ class CommandPaletteStore implements ICommandPaletteStore { projectService; pageService; + createIssueStoreType: EProjectStore = EProjectStore.PROJECT; + constructor(_rootStore: RootStore) { makeObservable(this, { // observable @@ -127,11 +144,13 @@ class CommandPaletteStore implements ICommandPaletteStore { } }; - toggleCreateIssueModal = (value?: boolean) => { + toggleCreateIssueModal = (value?: boolean, storeType?: EProjectStore) => { if (value) { this.isCreateIssueModalOpen = value; + this.createIssueStoreType = storeType || EProjectStore.PROJECT; } else { this.isCreateIssueModalOpen = !this.isCreateIssueModalOpen; + this.createIssueStoreType = EProjectStore.PROJECT; } }; diff --git a/web/store/issues/global/filter.store.ts b/web/store/issues/global/filter.store.ts new file mode 100644 index 000000000..a2304f36d --- /dev/null +++ b/web/store/issues/global/filter.store.ts @@ -0,0 +1,327 @@ +import { action, makeObservable, observable, runInAction } from "mobx"; +// types +import { RootStore } from "store/root"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; +import { EFilterType } from "store/issues/types"; +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store"; + +interface IProjectIssuesFiltersOptions { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; +} + +interface IProjectIssuesDisplayOptions { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; + displayProperties: IIssueDisplayProperties; +} + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface IGlobalIssuesFilterStore { + // observables + projectIssueFilters: { [workspaceId: string]: IProjectIssuesDisplayOptions } | undefined; + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // helpers + issueDisplayFilters: (workspaceId: string) => IProjectIssuesDisplayOptions | undefined; + // actions + fetchDisplayFilters: (workspaceSlug: string) => Promise; + updateDisplayFilters: ( + workspaceSlug: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions + ) => Promise; + fetchDisplayProperties: (workspaceSlug: string) => Promise; + updateDisplayProperties: ( + workspaceSlug: string, + properties: IIssueDisplayProperties + ) => Promise; + fetchFilters: (workspaceSlug: string) => Promise; + updateFilters: ( + workspaceSlug: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; +} + +export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGlobalIssuesFilterStore { + // observables + projectIssueFilters: { [projectId: string]: IProjectIssuesDisplayOptions } | undefined = undefined; + // root store + rootStore; + + constructor(_rootStore: RootStore) { + super(_rootStore); + + makeObservable(this, { + // observables + projectIssueFilters: observable.ref, + // computed + // actions + fetchDisplayFilters: action, + updateDisplayFilters: action, + fetchDisplayProperties: action, + updateDisplayProperties: action, + }); + // root store + this.rootStore = _rootStore; + } + + // computed + + // helpers + issueDisplayFilters = (workspaceId: string) => { + if (!workspaceId) return undefined; + return this.projectIssueFilters?.[workspaceId] || undefined; + }; + + // actions + fetchDisplayFilters = async (workspaceSlug: string) => { + try { + const filters: IIssueFilterOptions = { + assignees: null, + mentions: null, + created_by: null, + labels: null, + priority: null, + project: null, + start_date: null, + state: null, + state_group: null, + subscriber: null, + target_date: null, + }; + + const displayFilters: IIssueDisplayFilterOptions = { + calendar: { + show_weekends: false, + layout: "month", + }, + group_by: "state_detail.group", + sub_group_by: null, + layout: "list", + order_by: "-created_at", + show_empty_groups: false, + start_target_date: false, + sub_issue: false, + type: null, + }; + + const issueFilters: IProjectIssuesFiltersOptions = { + filters: filters, + displayFilters: displayFilters, + }; + + let _projectIssueFilters = this.projectIssueFilters; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) + _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + _projectIssueFilters[workspaceSlug] = { + ..._projectIssueFilters[workspaceSlug], + ...issueFilters, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return issueFilters; + } catch (error) { + throw error; + } + }; + + updateDisplayFilters = async ( + workspaceSlug: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions + ) => { + try { + let _projectIssueFilters = { ...this.projectIssueFilters }; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) + _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const _filters = { + filters: { ..._projectIssueFilters[workspaceSlug].filters }, + displayFilters: { ..._projectIssueFilters[workspaceSlug].displayFilters }, + }; + + if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; + else if (type === EFilterType.DISPLAY_FILTERS) + _filters.displayFilters = { ..._filters.displayFilters, ...filters }; + + // set sub_group_by to null if group_by is set to null + if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + + // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same + if ( + _filters.displayFilters.layout === "kanban" && + _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by + ) + _filters.displayFilters.sub_group_by = null; + + // set group_by to state if layout is switched to kanban and group_by is null + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + _filters.displayFilters.group_by = "state"; + + _projectIssueFilters[workspaceSlug] = { + filters: _filters.filters, + displayFilters: _filters.displayFilters, + displayProperties: _projectIssueFilters[workspaceSlug].displayProperties, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return _filters; + } catch (error) { + this.fetchDisplayFilters(workspaceSlug); + throw error; + } + }; + + fetchDisplayProperties = async (workspaceSlug: string) => { + try { + const displayProperties: IIssueDisplayProperties = { + assignee: false, + start_date: false, + due_date: false, + labels: false, + key: false, + priority: false, + state: false, + sub_issue_count: false, + link: false, + attachment_count: false, + estimate: false, + created_on: false, + updated_on: false, + }; + + let _projectIssueFilters = { ...this.projectIssueFilters }; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) + _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + _projectIssueFilters[workspaceSlug] = { + ..._projectIssueFilters[workspaceSlug], + displayProperties: displayProperties, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return displayProperties; + } catch (error) { + throw error; + } + }; + + updateDisplayProperties = async (workspaceSlug: string, properties: IIssueDisplayProperties) => { + try { + let _issueFilters = { ...this.projectIssueFilters }; + if (!_issueFilters) _issueFilters = {}; + if (!_issueFilters[workspaceSlug]) + _issueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const updatedDisplayProperties = { ..._issueFilters[workspaceSlug].displayProperties, ...properties }; + _issueFilters[workspaceSlug] = { ..._issueFilters[workspaceSlug], displayProperties: updatedDisplayProperties }; + + runInAction(() => { + this.projectIssueFilters = _issueFilters; + }); + + return properties; + } catch (error) { + this.fetchDisplayProperties(workspaceSlug); + throw error; + } + }; + + get issueFilters() { + const workspaceSlug = this.rootStore.workspace.workspaceSlug; + if (!workspaceSlug) return undefined; + const displayFilters = this.issueDisplayFilters(workspaceSlug); + + const _filters: IProjectIssuesFilters = { + filters: displayFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: userFilters?.displayFilters?.sub_issue || true, + show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, + start_target_date: userFilters?.displayFilters?.start_target_date || true, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "profile_issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchFilters = async (workspaceSlug: string) => { + try { + await this.fetchDisplayFilters(workspaceSlug); + await this.fetchDisplayProperties(workspaceSlug); + return; + } catch (error) { + throw Error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => { + try { + switch (filterType) { + case EFilterType.FILTERS: + await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueFilterOptions); + break; + case EFilterType.DISPLAY_FILTERS: + await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.updateDisplayProperties(workspaceSlug, filters as IIssueDisplayProperties); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issues/global/issue.store.ts b/web/store/issues/global/issue.store.ts new file mode 100644 index 000000000..c5a193dc6 --- /dev/null +++ b/web/store/issues/global/issue.store.ts @@ -0,0 +1,327 @@ +import { action, observable, makeObservable, computed, runInAction, autorun } from "mobx"; +// base class +import { IssueBaseStore } from "store/issues"; +// services +import { UserService } from "services/user.service"; +// types +import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../types"; +import { RootStore } from "store/root"; +import { IIssue } from "types"; + +interface IProfileIssueTabTypes { + assigned: IIssueResponse; + created: IIssueResponse; + subscribed: IIssueResponse; +} + +export interface IGlobalIssuesStore { + // observable + loader: TLoader; + issues: { [user_id: string]: IProfileIssueTabTypes } | undefined; + currentUserId: string | null; + currentUserIssueTab: "assigned" | "created" | "subscribed" | null; + // computed + getIssues: IIssueResponse | undefined; + getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined; + // actions + fetchIssues: ( + workspaceSlug: string, + userId: string, + loadType: TLoader, + type: "assigned" | "created" | "subscribed" + ) => Promise; + createIssue: (workspaceSlug: string, userId: string, data: Partial) => Promise; + updateIssue: ( + workspaceSlug: string, + userId: string, + issueId: string, + data: Partial + ) => Promise; + removeIssue: ( + workspaceSlug: string, + userId: string, + projectId: string, + issueId: string + ) => Promise; + quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise; + viewFlags: ViewFlags; +} + +export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesStore { + loader: TLoader = "init-loader"; + issues: { [user_id: string]: IProfileIssueTabTypes } | undefined = undefined; + currentUserId: string | null = null; + currentUserIssueTab: "assigned" | "created" | "subscribed" | null = null; + // root store + rootStore; + // service + userService; + + constructor(_rootStore: RootStore) { + super(_rootStore); + + makeObservable(this, { + // observable + loader: observable.ref, + issues: observable.ref, + currentUserId: observable.ref, + currentUserIssueTab: observable.ref, + // computed + getIssues: computed, + getIssuesIds: computed, + viewFlags: computed, + // action + fetchIssues: action, + createIssue: action, + updateIssue: action, + removeIssue: action, + quickAddIssue: action, + }); + + this.rootStore = _rootStore; + this.userService = new UserService(); + + autorun(() => { + const workspaceSlug = this.rootStore.workspace.workspaceSlug; + if (!workspaceSlug || !this.currentUserId || !this.currentUserIssueTab) return; + + const userFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.filters; + if (userFilters) this.fetchIssues(workspaceSlug, this.currentUserId, "mutation", this.currentUserIssueTab); + }); + } + + get getIssues() { + if (!this.currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[this.currentUserId]) + return undefined; + + return this.issues[this.currentUserId][this.currentUserIssueTab]; + } + + get getIssuesIds() { + const currentUserId = this.currentUserId; + const displayFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.displayFilters; + if (!displayFilters) return undefined; + + const groupBy = displayFilters?.group_by; + const orderBy = displayFilters?.order_by; + const layout = displayFilters?.layout; + + if (!currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[currentUserId]) return undefined; + + let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined; + + if (layout === "list" && orderBy) { + if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[currentUserId][this.currentUserIssueTab]); + else issues = this.unGroupedIssues(orderBy, this.issues[currentUserId][this.currentUserIssueTab]); + } + + return issues; + } + + get viewFlags() { + if (this.currentUserIssueTab === "subscribed") { + return { + enableQuickAdd: false, + enableIssueCreation: false, + enableInlineEditing: false, + }; + } + + return { + enableQuickAdd: false, + enableIssueCreation: true, + enableInlineEditing: true, + }; + } + + fetchIssues = async ( + workspaceSlug: string, + userId: string, + loadType: TLoader = "init-loader", + type: "assigned" | "created" | "subscribed" + ) => { + try { + this.loader = loadType; + this.currentUserId = userId; + if (type) this.currentUserIssueTab = type; + + let params: any = this.rootStore?.workspaceProfileIssuesFilter?.appliedFilters; + params = { + ...params, + assignees: undefined, + created_by: undefined, + subscriber: undefined, + }; + if (this.currentUserIssueTab === "assigned") + params = params ? { ...params, assignees: userId } : { assignees: userId }; + else if (this.currentUserIssueTab === "created") + params = params ? { ...params, created_by: userId } : { created_by: userId }; + else if (this.currentUserIssueTab === "subscribed") + params = params ? { ...params, subscriber: userId } : { subscriber: userId }; + + const response = await this.userService.getV3UserProfileIssues(workspaceSlug, userId, params); + + if (!this.currentUserIssueTab) return; + + const _issues: any = { + ...this.issues, + [userId]: { + ...this.issues?.[userId], + ...{ [this.currentUserIssueTab]: response }, + }, + }; + + runInAction(() => { + this.issues = _issues; + this.loader = undefined; + }); + + return _issues; + } catch (error) { + this.loader = undefined; + throw error; + } + }; + + createIssue = async (workspaceSlug: string, userId: string, data: Partial) => { + try { + const projectId = data.project; + const moduleId = data.module_id; + const cycleId = data.cycle_id; + + if (!projectId) return; + + let response = {} as IIssue; + response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); + + // if (moduleId) + // response = await this.rootStore.moduleIssues.addIssueToModule(workspaceSlug, projectId, moduleId, response); + + // if (cycleId) + // response = await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, cycleId, response); + + let _issues = this.issues; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + _issues[userId] = { ..._issues[userId], ...{ [response.id]: response } }; + + runInAction(() => { + this.issues = _issues; + }); + + return response; + } catch (error) { + if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + throw error; + } + }; + + updateIssue = async (workspaceSlug: string, userId: string, issueId: string, data: Partial) => { + try { + const projectId = data.project; + const moduleId = data.module_id; + const cycleId = data.cycle_id; + + if (!projectId || !this.currentUserIssueTab) return; + + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + _issues[projectId][this.currentUserIssueTab][userId] = { + ..._issues[projectId][this.currentUserIssueTab][userId], + ...data, + }; + + runInAction(() => { + this.issues = _issues; + }); + + let response = data as IIssue | undefined; + response = await this.rootStore.projectIssues.updateIssue( + workspaceSlug, + projectId, + data.id as keyof IIssue, + data + ); + + if (moduleId) + response = await this.rootStore.moduleIssues.updateIssue( + workspaceSlug, + projectId, + response.id as keyof IIssue, + response, + moduleId + ); + + if (cycleId) + response = await this.rootStore.cycleIssues.updateIssue( + workspaceSlug, + projectId, + data.id as keyof IIssue, + data, + cycleId + ); + + return response; + } catch (error) { + if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + throw error; + } + }; + + removeIssue = async (workspaceSlug: string, userId: string, projectId: string, issueId: string) => { + try { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + + if (this.currentUserIssueTab) delete _issues?.[userId]?.[this.currentUserIssueTab]?.[issueId]; + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.rootStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + + return response; + } catch (error) { + if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + throw error; + } + }; + + quickAddIssue = async (workspaceSlug: string, userId: string, data: IIssue) => { + try { + const projectId = data.project; + + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + _issues[userId] = { ..._issues[userId], ...{ [data.id as keyof IIssue]: data } }; + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); + + if (this.issues && this.currentUserIssueTab) { + delete this.issues[userId][this.currentUserIssueTab][data.id as keyof IIssue]; + + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + _issues[userId] = { ..._issues[userId], ...{ [response.id as keyof IIssue]: response } }; + + runInAction(() => { + this.issues = _issues; + }); + } + + return response; + } catch (error) { + if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + throw error; + } + }; +} diff --git a/web/store/issues/index.ts b/web/store/issues/index.ts index d1d5ff81e..eeb192030 100644 --- a/web/store/issues/index.ts +++ b/web/store/issues/index.ts @@ -34,7 +34,11 @@ export * from "./project-issues/draft/filter.store"; /** project issues and issue-filters ends */ /** profile issues and issue-filters starts */ +export * from "./profile/issue.store"; +export * from "./profile/filter.store"; /** profile issues and issue-filters ends */ /** global issues and issue-filters starts */ +export * from "./global/issue.store"; +export * from "./global/filter.store"; /** global issues and issue-filters ends */ diff --git a/web/store/issues/profile/filter.store.ts b/web/store/issues/profile/filter.store.ts new file mode 100644 index 000000000..3fb82d9e5 --- /dev/null +++ b/web/store/issues/profile/filter.store.ts @@ -0,0 +1,327 @@ +import { action, makeObservable, observable, runInAction } from "mobx"; +// types +import { RootStore } from "store/root"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; +import { EFilterType } from "store/issues/types"; +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store"; + +interface IProjectIssuesFiltersOptions { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; +} + +interface IProjectIssuesDisplayOptions { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; + displayProperties: IIssueDisplayProperties; +} + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface IProfileIssuesFilterStore { + // observables + projectIssueFilters: { [workspaceId: string]: IProjectIssuesDisplayOptions } | undefined; + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // helpers + issueDisplayFilters: (workspaceId: string) => IProjectIssuesDisplayOptions | undefined; + // actions + fetchDisplayFilters: (workspaceSlug: string) => Promise; + updateDisplayFilters: ( + workspaceSlug: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions + ) => Promise; + fetchDisplayProperties: (workspaceSlug: string) => Promise; + updateDisplayProperties: ( + workspaceSlug: string, + properties: IIssueDisplayProperties + ) => Promise; + fetchFilters: (workspaceSlug: string) => Promise; + updateFilters: ( + workspaceSlug: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; +} + +export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IProfileIssuesFilterStore { + // observables + projectIssueFilters: { [projectId: string]: IProjectIssuesDisplayOptions } | undefined = undefined; + // root store + rootStore; + + constructor(_rootStore: RootStore) { + super(_rootStore); + + makeObservable(this, { + // observables + projectIssueFilters: observable.ref, + // computed + // actions + fetchDisplayFilters: action, + updateDisplayFilters: action, + fetchDisplayProperties: action, + updateDisplayProperties: action, + }); + // root store + this.rootStore = _rootStore; + } + + // computed + + // helpers + issueDisplayFilters = (workspaceId: string) => { + if (!workspaceId) return undefined; + return this.projectIssueFilters?.[workspaceId] || undefined; + }; + + // actions + fetchDisplayFilters = async (workspaceSlug: string) => { + try { + const filters: IIssueFilterOptions = { + assignees: null, + mentions: null, + created_by: null, + labels: null, + priority: null, + project: null, + start_date: null, + state: null, + state_group: null, + subscriber: null, + target_date: null, + }; + + const displayFilters: IIssueDisplayFilterOptions = { + calendar: { + show_weekends: false, + layout: "month", + }, + group_by: "state_detail.group", + sub_group_by: null, + layout: "list", + order_by: "-created_at", + show_empty_groups: false, + start_target_date: false, + sub_issue: false, + type: null, + }; + + const issueFilters: IProjectIssuesFiltersOptions = { + filters: filters, + displayFilters: displayFilters, + }; + + let _projectIssueFilters = this.projectIssueFilters; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) + _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + _projectIssueFilters[workspaceSlug] = { + ..._projectIssueFilters[workspaceSlug], + ...issueFilters, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return issueFilters; + } catch (error) { + throw error; + } + }; + + updateDisplayFilters = async ( + workspaceSlug: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions + ) => { + try { + let _projectIssueFilters = { ...this.projectIssueFilters }; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) + _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const _filters = { + filters: { ..._projectIssueFilters[workspaceSlug].filters }, + displayFilters: { ..._projectIssueFilters[workspaceSlug].displayFilters }, + }; + + if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; + else if (type === EFilterType.DISPLAY_FILTERS) + _filters.displayFilters = { ..._filters.displayFilters, ...filters }; + + // set sub_group_by to null if group_by is set to null + if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + + // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same + if ( + _filters.displayFilters.layout === "kanban" && + _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by + ) + _filters.displayFilters.sub_group_by = null; + + // set group_by to state if layout is switched to kanban and group_by is null + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + _filters.displayFilters.group_by = "state"; + + _projectIssueFilters[workspaceSlug] = { + filters: _filters.filters, + displayFilters: _filters.displayFilters, + displayProperties: _projectIssueFilters[workspaceSlug].displayProperties, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return _filters; + } catch (error) { + this.fetchDisplayFilters(workspaceSlug); + throw error; + } + }; + + fetchDisplayProperties = async (workspaceSlug: string) => { + try { + const displayProperties: IIssueDisplayProperties = { + assignee: false, + start_date: false, + due_date: false, + labels: false, + key: false, + priority: false, + state: false, + sub_issue_count: false, + link: false, + attachment_count: false, + estimate: false, + created_on: false, + updated_on: false, + }; + + let _projectIssueFilters = { ...this.projectIssueFilters }; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) + _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + _projectIssueFilters[workspaceSlug] = { + ..._projectIssueFilters[workspaceSlug], + displayProperties: displayProperties, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return displayProperties; + } catch (error) { + throw error; + } + }; + + updateDisplayProperties = async (workspaceSlug: string, properties: IIssueDisplayProperties) => { + try { + let _issueFilters = { ...this.projectIssueFilters }; + if (!_issueFilters) _issueFilters = {}; + if (!_issueFilters[workspaceSlug]) + _issueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const updatedDisplayProperties = { ..._issueFilters[workspaceSlug].displayProperties, ...properties }; + _issueFilters[workspaceSlug] = { ..._issueFilters[workspaceSlug], displayProperties: updatedDisplayProperties }; + + runInAction(() => { + this.projectIssueFilters = _issueFilters; + }); + + return properties; + } catch (error) { + this.fetchDisplayProperties(workspaceSlug); + throw error; + } + }; + + get issueFilters() { + const workspaceSlug = this.rootStore.workspace.workspaceSlug; + if (!workspaceSlug) return undefined; + const displayFilters = this.issueDisplayFilters(workspaceSlug); + + const _filters: IProjectIssuesFilters = { + filters: displayFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: userFilters?.displayFilters?.sub_issue || true, + show_empty_groups: userFilters?.displayFilters?.show_empty_groups || true, + start_target_date: userFilters?.displayFilters?.start_target_date || true, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "profile_issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchFilters = async (workspaceSlug: string) => { + try { + await this.fetchDisplayFilters(workspaceSlug); + await this.fetchDisplayProperties(workspaceSlug); + return; + } catch (error) { + throw Error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => { + try { + switch (filterType) { + case EFilterType.FILTERS: + await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueFilterOptions); + break; + case EFilterType.DISPLAY_FILTERS: + await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.updateDisplayProperties(workspaceSlug, filters as IIssueDisplayProperties); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issues/profile/issue.store.ts b/web/store/issues/profile/issue.store.ts new file mode 100644 index 000000000..71a2938cd --- /dev/null +++ b/web/store/issues/profile/issue.store.ts @@ -0,0 +1,327 @@ +import { action, observable, makeObservable, computed, runInAction, autorun } from "mobx"; +// base class +import { IssueBaseStore } from "store/issues"; +// services +import { UserService } from "services/user.service"; +// types +import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../types"; +import { RootStore } from "store/root"; +import { IIssue } from "types"; + +interface IProfileIssueTabTypes { + assigned: IIssueResponse; + created: IIssueResponse; + subscribed: IIssueResponse; +} + +export interface IProfileIssuesStore { + // observable + loader: TLoader; + issues: { [user_id: string]: IProfileIssueTabTypes } | undefined; + currentUserId: string | null; + currentUserIssueTab: "assigned" | "created" | "subscribed" | null; + // computed + getIssues: IIssueResponse | undefined; + getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined; + // actions + fetchIssues: ( + workspaceSlug: string, + userId: string, + loadType: TLoader, + type?: "assigned" | "created" | "subscribed" + ) => Promise; + createIssue: (workspaceSlug: string, userId: string, data: Partial) => Promise; + updateIssue: ( + workspaceSlug: string, + userId: string, + issueId: string, + data: Partial + ) => Promise; + removeIssue: ( + workspaceSlug: string, + userId: string, + projectId: string, + issueId: string + ) => Promise; + quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise; + viewFlags: ViewFlags; +} + +export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssuesStore { + loader: TLoader = "init-loader"; + issues: { [user_id: string]: IProfileIssueTabTypes } | undefined = undefined; + currentUserId: string | null = null; + currentUserIssueTab: "assigned" | "created" | "subscribed" | null = null; + // root store + rootStore; + // service + userService; + + constructor(_rootStore: RootStore) { + super(_rootStore); + + makeObservable(this, { + // observable + loader: observable.ref, + issues: observable.ref, + currentUserId: observable.ref, + currentUserIssueTab: observable.ref, + // computed + getIssues: computed, + getIssuesIds: computed, + viewFlags: computed, + // action + fetchIssues: action, + createIssue: action, + updateIssue: action, + removeIssue: action, + quickAddIssue: action, + }); + + this.rootStore = _rootStore; + this.userService = new UserService(); + + autorun(() => { + const workspaceSlug = this.rootStore.workspace.workspaceSlug; + if (!workspaceSlug || !this.currentUserId || !this.currentUserIssueTab) return; + + const userFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.filters; + if (userFilters) this.fetchIssues(workspaceSlug, this.currentUserId, "mutation", this.currentUserIssueTab); + }); + } + + get getIssues() { + if (!this.currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[this.currentUserId]) + return undefined; + + return this.issues[this.currentUserId][this.currentUserIssueTab]; + } + + get getIssuesIds() { + const currentUserId = this.currentUserId; + const displayFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.displayFilters; + if (!displayFilters) return undefined; + + const groupBy = displayFilters?.group_by; + const orderBy = displayFilters?.order_by; + const layout = displayFilters?.layout; + + if (!currentUserId || !this.currentUserIssueTab || !this.issues || !this.issues[currentUserId]) return undefined; + + let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined; + + if (layout === "list" && orderBy) { + if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[currentUserId][this.currentUserIssueTab]); + else issues = this.unGroupedIssues(orderBy, this.issues[currentUserId][this.currentUserIssueTab]); + } + + return issues; + } + + get viewFlags() { + if (this.currentUserIssueTab === "subscribed") { + return { + enableQuickAdd: false, + enableIssueCreation: false, + enableInlineEditing: false, + }; + } + + return { + enableQuickAdd: false, + enableIssueCreation: true, + enableInlineEditing: true, + }; + } + + fetchIssues = async ( + workspaceSlug: string, + userId: string, + loadType: TLoader = "init-loader", + type?: "assigned" | "created" | "subscribed" + ) => { + try { + this.loader = loadType; + this.currentUserId = userId; + if (type) this.currentUserIssueTab = type; + + let params: any = this.rootStore?.workspaceProfileIssuesFilter?.appliedFilters; + params = { + ...params, + assignees: undefined, + created_by: undefined, + subscriber: undefined, + }; + if (this.currentUserIssueTab === "assigned") + params = params ? { ...params, assignees: userId } : { assignees: userId }; + else if (this.currentUserIssueTab === "created") + params = params ? { ...params, created_by: userId } : { created_by: userId }; + else if (this.currentUserIssueTab === "subscribed") + params = params ? { ...params, subscriber: userId } : { subscriber: userId }; + + const response = await this.userService.getV3UserProfileIssues(workspaceSlug, userId, params); + + if (!this.currentUserIssueTab) return; + + const _issues: any = { + ...this.issues, + [userId]: { + ...this.issues?.[userId], + ...{ [this.currentUserIssueTab]: response }, + }, + }; + + runInAction(() => { + this.issues = _issues; + this.loader = undefined; + }); + + return _issues; + } catch (error) { + this.loader = undefined; + throw error; + } + }; + + createIssue = async (workspaceSlug: string, userId: string, data: Partial) => { + try { + const projectId = data.project; + const moduleId = data.module_id; + const cycleId = data.cycle_id; + + if (!projectId) return; + + let response = {} as IIssue; + response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); + + // if (moduleId) + // response = await this.rootStore.moduleIssues.addIssueToModule(workspaceSlug, projectId, moduleId, response); + + // if (cycleId) + // response = await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, cycleId, response); + + let _issues = this.issues; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + _issues[userId] = { ..._issues[userId], ...{ [response.id]: response } }; + + runInAction(() => { + this.issues = _issues; + }); + + return response; + } catch (error) { + if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + throw error; + } + }; + + updateIssue = async (workspaceSlug: string, userId: string, issueId: string, data: Partial) => { + try { + const projectId = data.project; + const moduleId = data.module_id; + const cycleId = data.cycle_id; + + if (!projectId || !this.currentUserIssueTab) return; + + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + _issues[projectId][this.currentUserIssueTab][userId] = { + ..._issues[projectId][this.currentUserIssueTab][userId], + ...data, + }; + + runInAction(() => { + this.issues = _issues; + }); + + let response = data as IIssue | undefined; + response = await this.rootStore.projectIssues.updateIssue( + workspaceSlug, + projectId, + data.id as keyof IIssue, + data + ); + + if (moduleId) + response = await this.rootStore.moduleIssues.updateIssue( + workspaceSlug, + projectId, + response.id as keyof IIssue, + response, + moduleId + ); + + if (cycleId) + response = await this.rootStore.cycleIssues.updateIssue( + workspaceSlug, + projectId, + data.id as keyof IIssue, + data, + cycleId + ); + + return response; + } catch (error) { + if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + throw error; + } + }; + + removeIssue = async (workspaceSlug: string, userId: string, projectId: string, issueId: string) => { + try { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + + if (this.currentUserIssueTab) delete _issues?.[userId]?.[this.currentUserIssueTab]?.[issueId]; + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.rootStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + + return response; + } catch (error) { + if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + throw error; + } + }; + + quickAddIssue = async (workspaceSlug: string, userId: string, data: IIssue) => { + try { + const projectId = data.project; + + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + _issues[userId] = { ..._issues[userId], ...{ [data.id as keyof IIssue]: data } }; + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); + + if (this.issues && this.currentUserIssueTab) { + delete this.issues[userId][this.currentUserIssueTab][data.id as keyof IIssue]; + + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; + _issues[userId] = { ..._issues[userId], ...{ [response.id as keyof IIssue]: response } }; + + runInAction(() => { + this.issues = _issues; + }); + } + + return response; + } catch (error) { + if (this.currentUserIssueTab) this.fetchIssues(workspaceSlug, userId, "mutation", this.currentUserIssueTab); + throw error; + } + }; +} diff --git a/web/store/issues/project-issues/archived/issue.store.ts b/web/store/issues/project-issues/archived/issue.store.ts index 837a1e74d..a2fa7bd37 100644 --- a/web/store/issues/project-issues/archived/issue.store.ts +++ b/web/store/issues/project-issues/archived/issue.store.ts @@ -4,7 +4,7 @@ import { IssueBaseStore } from "store/issues"; // services import { IssueArchiveService } from "services/issue"; // types -import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "../../types"; +import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types"; import { RootStore } from "store/root"; export interface IProjectArchivedIssuesStore { @@ -18,6 +18,9 @@ export interface IProjectArchivedIssuesStore { fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; removeIssueFromArchived: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + + quickAddIssue: undefined; + viewFlags: ViewFlags; } export class ProjectArchivedIssuesStore extends IssueBaseStore implements IProjectArchivedIssuesStore { @@ -28,6 +31,13 @@ export class ProjectArchivedIssuesStore extends IssueBaseStore implements IProje // service archivedIssueService; + //viewData + viewFlags = { + enableQuickAdd: false, + enableIssueCreation: false, + enableInlineEditing: true, + }; + constructor(_rootStore: RootStore) { super(_rootStore); @@ -101,7 +111,7 @@ export class ProjectArchivedIssuesStore extends IssueBaseStore implements IProje return response; } catch (error) { - // this.fetchIssues(workspaceSlug, projectId); + console.error(error); this.loader = undefined; throw error; } @@ -124,4 +134,6 @@ export class ProjectArchivedIssuesStore extends IssueBaseStore implements IProje throw error; } }; + + quickAddIssue: undefined; } diff --git a/web/store/issues/project-issues/base-issue.store.ts b/web/store/issues/project-issues/base-issue.store.ts index 4f5901a91..d10dba31e 100644 --- a/web/store/issues/project-issues/base-issue.store.ts +++ b/web/store/issues/project-issues/base-issue.store.ts @@ -87,9 +87,8 @@ export class IssueBaseStore implements IIssueBaseStore { for (const subGroup of subGroupArray) { for (const group of groupArray) { - if (subGroup && group && issues[subGroup]) { - _issues[subGroup][group].push(_issue.id); - } + if (subGroup && group && _issues[subGroup][group]) _issues[subGroup][group].push(_issue.id); + else if (subGroup && group) _issues[subGroup][group] = [_issue.id]; } } } diff --git a/web/store/issues/project-issues/cycle/issue.store.ts b/web/store/issues/project-issues/cycle/issue.store.ts index 1c36a1e9b..45c7afdc5 100644 --- a/web/store/issues/project-issues/cycle/issue.store.ts +++ b/web/store/issues/project-issues/cycle/issue.store.ts @@ -7,7 +7,7 @@ import { CycleService } from "services/cycle.service"; // types import { TIssueGroupByOptions } from "types"; import { IIssue } from "types/issues"; -import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "../../types"; +import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types"; import { RootStore } from "store/root"; export interface ICycleIssuesStore { @@ -49,6 +49,7 @@ export interface ICycleIssuesStore { data: IIssue, cycleId?: string | undefined ) => Promise; + addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: IIssue) => Promise; removeIssueFromCycle: ( workspaceSlug: string, projectId: string, @@ -56,6 +57,8 @@ export interface ICycleIssuesStore { issueId: string, issueBridgeId: string ) => Promise; + + viewFlags: ViewFlags; } export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStore { @@ -67,6 +70,13 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor cycleService; issueService; + //viewData + viewFlags = { + enableQuickAdd: true, + enableIssueCreation: true, + enableInlineEditing: true, + }; + constructor(_rootStore: RootStore) { super(_rootStore); @@ -83,6 +93,7 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor updateIssue: action, removeIssue: action, quickAddIssue: action, + addIssueToCycle: action, removeIssueFromCycle: action, }); @@ -158,7 +169,7 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor return response; } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + console.error(error); this.loader = undefined; throw error; } @@ -174,18 +185,7 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor try { const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); - const issueToCycle = await this.issueService.addIssueToCycle(workspaceSlug, projectId, cycleId, { - issues: [response.id], - }); - - let _issues = this.issues; - if (!_issues) _issues = {}; - if (!_issues[cycleId]) _issues[cycleId] = {}; - _issues[cycleId] = { ..._issues[cycleId], ...{ [response.id]: response } }; - - runInAction(() => { - this.issues = _issues; - }); + const issueToCycle = await this.addIssueToCycle(workspaceSlug, projectId, cycleId, response); return issueToCycle; } catch (error) { @@ -287,6 +287,28 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor } }; + addIssueToCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: IIssue) => { + try { + const issueToCycle = await this.issueService.addIssueToCycle(workspaceSlug, projectId, cycleId, { + issues: [data.id], + }); + + let _issues = this.issues; + if (!_issues) _issues = {}; + if (!_issues[cycleId]) _issues[cycleId] = {}; + _issues[cycleId] = { ..._issues[cycleId], ...{ [data.id]: data } }; + + runInAction(() => { + this.issues = _issues; + }); + + return issueToCycle; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + throw error; + } + }; + removeIssueFromCycle = async ( workspaceSlug: string, projectId: string, diff --git a/web/store/issues/project-issues/draft/issue.store.ts b/web/store/issues/project-issues/draft/issue.store.ts index 1687c8d4e..f3565c9e9 100644 --- a/web/store/issues/project-issues/draft/issue.store.ts +++ b/web/store/issues/project-issues/draft/issue.store.ts @@ -2,10 +2,10 @@ import { action, observable, makeObservable, computed, runInAction, autorun } fr // base class import { IssueBaseStore } from "store/issues"; // services -import { IssueService } from "services/issue/issue.service"; +import { IssueDraftService } from "services/issue/issue_draft.service"; // types import { IIssue } from "types/issues"; -import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "../../types"; +import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types"; import { RootStore } from "store/root"; export interface IProjectDraftIssuesStore { @@ -20,6 +20,9 @@ export interface IProjectDraftIssuesStore { createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + + quickAddIssue: undefined; + viewFlags: ViewFlags; } export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectDraftIssuesStore { @@ -28,7 +31,14 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD // root store rootStore; // service - issueService; + issueDraftService; + + //viewData + viewFlags = { + enableQuickAdd: false, + enableIssueCreation: false, + enableInlineEditing: false, + }; constructor(_rootStore: RootStore) { super(_rootStore); @@ -48,7 +58,7 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD }); this.rootStore = _rootStore; - this.issueService = new IssueService(); + this.issueDraftService = new IssueDraftService(); autorun(() => { const workspaceSlug = this.rootStore.workspace.workspaceSlug; @@ -97,7 +107,7 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD this.loader = loadType; const params = this.rootStore?.projectDraftIssuesFilter?.appliedFilters; - const response = await this.issueService.getV3Issues(workspaceSlug, projectId, params); + const response = await this.issueDraftService.getV3DraftIssues(workspaceSlug, projectId, params); const _issues = { ...this.issues, [projectId]: { ...response } }; @@ -108,7 +118,7 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD return response; } catch (error) { - this.fetchIssues(workspaceSlug, projectId); + console.error(error); this.loader = undefined; throw error; } @@ -116,7 +126,7 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD createIssue = async (workspaceSlug: string, projectId: string, data: Partial) => { try { - const response = await this.issueService.createIssue(workspaceSlug, projectId, data); + const response = await this.issueDraftService.createDraftIssue(workspaceSlug, projectId, data); let _issues = this.issues; if (!_issues) _issues = {}; @@ -145,7 +155,7 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD this.issues = _issues; }); - const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); + const response = await this.issueDraftService.updateDraftIssue(workspaceSlug, projectId, issueId, data); return response; } catch (error) { @@ -165,7 +175,7 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD this.issues = _issues; }); - const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); + const response = await this.issueDraftService.deleteDraftIssue(workspaceSlug, projectId, issueId); return response; } catch (error) { @@ -173,4 +183,6 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD throw error; } }; + + quickAddIssue: undefined; } diff --git a/web/store/issues/project-issues/module/issue.store.ts b/web/store/issues/project-issues/module/issue.store.ts index 966a41443..9d1bc3c93 100644 --- a/web/store/issues/project-issues/module/issue.store.ts +++ b/web/store/issues/project-issues/module/issue.store.ts @@ -7,7 +7,7 @@ import { ModuleService } from "services/module.service"; // types import { TIssueGroupByOptions } from "types"; import { IIssue } from "types/issues"; -import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "../../types"; +import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types"; import { RootStore } from "store/root"; export interface IModuleIssuesStore { @@ -49,6 +49,7 @@ export interface IModuleIssuesStore { data: IIssue, moduleId?: string | undefined ) => Promise; + addIssueToModule: (workspaceSlug: string, projectId: string, moduleId: string, data: IIssue) => Promise; removeIssueFromModule: ( workspaceSlug: string, projectId: string, @@ -56,6 +57,8 @@ export interface IModuleIssuesStore { issueId: string, issueBridgeId: string ) => Promise; + + viewFlags: ViewFlags; } export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesStore { @@ -67,6 +70,13 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt moduleService; issueService; + //viewData + viewFlags = { + enableQuickAdd: true, + enableIssueCreation: true, + enableInlineEditing: true, + }; + constructor(_rootStore: RootStore) { super(_rootStore); @@ -83,6 +93,7 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt updateIssue: action, removeIssue: action, quickAddIssue: action, + addIssueToModule: action, removeIssueFromModule: action, }); @@ -158,7 +169,7 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt return response; } catch (error) { - this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId); + console.error(error); this.loader = undefined; throw error; } @@ -174,18 +185,7 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt try { const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); - const issueToModule = await this.moduleService.addIssuesToModule(workspaceSlug, projectId, moduleId, { - issues: [response.id], - }); - - let _issues = this.issues; - if (!_issues) _issues = {}; - if (!_issues[moduleId]) _issues[moduleId] = {}; - _issues[moduleId] = { ..._issues[moduleId], ...{ [response.id]: response } }; - - runInAction(() => { - this.issues = _issues; - }); + const issueToModule = await this.addIssueToModule(workspaceSlug, projectId, moduleId, response); return issueToModule; } catch (error) { @@ -289,6 +289,28 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt } }; + addIssueToModule = async (workspaceSlug: string, projectId: string, moduleId: string, data: IIssue) => { + try { + const issueToModule = await this.moduleService.addIssuesToModule(workspaceSlug, projectId, moduleId, { + issues: [data.id], + }); + + let _issues = this.issues; + if (!_issues) _issues = {}; + if (!_issues[moduleId]) _issues[moduleId] = {}; + _issues[moduleId] = { ..._issues[moduleId], ...{ [data.id]: data } }; + + runInAction(() => { + this.issues = _issues; + }); + + return issueToModule; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId); + throw error; + } + }; + removeIssueFromModule = async ( workspaceSlug: string, projectId: string, diff --git a/web/store/issues/project-issues/project-view/issue.store.ts b/web/store/issues/project-issues/project-view/issue.store.ts index 17d7be030..a1dfb6d0b 100644 --- a/web/store/issues/project-issues/project-view/issue.store.ts +++ b/web/store/issues/project-issues/project-view/issue.store.ts @@ -6,7 +6,7 @@ import { IssueService } from "services/issue/issue.service"; // types import { TIssueGroupByOptions } from "types"; import { IIssue } from "types/issues"; -import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "../../types"; +import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types"; import { RootStore } from "store/root"; export interface IViewIssuesStore { @@ -22,6 +22,8 @@ export interface IViewIssuesStore { updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise; + + viewFlags: ViewFlags; } export class ViewIssuesStore extends IssueBaseStore implements IViewIssuesStore { @@ -32,6 +34,13 @@ export class ViewIssuesStore extends IssueBaseStore implements IViewIssuesStore // service issueService; + //viewData + viewFlags = { + enableQuickAdd: true, + enableIssueCreation: true, + enableInlineEditing: true, + }; + constructor(_rootStore: RootStore) { super(_rootStore); @@ -114,7 +123,7 @@ export class ViewIssuesStore extends IssueBaseStore implements IViewIssuesStore return response; } catch (error) { - this.fetchIssues(workspaceSlug, projectId); + console.error(error); this.loader = undefined; throw error; } diff --git a/web/store/issues/project-issues/project/issue.store.ts b/web/store/issues/project-issues/project/issue.store.ts index 684df6ed3..127c6e328 100644 --- a/web/store/issues/project-issues/project/issue.store.ts +++ b/web/store/issues/project-issues/project/issue.store.ts @@ -6,7 +6,7 @@ import { IssueService } from "services/issue/issue.service"; // types import { TIssueGroupByOptions } from "types"; import { IIssue } from "types/issues"; -import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "../../types"; +import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types"; import { RootStore } from "store/root"; export interface IProjectIssuesStore { @@ -22,6 +22,8 @@ export interface IProjectIssuesStore { updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise; + + viewFlags: ViewFlags; } export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssuesStore { @@ -32,6 +34,13 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues // service issueService; + //viewData + viewFlags = { + enableQuickAdd: true, + enableIssueCreation: true, + enableInlineEditing: true, + }; + constructor(_rootStore: RootStore) { super(_rootStore); @@ -85,15 +94,24 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined; if (layout === "list" && orderBy) { + console.log("list"); if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[projectId]); else issues = this.unGroupedIssues(orderBy, this.issues[projectId]); } else if (layout === "kanban" && groupBy && orderBy) { + console.log("kanban"); if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, this.issues[projectId]); else issues = this.groupedIssues(groupBy, orderBy, this.issues[projectId]); - } else if (layout === "calendar") + console.log("issues", issues); + } else if (layout === "calendar") { + console.log("calendar"); issues = this.groupedIssues("target_date" as TIssueGroupByOptions, "target_date", this.issues[projectId], true); - else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[projectId]); - else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", this.issues[projectId]); + } else if (layout === "spreadsheet") { + console.log("spreadsheet"); + issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[projectId]); + } else if (layout === "gantt_chart") { + console.log("gantt_chart"); + issues = this.unGroupedIssues(orderBy ?? "sort_order", this.issues[projectId]); + } return issues; } @@ -114,7 +132,7 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues return response; } catch (error) { - this.fetchIssues(workspaceSlug, projectId); + console.error(error); this.loader = undefined; throw error; } diff --git a/web/store/issues/types.ts b/web/store/issues/types.ts index b630c9410..9330aba01 100644 --- a/web/store/issues/types.ts +++ b/web/store/issues/types.ts @@ -25,3 +25,9 @@ export interface IIssueResponse { } export type TLoader = "init-loader" | "mutation" | undefined; + +export interface ViewFlags { + enableQuickAdd: boolean; + enableIssueCreation: boolean; + enableInlineEditing: boolean; +} diff --git a/web/store/root.ts b/web/store/root.ts index 2c2adb69f..c15890298 100644 --- a/web/store/root.ts +++ b/web/store/root.ts @@ -152,6 +152,18 @@ import { // draft issues filter IProjectDraftIssuesFilterStore, ProjectDraftIssuesFilterStore, + // profile issues + IProfileIssuesStore, + ProfileIssuesStore, + // profile issues filter + IProfileIssuesFilterStore, + ProfileIssuesFilterStore, + // global issues + IGlobalIssuesStore, + GlobalIssuesStore, + // global issues filter + IGlobalIssuesFilterStore, + GlobalIssuesFilterStore, } from "store/issues"; import { CycleIssueFiltersStore, ICycleIssueFiltersStore } from "store/cycle-issues"; @@ -256,6 +268,12 @@ export class RootStore { projectDraftIssues: IProjectDraftIssuesStore; projectDraftIssuesFilter: IProjectDraftIssuesFilterStore; + + workspaceProfileIssues: IProfileIssuesStore; + workspaceProfileIssuesFilter: IProfileIssuesFilterStore; + + workspaceGlobalIssues: IGlobalIssuesStore; + workspaceGlobalIssuesFilter: IGlobalIssuesFilterStore; // project v3 issue and issue-filters ends cycleIssueFilters: ICycleIssueFiltersStore; @@ -354,6 +372,12 @@ export class RootStore { this.projectDraftIssues = new ProjectDraftIssuesStore(this); this.projectDraftIssuesFilter = new ProjectDraftIssuesFilterStore(this); + + this.workspaceProfileIssues = new ProfileIssuesStore(this); + this.workspaceProfileIssuesFilter = new ProfileIssuesFilterStore(this); + + this.workspaceGlobalIssues = new GlobalIssuesStore(this); + this.workspaceGlobalIssuesFilter = new GlobalIssuesFilterStore(this); // project v3 issue and issue-filters ends this.cycleIssueFilters = new CycleIssueFiltersStore(this);