From 61f184a2efb3fe0af666394cecc0c2d3e21ca031 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Fri, 16 Feb 2024 18:28:21 +0530 Subject: [PATCH] chore: view ui and new store workflow --- packages/types/src/view/base.d.ts | 2 - packages/types/src/view/filter.d.ts | 2 +- .../roots/global-view-issue-layout-root.tsx | 128 +++++++ .../issues/issue-layouts/roots/index.ts | 1 + .../view/applied-filters/filter-item.tsx | 5 +- .../view/applied-filters/filter.tsx | 8 +- web/components/view/applied-filters/root.tsx | 14 +- .../display-filter-selection.tsx | 2 +- .../view/display-filters/extra-options.tsx | 2 +- .../display-properties/property-selection.tsx | 4 +- .../view/empty-states/empty-filter-state.tsx | 103 ++++++ .../view/empty-states/empty-state.tsx | 103 ++++++ .../empty-states/workspace-empty-state.tsx | 37 ++ web/components/view/filters/dropdown.tsx | 3 + web/components/view/filters/edit-dropdown.tsx | 5 +- .../view/filters/filter-item-root.tsx | 16 +- web/components/view/filters/filter-item.tsx | 1 - .../view/filters/filter-selection.tsx | 19 +- web/components/view/filters/root.tsx | 5 +- web/components/view/index.ts | 3 + web/components/view/root.tsx | 329 ++++++++++-------- web/components/view/types.d.ts | 6 +- .../view/views/create-edit-form.tsx | 33 +- web/components/view/views/edit-dropdown.tsx | 11 +- web/components/view/views/root.tsx | 23 +- web/components/view/views/view-dropdown.tsx | 2 +- web/constants/view/root.ts | 12 +- web/hooks/store/views/use-view-detail.tsx | 9 +- web/hooks/store/views/use-view.tsx | 2 +- .../[projectId]/views/private/index.tsx | 8 +- .../[projectId]/views/public/index.tsx | 8 +- .../views/private/[viewId].tsx | 28 +- .../[workspaceSlug]/views/public/[viewId].tsx | 14 +- web/store/issue/workspace/issue.store.ts | 42 ++- web/store/view/helpers/filters_helpers.ts | 8 +- web/store/view/root.store.ts | 33 ++ web/store/view/view-root.store.ts | 218 +++++++----- web/store/view/view.store.ts | 64 ++-- 38 files changed, 957 insertions(+), 356 deletions(-) create mode 100644 web/components/issues/issue-layouts/roots/global-view-issue-layout-root.tsx create mode 100644 web/components/view/empty-states/empty-filter-state.tsx create mode 100644 web/components/view/empty-states/empty-state.tsx create mode 100644 web/components/view/empty-states/workspace-empty-state.tsx diff --git a/packages/types/src/view/base.d.ts b/packages/types/src/view/base.d.ts index 0f017fa2d..0018e9328 100644 --- a/packages/types/src/view/base.d.ts +++ b/packages/types/src/view/base.d.ts @@ -43,8 +43,6 @@ export type TView = { updated_at: Date | undefined; // local view variables is_local_view: boolean; - is_create: boolean; - is_editable: boolean; }; export type TUpdateView = { diff --git a/packages/types/src/view/filter.d.ts b/packages/types/src/view/filter.d.ts index 1794e407b..593a57dfd 100644 --- a/packages/types/src/view/filter.d.ts +++ b/packages/types/src/view/filter.d.ts @@ -78,7 +78,7 @@ export type TViewDisplayFilters = { layout: TViewLayouts; group_by: TViewDisplayFiltersGrouped | undefined; sub_group_by: TViewDisplayFiltersGrouped | undefined; - order_by: TViewDisplayFiltersOrderBy | string; + order_by: TViewDisplayFiltersOrderBy; type: TViewDisplayFiltersType | undefined; sub_issue: boolean; show_empty_groups: boolean; diff --git a/web/components/issues/issue-layouts/roots/global-view-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/global-view-issue-layout-root.tsx new file mode 100644 index 000000000..3c19a1785 --- /dev/null +++ b/web/components/issues/issue-layouts/roots/global-view-issue-layout-root.tsx @@ -0,0 +1,128 @@ +import React, { Fragment, useCallback, useMemo } from "react"; +import { observer } from "mobx-react-lite"; +// hooks +import { useIssues, useUser, useViewDetail } from "hooks/store"; +import { useWorkspaceIssueProperties } from "hooks/use-workspace-issue-properties"; +// components +import { IssuePeekOverview } from "components/issues"; +import { SpreadsheetView } from "components/issues/issue-layouts"; +import { AllIssueQuickActions } from "components/issues/issue-layouts/quick-action-dropdowns"; +// ui +import { SpreadsheetLayoutLoader } from "components/ui"; +// types +import { TIssue, IIssueDisplayFilterOptions, TViewTypes } from "@plane/types"; +import { EIssueActions } from "../types"; +// constants +import { EUserProjectRoles } from "constants/project"; +import { EIssuesStoreType } from "constants/issue"; +import { EViewPageType } from "constants/view"; + +type TGlobalViewIssueLayoutRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + viewType: TViewTypes; + viewPageType: EViewPageType; +}; + +export const GlobalViewIssueLayoutRoot: React.FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType } = props; + // hooks + const { + issues: { loader, groupedIssueIds, updateIssue, removeIssue }, + } = useIssues(EIssuesStoreType.GLOBAL); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const { + membership: { currentWorkspaceAllProjectsRole }, + } = useUser(); + const { issueIds } = groupedIssueIds; + + //swr hook for fetching issue properties + useWorkspaceIssueProperties(workspaceSlug); + + const issueActions = useMemo( + () => ({ + [EIssueActions.UPDATE]: async (issue: TIssue) => { + const projectId = issue.project_id; + if (!workspaceSlug || !projectId || !viewId) return; + + await updateIssue(workspaceSlug.toString(), projectId, issue.id, issue, viewId.toString()); + }, + [EIssueActions.DELETE]: async (issue: TIssue) => { + const projectId = issue.project_id; + if (!workspaceSlug || !projectId || !viewId) return; + + await removeIssue(workspaceSlug.toString(), projectId, issue.id, viewId.toString()); + }, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [updateIssue, removeIssue, workspaceSlug] + ); + + const handleIssues = useCallback( + async (issue: TIssue, action: EIssueActions) => { + if (action === EIssueActions.UPDATE) await issueActions[action]!(issue); + if (action === EIssueActions.DELETE) await issueActions[action]!(issue); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + const handleDisplayFiltersUpdate = useCallback( + (updatedDisplayFilter: Partial) => { + if (!workspaceSlug || !viewId) return; + viewDetailStore?.setDisplayFilters({ order_by: updatedDisplayFilter?.order_by }); + }, + [viewDetailStore, workspaceSlug, viewId] + ); + + const renderQuickActions = useCallback( + (issue: TIssue, customActionButton?: React.ReactElement, portalElement?: HTMLDivElement | null) => ( + handleIssues({ ...issue }, EIssueActions.UPDATE)} + handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)} + portalElement={portalElement} + /> + ), + [handleIssues] + ); + + const canEditProperties = useCallback( + (projectId: string | undefined) => { + if (!projectId) return false; + + const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId]; + return !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; + }, + [currentWorkspaceAllProjectsRole] + ); + + if (loader === "init-loader" || !issueIds) { + return ; + } + + return ( +
+ {issueIds.length === 0 ? ( +
Empty state
+ ) : ( + + + {/* peek overview */} + + + )} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/roots/index.ts b/web/components/issues/issue-layouts/roots/index.ts index 727e3e393..ddc61c1b0 100644 --- a/web/components/issues/issue-layouts/roots/index.ts +++ b/web/components/issues/issue-layouts/roots/index.ts @@ -5,3 +5,4 @@ export * from "./project-view-layout-root"; export * from "./archived-issue-layout-root"; export * from "./draft-issue-layout-root"; export * from "./all-issue-layout-root"; +export * from "./global-view-issue-layout-root"; diff --git a/web/components/view/applied-filters/filter-item.tsx b/web/components/view/applied-filters/filter-item.tsx index 4c3cf54c0..70ce40078 100644 --- a/web/components/view/applied-filters/filter-item.tsx +++ b/web/components/view/applied-filters/filter-item.tsx @@ -12,12 +12,13 @@ type TViewAppliedFiltersItem = { viewType: TViewTypes; filterKey: keyof TViewFilters; propertyId: string; + isLocalView: boolean; }; export const ViewAppliedFiltersItem: FC = (props) => { - const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId } = props; + const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId, isLocalView } = props; // hooks - const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView); const viewFilterHelper = useViewFilter(workspaceSlug, projectId); const propertyDetail = viewFilterHelper?.propertyDetails(filterKey, propertyId) || undefined; diff --git a/web/components/view/applied-filters/filter.tsx b/web/components/view/applied-filters/filter.tsx index 150180fd9..c98e98df9 100644 --- a/web/components/view/applied-filters/filter.tsx +++ b/web/components/view/applied-filters/filter.tsx @@ -16,12 +16,13 @@ type TViewAppliedFilters = { viewType: TViewTypes; filterKey: keyof TViewFilters; propertyVisibleCount?: number | undefined; + isLocalView: boolean; }; export const ViewAppliedFilters: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyVisibleCount } = props; + const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyVisibleCount, isLocalView } = props; // hooks - const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView); const viewFilterStore = useViewFilter(workspaceSlug, projectId); const currentDefaultFilterDetails = useMemo( @@ -44,7 +45,7 @@ export const ViewAppliedFilters: FC = observer((props) => { if (!propertyValues || propertyValues.length <= 0) return <>; return ( -
+
{filterKey.replaceAll("_", " ")}
{propertyVisibleCount && propertyValues.length >= propertyVisibleCount ? ( @@ -65,6 +66,7 @@ export const ViewAppliedFilters: FC = observer((props) => { viewType={viewType} filterKey={filterKey} propertyId={propertyId} + isLocalView={isLocalView} /> ))} diff --git a/web/components/view/applied-filters/root.tsx b/web/components/view/applied-filters/root.tsx index fa64982c3..6ef393a83 100644 --- a/web/components/view/applied-filters/root.tsx +++ b/web/components/view/applied-filters/root.tsx @@ -16,12 +16,21 @@ type TViewAppliedFiltersRoot = { viewType: TViewTypes; propertyVisibleCount?: number | undefined; showClearAll?: boolean; + isLocalView?: boolean; }; export const ViewAppliedFiltersRoot: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, propertyVisibleCount = undefined, showClearAll = false } = props; + const { + workspaceSlug, + projectId, + viewId, + viewType, + propertyVisibleCount = undefined, + showClearAll = false, + isLocalView = false, + } = props; // hooks - const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView); const filterKeys = useMemo( () => @@ -52,6 +61,7 @@ export const ViewAppliedFiltersRoot: FC = observer((pro viewType={viewType} filterKey={filterKey} propertyVisibleCount={propertyVisibleCount} + isLocalView={isLocalView} /> ); diff --git a/web/components/view/display-filters/display-filter-selection.tsx b/web/components/view/display-filters/display-filter-selection.tsx index 9f31c1ae8..b7fd7bcd9 100644 --- a/web/components/view/display-filters/display-filter-selection.tsx +++ b/web/components/view/display-filters/display-filter-selection.tsx @@ -32,7 +32,7 @@ export const ViewDisplayFilterSelection: FC = obser : "border-custom-border-400 bg-custom-background-100" }`} > - {isSelected && } + {isSelected && }
); }); diff --git a/web/components/view/display-filters/extra-options.tsx b/web/components/view/display-filters/extra-options.tsx index a8066c3fe..9e5c5b82b 100644 --- a/web/components/view/display-filters/extra-options.tsx +++ b/web/components/view/display-filters/extra-options.tsx @@ -43,7 +43,7 @@ export const DisplayFilterExtraOptions: FC = observe : "border-custom-border-400 bg-custom-background-100" }`} > - {isSelected && } + {isSelected && }
{optionTitle || "Extra Option"} diff --git a/web/components/view/display-properties/property-selection.tsx b/web/components/view/display-properties/property-selection.tsx index 492dc711c..6bbe47707 100644 --- a/web/components/view/display-properties/property-selection.tsx +++ b/web/components/view/display-properties/property-selection.tsx @@ -27,10 +27,10 @@ export const ViewDisplayPropertySelection: FC = o return (
= observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, children } = props; + // hooks + const { commandPalette } = useApplication(); + const { resolvedTheme } = useTheme(); + const { workspaceProjectIds } = useProject(); + const { + currentUser, + membership: { currentWorkspaceRole }, + } = useUser(); + const { setTrackElement } = useEventTracker(); + const { issueMap } = useIssues(); + const viewStore = useView(workspaceSlug, projectId, viewType); + + const isDefaultView = ["all-issues", "assigned", "created", "subscribed"].includes(viewId); + const currentView = isDefaultView ? viewId : "custom-view"; + const currentViewDetails = ALL_ISSUES_EMPTY_STATE_DETAILS[currentView as keyof typeof ALL_ISSUES_EMPTY_STATE_DETAILS]; + + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, isLightMode); + + const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + + useSWR( + workspaceSlug && viewId && viewType && viewStore ? `WORKSPACE_VIEWS_${workspaceSlug}_${viewType}` : null, + async () => { + if (workspaceSlug && viewType && viewStore) + await viewStore?.fetch( + workspaceSlug, + projectId, + viewStore?.viewIds.length > 0 ? "view-mutation-loader" : "view-loader" + ); + await viewStore?.fetchById(workspaceSlug, projectId, viewId); + } + ); + + const issueIds = projectId ? true : (Object.values(issueMap) ?? []).length === 0; + + if (!workspaceSlug) return <>; + return ( + <> + {(workspaceProjectIds ?? []).length === 0 || issueIds ? ( +
+ 0 ? currentViewDetails.title : "No project"} + description={ + (workspaceProjectIds ?? []).length > 0 + ? currentViewDetails.description + : "To create issues or manage your work, you need to create a project or be a part of one." + } + size="sm" + primaryButton={ + (workspaceProjectIds ?? []).length > 0 + ? currentView !== "custom-view" && currentView !== "subscribed" + ? { + text: "Create new issue", + onClick: () => { + setTrackElement("All issues empty state"); + commandPalette.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT); + }, + } + : undefined + : { + text: "Start your first project", + onClick: () => { + setTrackElement("All issues empty state"); + commandPalette.toggleCreateProjectModal(true); + }, + } + } + disabled={!isEditingAllowed} + /> +
+ ) : ( + <>{children} + )} + + ); +}); diff --git a/web/components/view/empty-states/empty-state.tsx b/web/components/view/empty-states/empty-state.tsx new file mode 100644 index 000000000..09fbf0863 --- /dev/null +++ b/web/components/view/empty-states/empty-state.tsx @@ -0,0 +1,103 @@ +import { FC, ReactNode } from "react"; +import { observer } from "mobx-react-lite"; +import { useTheme } from "next-themes"; +import useSWR from "swr"; +// hooks +import { useApplication, useEventTracker, useIssues, useProject, useUser, useView } from "hooks/store"; +// components +import { EmptyState, getEmptyStateImagePath } from "components/empty-state"; +// types +import { TViewTypes } from "@plane/types"; +// constants +import { ALL_ISSUES_EMPTY_STATE_DETAILS } from "constants/empty-state"; +import { EUserWorkspaceRoles } from "constants/workspace"; +import { EIssuesStoreType } from "constants/issue"; + +type TViewEmptyStateRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + viewType: TViewTypes; + children: ReactNode; +}; + +export const ViewEmptyStateRoot: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, children } = props; + // hooks + const { commandPalette } = useApplication(); + const { resolvedTheme } = useTheme(); + const { workspaceProjectIds } = useProject(); + const { + currentUser, + membership: { currentWorkspaceRole }, + } = useUser(); + const { setTrackElement } = useEventTracker(); + const { issueMap } = useIssues(); + const viewStore = useView(workspaceSlug, projectId, viewType); + + const isDefaultView = ["all-issues", "assigned", "created", "subscribed"].includes(viewId); + const currentView = isDefaultView ? viewId : "custom-view"; + const currentViewDetails = ALL_ISSUES_EMPTY_STATE_DETAILS[currentView as keyof typeof ALL_ISSUES_EMPTY_STATE_DETAILS]; + + const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light"; + const emptyStateImage = getEmptyStateImagePath("all-issues", currentView, isLightMode); + + const isEditingAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + + useSWR( + workspaceSlug && viewId && viewType && viewStore ? `WORKSPACE_VIEWS_${workspaceSlug}_${viewType}` : null, + async () => { + if (workspaceSlug && viewType && viewStore) + await viewStore?.fetch( + workspaceSlug, + projectId, + viewStore?.viewIds.length > 0 ? "view-mutation-loader" : "view-loader" + ); + await viewStore?.fetchById(workspaceSlug, projectId, viewId); + } + ); + + const issueIds = projectId ? true : (Object.values(issueMap) ?? []).length === 0; + + if (!workspaceSlug) return <>; + return ( + <> + {(workspaceProjectIds ?? []).length === 0 || issueIds ? ( +
+ 0 ? currentViewDetails.title : "No project"} + description={ + (workspaceProjectIds ?? []).length > 0 + ? currentViewDetails.description + : "To create issues or manage your work, you need to create a project or be a part of one." + } + size="sm" + primaryButton={ + (workspaceProjectIds ?? []).length > 0 + ? currentView !== "custom-view" && currentView !== "subscribed" + ? { + text: "Create new issue", + onClick: () => { + setTrackElement("All issues empty state"); + commandPalette.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT); + }, + } + : undefined + : { + text: "Start your first project", + onClick: () => { + setTrackElement("All issues empty state"); + commandPalette.toggleCreateProjectModal(true); + }, + } + } + disabled={!isEditingAllowed} + /> +
+ ) : ( + <>{children} + )} + + ); +}); diff --git a/web/components/view/empty-states/workspace-empty-state.tsx b/web/components/view/empty-states/workspace-empty-state.tsx new file mode 100644 index 000000000..7b22b2d88 --- /dev/null +++ b/web/components/view/empty-states/workspace-empty-state.tsx @@ -0,0 +1,37 @@ +import { FC, ReactNode } from "react"; +import { observer } from "mobx-react-lite"; +// hooks +import { useIssues, useProject } from "hooks/store"; + +// types +import { TViewTypes } from "@plane/types"; + +type TViewEmptyStateRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + viewType: TViewTypes; + children: ReactNode; +}; + +export const ViewEmptyStateRoot: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, children } = props; + // hooks + const { workspaceProjectIds } = useProject(); + const { issueMap } = useIssues(); + + const areIssueAvailable = projectId ? true : (Object.values(issueMap) ?? []).length === 0 ? true : false; + + if (!workspaceSlug) return <>; + return ( + <> + {(workspaceProjectIds ?? []).length === 0 ? ( +
No Projects are available.
+ ) : ( + <> + {areIssueAvailable ? <>{children} :
No issues are available.
} + + )} + + ); +}); diff --git a/web/components/view/filters/dropdown.tsx b/web/components/view/filters/dropdown.tsx index 95c30455b..08405ebb2 100644 --- a/web/components/view/filters/dropdown.tsx +++ b/web/components/view/filters/dropdown.tsx @@ -23,6 +23,7 @@ type TViewFiltersDropdown = { children?: ReactNode; displayDropdownText?: boolean; dropdownPlacement?: Placement; + isLocalView?: boolean; }; export const ViewFiltersDropdown: FC = observer((props) => { @@ -35,6 +36,7 @@ export const ViewFiltersDropdown: FC = observer((props) => children, displayDropdownText = true, dropdownPlacement = "bottom-start", + isLocalView = false, } = props; // state const [dropdownToggle, setDropdownToggle] = useState(false); @@ -139,6 +141,7 @@ export const ViewFiltersDropdown: FC = observer((props) => viewPageType={viewPageType} dateCustomFilterToggle={dateCustomFilterToggle} setDateCustomFilterToggle={setDateCustomFilterToggle} + isLocalView={isLocalView} />
diff --git a/web/components/view/filters/edit-dropdown.tsx b/web/components/view/filters/edit-dropdown.tsx index 5eb0b9ffc..c8bf726e0 100644 --- a/web/components/view/filters/edit-dropdown.tsx +++ b/web/components/view/filters/edit-dropdown.tsx @@ -55,19 +55,20 @@ export const ViewFiltersEditDropdown: FC = observer((p key: "save_as_new", label: "Save as new view", onClick: () => { - viewOperations.localViewCreateEdit(undefined, viewDetailStore?.filtersToUpdate); + viewOperations.localViewCreateEdit(undefined, "SAVE_AS_NEW"); }, }, { icon: RotateCcw, key: "reset_changes", - label: "Reset changes", + label: "Reset filter changes", onClick: () => viewDetailStore?.resetChanges(), }, ], [viewOperations, viewDetailStore] ); + if (viewDetailStore?.is_local_view) return <>; if (!viewDetailStore?.isFiltersUpdateEnabled) return <>; return ( diff --git a/web/components/view/filters/filter-item-root.tsx b/web/components/view/filters/filter-item-root.tsx index ff56a4a53..6839e21ee 100644 --- a/web/components/view/filters/filter-item-root.tsx +++ b/web/components/view/filters/filter-item-root.tsx @@ -16,13 +16,22 @@ type TViewFiltersItemRoot = { filterKey: keyof TViewFilters; dateCustomFilterToggle: string | undefined; setDateCustomFilterToggle: (value: string | undefined) => void; + isLocalView: boolean; }; export const ViewFiltersItemRoot: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, filterKey, dateCustomFilterToggle, setDateCustomFilterToggle } = - props; + const { + workspaceSlug, + projectId, + viewId, + viewType, + filterKey, + dateCustomFilterToggle, + setDateCustomFilterToggle, + isLocalView, + } = props; // hooks - const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView); const viewFilterHelper = useViewFilter(workspaceSlug, projectId); // state const [viewAll, setViewAll] = useState(false); @@ -76,6 +85,7 @@ export const ViewFiltersItemRoot: FC = observer((props) => viewType={viewType} filterKey={filterKey} propertyId={propertyId} + isLocalView={isLocalView} /> = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId } = props; + const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId, isLocalView } = props; - const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView); const propertyIds = useMemo( () => viewDetailStore?.appliedFilters?.filters?.[filterKey] || [], @@ -33,18 +34,6 @@ export const ViewFilterSelection: FC = observer((props) => : propertyIds?.includes(propertyId) : propertyIds?.includes(propertyId) || false; - // const isSelected = useMemo( - // () => - // ["start_date", "target_date"].includes(filterKey) - // ? propertyId === "custom" - // ? propertyIds.filter((id) => id.includes("-")).length > 0 - // ? true - // : false - // : propertyIds?.includes(propertyId) - // : propertyIds?.includes(propertyId) || false, - // [filterKey, propertyId, propertyIds] - // ); - return (
= observer((props) => : "border-custom-border-400 bg-custom-background-100" }`} > - {isSelected && } + {isSelected && }
); }); diff --git a/web/components/view/filters/root.tsx b/web/components/view/filters/root.tsx index 4eeea10f3..52a93fc66 100644 --- a/web/components/view/filters/root.tsx +++ b/web/components/view/filters/root.tsx @@ -20,6 +20,7 @@ type TViewFiltersRoot = { viewPageType: EViewPageType; dateCustomFilterToggle: string | undefined; setDateCustomFilterToggle: (value: string | undefined) => void; + isLocalView: boolean; }; export const ViewFiltersRoot: FC = observer((props) => { @@ -31,9 +32,10 @@ export const ViewFiltersRoot: FC = observer((props) => { viewPageType, dateCustomFilterToggle, setDateCustomFilterToggle, + isLocalView, } = props; // hooks - const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView); // state const [filterVisibility, setFilterVisibility] = useState[]>([]); const handleFilterVisibility = useCallback((key: keyof TViewFilters) => { @@ -73,6 +75,7 @@ export const ViewFiltersRoot: FC = observer((props) => { filterKey={filterKey} dateCustomFilterToggle={dateCustomFilterToggle} setDateCustomFilterToggle={setDateCustomFilterToggle} + isLocalView={isLocalView} /> )}
diff --git a/web/components/view/index.ts b/web/components/view/index.ts index 9379e3e7b..a12a795d8 100644 --- a/web/components/view/index.ts +++ b/web/components/view/index.ts @@ -2,6 +2,9 @@ export * from "./root"; export * from "./header-tabs"; +// empty states +export * from "./empty-states/empty-state"; + // views export * from "./views/root"; export * from "./views/view-item"; diff --git a/web/components/view/root.tsx b/web/components/view/root.tsx index 42e6904a2..74abef638 100644 --- a/web/components/view/root.tsx +++ b/web/components/view/root.tsx @@ -1,7 +1,6 @@ import { FC, Fragment, useCallback, useEffect, useMemo, useState } from "react"; +import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -import { v4 as uuidV4 } from "uuid"; -import cloneDeep from "lodash/cloneDeep"; // hooks import { useView, useViewDetail } from "hooks/store"; import useToast from "hooks/use-toast"; @@ -17,14 +16,20 @@ import { ViewAppliedFiltersRoot, ViewDuplicateConfirmationModal, ViewDeleteConfirmationModal, -} from "."; +} from "./"; // ui -import { Spinner } from "@plane/ui"; +import { Loader } from "@plane/ui"; // constants -import { EViewPageType, viewLocalPayload } from "constants/view"; +import { + ELocalViews, + EViewLayouts, + EViewPageType, + TViewCRUD, + viewDefaultFilterParametersByViewTypeAndLayout, +} from "constants/view"; // types import { TViewOperations } from "./types"; -import { TView, TViewTypes } from "@plane/types"; +import { TViewTypes } from "@plane/types"; type TGlobalViewRoot = { workspaceSlug: string; @@ -36,11 +41,13 @@ type TGlobalViewRoot = { }; type TViewOperationsToggle = { - type: "CREATE" | "EDIT" | "DUPLICATE" | "DELETE" | undefined; + type: "DUPLICATE" | "DELETE" | undefined; viewId: string | undefined; }; export const GlobalViewRoot: FC = observer((props) => { + // router + const router = useRouter(); const { workspaceSlug, projectId, viewId, viewType, viewPageType, baseRoute } = props; // hooks const viewStore = useView(workspaceSlug, projectId, viewType); @@ -55,38 +62,11 @@ export const GlobalViewRoot: FC = observer((props) => { (type: TViewOperationsToggle["type"], viewId: string | undefined) => setViewOperationsToggle({ type, viewId }), [] ); - // hooks - const viewDetailCreateEditStore = useViewDetail( - workspaceSlug, - projectId, - viewOperationsToggle?.viewId || viewId, - viewType - ); const viewOperations: TViewOperations = useMemo( () => ({ - localViewCreateEdit: (viewId: string | undefined, currentView = undefined) => { - if (viewId === undefined) { - if (currentView !== undefined) { - // creating new view - const currentViewPayload = cloneDeep({ ...currentView, id: uuidV4() }); - handleViewOperationsToggle("CREATE", currentViewPayload.id); - viewStore?.localViewCreate(workspaceSlug, projectId, currentViewPayload as TView); - } else { - // if current view is available, create a new view with the same data - const viewPayload = viewLocalPayload; - handleViewOperationsToggle("CREATE", viewPayload.id); - viewStore?.localViewCreate(workspaceSlug, projectId, viewPayload as TView); - } - } else { - handleViewOperationsToggle("EDIT", viewId); - viewDetailCreateEditStore?.setIsEditable(true); - } - }, - localViewCreateEditClear: async (viewId: string | undefined) => { - if (viewDetailCreateEditStore?.is_create && viewId) viewStore?.remove(workspaceSlug, projectId, viewId); - if (viewDetailCreateEditStore?.is_editable && viewId) viewDetailCreateEditStore.resetChanges(); - handleViewOperationsToggle(undefined, undefined); + localViewCreateEdit: (viewId: string | undefined, status: TViewCRUD) => { + viewStore?.localViewHandler(viewId, status); }, fetch: async () => { @@ -100,9 +80,9 @@ export const GlobalViewRoot: FC = observer((props) => { }); } }, - create: async (data: Partial) => { + create: async () => { try { - await viewStore?.create(workspaceSlug, projectId, data); + await viewStore?.create(); handleViewOperationsToggle(undefined, undefined); setToastAlert({ type: "success", @@ -117,23 +97,6 @@ export const GlobalViewRoot: FC = observer((props) => { }); } }, - remove: async (viewId: string) => { - try { - await viewStore?.remove(workspaceSlug, projectId, viewId); - handleViewOperationsToggle(undefined, undefined); - setToastAlert({ - type: "success", - title: "Success!", - message: "View removed successfully.", - }); - } catch { - setToastAlert({ - type: "error", - title: "Error!", - message: "Something went wrong. Please try again later or contact the support team.", - }); - } - }, update: async () => { try { await viewDetailStore?.saveChanges(); @@ -151,25 +114,80 @@ export const GlobalViewRoot: FC = observer((props) => { }); } }, + remove: async (viewId: string) => { + try { + await viewStore?.remove(viewId); + handleViewOperationsToggle(undefined, undefined); + setToastAlert({ + type: "success", + title: "Success!", + message: "View removed successfully.", + }); + } catch { + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong. Please try again later or contact the support team.", + }); + } + }, + duplicate: async (viewId: string) => { + try { + await viewStore?.duplicate(viewId); + handleViewOperationsToggle(undefined, undefined); + setToastAlert({ + type: "success", + title: "Success!", + message: "View removed successfully.", + }); + } catch { + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong. Please try again later or contact the support team.", + }); + } + }, }), - [ - workspaceSlug, - projectId, - viewStore, - viewDetailStore, - setToastAlert, - viewDetailCreateEditStore, - handleViewOperationsToggle, - ] + [viewStore, viewDetailStore, handleViewOperationsToggle, setToastAlert, workspaceSlug, projectId] ); + const applyFIltersFromRouter = () => { + if (workspaceSlug && viewId && Object.values(ELocalViews).includes(viewId as ELocalViews)) { + const routerQueryParams = { ...router.query }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { ["workspaceSlug"]: _workspaceSlug, ["viewId"]: _viewId, ...filters } = routerQueryParams; + + const acceptedFilters = viewDefaultFilterParametersByViewTypeAndLayout( + viewPageType, + EViewLayouts.SPREADSHEET, + "filters" + ); + + Object.keys(filters).forEach((key) => { + const filterKey: any = key; + const filterValue = filters[key]?.toString() || undefined; + if (filterKey && filterValue && acceptedFilters.includes(filterKey)) { + const _filterValues = filterValue.split(","); + _filterValues.forEach((element) => { + console.log("filterKey", filterKey); + console.log("element", element); + viewDetailStore?.setFilters(filterKey, element); + }); + } + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }; + // fetch all views useEffect(() => { const fetchViews = async () => { await viewStore?.fetch( workspaceSlug, projectId, - viewStore?.viewIds.length > 0 ? "mutation-loader" : "init-loader" + viewStore?.viewIds.length > 0 ? "view-mutation-loader" : "view-loader" ); }; if (workspaceSlug && viewType && viewStore) fetchViews(); @@ -177,18 +195,34 @@ export const GlobalViewRoot: FC = observer((props) => { // fetch view by id useEffect(() => { - const fetchViews = async () => { - viewId && (await viewStore?.fetchById(workspaceSlug, projectId, viewId)); + const fetchViewByViewId = async () => { + await viewStore?.fetchById(workspaceSlug, projectId, viewId); + // applyFIltersFromRouter(); }; - if (workspaceSlug && viewId && viewType && viewStore) fetchViews(); + if (workspaceSlug && viewId && viewType && viewStore) { + fetchViewByViewId(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [workspaceSlug, projectId, viewId, viewType, viewStore]); + console.log("viewStore? -->", viewStore?.viewMapCEN?.id); + return (
- {viewStore?.loader && viewStore?.loader === "init-loader" ? ( -
- -
+ {viewStore?.loader && viewStore?.loader === "view-loader" ? ( + +
+ +
+
+ + + + +
+ +
+ ) : ( <>
@@ -202,85 +236,102 @@ export const GlobalViewRoot: FC = observer((props) => { />
-
-
- -
+ {viewStore?.loader === "view-detail-loader" ? ( + +
+ + + + +
+ + + + +
+ ) : ( +
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
- +
+ +
-
+ )} )} {/* create edit modal */} + {viewStore?.viewMapCEN?.id && ( + + )} + {viewOperationsToggle.type && viewOperationsToggle.viewId && ( - {["CREATE", "EDIT"].includes(viewOperationsToggle.type) && ( - - )} - {["DUPLICATE"].includes(viewOperationsToggle.type) && ( )} diff --git a/web/components/view/types.d.ts b/web/components/view/types.d.ts index d34070952..e4bb10008 100644 --- a/web/components/view/types.d.ts +++ b/web/components/view/types.d.ts @@ -1,15 +1,17 @@ import { LucideIcon } from "lucide-react"; // types import { TView, TUpdateView } from "@plane/types"; +// constants +import { TViewCRUD } from "constants/view"; export type TViewOperations = { - localViewCreateEdit: (viewId: string | undefined, currentView?: TUpdateView | undefined) => void; - localViewCreateEditClear: (viewId: string | undefined) => Promise; + localViewCreateEdit: (viewId: string | undefined, status: TViewCRUD) => void; fetch: () => Promise; create: (data: Partial) => Promise; update: () => Promise; remove: (viewId: string) => Promise; + duplicate: (viewId: string) => Promise; }; // view and view filter edit dropdowns diff --git a/web/components/view/views/create-edit-form.tsx b/web/components/view/views/create-edit-form.tsx index 2fbd62208..6c8217763 100644 --- a/web/components/view/views/create-edit-form.tsx +++ b/web/components/view/views/create-edit-form.tsx @@ -21,12 +21,13 @@ type TViewCreateEditForm = { viewType: TViewTypes; viewPageType: EViewPageType; viewOperations: TViewOperations; + isLocalView: boolean; }; export const ViewCreateEditForm: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, viewPageType, viewOperations } = props; + const { workspaceSlug, projectId, viewId, viewType, viewPageType, viewOperations, isLocalView } = props; // hooks - const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType, isLocalView); const { getProjectById } = useProject(); // states const [modalToggle, setModalToggle] = useState(false); @@ -36,9 +37,9 @@ export const ViewCreateEditForm: FC = observer((props) => { const modalClose = useCallback(() => { setModalToggle(false); setTimeout(() => { - viewOperations.localViewCreateEditClear(viewId); + viewOperations.localViewCreateEdit(undefined, "CLEAR"); }, 200); - }, [viewId, setModalToggle, viewOperations]); + }, [setModalToggle, viewOperations]); useEffect(() => { if (viewId) modalOpen(); @@ -46,16 +47,16 @@ export const ViewCreateEditForm: FC = observer((props) => { const onContinue = async () => { setLoader(true); - if (viewDetailStore?.is_create) { - const payload = viewDetailStore?.filtersToUpdate; - await viewOperations.create(payload); - modalClose(); - } else { - const payload = viewDetailStore?.filtersToUpdate; - if (!payload) return; - await viewOperations.update(); - modalClose(); - } + // if (viewDetailStore?.id != "create") { + // const payload = viewDetailStore?.filtersToUpdate; + // await viewOperations.create(payload); + // modalClose(); + // } else { + // const payload = viewDetailStore?.filtersToUpdate; + // if (!payload) return; + // await viewOperations.update(); + // modalClose(); + // } setLoader(false); }; @@ -131,6 +132,7 @@ export const ViewCreateEditForm: FC = observer((props) => { viewType={viewType} viewPageType={viewPageType} dropdownPlacement="right" + isLocalView={isLocalView} >
@@ -161,6 +163,7 @@ export const ViewCreateEditForm: FC = observer((props) => { viewId={viewId} viewType={viewType} propertyVisibleCount={undefined} + isLocalView={isLocalView} />
@@ -169,7 +172,7 @@ export const ViewCreateEditForm: FC = observer((props) => { Cancel
diff --git a/web/components/view/views/edit-dropdown.tsx b/web/components/view/views/edit-dropdown.tsx index 0ee24e920..2a92c8470 100644 --- a/web/components/view/views/edit-dropdown.tsx +++ b/web/components/view/views/edit-dropdown.tsx @@ -3,18 +3,24 @@ import { observer } from "mobx-react-lite"; import { Menu, Transition } from "@headlessui/react"; import { usePopper } from "react-popper"; import { Copy, Eye, Globe2, Link2, Pencil, Trash } from "lucide-react"; +// hooks +import { useViewDetail } from "hooks/store"; // types import { TViewEditDropdownOptions, TViewOperations } from "../types"; +import { TViewTypes } from "@plane/types"; type TViewEditDropdown = { workspaceSlug: string; projectId: string | undefined; viewId: string; + viewType: TViewTypes; viewOperations: TViewOperations; }; export const ViewEditDropdown: FC = observer((props) => { - const { viewId, viewOperations } = props; + const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; + // hooks + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); // refs const dropdownRef = useRef(null); // popper-js refs @@ -46,7 +52,7 @@ export const ViewEditDropdown: FC = observer((props) => { icon: Pencil, key: "rename", label: "Rename", - onClick: () => viewOperations.localViewCreateEdit(viewId), + onClick: () => viewOperations.localViewCreateEdit(viewId, "EDIT"), children: undefined, }, { @@ -96,6 +102,7 @@ export const ViewEditDropdown: FC = observer((props) => { [viewOperations, viewId] ); + if (viewDetailStore?.is_local_view) return <>; return ( = observer((props) => { const handleViewTabsVisibility = () => { const tabContainer = document.getElementById("tab-container"); const tabItemViewMore = document.getElementById("tab-item-view-more"); - const itemWidth = 124; + const itemWidth = 122; if (!tabContainer || !tabItemViewMore) return; const containerWidth = tabContainer.clientWidth; @@ -49,14 +49,11 @@ export const ViewRoot: FC = observer((props) => { return () => window.removeEventListener("resize", () => handleViewTabsVisibility()); }, [viewStore?.viewIds]); - const viewIds = useMemo(() => { - const ids = viewStore?.viewIds?.slice(0, itemsToRenderViewsCount || viewStore?.viewIds.length) || []; - if (!ids.includes(viewId)) { - ids.pop(); - ids.push(viewId); - } - return ids; - }, [viewId, viewStore, itemsToRenderViewsCount]); + const viewIds = viewStore?.viewIds?.slice(0, itemsToRenderViewsCount || viewStore?.viewIds.length) || []; + if (!viewIds.includes(viewId)) { + viewIds.pop(); + viewIds.push(viewId); + } return (
@@ -102,7 +99,11 @@ export const ViewRoot: FC = observer((props) => {
-
diff --git a/web/components/view/views/view-dropdown.tsx b/web/components/view/views/view-dropdown.tsx index 92676f23d..70dd5b746 100644 --- a/web/components/view/views/view-dropdown.tsx +++ b/web/components/view/views/view-dropdown.tsx @@ -134,7 +134,7 @@ export const ViewDropdown: FC = (props) => {
viewOperations?.localViewCreateEdit(undefined)} + onClick={() => viewOperations?.localViewCreateEdit(undefined, "CREATE")} >
New view
diff --git a/web/constants/view/root.ts b/web/constants/view/root.ts index e6ca70b72..62d9fb8e4 100644 --- a/web/constants/view/root.ts +++ b/web/constants/view/root.ts @@ -1,4 +1,3 @@ -import { v4 as uuidV4 } from "uuid"; // types import { TViewTypes, TView } from "@plane/types"; @@ -9,13 +8,20 @@ export const VIEW_TYPES: Record = { PROJECT_PUBLIC_VIEWS: "PROJECT_PUBLIC_VIEWS", }; +export type TViewCRUD = "CREATE" | "EDIT" | "SAVE_AS_NEW" | "CLEAR"; + export const viewLocalPayload: Partial = { - id: uuidV4(), + id: "create", name: "", description: "", filters: undefined, display_filters: undefined, display_properties: undefined, is_local_view: false, - is_create: true, }; + +export const generateViewStoreKey = ( + workspaceSlug: string, + projectId: string | undefined, + viewType: TViewTypes +): string => `${workspaceSlug}_${projectId}_${viewType}`; diff --git a/web/hooks/store/views/use-view-detail.tsx b/web/hooks/store/views/use-view-detail.tsx index c46f5a947..972064377 100644 --- a/web/hooks/store/views/use-view-detail.tsx +++ b/web/hooks/store/views/use-view-detail.tsx @@ -11,8 +11,9 @@ import { VIEW_TYPES } from "constants/view"; export const useViewDetail = ( workspaceSlug: string, projectId: string | undefined, - viewId: string, - viewType: TViewTypes | undefined + viewId: string | undefined, + viewType: TViewTypes | undefined, + isEditable: boolean = false ): TViewStore | undefined => { const context = useContext(StoreContext); if (context === undefined) throw new Error("useViewDetail must be used within StoreProvider"); @@ -21,14 +22,18 @@ export const useViewDetail = ( switch (viewType) { case VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS: + if (isEditable) return context.view.workspacePrivateViewStore.viewMapCEN; return context.view.workspacePrivateViewStore.viewById(viewId); case VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS: + if (isEditable) return context.view.workspacePrivateViewStore.viewMapCEN; return context.view.workspacePublicViewStore.viewById(viewId); case VIEW_TYPES.PROJECT_PRIVATE_VIEWS: if (!projectId) return undefined; + if (isEditable) return context.view.workspacePrivateViewStore.viewMapCEN; return context.view.projectPrivateViewStore.viewById(viewId); case VIEW_TYPES.PROJECT_PUBLIC_VIEWS: if (!projectId) return undefined; + if (isEditable) return context.view.workspacePrivateViewStore.viewMapCEN; return context.view.projectPublicViewStore.viewById(viewId); default: return undefined; diff --git a/web/hooks/store/views/use-view.tsx b/web/hooks/store/views/use-view.tsx index 70339cdbc..ff75d16bc 100644 --- a/web/hooks/store/views/use-view.tsx +++ b/web/hooks/store/views/use-view.tsx @@ -8,7 +8,7 @@ import { TViewTypes } from "@plane/types"; import { VIEW_TYPES } from "constants/view"; export const useView = ( - workspaceSlug: string, + workspaceSlug: string | undefined, projectId: string | undefined, viewType: TViewTypes | undefined ): ViewRootStore | undefined => { diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/views/private/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/views/private/index.tsx index 8213069c5..ba71a05ee 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/views/private/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/views/private/index.tsx @@ -36,8 +36,10 @@ const ProjectPublicViewPage: NextPageWithLayout = observer(() => { ? `PROJECT_VIEWS_${VIEW_TYPES.PROJECT_PUBLIC_VIEWS}_${workspaceSlug.toString()}_${projectId.toString()}` : null, async () => { - await viewStore?.fetch(); - console.log("viewStore", viewStore?.viewIds); + if (workspaceSlug && projectId) { + await viewStore?.fetch(workspaceSlug?.toString(), projectId?.toString()); + console.log("viewStore", viewStore?.viewIds); + } } ); @@ -66,7 +68,7 @@ const ProjectPublicViewPage: NextPageWithLayout = observer(() => { if (!workspaceSlug || !projectId) return <>; return (
- {viewStore?.loader === "init-loader" ? ( + {viewStore?.loader === "view-loader" ? (
diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/views/public/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/views/public/index.tsx index e5b803e0a..26bda4da8 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/views/public/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/views/public/index.tsx @@ -36,8 +36,10 @@ const ProjectPublicViewPage: NextPageWithLayout = observer(() => { ? `PROJECT_VIEWS_${VIEW_TYPES.PROJECT_PUBLIC_VIEWS}_${workspaceSlug.toString()}_${projectId.toString()}` : null, async () => { - await viewStore?.fetch(); - console.log("viewStore", viewStore?.viewIds); + if (workspaceSlug && projectId) { + await viewStore?.fetch(workspaceSlug.toString(), projectId.toString()); + console.log("viewStore", viewStore?.viewIds); + } } ); @@ -66,7 +68,7 @@ const ProjectPublicViewPage: NextPageWithLayout = observer(() => { if (!workspaceSlug || !projectId) return <>; return (
- {viewStore?.loader === "init-loader" ? ( + {viewStore?.loader === "view-loader" ? (
diff --git a/web/pages/[workspaceSlug]/views/private/[viewId].tsx b/web/pages/[workspaceSlug]/views/private/[viewId].tsx index 15bc3a345..d5dc695d8 100644 --- a/web/pages/[workspaceSlug]/views/private/[viewId].tsx +++ b/web/pages/[workspaceSlug]/views/private/[viewId].tsx @@ -5,10 +5,11 @@ import { CheckCircle } from "lucide-react"; import { AppLayout } from "layouts/app-layout"; // components import { GlobalViewRoot, ViewHeader } from "components/view"; +import { GlobalViewIssueLayoutRoot } from "components/issues"; // types import { NextPageWithLayout } from "lib/types"; // constants -import { EViewPageType, VIEW_TYPES } from "constants/view"; +import { ELocalViews, EViewPageType, VIEW_TYPES } from "constants/view"; const WorkspacePrivateViewPage: NextPageWithLayout = () => { const router = useRouter(); @@ -19,12 +20,12 @@ const WorkspacePrivateViewPage: NextPageWithLayout = () => { { key: VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS, title: "Private", - href: `/${workspaceSlug}/views/private/assigned`, + href: `/${workspaceSlug}/views/private/${ELocalViews.ASSIGNED}`, }, { key: VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS, title: "Public", - href: `/${workspaceSlug}/views/public/all-issues`, + href: `/${workspaceSlug}/views/public/${ELocalViews.ALL_ISSUES}`, }, ], [workspaceSlug] @@ -44,7 +45,9 @@ const WorkspacePrivateViewPage: NextPageWithLayout = () => { workspaceViewTabOptions={workspaceViewTabOptions} />
+
+
{/* content */} { />
-
- Issues render placeholder + {/* issues */} +
+
+ + {/* TODO: once the functionality is done implement the empty states */} + {/* */}
); }; diff --git a/web/pages/[workspaceSlug]/views/public/[viewId].tsx b/web/pages/[workspaceSlug]/views/public/[viewId].tsx index 168f8b8cc..a183ca389 100644 --- a/web/pages/[workspaceSlug]/views/public/[viewId].tsx +++ b/web/pages/[workspaceSlug]/views/public/[viewId].tsx @@ -5,6 +5,7 @@ import { CheckCircle } from "lucide-react"; import { AppLayout } from "layouts/app-layout"; // components import { GlobalViewRoot, ViewHeader } from "components/view"; +import { GlobalViewIssueLayoutRoot } from "components/issues"; // types import { NextPageWithLayout } from "lib/types"; // constants @@ -44,7 +45,9 @@ const WorkspacePublicViewPage: NextPageWithLayout = () => { workspaceViewTabOptions={workspaceViewTabOptions} />
+
+
{/* content */} { />
-
- Issues render placeholder + {/* issues */} +
+
); diff --git a/web/store/issue/workspace/issue.store.ts b/web/store/issue/workspace/issue.store.ts index e168f85c2..e445198f6 100644 --- a/web/store/issue/workspace/issue.store.ts +++ b/web/store/issue/workspace/issue.store.ts @@ -7,7 +7,8 @@ import { WorkspaceService } from "services/workspace.service"; import { IssueService } from "services/issue"; // types import { IIssueRootStore } from "../root.store"; -import { TIssue, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types"; +import { TIssue, TLoader, TUnGroupedIssues, TViewDisplayFilters, ViewFlags } from "@plane/types"; +import { VIEW_TYPES, generateViewStoreKey } from "constants/view"; export interface IWorkspaceIssues { // observable @@ -17,7 +18,7 @@ export interface IWorkspaceIssues { // computed groupedIssueIds: { dataViewId: string; issueIds: TUnGroupedIssues | undefined }; // actions - fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader) => Promise; + fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader, query?: any) => Promise; createIssue: ( workspaceSlug: string, projectId: string, @@ -76,39 +77,52 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue } get groupedIssueIds() { - const viewId = this.rootIssueStore.globalViewId; - const workspaceSlug = this.rootIssueStore.workspaceSlug; - if (!workspaceSlug || !viewId) return { dataViewId: "", issueIds: undefined }; + const { workspaceSlug, projectId, currentViewType, currentViewId } = this.rootIssueStore.rootStore.view; + if (!workspaceSlug || !currentViewId || !currentViewType) return { dataViewId: "", issueIds: undefined }; - const uniqueViewId = `${workspaceSlug}_${viewId}`; + const viewRootKey = generateViewStoreKey(workspaceSlug, projectId, currentViewType); + let displayFilters: TViewDisplayFilters | undefined = undefined; - const displayFilters = this.rootIssueStore?.workspaceIssuesFilter?.filters?.[viewId]?.displayFilters; - if (!displayFilters) return { dataViewId: viewId, issueIds: undefined }; + if (currentViewType === VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS) + displayFilters = + this.rootIssueStore?.rootStore?.view?.workspacePrivateViewStore?.viewMap?.[viewRootKey]?.[currentViewId] + ?.appliedFilters?.display_filters; + else if (currentViewType === VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS) + displayFilters = + this.rootIssueStore?.rootStore?.view?.workspacePublicViewStore?.viewMap?.[viewRootKey]?.[currentViewId] + ?.appliedFilters?.display_filters; + + if (!displayFilters) return { dataViewId: currentViewId, issueIds: undefined }; const orderBy = displayFilters?.order_by; + const uniqueViewId = `${workspaceSlug}_${currentViewId}`; const viewIssueIds = this.issues[uniqueViewId]; - if (!viewIssueIds) return { dataViewId: viewId, issueIds: undefined }; + if (!viewIssueIds) return { dataViewId: currentViewId, issueIds: undefined }; const _issues = this.rootStore.issues.getIssuesByIds(viewIssueIds); - if (!_issues) return { dataViewId: viewId, issueIds: [] }; + if (!_issues) return { dataViewId: currentViewId, issueIds: [] }; let issueIds: TIssue | TUnGroupedIssues | undefined = undefined; issueIds = this.unGroupedIssues(orderBy ?? "-created_at", _issues); - return { dataViewId: viewId, issueIds }; + return { dataViewId: currentViewId, issueIds }; } - fetchIssues = async (workspaceSlug: string, viewId: string, loadType: TLoader = "init-loader") => { + fetchIssues = async ( + workspaceSlug: string, + viewId: string, + loadType: TLoader = "init-loader", + query: any = undefined + ) => { try { this.loader = loadType; const uniqueViewId = `${workspaceSlug}_${viewId}`; - const params = this.rootIssueStore?.workspaceIssuesFilter?.getAppliedFilters(viewId); - const response = await this.workspaceService.getViewIssues(workspaceSlug, params); + const response = await this.workspaceService.getViewIssues(workspaceSlug, query || {}); runInAction(() => { set( diff --git a/web/store/view/helpers/filters_helpers.ts b/web/store/view/helpers/filters_helpers.ts index 5b0d43096..621414f03 100644 --- a/web/store/view/helpers/filters_helpers.ts +++ b/web/store/view/helpers/filters_helpers.ts @@ -101,8 +101,10 @@ export class FiltersHelper { Object.keys(filteredParams).forEach((key) => { const _key = key as TViewFilterQueryParams; const _value: string | boolean | string[] | undefined = filteredParams[_key]; - if (_value != undefined && acceptableParamsByLayout.includes(_key)) - paramsObject[_key] = Array.isArray(_value) ? _value.join(",") : _value; + if (_value != undefined && acceptableParamsByLayout.includes(_key)) { + if (Array.isArray(_value)) _value.length > 0 && (paramsObject[_key] = _value.join(",")); + else paramsObject[_key] = _value; + } }); if (paramsObject && !isEmpty(paramsObject)) { @@ -110,7 +112,7 @@ export class FiltersHelper { .map((key) => { const _key = key as TViewFilterQueryParams; const _value: string | boolean | undefined = paramsObject[_key]; - if (!undefined) return `${_key}=${_value}`; + if (_value) return `${_key}=${_value}`; }) .join("&"); } diff --git a/web/store/view/root.store.ts b/web/store/view/root.store.ts index b9bbadb87..f4e27be53 100644 --- a/web/store/view/root.store.ts +++ b/web/store/view/root.store.ts @@ -1,3 +1,4 @@ +import { action, autorun, makeObservable, observable, runInAction } from "mobx"; // stores import { ViewRootStore } from "./view-root.store"; // services @@ -13,14 +14,39 @@ import { import { RootStore } from "store/root.store"; // constants import { EViewPageType, VIEW_TYPES } from "constants/view"; +import { TViewTypes } from "@plane/types"; export class GlobalViewRootStore { + workspaceSlug: string | undefined = undefined; + projectId: string | undefined = undefined; + currentViewId: string | undefined = undefined; + currentViewType: TViewTypes | undefined = undefined; + currentUserId: string | undefined = undefined; + workspacePrivateViewStore: ViewRootStore; workspacePublicViewStore: ViewRootStore; projectPrivateViewStore: ViewRootStore; projectPublicViewStore: ViewRootStore; constructor(private store: RootStore) { + makeObservable(this, { + workspaceSlug: observable.ref, + projectId: observable.ref, + currentViewId: observable.ref, + currentViewType: observable.ref, + currentUserId: observable.ref, + // actions + setWorkspaceSlug: action, + setProjectId: action, + setCurrentViewId: action, + setCurrentViewType: action, + setCurrentUserId: action, + }); + + autorun(() => { + this.currentUserId = store.user.currentUser?.id; + }); + const workspacePrivateDefaultViews: any[] = [ { id: "assigned", @@ -80,4 +106,11 @@ export class GlobalViewRootStore { VIEW_TYPES.PROJECT_PUBLIC_VIEWS ); } + + // helper actions + setWorkspaceSlug = (workspaceSlug: string | undefined) => runInAction(() => (this.workspaceSlug = workspaceSlug)); + setProjectId = (projectId: string | undefined) => runInAction(() => (this.projectId = projectId)); + setCurrentViewId = (viewId: string | undefined) => runInAction(() => (this.currentViewId = viewId)); + setCurrentViewType = (viewType: TViewTypes | undefined) => runInAction(() => (this.currentViewType = viewType)); + setCurrentUserId = (userId: string | undefined) => runInAction(() => (this.currentUserId = userId)); } diff --git a/web/store/view/view-root.store.ts b/web/store/view/view-root.store.ts index 58b4370af..343dbd274 100644 --- a/web/store/view/view-root.store.ts +++ b/web/store/view/view-root.store.ts @@ -3,6 +3,7 @@ import { computedFn } from "mobx-utils"; import set from "lodash/set"; import sortBy from "lodash/sortBy"; import reverse from "lodash/reverse"; +import cloneDeep from "lodash/cloneDeep"; // stores import { RootStore } from "store/root.store"; import { ViewStore } from "./view.store"; @@ -10,30 +11,40 @@ import { ViewStore } from "./view.store"; import { TUserViewService, TViewService } from "services/view/types"; import { TView, TViewTypes } from "@plane/types"; // constants -import { EViewPageType } from "constants/view"; +import { EViewPageType, TViewCRUD, generateViewStoreKey, viewLocalPayload } from "constants/view"; -export type TLoader = "init-loader" | "mutation-loader" | "submitting" | undefined; +export type TLoader = + | "view-loader" + | "view-mutation-loader" + | "view-detail-loader" + | "create-submitting" + | "delete-submitting" + | "duplicate-submitting" + | undefined; type TViewRootStore = { // observables loader: TLoader; - viewMap: Record>>; // workspaceSlug/projectId, public/private, viewId -> ViewStore + viewMapCEN: ViewStore | undefined; // view map Create, Edit, and save as New + viewMap: Record>; // workspaceSlug/projectId/TViewType.toString(), viewId -> ViewStore // computed viewIds: string[]; viewById: (viewId: string) => ViewStore | undefined; + localView: () => ViewStore | undefined; // actions - localViewCreate: (workspaceSlug: string, projectId: string | undefined, view: TView) => Promise; fetch: (workspaceSlug: string, projectId: string | undefined, _loader?: TLoader) => Promise; fetchById: (workspaceSlug: string, projectId: string | undefined, viewId: string) => Promise; - create: (workspaceSlug: string, projectId: string | undefined, view: Partial) => Promise; - remove: (workspaceSlug: string, projectId: string | undefined, viewId: string) => Promise; - duplicate: (workspaceSlug: string, projectId: string | undefined, viewId: string) => Promise; + remove: (viewId: string) => Promise; + localViewHandler: (viewId: string | undefined, status: TViewCRUD) => void; + create: () => Promise; + duplicate: (viewId: string) => Promise; }; export class ViewRootStore implements TViewRootStore { // observables - loader: TLoader = "init-loader"; - viewMap: Record>> = {}; + loader: TLoader = "view-loader"; + viewMapCEN: ViewStore | undefined = undefined; + viewMap: Record> = {}; constructor( private store: RootStore, @@ -46,11 +57,12 @@ export class ViewRootStore implements TViewRootStore { makeObservable(this, { // observables loader: observable.ref, + viewMapCEN: observable, viewMap: observable, // computed viewIds: computed, // actions - localViewCreate: action, + localViewHandler: action, fetch: action, fetchById: action, create: action, @@ -61,56 +73,46 @@ export class ViewRootStore implements TViewRootStore { // computed get viewIds() { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return []; + const { workspaceSlug, projectId, currentViewType } = this.store.view; + if (!workspaceSlug || !currentViewType) return []; - const viewRootSlug = projectId ? projectId : workspaceSlug; - const views = this.viewMap?.[viewRootSlug]?.[this.viewType] - ? Object.values(this.viewMap?.[viewRootSlug]?.[this.viewType]) - : []; + const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, currentViewType); + const views = this.viewMap?.[viewRootSlug] ? Object.values(this.viewMap?.[viewRootSlug]) : []; const localViews = views.filter((view) => view.is_local_view); - let apiViews = views.filter((view) => !view.is_local_view && !view.is_create); + let apiViews = views.filter((view) => !view.is_local_view); apiViews = reverse(sortBy(apiViews, "sort_order")); const _viewIds = [...localViews.map((view) => view.id), ...apiViews.map((view) => view.id)]; return _viewIds as string[]; } viewById = computedFn((viewId: string) => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return undefined; + const { workspaceSlug, projectId, currentViewType } = this.store.view; + if (!workspaceSlug || !currentViewType) return undefined; - const viewRootSlug = projectId ? projectId : workspaceSlug; - return this.viewMap?.[viewRootSlug]?.[this.viewType]?.[viewId] || undefined; + const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, currentViewType); + return this.viewMap?.[viewRootSlug]?.[viewId] || undefined; }); + localView = computedFn(() => this.viewMapCEN); + // actions - localViewCreate = async (workspaceSlug: string, projectId: string | undefined, view: TView) => { - const viewRootSlug = projectId ? projectId : workspaceSlug; - - runInAction(() => { - if (view.id) - set( - this.viewMap, - [viewRootSlug, this.viewType, view.id], - new ViewStore(this.store, view, this.service, this.userService, this.viewPageType) - ); - }); - }; - - fetch = async (workspaceSlug: string, projectId: string | undefined, _loader: TLoader = "init-loader") => { + fetch = async (workspaceSlug: string, projectId: string | undefined, _loader: TLoader = "view-loader") => { try { - const viewRootSlug = projectId ? projectId : workspaceSlug; + runInAction(() => (this.loader = _loader)); - this.loader = _loader; + this.store.view.setWorkspaceSlug(workspaceSlug); + this.store.view.setProjectId(projectId); + this.store.view.setCurrentViewType(this.viewType); + const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType); if (this.defaultViews && this.defaultViews.length > 0) runInAction(() => { this.defaultViews?.forEach((view) => { if (view.id) set( this.viewMap, - [viewRootSlug, this.viewType, view.id], + [viewRootSlug, view.id], new ViewStore(this.store, view, this.service, this.userService, this.viewPageType) ); }); @@ -118,100 +120,155 @@ export class ViewRootStore implements TViewRootStore { const views = await this.service.fetch(workspaceSlug, projectId); if (!views) return; - runInAction(() => { views.forEach((view) => { if (view.id) set( this.viewMap, - [viewRootSlug, this.viewType, view.id], + [viewRootSlug, view.id], new ViewStore(this.store, view, this.service, this.userService, this.viewPageType) ); }); this.loader = undefined; }); - } catch {} + } catch { + runInAction(() => (this.loader = undefined)); + } }; fetchById = async (workspaceSlug: string, projectId: string | undefined, viewId: string) => { try { - const viewRootSlug = projectId ? projectId : workspaceSlug; + runInAction(() => (this.loader = "view-detail-loader")); + + this.store.view.setWorkspaceSlug(workspaceSlug); + this.store.view.setProjectId(projectId); + this.store.view.setCurrentViewId(viewId); + this.store.view.setCurrentViewType(this.viewType); + const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType); const userView = await this.userService.fetch(workspaceSlug, projectId); if (!userView) return; let view: TView | undefined = undefined; - if (["all-issues", "assigned", "created", "subscribed"].includes(viewId)) { const currentView = { ...this.viewById(viewId) } as TView; if (!currentView) return; - view = currentView; - view.filters = userView.filters; - view.display_filters = userView.display_filters; - view.display_properties = userView.display_properties; } else { const currentView = await this.service.fetchById(workspaceSlug, viewId, projectId); if (!currentView) return; - view = currentView; - view?.display_filters && (view.display_filters = userView.display_filters); - view?.display_properties && (view.display_properties = userView.display_properties); } + view?.display_filters && (view.display_filters = userView.display_filters); + view?.display_properties && (view.display_properties = userView.display_properties); + if (!view) return; runInAction(() => { if (view?.id) set( this.viewMap, - [viewRootSlug, this.viewType, view.id], + [viewRootSlug, view.id], new ViewStore(this.store, view, this.service, this.userService, this.viewPageType) ); }); - } catch {} + + // fetching the issues + const filterParams = this.viewMap?.[viewRootSlug]?.[viewId]?.appliedFiltersQueryParams?.params; + this.store.issue.workspaceIssues.fetchIssues(workspaceSlug, viewId, "init-loader", filterParams); + + runInAction(() => (this.loader = undefined)); + } catch { + runInAction(() => (this.loader = undefined)); + } }; - create = async (workspaceSlug: string, projectId: string | undefined, data: Partial) => { + remove = async (viewId: string) => { try { - const viewRootSlug = projectId ? projectId : workspaceSlug; + const { workspaceSlug, projectId, currentViewType } = this.store.view; + if (!workspaceSlug || !currentViewType) return undefined; - const view = await this.service.create(workspaceSlug, data, projectId); + runInAction(() => (this.loader = "delete-submitting")); + + const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType); + + await this.service.remove?.(workspaceSlug, viewId, projectId); + + runInAction(() => { + delete this.viewMap?.[viewRootSlug]?.[viewId]; + this.loader = undefined; + }); + } catch { + runInAction(() => (this.loader = undefined)); + } + }; + + localViewHandler = (viewId: string | undefined, status: TViewCRUD) => { + const { workspaceSlug, projectId, currentViewType } = this.store.view; + if (!workspaceSlug || !currentViewType) return undefined; + + if (status === "CLEAR") { + runInAction(() => (this.viewMapCEN = undefined)); + return; + } + + const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, currentViewType); + + let _view: Partial = {}; + if (status === "CREATE") _view = cloneDeep(viewLocalPayload); + else if (status === "EDIT") { + if (!viewId) return; + _view = cloneDeep(this.viewMap?.[viewRootSlug]?.[viewId]); + } else if (status === "SAVE_AS_NEW") { + if (!viewId) return; + _view = cloneDeep(this.viewMap?.[viewRootSlug]?.[viewId]); + } else return; + + runInAction(() => { + if (_view.id) + set( + this, + ["viewMapCEN"], + new ViewStore(this.store, _view as TView, this.service, this.userService, this.viewPageType) + ); + }); + }; + + create = async () => { + try { + const { workspaceSlug, projectId } = this.store.view; + if (!workspaceSlug || !this.viewMapCEN) return; + + runInAction(() => (this.loader = "create-submitting")); + + const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType); + + const view = await this.service.create(workspaceSlug, this.viewMapCEN, projectId); if (!view) return; runInAction(() => { if (view.id) set( this.viewMap, - [viewRootSlug, this.viewType, view.id], + [viewRootSlug, view.id], new ViewStore(this.store, view, this.service, this.userService, this.viewPageType) ); + this.viewMapCEN = undefined; + this.loader = undefined; }); - - if (data.id) this.remove(workspaceSlug, projectId, data.id); - } catch {} + } catch { + runInAction(() => (this.loader = undefined)); + } }; - remove = async (workspaceSlug: string, projectId: string | undefined, viewId: string) => { + duplicate = async (viewId: string) => { try { - const viewRootSlug = projectId ? projectId : workspaceSlug; + const { workspaceSlug, projectId } = this.store.view; + if (!workspaceSlug || !this.service.duplicate) return; - if ( - this.viewMap?.[viewRootSlug]?.[this.viewType]?.[viewId] != undefined && - !this.viewMap?.[viewRootSlug]?.[this.viewType]?.[viewId]?.is_create - ) - await this.service.remove?.(workspaceSlug, viewId, projectId); + runInAction(() => (this.loader = "duplicate-submitting")); - runInAction(() => { - delete this.viewMap?.[viewRootSlug]?.[this.viewType]?.[viewId]; - }); - } catch {} - }; - - duplicate = async (workspaceSlug: string, projectId: string | undefined, viewId: string) => { - try { - if (!this.service.duplicate) return; - - const viewRootSlug = projectId ? projectId : workspaceSlug; + const viewRootSlug = generateViewStoreKey(workspaceSlug, projectId, this.viewType); const view = await this.service.duplicate(workspaceSlug, viewId, projectId); if (!view) return; @@ -220,10 +277,13 @@ export class ViewRootStore implements TViewRootStore { if (view.id) set( this.viewMap, - [viewRootSlug, this.viewType, view.id], + [viewRootSlug, view.id], new ViewStore(this.store, view, this.service, this.userService, this.viewPageType) ); + this.loader = undefined; }); - } catch {} + } catch { + runInAction(() => (this.loader = undefined)); + } }; } diff --git a/web/store/view/view.store.ts b/web/store/view/view.store.ts index 6ecaf0b40..f155e3f2d 100644 --- a/web/store/view/view.store.ts +++ b/web/store/view/view.store.ts @@ -36,7 +36,7 @@ export type TViewStore = TView & { filtersToUpdate: TUpdateView; // computed appliedFilters: TViewFilterProps | undefined; - appliedFiltersQueryParams: string | undefined; + appliedFiltersQueryParams: { params: Object; query: string } | undefined; isFiltersApplied: boolean; isFiltersUpdateEnabled: boolean; // helper actions @@ -45,7 +45,6 @@ export type TViewStore = TView & { setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => void; setDisplayFilters: (display_filters: Partial) => void; setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void; - setIsEditable: (id_editable: boolean) => void; resetChanges: () => void; saveChanges: () => Promise; // actions @@ -77,8 +76,6 @@ export class ViewStore extends FiltersHelper implements TViewStore { created_at: Date | undefined; updated_at: Date | undefined; is_local_view: boolean = false; - is_create: boolean = false; - is_editable: boolean = false; loader: TLoader = undefined; filtersToUpdate: TUpdateView; @@ -110,8 +107,6 @@ export class ViewStore extends FiltersHelper implements TViewStore { this.created_at = _view.created_at; this.updated_at = _view.updated_at; this.is_local_view = _view.is_local_view; - this.is_create = _view.is_create; - this.is_editable = _view.is_editable; this.filtersToUpdate = { name: this.name, description: this.description, @@ -142,8 +137,7 @@ export class ViewStore extends FiltersHelper implements TViewStore { created_at: observable.ref, updated_at: observable.ref, is_local_view: observable.ref, - is_create: observable.ref, - is_editable: observable.ref, + loader: observable.ref, filtersToUpdate: observable, // computed @@ -156,7 +150,6 @@ export class ViewStore extends FiltersHelper implements TViewStore { setFilters: action, setDisplayFilters: action, setDisplayProperties: action, - setIsEditable: action, resetChanges: action, saveChanges: action, // actions @@ -197,8 +190,7 @@ export class ViewStore extends FiltersHelper implements TViewStore { layout, EFilterTypes.FILTERS ); - - return this.computeAppliedFiltersQueryParameters(appliedFilters, requiredFilterProperties)?.query || undefined; + return this.computeAppliedFiltersQueryParameters(appliedFilters, requiredFilterProperties) || undefined; } get isFiltersApplied() { @@ -247,6 +239,12 @@ export class ViewStore extends FiltersHelper implements TViewStore { return concat(_values, filterValue); }); }); + + const { workspaceSlug } = this.store.view; + if (workspaceSlug && this.id) { + const filterParams = this.appliedFiltersQueryParams?.params; + this.store.issue.workspaceIssues.fetchIssues(workspaceSlug, this.id, "mutation", filterParams); + } }; setDisplayFilters = async (display_filters: Partial) => { @@ -288,12 +286,6 @@ export class ViewStore extends FiltersHelper implements TViewStore { this.updateUserDisplayProperties({ [EFilterTypes.DISPLAY_PROPERTIES]: this.filtersToUpdate.display_properties }); }; - setIsEditable = (is_editable: boolean) => { - runInAction(() => { - this.is_editable = is_editable; - }); - }; - resetChanges = () => { runInAction(() => { const _view = cloneDeep(this); @@ -310,11 +302,8 @@ export class ViewStore extends FiltersHelper implements TViewStore { saveChanges = async () => { try { if (!this.id) return; - - if (["all-issues", "assigned", "created", "subscribed"].includes(this.id)) { - const payload = this.filtersToUpdate.filters; - await this.updateUserFilters({ [EFilterTypes.FILTERS]: payload }); - } else await this.update(this.filtersToUpdate); + console.log("coming here"); + await this.update(this.filtersToUpdate); } catch { Object.keys(this.filtersToUpdate).forEach((key) => { const _key = key as keyof TUpdateView; @@ -326,13 +315,14 @@ export class ViewStore extends FiltersHelper implements TViewStore { // actions update = async (viewData: TUpdateView) => { try { - if (!this.workspace || !this.id) return; + const { workspaceSlug, projectId } = this.store.view; + if (!workspaceSlug || !this.id) return; runInAction(() => { this.loader = "updating"; }); - const view = await this.service.update(this.workspace, this.id, viewData, this.project); + const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); if (!view) return; runInAction(() => { @@ -349,9 +339,10 @@ export class ViewStore extends FiltersHelper implements TViewStore { lockView = async () => { try { - if (!this.workspace || !this.id || !this.service.lock) return; + const { workspaceSlug, projectId } = this.store.view; + if (!workspaceSlug || !this.id || !this.service.lock) return; - const view = await this.service.lock(this.workspace, this.id, this.project); + const view = await this.service.lock(workspaceSlug, this.id, projectId); if (!view) return; runInAction(() => { @@ -364,9 +355,10 @@ export class ViewStore extends FiltersHelper implements TViewStore { unlockView = async () => { try { - if (!this.workspace || !this.id || !this.service.unlock) return; + const { workspaceSlug, projectId } = this.store.view; + if (!workspaceSlug || !this.id || !this.service.unlock) return; - const view = await this.service.unlock(this.workspace, this.id, this.project); + const view = await this.service.unlock(workspaceSlug, this.id, projectId); if (!view) return; runInAction(() => { @@ -379,9 +371,10 @@ export class ViewStore extends FiltersHelper implements TViewStore { makeFavorite = async () => { try { - if (!this.workspace || !this.id || !this.service.makeFavorite) return; + const { workspaceSlug, projectId } = this.store.view; + if (!workspaceSlug || !this.id || !this.service.makeFavorite) return; - const view = await this.service.makeFavorite(this.workspace, this.id, this.project); + const view = await this.service.makeFavorite(workspaceSlug, this.id, projectId); if (!view) return; runInAction(() => { @@ -394,9 +387,10 @@ export class ViewStore extends FiltersHelper implements TViewStore { removeFavorite = async () => { try { - if (!this.workspace || !this.id || !this.service.removeFavorite) return; + const { workspaceSlug, projectId } = this.store.view; + if (!workspaceSlug || !this.id || !this.service.removeFavorite) return; - const view = await this.service.removeFavorite(this.workspace, this.id, this.project); + const view = await this.service.removeFavorite(workspaceSlug, this.id, projectId); if (!view) return; runInAction(() => { @@ -410,7 +404,7 @@ export class ViewStore extends FiltersHelper implements TViewStore { // updating the user specific filters, display filters, and display properties updateUserFilters = async (filters: { [EFilterTypes.FILTERS]: TViewFilters }) => { try { - const { workspaceSlug, projectId } = this.store.app.router; + const { workspaceSlug, projectId } = this.store.view; if (!workspaceSlug) return; const userView = await this.userService.update(workspaceSlug, filters, projectId); @@ -428,7 +422,7 @@ export class ViewStore extends FiltersHelper implements TViewStore { updateUserDisplayFilters = async (display_filters: { [EFilterTypes.DISPLAY_FILTERS]: TViewDisplayFilters }) => { try { - const { workspaceSlug, projectId } = this.store.app.router; + const { workspaceSlug, projectId } = this.store.view; if (!workspaceSlug) return; const userView = await this.userService.update(workspaceSlug, display_filters, projectId); @@ -448,7 +442,7 @@ export class ViewStore extends FiltersHelper implements TViewStore { [EFilterTypes.DISPLAY_PROPERTIES]: TViewDisplayProperties; }) => { try { - const { workspaceSlug, projectId } = this.store.app.router; + const { workspaceSlug, projectId } = this.store.view; if (!workspaceSlug) return; const userView = await this.userService.update(workspaceSlug, display_properties, projectId);