From d57d91e530d372f15fed2f5a824a116537fb7282 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Fri, 2 Feb 2024 13:52:38 +0530 Subject: [PATCH] fix: store updates --- packages/types/src/view/base.d.ts | 59 +++--- packages/types/src/view/filter.d.ts | 37 ++-- packages/types/src/view/root.d.ts | 1 + packages/types/src/view/user-base.d.ts | 21 ++ web/components/issues/issue-detail/root.tsx | 2 +- .../view/applied-filters/filter-item.tsx | 17 ++ .../view/applied-filters/filter.tsx | 51 +++++ .../view/applied-filters/helper.tsx | 66 +++++++ web/components/view/applied-filters/root.tsx | 45 +++++ web/components/view/display-filters/root.tsx | 17 ++ .../view/display-properties/root.tsx | 17 ++ web/components/view/filters/root.tsx | 34 ++++ web/components/view/index.ts | 18 ++ web/components/view/root.tsx | 77 ++++++++ web/components/view/types.d.ts | 6 + .../view/views/create-edit-form.tsx | 104 ++++++++++ web/components/view/views/create-edit.tsx | 42 ++++ web/components/view/views/root.tsx | 36 ++++ web/constants/view.ts | 47 +++++ web/hooks/store/index.ts | 4 + web/hooks/store/use-view-detail.tsx | 38 ++++ web/hooks/store/use-view.tsx | 35 ++++ web/services/issue_filter.service.ts | 35 ++-- web/services/view/index.ts | 14 ++ web/services/view/project.service.ts | 4 +- web/services/view/project_me.service.ts | 4 +- web/services/view/types.d.ts | 25 ++- web/services/view/user/cycle.service.ts | 36 ++++ web/services/view/user/module.service.ts | 39 ++++ web/services/view/user/project.service.ts | 35 ++++ web/services/view/user/workspace.service.ts | 29 +++ web/services/view/workspace.service.ts | 4 +- web/services/view/workspace_me.service.ts | 6 +- web/store/root.store.ts | 6 + .../view/{ => helpers}/filters_helpers.ts | 32 +-- web/store/view/root.store.ts | 75 +++++-- web/store/view/user/view-root.store.ts | 64 ++++++ web/store/view/user/view.store.ts | 183 ++++++++++++++++++ web/store/view/view-root.store.ts | 53 ++--- web/store/view/view.store.ts | 150 ++++++-------- 40 files changed, 1338 insertions(+), 230 deletions(-) create mode 100644 packages/types/src/view/user-base.d.ts create mode 100644 web/components/view/applied-filters/filter-item.tsx create mode 100644 web/components/view/applied-filters/filter.tsx create mode 100644 web/components/view/applied-filters/helper.tsx create mode 100644 web/components/view/applied-filters/root.tsx create mode 100644 web/components/view/display-filters/root.tsx create mode 100644 web/components/view/display-properties/root.tsx create mode 100644 web/components/view/filters/root.tsx create mode 100644 web/components/view/index.ts create mode 100644 web/components/view/root.tsx create mode 100644 web/components/view/types.d.ts create mode 100644 web/components/view/views/create-edit-form.tsx create mode 100644 web/components/view/views/create-edit.tsx create mode 100644 web/components/view/views/root.tsx create mode 100644 web/constants/view.ts create mode 100644 web/hooks/store/use-view-detail.tsx create mode 100644 web/hooks/store/use-view.tsx create mode 100644 web/services/view/index.ts create mode 100644 web/services/view/user/cycle.service.ts create mode 100644 web/services/view/user/module.service.ts create mode 100644 web/services/view/user/project.service.ts create mode 100644 web/services/view/user/workspace.service.ts rename web/store/view/{ => helpers}/filters_helpers.ts (84%) create mode 100644 web/store/view/user/view-root.store.ts create mode 100644 web/store/view/user/view.store.ts diff --git a/packages/types/src/view/base.d.ts b/packages/types/src/view/base.d.ts index 5108f891c..39e17b06b 100644 --- a/packages/types/src/view/base.d.ts +++ b/packages/types/src/view/base.d.ts @@ -1,34 +1,45 @@ -import { TFilters, TDisplayFilters, TDisplayProperties } from "./filter"; +import { + TViewFilters, + TViewDisplayFilters, + TViewDisplayProperties, +} from "./filter"; -declare enum EGlobalViewAccess { +export type TViewTypes = + | "WORKSPACE_YOUR_VIEWS" + | "WORKSPACE_VIEWS" + | "WORKSPACE_PROJECT_VIEWS" + | "PROJECT_VIEWS" + | "PROJECT_YOUR_VIEWS"; + +declare enum EViewAccess { "public" = 0, "private" = 1, "shared" = 2, } export type TViewAccess = - | EGlobalViewAccess.public - | EGlobalViewAccess.private - | EGlobalViewAccess.shared; + | EViewAccess.public + | EViewAccess.private + | EViewAccess.shared; export type TView = { - readonly id: string; - readonly workspace: string; - readonly project: string | undefined; - name: string; - description: string; - readonly query: string; - filters: TFilters; - display_filters: TDisplayFilters; - display_properties: TDisplayProperties; - readonly access: TViewAccess; - readonly owned_by: string; - readonly sort_order: number; - readonly is_locked: boolean; - readonly is_pinned: boolean; - readonly is_favorite: boolean; - readonly created_by: string; - readonly updated_by: string; - readonly created_at: Date; - readonly updated_at: Date; + id: string | undefined; + workspace: string | undefined; + project: string | undefined; + name: string | undefined; + description: string | undefined; + query: string | undefined; + filters: TViewFilters | undefined; + display_filters: TViewDisplayFilters | undefined; + display_properties: TViewDisplayProperties | undefined; + access: TViewAccess | undefined; + owned_by: string | undefined; + sort_order: number | undefined; + is_locked: boolean | undefined; + is_pinned: boolean | undefined; + is_favorite: boolean | undefined; + created_by: string | undefined; + updated_by: string | undefined; + created_at: Date | undefined; + updated_at: Date | undefined; }; diff --git a/packages/types/src/view/filter.d.ts b/packages/types/src/view/filter.d.ts index 6c2389139..f28b1bda0 100644 --- a/packages/types/src/view/filter.d.ts +++ b/packages/types/src/view/filter.d.ts @@ -1,8 +1,13 @@ -export type TLayouts = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt"; +export type TViewLayouts = + | "list" + | "kanban" + | "calendar" + | "spreadsheet" + | "gantt"; -export type TCalendarLayouts = "month" | "week"; +export type TViewCalendarLayouts = "month" | "week"; -export type TFilters = { +export type TViewFilters = { project: string[]; priority: string[]; state: string[]; @@ -16,8 +21,8 @@ export type TFilters = { target_date: string[]; }; -export type TDisplayFilters = { - layout: TLayouts; +export type TViewDisplayFilters = { + layout: TViewLayouts; group_by: string | undefined; sub_group_by: string | undefined; order_by: string; @@ -26,11 +31,11 @@ export type TDisplayFilters = { show_empty_groups: boolean; calendar: { show_weekends: boolean; - layout: TCalendarLayouts; + layout: TViewCalendarLayouts; }; }; -export type TDisplayProperties = { +export type TViewDisplayProperties = { assignee: boolean; start_date: boolean; due_date: boolean; @@ -46,19 +51,19 @@ export type TDisplayProperties = { updated_on: boolean; }; -export type TFilterProps = { - filters: TFilters; - display_filters: TDisplayFilters; - display_properties: TDisplayProperties; +export type TViewFilterProps = { + filters: TViewFilters | undefined; + display_filters: TViewDisplayFilters | undefined; + display_properties: TViewDisplayProperties | undefined; }; -export type TFilterPartialProps = { - filters: Partial; - display_filters: Partial; - display_properties: Partial; +export type TViewFilterPartialProps = { + filters: Partial | undefined; + display_filters: Partial | undefined; + display_properties: Partial | undefined; }; -export type TFilterQueryParams = +export type TViewFilterQueryParams = | "project" | "priority" | "state" diff --git a/packages/types/src/view/root.d.ts b/packages/types/src/view/root.d.ts index 3555fb52c..b6b3b9b8d 100644 --- a/packages/types/src/view/root.d.ts +++ b/packages/types/src/view/root.d.ts @@ -1,2 +1,3 @@ export * from "./filter"; export * from "./base"; +export * from "./user-base"; diff --git a/packages/types/src/view/user-base.d.ts b/packages/types/src/view/user-base.d.ts new file mode 100644 index 000000000..b076b4e6b --- /dev/null +++ b/packages/types/src/view/user-base.d.ts @@ -0,0 +1,21 @@ +import { + TViewFilters, + TViewDisplayFilters, + TViewDisplayProperties, +} from "./filter"; + +export type TUserView = { + id: string | undefined; + workspace: string | undefined; + project: string | undefined; + module: string | undefined; + cycle: string | undefined; + filters: TViewFilters | undefined; + display_filters: TViewDisplayFilters | undefined; + display_properties: TViewDisplayProperties | undefined; + user: string | undefined; + created_by: string | undefined; + updated_by: string | undefined; + created_at: Date | undefined; + updated_at: Date | undefined; +}; diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index fda73e94f..c184e574b 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo } from "react"; +import { FC, } from "react"; import { useRouter } from "next/router"; // components import { IssuePeekOverview } from "components/issues"; diff --git a/web/components/view/applied-filters/filter-item.tsx b/web/components/view/applied-filters/filter-item.tsx new file mode 100644 index 000000000..96de45c99 --- /dev/null +++ b/web/components/view/applied-filters/filter-item.tsx @@ -0,0 +1,17 @@ +import { FC } from "react"; + +type TViewFiltersItem = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; +}; + +export const ViewFiltersItem: FC = (props) => { + const { workspaceSlug, projectId, viewId } = props; + + return ( +
+
ViewFiltersItem
+
+ ); +}; diff --git a/web/components/view/applied-filters/filter.tsx b/web/components/view/applied-filters/filter.tsx new file mode 100644 index 000000000..55a032f71 --- /dev/null +++ b/web/components/view/applied-filters/filter.tsx @@ -0,0 +1,51 @@ +import { FC } from "react"; +import { observer } from "mobx-react-lite"; +import isEmpty from "lodash/isEmpty"; +import { X } from "lucide-react"; +// hooks +import { useViewDetail } from "hooks/store"; +// helpers +import { generateTitle } from "./helper"; +// types +import { TFilters } from "@plane/types"; + +type TViewAppliedFilters = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + filterKey: keyof TFilters; +}; + +export const ViewAppliedFilters: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, filterKey } = props; + + const view = useViewDetail("WORKSPACE", workspaceSlug, projectId, viewId); + + const filterKeyValue = + view?.appliedFilters?.filters && !isEmpty(view?.appliedFilters?.filters) + ? view?.appliedFilters?.filters?.[filterKey] || undefined + : undefined; + + if (!filterKeyValue || filterKeyValue.length <= 0) return <>; + return ( +
+
{generateTitle(filterKey)}
+
+ {/*
+
Icon
+
Title
+
Close
+
+ +
+
Icon
+
Title
+
Close
+
*/} +
+
+ +
+
+ ); +}); diff --git a/web/components/view/applied-filters/helper.tsx b/web/components/view/applied-filters/helper.tsx new file mode 100644 index 000000000..ac48c26a0 --- /dev/null +++ b/web/components/view/applied-filters/helper.tsx @@ -0,0 +1,66 @@ +import { ReactNode } from "react"; +import isEmpty from "lodash/isEmpty"; +// types +import { TFilters } from "@plane/types"; + +type TComputedAppliedFilters = { + key: string; + title: string; + selectedOptions?: { id: string; icon: ""; title: ""; component: ReactNode }[]; + dropdownOptions?: { id: string; icon: ""; title: ""; component: ReactNode }[]; +}[]; + +export const filterOptions = (key: keyof TFilters, selectedFilters: string[]) => { + switch (key) { + case "project": + return []; + case "priority": + return []; + case "state": + return []; + case "state_group": + return []; + case "assignees": + return []; + case "mentions": + return []; + case "subscriber": + return []; + case "created_by": + return []; + case "labels": + return []; + case "start_date": + return []; + case "target_date": + return []; + default: + return []; + } +}; + +export const generateTitle = (title: string) => + title + .split("_") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + +export const constructAppliedFilters = (filters: TFilters): TComputedAppliedFilters => { + const appliedFilters: TComputedAppliedFilters = []; + + if (filters && !isEmpty(filters)) { + Object.keys(filters).forEach((_filterKey) => { + const _key = _filterKey as keyof TFilters; + const _value = filters[_key]; + + if (_value && !isEmpty(_value)) { + appliedFilters.push({ + key: _key, + title: generateTitle(_key), + }); + } + }); + } + + return appliedFilters; +}; diff --git a/web/components/view/applied-filters/root.tsx b/web/components/view/applied-filters/root.tsx new file mode 100644 index 000000000..fbe2d2803 --- /dev/null +++ b/web/components/view/applied-filters/root.tsx @@ -0,0 +1,45 @@ +import { FC } from "react"; +import { observer } from "mobx-react-lite"; +import isEmpty from "lodash/isEmpty"; +// hooks +import { useViewDetail } from "hooks/store"; +// components +import { ViewAppliedFilters } from "./filter"; +// types +import { TFilters } from "@plane/types"; +import { TViewOperations } from "../types"; + +type TViewAppliedFiltersRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + viewOperations: TViewOperations; +}; + +export const ViewAppliedFiltersRoot: FC = observer((props) => { + const { workspaceSlug, projectId, viewId } = props; + // hooks + const view = useViewDetail("WORKSPACE", workspaceSlug, projectId, viewId); + + const filterKeys = + view?.appliedFilters && !isEmpty(view?.appliedFilters?.filters) + ? Object.keys(view?.appliedFilters?.filters) + : undefined; + + if (!filterKeys) return <>; + return ( +
+ {filterKeys.map((key) => { + const filterKey = key as keyof TFilters; + return ( + + ); + })} +
+ ); +}); diff --git a/web/components/view/display-filters/root.tsx b/web/components/view/display-filters/root.tsx new file mode 100644 index 000000000..53991e3a1 --- /dev/null +++ b/web/components/view/display-filters/root.tsx @@ -0,0 +1,17 @@ +import { FC } from "react"; + +type TViewDisplayFiltersRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; +}; + +export const ViewDisplayFiltersRoot: FC = (props) => { + const { workspaceSlug, projectId, viewId } = props; + + return ( +
+
ViewDisplayFiltersRoot
+
+ ); +}; diff --git a/web/components/view/display-properties/root.tsx b/web/components/view/display-properties/root.tsx new file mode 100644 index 000000000..4b35bb62e --- /dev/null +++ b/web/components/view/display-properties/root.tsx @@ -0,0 +1,17 @@ +import { FC } from "react"; + +type TViewDisplayPropertiesRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; +}; + +export const ViewDisplayPropertiesRoot: FC = (props) => { + const { workspaceSlug, projectId, viewId } = props; + + return ( +
+
ViewDisplayPropertiesRoot
+
+ ); +}; diff --git a/web/components/view/filters/root.tsx b/web/components/view/filters/root.tsx new file mode 100644 index 000000000..d8bee27ab --- /dev/null +++ b/web/components/view/filters/root.tsx @@ -0,0 +1,34 @@ +import { FC } from "react"; +// types +import { TViewOperations } from "../types"; + +type TViewFiltersRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; + viewOperations: TViewOperations; +}; + +export const ViewFiltersRoot: FC = (props) => { + const { workspaceSlug, projectId, viewId, viewOperations } = props; + + const filters = { + project: ["1", "2", "3", "4", "5", "6"], + priority: ["1", "2", "3", "4", "5", "6"], + state: ["1", "2", "3", "4", "5", "6"], + state_group: ["1", "2", "3", "4", "5", "6"], + assignees: ["1", "2", "3", "4", "5", "6"], + mentions: ["1", "2", "3", "4", "5", "6"], + subscriber: ["1", "2", "3", "4", "5", "6"], + created_by: ["1", "2", "3", "4", "5", "6"], + labels: ["1", "2", "3", "4", "5", "6"], + start_date: ["1", "2", "3", "4", "5", "6"], + target_date: ["1", "2", "3", "4", "5", "6"], + }; + + return ( +
+
ViewFiltersRoot
+
+ ); +}; diff --git a/web/components/view/index.ts b/web/components/view/index.ts new file mode 100644 index 000000000..e061452e0 --- /dev/null +++ b/web/components/view/index.ts @@ -0,0 +1,18 @@ +export * from "./root"; + +// views +export * from "./views/root"; +export * from "./views/create-edit"; +export * from "./views/create-edit-form"; + +// view filters +export * from "./filters/root"; + +// view display filters +export * from "./display-filters/root"; + +// view display properties +export * from "./display-properties/root"; + +// view applied filters +export * from "./applied-filters/root"; diff --git a/web/components/view/root.tsx b/web/components/view/root.tsx new file mode 100644 index 000000000..1f45bdcb0 --- /dev/null +++ b/web/components/view/root.tsx @@ -0,0 +1,77 @@ +import { FC, ReactNode, useEffect, useMemo } from "react"; +import { observer } from "mobx-react-lite"; +// hooks +import { useView } from "hooks/store/use-view"; +// components +import { ViewRoot, ViewCreateEdit, ViewFiltersRoot, ViewAppliedFiltersRoot } from "./"; +// types +import { TViewOperations } from "./types"; +import { TViewTypes } from "@plane/types"; + +type TWorkspaceViewRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; + viewType: TViewTypes; +}; + +export const WorkspaceViewRoot: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType } = props; + // hooks + const views = useView(workspaceSlug, projectId, viewType); + + const viewOperations: TViewOperations = useMemo( + () => ({ + create: async (data) => { + await views?.create(data); + }, + fetch: async () => { + await views?.fetch(); + }, + }), + [views] + ); + + useEffect(() => { + if (workspaceSlug && viewId && viewOperations) viewOperations.fetch(); + }, [workspaceSlug, viewId, viewOperations]); + + console.log("views?.viewMap", Object.keys(views?.viewMap).length); + + Object.keys(views?.viewMap).map((viewId) => { + console.log(views?.viewMap?.[viewId]?.access); + }); + + return ( +
+ + + {/* */} + + {/* + */} +
+ ); +}); diff --git a/web/components/view/types.d.ts b/web/components/view/types.d.ts new file mode 100644 index 000000000..1e4c52c99 --- /dev/null +++ b/web/components/view/types.d.ts @@ -0,0 +1,6 @@ +import { TView } from "@plane/types"; + +export type TViewOperations = { + create: (data: Partial) => void; + fetch: () => void; +}; diff --git a/web/components/view/views/create-edit-form.tsx b/web/components/view/views/create-edit-form.tsx new file mode 100644 index 000000000..fa47392e5 --- /dev/null +++ b/web/components/view/views/create-edit-form.tsx @@ -0,0 +1,104 @@ +import { FC, Fragment } from "react"; +import { Dialog, Transition } from "@headlessui/react"; +import { Trash2, Plus, X } from "lucide-react"; +// ui +import { Input, Button } from "@plane/ui"; +// types +import { TViewOperations } from "../types"; + +type TViewCreateEditForm = { + modalToggle: boolean; + + handleModalClose: () => void; + viewOperations?: TViewOperations; +}; + +export const ViewCreateEditForm: FC = (props) => { + const { modalToggle, handleModalClose, viewOperations } = props; + + const createView = () => { + viewOperations?.create({ name: "create" }); + }; + + return ( + + + +
+ + +
+
+ + +
+
+
+ +
+
Project Identifier
+
+
Create|Edit View
+
+ +
+ +
+ +
+
+
+ +
+
Filters
+
+
+
Clear all filters
+
+ +
+
+
+ +
Applied Filters with each dropdown
+ +
+ + +
+
+
+
+
+
+
+ ); +}; diff --git a/web/components/view/views/create-edit.tsx b/web/components/view/views/create-edit.tsx new file mode 100644 index 000000000..90d9d6ff3 --- /dev/null +++ b/web/components/view/views/create-edit.tsx @@ -0,0 +1,42 @@ +import { FC, useState } from "react"; +import { Plus } from "lucide-react"; +// ui +import { Button } from "@plane/ui"; +// components +import { ViewCreateEditForm } from "./create-edit-form"; +// types +import { TViewOperations } from "../types"; +import { TViewTypes } from "@plane/types"; + +type TViewCreateEdit = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; + viewType: TViewTypes; + viewOperations: TViewOperations; +}; + +export const ViewCreateEdit: FC = (props) => { + const { workspaceSlug, projectId, viewId, viewOperations } = props; + // states + const [modalToggle, setModalToggle] = useState(false); + + const handleModalOpen = () => setModalToggle(true); + const handleModalClose = () => setModalToggle(false); + + const createView = () => { + viewOperations?.create({ name: "create" }); + }; + + return ( + <> + +
+ +
+ + ); +}; diff --git a/web/components/view/views/root.tsx b/web/components/view/views/root.tsx new file mode 100644 index 000000000..adf8c0f59 --- /dev/null +++ b/web/components/view/views/root.tsx @@ -0,0 +1,36 @@ +import { FC } from "react"; +import { ChevronRight } from "lucide-react"; +// types +import { TViewOperations } from "../types"; +import { TViewTypes } from "@plane/types"; + +type TViewRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; + viewType: TViewTypes; + viewOperations: TViewOperations; +}; + +export const ViewRoot: FC = (props) => { + const {} = props; + + return ( +
+ {/* header */} +
Workspace Views
+ {/* divider */} +
+
+
+ +
+
+ {/* views content */} +
+
Icon
+
Title
+
+
+ ); +}; diff --git a/web/constants/view.ts b/web/constants/view.ts new file mode 100644 index 000000000..cdce771ae --- /dev/null +++ b/web/constants/view.ts @@ -0,0 +1,47 @@ +import { TViewTypes } from "@plane/types"; + +export const VIEW_TYPES: Record = { + WORKSPACE_YOUR_VIEWS: "WORKSPACE_YOUR_VIEWS", + WORKSPACE_VIEWS: "WORKSPACE_VIEWS", + WORKSPACE_PROJECT_VIEWS: "WORKSPACE_PROJECT_VIEWS", + PROJECT_VIEWS: "PROJECT_VIEWS", + PROJECT_YOUR_VIEWS: "PROJECT_YOUR_VIEWS", +}; + +export const VIEW_DEFAULT_FILTER_PARAMETERS = { + filters: { + default: [ + "project", + "priority", + "state", + "state_group", + "assignees", + "mentions", + "subscriber", + "created_by", + "labels", + "start_date", + "target_date", + ], + }, + display_filters: { + default: ["layout", "group_by", "sub_group_by", "order_by", "type", "sub_issue", "show_empty_groups", "calendar"], + }, + display_properties: { + default: [ + "assignee", + "start_date", + "due_date", + "labels", + "key", + "priority", + "state", + "sub_issue_count", + "link", + "attachment_count", + "estimate", + "created_on", + "updated_on", + ], + }, +}; diff --git a/web/hooks/store/index.ts b/web/hooks/store/index.ts index a0ce6b0e2..1d52f4d24 100644 --- a/web/hooks/store/index.ts +++ b/web/hooks/store/index.ts @@ -21,3 +21,7 @@ export * from "./use-kanban-view"; export * from "./use-issue-detail"; export * from "./use-inbox"; export * from "./use-inbox-issues"; + +// new store +export * from "./use-view"; +export * from "./use-view-detail"; diff --git a/web/hooks/store/use-view-detail.tsx b/web/hooks/store/use-view-detail.tsx new file mode 100644 index 000000000..e61c558e0 --- /dev/null +++ b/web/hooks/store/use-view-detail.tsx @@ -0,0 +1,38 @@ +import { useContext } from "react"; +// mobx store +import { StoreContext } from "contexts/store-context"; +// store +import { TViewStore } from "store/view/view.store"; +// types +import { TViewTypes } from "@plane/types"; + +export const useViewDetail = ( + workspaceSlug: string, + projectId: string | undefined, + viewId: string, + viewType: TViewTypes | undefined +): TViewStore | undefined => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("useViewDetail must be used within StoreProvider"); + + if (!workspaceSlug) throw new Error("useViewDetail hook must require workspaceSlug"); + + if (!viewId) throw new Error("useViewDetail hook must require viewId"); + + switch (viewType) { + case "WORKSPACE_YOUR_VIEWS": + return context.view.workspaceViewStore.viewById(viewId); + case "WORKSPACE_VIEWS": + return context.view.workspaceViewMeStore.viewById(viewId); + case "WORKSPACE_PROJECT_VIEWS": + return context.view.workspaceViewMeStore.viewById(viewId); + case "PROJECT_YOUR_VIEWS": + if (!projectId) throw new Error("useView hook must require projectId"); + return context.view.projectViewMeStore.viewById(viewId); + case "PROJECT_VIEWS": + if (!projectId) throw new Error("useView hook must require projectId"); + return context.view.projectViewStore.viewById(viewId); + default: + throw new Error("useView hook must require viewType"); + } +}; diff --git a/web/hooks/store/use-view.tsx b/web/hooks/store/use-view.tsx new file mode 100644 index 000000000..382a268b7 --- /dev/null +++ b/web/hooks/store/use-view.tsx @@ -0,0 +1,35 @@ +import { useContext } from "react"; +// mobx store +import { StoreContext } from "contexts/store-context"; +// types +import { ViewRoot } from "store/view/view-root.store"; +// types +import { TViewTypes } from "@plane/types"; + +export const useView = ( + workspaceSlug: string, + projectId: string | undefined, + viewType: TViewTypes | undefined +): ViewRoot => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("useView must be used within StoreProvider"); + + if (!workspaceSlug) throw new Error("useView hook must require workspaceSlug"); + + switch (viewType) { + case "WORKSPACE_YOUR_VIEWS": + return context.view.workspaceViewStore; + case "WORKSPACE_VIEWS": + return context.view.workspaceViewMeStore; + case "WORKSPACE_PROJECT_VIEWS": + return context.view.workspaceViewMeStore; + case "PROJECT_YOUR_VIEWS": + if (!projectId) throw new Error("useView hook must require projectId"); + return context.view.projectViewMeStore; + case "PROJECT_VIEWS": + if (!projectId) throw new Error("useView hook must require projectId"); + return context.view.projectViewStore; + default: + throw new Error("useView hook must require viewType"); + } +}; diff --git a/web/services/issue_filter.service.ts b/web/services/issue_filter.service.ts index 5103a4bc8..07c8efbc0 100644 --- a/web/services/issue_filter.service.ts +++ b/web/services/issue_filter.service.ts @@ -10,23 +10,24 @@ export class IssueFiltersService extends APIService { } // // workspace issue filters - // async fetchWorkspaceFilters(workspaceSlug: string): Promise { - // return this.get(`/api/workspaces/${workspaceSlug}/user-properties/`) - // .then((response) => response?.data) - // .catch((error) => { - // throw error?.response?.data; - // }); - // } - // async patchWorkspaceFilters( - // workspaceSlug: string, - // data: Partial - // ): Promise { - // return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data) - // .then((response) => response?.data) - // .catch((error) => { - // throw error?.response?.data; - // }); - // } + async fetchWorkspaceFilters(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/user-properties/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async patchWorkspaceFilters( + workspaceSlug: string, + data: Partial + ): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } // project issue filters async fetchProjectIssueFilters(workspaceSlug: string, projectId: string): Promise { diff --git a/web/services/view/index.ts b/web/services/view/index.ts new file mode 100644 index 000000000..17dc9c210 --- /dev/null +++ b/web/services/view/index.ts @@ -0,0 +1,14 @@ +// view services +export * from "./workspace_me.service"; +export * from "./workspace.service"; +export * from "./project_me.service"; +export * from "./project.service"; + +// user view services +export * from "./user/workspace.service"; +export * from "./user/project.service"; +export * from "./user/module.service"; +export * from "./user/cycle.service"; + +// views that are being stored in the local-store +// export * from "./user/local_storage.service"; diff --git a/web/services/view/project.service.ts b/web/services/view/project.service.ts index 5c0f877b4..ffa9d9688 100644 --- a/web/services/view/project.service.ts +++ b/web/services/view/project.service.ts @@ -91,7 +91,7 @@ export class ProjectViewService extends APIService implements TViewService { projectId: string | undefined = undefined ): Promise { if (!projectId) return undefined; - return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/unlock/`) + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/lock/`) .then((response) => response?.data) .catch((error) => { throw error?.response; @@ -130,7 +130,7 @@ export class ProjectViewService extends APIService implements TViewService { projectId: string | undefined = undefined ): Promise { if (!projectId) return undefined; - return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/unfavorite/`) + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/favorite/`) .then((response) => response?.data) .catch((error) => { throw error?.response; diff --git a/web/services/view/project_me.service.ts b/web/services/view/project_me.service.ts index cbda0c17b..d647a08d0 100644 --- a/web/services/view/project_me.service.ts +++ b/web/services/view/project_me.service.ts @@ -91,7 +91,7 @@ export class ProjectViewMeService extends APIService implements TViewService { projectId: string | undefined = undefined ): Promise { if (!projectId) return undefined; - return this.post(`/api/users/me/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/unlock/`) + return this.delete(`/api/users/me/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/lock/`) .then((response) => response?.data) .catch((error) => { throw error?.response; @@ -130,7 +130,7 @@ export class ProjectViewMeService extends APIService implements TViewService { projectId: string | undefined = undefined ): Promise { if (!projectId) return undefined; - return this.post(`/api/users/me/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/unfavorite/`) + return this.delete(`/api/users/me/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/favorite/`) .then((response) => response?.data) .catch((error) => { throw error?.response; diff --git a/web/services/view/types.d.ts b/web/services/view/types.d.ts index f35ab1502..e1842622b 100644 --- a/web/services/view/types.d.ts +++ b/web/services/view/types.d.ts @@ -1,4 +1,15 @@ -import { TView } from "@plane/types"; +import { TView, TUserView } from "@plane/types"; + +export type TUserViewService = { + // featureId represents moduleId/cycleId + fetch: (workspaceSlug: string, projectId?: string, featureId?: string) => Promise; + update: ( + workspaceSlug: string, + data: Partial, + projectId?: string, + featureId?: string + ) => Promise; +}; export type TViewService = { fetch: (workspaceSlug: string, projectId?: string) => Promise; @@ -10,10 +21,10 @@ export type TViewService = { data: Partial, projectId?: string ) => Promise; - remove: (workspaceSlug: string, viewId: string, projectId?: string) => Promise | undefined; - lock: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; - unlock: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; - duplicate: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; - makeFavorite: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; - removeFavorite: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; + remove?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise | undefined; + lock?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; + unlock?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; + duplicate?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; + makeFavorite?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; + removeFavorite?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise; }; diff --git a/web/services/view/user/cycle.service.ts b/web/services/view/user/cycle.service.ts new file mode 100644 index 000000000..63ef7c5da --- /dev/null +++ b/web/services/view/user/cycle.service.ts @@ -0,0 +1,36 @@ +// services +import { APIService } from "services/api.service"; +// types +import type { TViewFilterProps, TUserView } from "@plane/types"; +import { TUserViewService } from "../types"; +// helpers +import { API_BASE_URL } from "helpers/common.helper"; + +export class CycleFiltersService extends APIService implements TUserViewService { + constructor() { + super(API_BASE_URL); + } + + async fetch(workspaceSlug: string, projectId?: string, cycleId?: string): Promise { + if (!projectId || !cycleId) return undefined; + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}user-properties/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async update( + workspaceSlug: string, + data: Partial, + projectId?: string, + cycleId?: string + ): Promise { + if (!projectId || !cycleId) return undefined; + return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/${cycleId}user-properties/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/web/services/view/user/module.service.ts b/web/services/view/user/module.service.ts new file mode 100644 index 000000000..0e475d3b8 --- /dev/null +++ b/web/services/view/user/module.service.ts @@ -0,0 +1,39 @@ +// services +import { APIService } from "services/api.service"; +// types +import type { TViewFilterProps, TUserView } from "@plane/types"; +import { TUserViewService } from "../types"; +// helpers +import { API_BASE_URL } from "helpers/common.helper"; + +export class ModuleFiltersService extends APIService implements TUserViewService { + constructor() { + super(API_BASE_URL); + } + + async fetch(workspaceSlug: string, projectId?: string, moduleId?: string): Promise { + if (!projectId || !moduleId) return undefined; + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}user-properties/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async update( + workspaceSlug: string, + data: Partial, + projectId?: string, + moduleId?: string + ): Promise { + if (!projectId || !moduleId) return undefined; + return this.patch( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/modules/${moduleId}user-properties/`, + data + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/web/services/view/user/project.service.ts b/web/services/view/user/project.service.ts new file mode 100644 index 000000000..7300c5d12 --- /dev/null +++ b/web/services/view/user/project.service.ts @@ -0,0 +1,35 @@ +// services +import { APIService } from "services/api.service"; +// types +import type { TViewFilterProps, TUserView } from "@plane/types"; +import { TUserViewService } from "../types"; +// helpers +import { API_BASE_URL } from "helpers/common.helper"; + +export class ProjectFiltersService extends APIService implements TUserViewService { + constructor() { + super(API_BASE_URL); + } + + async fetch(workspaceSlug: string, projectId?: string): Promise { + if (!projectId) return undefined; + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-properties/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async update( + workspaceSlug: string, + data: Partial, + projectId?: string + ): Promise { + if (!projectId) return undefined; + return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/web/services/view/user/workspace.service.ts b/web/services/view/user/workspace.service.ts new file mode 100644 index 000000000..42452a3eb --- /dev/null +++ b/web/services/view/user/workspace.service.ts @@ -0,0 +1,29 @@ +// services +import { APIService } from "services/api.service"; +// types +import type { TViewFilterProps, TUserView } from "@plane/types"; +import { TUserViewService } from "../types"; +// helpers +import { API_BASE_URL } from "helpers/common.helper"; + +export class WorkspaceFiltersService extends APIService implements TUserViewService { + constructor() { + super(API_BASE_URL); + } + + async fetch(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/user-properties/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async update(workspaceSlug: string, data: Partial): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } +} diff --git a/web/services/view/workspace.service.ts b/web/services/view/workspace.service.ts index dc2873273..9802f4a01 100644 --- a/web/services/view/workspace.service.ts +++ b/web/services/view/workspace.service.ts @@ -59,7 +59,7 @@ export class WorkspaceViewService extends APIService implements TViewService { } async unlock(workspaceSlug: string, viewId: string): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/views/${viewId}/unlock/`) + return this.delete(`/api/workspaces/${workspaceSlug}/views/${viewId}/lock/`) .then((response) => response?.data) .catch((error) => { throw error?.response; @@ -83,7 +83,7 @@ export class WorkspaceViewService extends APIService implements TViewService { } async removeFavorite(workspaceSlug: string, viewId: string): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/views/${viewId}/unfavorite/`) + return this.delete(`/api/workspaces/${workspaceSlug}/views/${viewId}/favorite/`) .then((response) => response?.data) .catch((error) => { throw error?.response; diff --git a/web/services/view/workspace_me.service.ts b/web/services/view/workspace_me.service.ts index 4e044e558..3c51c9411 100644 --- a/web/services/view/workspace_me.service.ts +++ b/web/services/view/workspace_me.service.ts @@ -11,7 +11,7 @@ export class WorkspaceMeViewService extends APIService implements TViewService { } async fetch(workspaceSlug: string): Promise { - return this.get(`/api/users/me/workspaces/${workspaceSlug}/views/`) + return this.get(`/api/users/me/workspaces/${workspaceSlug}/views/?type=workspace`) .then((response) => response?.data) .catch((error) => { throw error?.response; @@ -59,7 +59,7 @@ export class WorkspaceMeViewService extends APIService implements TViewService { } async unlock(workspaceSlug: string, viewId: string): Promise { - return this.post(`/api/users/me/workspaces/${workspaceSlug}/views/${viewId}/unlock/`) + return this.delete(`/api/users/me/workspaces/${workspaceSlug}/views/${viewId}/lock/`) .then((response) => response?.data) .catch((error) => { throw error?.response; @@ -83,7 +83,7 @@ export class WorkspaceMeViewService extends APIService implements TViewService { } async removeFavorite(workspaceSlug: string, viewId: string): Promise { - return this.post(`/api/users/me/workspaces/${workspaceSlug}/views/${viewId}/unfavorite/`) + return this.delete(`/api/users/me/workspaces/${workspaceSlug}/views/${viewId}/favorite/`) .then((response) => response?.data) .catch((error) => { throw error?.response; diff --git a/web/store/root.store.ts b/web/store/root.store.ts index b3aeeea04..b7629c22a 100644 --- a/web/store/root.store.ts +++ b/web/store/root.store.ts @@ -17,10 +17,14 @@ import { IMentionStore, MentionStore } from "./mention.store"; import { DashboardStore, IDashboardStore } from "./dashboard.store"; import { IProjectPageStore, ProjectPageStore } from "./project-page.store"; import { ILabelStore, LabelStore } from "./label.store"; +// new stores +import { GlobalViewRootStore } from "./view/root.store"; enableStaticRendering(typeof window === "undefined"); export class RootStore { + view: GlobalViewRootStore; + // old store structure app: IAppRootStore; user: IUserRootStore; workspaceRoot: IWorkspaceRootStore; @@ -40,6 +44,8 @@ export class RootStore { projectPages: IProjectPageStore; constructor() { + this.view = new GlobalViewRootStore(this); + // old store structure this.app = new AppRootStore(this); this.user = new UserRootStore(this); this.workspaceRoot = new WorkspaceRootStore(this); diff --git a/web/store/view/filters_helpers.ts b/web/store/view/helpers/filters_helpers.ts similarity index 84% rename from web/store/view/filters_helpers.ts rename to web/store/view/helpers/filters_helpers.ts index f2ade24f6..0a180a7c1 100644 --- a/web/store/view/filters_helpers.ts +++ b/web/store/view/helpers/filters_helpers.ts @@ -1,10 +1,16 @@ import isEmpty from "lodash/isEmpty"; // types -import { TFilters, TDisplayFilters, TDisplayProperties, TFilterProps, TFilterQueryParams } from "@plane/types"; +import { + TViewFilters, + TViewDisplayFilters, + TViewDisplayProperties, + TViewFilterProps, + TViewFilterQueryParams, +} from "@plane/types"; export class FiltersHelper { // computed filters - computedFilters = (filters: TFilters, defaultValues?: Partial): TFilters => ({ + computedFilters = (filters: TViewFilters, defaultValues?: Partial): TViewFilters => ({ project: filters?.project || defaultValues?.project || [], priority: filters?.priority || defaultValues?.priority || [], state: filters?.state || defaultValues?.state || [], @@ -20,9 +26,9 @@ export class FiltersHelper { // computed display filters computedDisplayFilters = ( - displayFilters: TDisplayFilters, - defaultValues?: Partial - ): TDisplayFilters => ({ + displayFilters: TViewDisplayFilters, + defaultValues?: Partial + ): TViewDisplayFilters => ({ layout: displayFilters?.layout || defaultValues?.layout || "list", group_by: displayFilters?.group_by || defaultValues?.group_by || "none", sub_group_by: displayFilters?.sub_group_by || defaultValues?.sub_group_by || undefined, @@ -38,9 +44,9 @@ export class FiltersHelper { // computed display properties computedDisplayProperties = ( - displayProperties: TDisplayProperties, - defaultValues?: Partial - ): TDisplayProperties => ({ + displayProperties: TViewDisplayProperties, + defaultValues?: Partial + ): TViewDisplayProperties => ({ assignee: displayProperties?.assignee || defaultValues?.assignee || true, start_date: displayProperties?.start_date || defaultValues?.start_date || true, due_date: displayProperties?.due_date || defaultValues?.due_date || true, @@ -58,13 +64,13 @@ export class FiltersHelper { // compute filters and display_filters issue query parameters computeAppliedFiltersQueryParameters = ( - filters: TFilterProps, + filters: TViewFilterProps, acceptableParamsByLayout: string[] ): { params: any; query: string } => { - const paramsObject: Partial> = {}; + const paramsObject: Partial> = {}; let paramsString = ""; - const filteredParams: Partial> = { + const filteredParams: Partial> = { // issue filters priority: filters.filters?.priority || undefined, state_group: filters.filters?.state_group || undefined, @@ -83,7 +89,7 @@ export class FiltersHelper { }; Object.keys(filteredParams).forEach((key) => { - const _key = key as TFilterQueryParams; + 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; @@ -92,7 +98,7 @@ export class FiltersHelper { if (paramsObject && !isEmpty(paramsObject)) { paramsString = Object.keys(paramsObject) .map((key) => { - const _key = key as TFilterQueryParams; + const _key = key as TViewFilterQueryParams; const _value: string | boolean | undefined = paramsObject[_key]; if (!undefined) return `${_key}=${_value}`; }) diff --git a/web/store/view/root.store.ts b/web/store/view/root.store.ts index 185c2c7e9..c54fb55df 100644 --- a/web/store/view/root.store.ts +++ b/web/store/view/root.store.ts @@ -1,23 +1,70 @@ // services -import { WorkspaceViewService } from "services/view/workspace.service"; -import { WorkspaceMeViewService } from "services/view/workspace_me.service"; -import { ProjectViewService } from "services/view/project.service"; -import { ProjectViewMeService } from "services/view/project_me.service"; + +import { + WorkspaceViewService, + WorkspaceMeViewService, + ProjectViewService, + ProjectViewMeService, + WorkspaceFiltersService, + ProjectFiltersService, + ModuleFiltersService, + CycleFiltersService, + // LocalStorageFiltersService, +} from "services/view"; // stores -import { ViewRoot } from "./view-root.store"; +import { ViewRootStore } from "./view-root.store"; +import { userViewRootStore } from "./user/view-root.store"; // types import { RootStore } from "store/root.store"; -export class ViewRootStore { - workspaceViewStore: ViewRoot; - workspaceViewMeStore: ViewRoot; - projectViewStore: ViewRoot; - projectViewMeStore: ViewRoot; +export class GlobalViewRootStore { + // views root + workspaceViewMeStore: ViewRootStore; + workspaceViewStore: ViewRootStore; + workspaceViewProjectStore: ViewRootStore; + projectViewStore: ViewRootStore; + projectViewMeStore: ViewRootStore; + + // user views root + workspaceUserViewStore?: userViewRootStore; + projectUserViewStore?: userViewRootStore; + moduleUserViewStore?: userViewRootStore; + cycleUserViewStore?: userViewRootStore; constructor(private store: RootStore) { - this.workspaceViewStore = new ViewRoot(this.store, new WorkspaceViewService()); - this.workspaceViewMeStore = new ViewRoot(this.store, new WorkspaceMeViewService()); - this.projectViewStore = new ViewRoot(this.store, new ProjectViewService()); - this.projectViewMeStore = new ViewRoot(this.store, new ProjectViewMeService()); + // views root + this.workspaceViewMeStore = new ViewRootStore(this.store, new WorkspaceMeViewService()); + this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService()); + this.workspaceViewProjectStore = new ViewRootStore(this.store, new WorkspaceMeViewService()); + this.projectViewStore = new ViewRootStore(this.store, new ProjectViewService()); + this.projectViewMeStore = new ViewRootStore(this.store, new ProjectViewMeService()); + + // user views root + this.workspaceUserViewStore = new userViewRootStore( + new WorkspaceFiltersService(), + store.app?.router?.workspaceSlug, + undefined, + undefined + ); + this.projectUserViewStore = new userViewRootStore( + new ProjectFiltersService(), + store.app?.router?.workspaceSlug, + store.app?.router?.projectId, + undefined + ); + this.moduleUserViewStore = new userViewRootStore( + new ModuleFiltersService(), + store.app?.router?.workspaceSlug, + store.app?.router?.projectId, + store.app?.router?.moduleId + ); + this.cycleUserViewStore = new userViewRootStore( + new CycleFiltersService(), + store.app?.router?.workspaceSlug, + store.app?.router?.projectId, + store.app?.router?.cycleId + ); + // this.archivedUserViewStore = new userViewRootStore( new LocalStorageFiltersService()); + // this.draftUserViewStore = new userViewRootStore( new LocalStorageFiltersService()); } } diff --git a/web/store/view/user/view-root.store.ts b/web/store/view/user/view-root.store.ts new file mode 100644 index 000000000..2343cec55 --- /dev/null +++ b/web/store/view/user/view-root.store.ts @@ -0,0 +1,64 @@ +import { action, computed, makeObservable, observable, runInAction } from "mobx"; +import set from "lodash/set"; +// stores +import { RootStore } from "store/root.store"; +import { UserViewStore } from "./view.store"; +// types +import { TUserViewService } from "services/view/types"; + +type TUserViewRootStore = { + // observables + viewMap: Record; + // computed + viewIds: string[]; + // helper actions + viewById: (viewId: string) => UserViewStore | undefined; + // actions + fetch: () => Promise; +}; + +export class userViewRootStore implements TUserViewRootStore { + // observables + viewMap: Record = {}; + + constructor( + private service: TUserViewService, + private workspaceSlug: string | undefined, + private projectId: string | undefined, + private featureId: string | undefined // moduleId/cycleId + ) { + makeObservable(this, { + // observables + viewMap: observable.ref, + // computed + viewIds: computed, + // actions + fetch: action, + }); + } + + // computed + get viewIds() { + return Object.keys(this.viewMap); + } + + // helper actions + viewById = (viewId: string) => this.viewMap?.[viewId] || undefined; + + // actions + fetch = async () => { + if (!this.workspaceSlug) return; + + const view = await this.service.fetch(this.workspaceSlug, this.projectId, this.featureId); + if (!view) return; + + runInAction(() => { + if (this.workspaceSlug && view.id) + set( + this.viewMap, + [view.id], + new UserViewStore(view, this.service, this.workspaceSlug, this.projectId, this.featureId) + ); + }); + }; +} diff --git a/web/store/view/user/view.store.ts b/web/store/view/user/view.store.ts new file mode 100644 index 000000000..a44ee0fb0 --- /dev/null +++ b/web/store/view/user/view.store.ts @@ -0,0 +1,183 @@ +import { action, computed, makeObservable, observable, runInAction } from "mobx"; +import set from "lodash/set"; +// types +import { TUserViewService } from "services/view/types"; +import { + TUserView, + TViewFilters, + TViewDisplayFilters, + TViewDisplayProperties, + TViewFilterProps, + TViewFilterPartialProps, +} from "@plane/types"; +// helpers +import { FiltersHelper } from "../helpers/filters_helpers"; + +type TLoader = "submitting" | "submit" | undefined; + +export type TUserViewStore = TUserView & { + // observables + loader: TLoader; + filtersToUpdate: TViewFilterPartialProps; + // computed + appliedFilters: TViewFilterProps | undefined; + appliedFiltersQueryParams: string | undefined; + // helper actions + updateFilters: (filters: Partial) => void; + updateDisplayFilters: (display_filters: Partial) => void; + updateDisplayProperties: (display_properties: Partial) => void; + resetFilterChanges: () => void; + saveFilterChanges: () => void; + // actions + update: (viewData: Partial) => Promise; +}; + +export class UserViewStore extends FiltersHelper implements TUserViewStore { + id: string | undefined; + workspace: string | undefined; + project: string | undefined; + module: string | undefined; + cycle: string | undefined; + filters: TViewFilters | undefined; + display_filters: TViewDisplayFilters | undefined; + display_properties: TViewDisplayProperties | undefined; + user: string | undefined; + created_by: string | undefined; + updated_by: string | undefined; + created_at: Date | undefined; + updated_at: Date | undefined; + + loader: TLoader = undefined; + filtersToUpdate: TViewFilterPartialProps = { + filters: {}, + display_filters: {}, + display_properties: {}, + }; + + constructor( + _view: TUserView, + private service: TUserViewService, + private workspaceSlug: string, + private projectId: string | undefined, + private featureId: string | undefined // moduleId/cycleId + ) { + super(); + this.id = _view.id; + this.workspace = _view.workspace; + this.project = _view.project; + this.filters = _view.filters ? this.computedFilters(_view.filters) : undefined; + this.display_filters = _view.display_filters ? this.computedDisplayFilters(_view.display_filters) : undefined; + this.display_properties = _view.display_properties + ? this.computedDisplayProperties(_view.display_properties) + : undefined; + this.user = _view.user; + this.created_by = _view.created_by; + this.updated_by = _view.updated_by; + this.created_at = _view.created_at; + this.updated_at = _view.updated_at; + + makeObservable(this, { + // observables + loader: observable, + filtersToUpdate: observable.ref, + // computed + appliedFilters: computed, + appliedFiltersQueryParams: computed, + // helper actions + updateFilters: action, + updateDisplayFilters: action, + updateDisplayProperties: action, + resetFilterChanges: action, + saveFilterChanges: action, + // actions + update: action, + }); + } + + // computed + get appliedFilters() { + return { + filters: this.filters ? this.computedFilters(this.filters, this.filtersToUpdate.filters) : undefined, + display_filters: this.display_filters + ? this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters) + : undefined, + display_properties: this.display_properties + ? this.computedDisplayProperties(this.display_properties, this.filtersToUpdate.display_properties) + : undefined, + }; + } + + get appliedFiltersQueryParams() { + const filters = this.appliedFilters; + if (!filters) return undefined; + return this.computeAppliedFiltersQueryParameters(filters, [])?.query || undefined; + } + + // helper actions + updateFilters = (filters: Partial) => { + runInAction(() => { + this.loader = "submit"; + this.filtersToUpdate.filters = filters; + }); + }; + + updateDisplayFilters = async (display_filters: Partial) => { + const appliedFilters = this.appliedFilters; + + const layout = appliedFilters?.display_filters?.layout; + const sub_group_by = appliedFilters?.display_filters?.sub_group_by; + const group_by = appliedFilters?.display_filters?.group_by; + const sub_issue = appliedFilters?.display_filters?.sub_issue; + + if (group_by === undefined) display_filters.sub_group_by = undefined; + if (layout === "kanban") { + if (sub_group_by === group_by) display_filters.group_by = undefined; + if (group_by === null) display_filters.group_by = "state"; + } + if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false; + + runInAction(() => { + this.loader = "submit"; + this.filtersToUpdate.display_filters = display_filters; + }); + }; + + updateDisplayProperties = async (display_properties: Partial) => { + runInAction(() => { + this.loader = "submit"; + this.filtersToUpdate.display_properties = display_properties; + }); + }; + + resetFilterChanges = () => { + runInAction(() => { + this.loader = undefined; + this.filtersToUpdate = { + filters: {}, + display_filters: {}, + display_properties: {}, + }; + }); + }; + + saveFilterChanges = async () => { + this.loader = "submitting"; + if (this.appliedFilters) await this.update(this.appliedFilters); + this.loader = undefined; + }; + + // actions + update = async (viewData: Partial) => { + if (!this.workspaceSlug || !this.id) return; + + const view = await this.service.update(this.workspaceSlug, viewData, this.projectId, this.featureId); + if (!view) return; + + runInAction(() => { + Object.keys(viewData).forEach((key) => { + const _key = key as keyof TViewFilterProps; + set(this, _key, viewData[_key]); + }); + }); + }; +} diff --git a/web/store/view/view-root.store.ts b/web/store/view/view-root.store.ts index e692a37f9..654b55f1d 100644 --- a/web/store/view/view-root.store.ts +++ b/web/store/view/view-root.store.ts @@ -1,20 +1,21 @@ -// types import { action, computed, makeObservable, observable, runInAction } from "mobx"; +import set from "lodash/set"; // stores import { RootStore } from "store/root.store"; -import { ViewsStore } from "./view.store"; +import { ViewStore } from "./view.store"; // types import { TViewService } from "services/view/types"; import { TView } from "@plane/types"; -import { set } from "lodash"; export type TLoader = "" | undefined; -type TViewRoot = { +type TViewRootStore = { // observables - viewMap: Record; + viewMap: Record; // computed viewIds: string[]; + // helper actions + viewById: (viewId: string) => ViewStore | undefined; // actions fetch: () => Promise; create: (view: Partial) => Promise; @@ -22,13 +23,13 @@ type TViewRoot = { duplicate: (viewId: string) => Promise; }; -export class ViewRoot implements TViewRoot { - viewMap: Record = {}; +export class ViewRootStore implements TViewRootStore { + viewMap: Record = {}; constructor(private store: RootStore, private service: TViewService) { makeObservable(this, { // observables - viewMap: observable, + viewMap: observable.ref, // computed viewIds: computed, // actions @@ -44,15 +45,10 @@ export class ViewRoot implements TViewRoot { return Object.keys(this.viewMap); } - get views() { - return Object.values(this.viewMap); - } + // helper actions + viewById = (viewId: string) => this.viewMap?.[viewId] || undefined; // actions - /** - * @description This method is used to fetch all the views - * @returns - */ fetch = async () => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -62,16 +58,11 @@ export class ViewRoot implements TViewRoot { runInAction(() => { views.forEach((view) => { - set(this.viewMap, [view.id], new ViewsStore(this.store, view, this.service)); + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); }); }); }; - /** - * @description This method is used to create a view - * @param data: Partial - * @returns - */ create = async (data: Partial) => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -80,40 +71,30 @@ export class ViewRoot implements TViewRoot { if (!view) return; runInAction(() => { - set(this.viewMap, [view.id], new ViewsStore(this.store, view, this.service)); + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); }); }; - /** - * @description This method is used to remove a view - * @param viewId: string - * @returns - */ remove = async (viewId: string) => { const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + if (!workspaceSlug || !viewId) return; - await this.service.remove(workspaceSlug, viewId, projectId); + await this.service.remove?.(workspaceSlug, viewId, projectId); runInAction(() => { delete this.viewMap[viewId]; }); }; - /** - * @description This method is used to duplicate a view - * @param viewId: string - * @returns - */ duplicate = async (viewId: string) => { const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + if (!workspaceSlug || !this.service.duplicate) return; const view = await this.service.duplicate(workspaceSlug, viewId, projectId); if (!view) return; runInAction(() => { - set(this.viewMap, [view.id], new ViewsStore(this.store, view, this.service)); + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); }); }; } diff --git a/web/store/view/view.store.ts b/web/store/view/view.store.ts index 7341be21e..3e9197b08 100644 --- a/web/store/view/view.store.ts +++ b/web/store/view/view.store.ts @@ -6,29 +6,29 @@ import { RootStore } from "store/root.store"; import { TViewService } from "services/view/types"; import { TView, - TFilters, - TDisplayFilters, - TDisplayProperties, - TFilterProps, - TFilterPartialProps, + TViewFilters, + TViewDisplayFilters, + TViewDisplayProperties, + TViewFilterProps, + TViewFilterPartialProps, TViewAccess, } from "@plane/types"; // helpers -import { FiltersHelper } from "./filters_helpers"; +import { FiltersHelper } from "./helpers/filters_helpers"; type TLoader = "submitting" | "submit" | undefined; -export type TViewsStore = TView & { +export type TViewStore = TView & { // observables loader: TLoader; - filtersToUpdate: TFilterPartialProps; + filtersToUpdate: TViewFilterPartialProps; // computed - appliedFilters: TFilterProps | undefined; + appliedFilters: TViewFilterProps | undefined; appliedFiltersQueryParams: string | undefined; // helper actions - updateFilters: (filters: Partial) => void; - updateDisplayFilters: (display_filters: Partial) => void; - updateDisplayProperties: (display_properties: Partial) => void; + updateFilters: (filters: Partial) => void; + updateDisplayFilters: (display_filters: Partial) => void; + updateDisplayProperties: (display_properties: Partial) => void; resetFilterChanges: () => void; saveFilterChanges: () => void; // actions @@ -39,29 +39,29 @@ export type TViewsStore = TView & { update: (viewData: Partial) => Promise; }; -export class ViewsStore extends FiltersHelper implements TViewsStore { - id: string; - workspace: string; +export class ViewStore extends FiltersHelper implements TViewStore { + id: string | undefined; + workspace: string | undefined; project: string | undefined; - name: string; - description: string; - query: string; - filters: TFilters; - display_filters: TDisplayFilters; - display_properties: TDisplayProperties; - access: TViewAccess; - owned_by: string; - sort_order: number; - is_locked: boolean; - is_pinned: boolean; - is_favorite: boolean; - created_by: string; - updated_by: string; - created_at: Date; - updated_at: Date; + name: string | undefined; + description: string | undefined; + query: string | undefined; + filters: TViewFilters | undefined; + display_filters: TViewDisplayFilters | undefined; + display_properties: TViewDisplayProperties | undefined; + access: TViewAccess | undefined; + owned_by: string | undefined; + sort_order: number | undefined; + is_locked: boolean | undefined; + is_pinned: boolean | undefined; + is_favorite: boolean | undefined; + created_by: string | undefined; + updated_by: string | undefined; + created_at: Date | undefined; + updated_at: Date | undefined; loader: TLoader = undefined; - filtersToUpdate: TFilterPartialProps = { + filtersToUpdate: TViewFilterPartialProps = { filters: {}, display_filters: {}, display_properties: {}, @@ -75,9 +75,11 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { this.name = _view.name; this.description = _view.description; this.query = _view.query; - this.filters = this.computedFilters(_view.filters); - this.display_filters = this.computedDisplayFilters(_view.display_filters); - this.display_properties = this.computedDisplayProperties(_view.display_properties); + this.filters = _view.filters ? this.computedFilters(_view.filters) : undefined; + this.display_filters = _view.display_filters ? this.computedDisplayFilters(_view.display_filters) : undefined; + this.display_properties = _view.display_properties + ? this.computedDisplayProperties(_view.display_properties) + : undefined; this.access = _view.access; this.owned_by = _view.owned_by; this.sort_order = _view.sort_order; @@ -112,51 +114,43 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { // computed get appliedFilters() { return { - filters: this.computedFilters(this.filters, this.filtersToUpdate.filters), - display_filters: this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters), - display_properties: this.computedDisplayProperties( - this.display_properties, - this.filtersToUpdate.display_properties - ), + filters: this.filters ? this.computedFilters(this.filters, this.filtersToUpdate.filters) : undefined, + display_filters: this.display_filters + ? this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters) + : undefined, + display_properties: this.display_properties + ? this.computedDisplayProperties(this.display_properties, this.filtersToUpdate.display_properties) + : undefined, }; } get appliedFiltersQueryParams() { const filters = this.appliedFilters; + if (!filters) return undefined; return this.computeAppliedFiltersQueryParameters(filters, [])?.query || undefined; } // helper actions - /** - * @description This method is used to update the filters of the view - * @param filters: Partial - */ - updateFilters = (filters: Partial) => { + updateFilters = (filters: Partial) => { runInAction(() => { this.loader = "submit"; this.filtersToUpdate.filters = filters; }); }; - /** - * @description This method is used to update the display filters of the view - * @param display_filters: Partial - */ - updateDisplayFilters = async (display_filters: Partial) => { + updateDisplayFilters = async (display_filters: Partial) => { const appliedFilters = this.appliedFilters; - const layout = appliedFilters.display_filters.layout; - const sub_group_by = appliedFilters.display_filters.sub_group_by; - const group_by = appliedFilters.display_filters.group_by; - const sub_issue = appliedFilters.display_filters.sub_issue; + const layout = appliedFilters?.display_filters?.layout; + const sub_group_by = appliedFilters?.display_filters?.sub_group_by; + const group_by = appliedFilters?.display_filters?.group_by; + const sub_issue = appliedFilters?.display_filters?.sub_issue; if (group_by === undefined) display_filters.sub_group_by = undefined; - if (layout === "kanban") { if (sub_group_by === group_by) display_filters.group_by = undefined; if (group_by === null) display_filters.group_by = "state"; } - if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false; runInAction(() => { @@ -165,20 +159,13 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { }); }; - /** - * @description This method is used to update the display properties of the view - * @param display_properties: Partial - */ - updateDisplayProperties = async (display_properties: Partial) => { + updateDisplayProperties = async (display_properties: Partial) => { runInAction(() => { this.loader = "submit"; this.filtersToUpdate.display_properties = display_properties; }); }; - /** - * @description This method is used to reset the changes made to the filters - */ resetFilterChanges = () => { runInAction(() => { this.loader = undefined; @@ -190,9 +177,6 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { }); }; - /** - * @description This method is used to save the changes made to the filters - */ saveFilterChanges = async () => { this.loader = "submitting"; if (this.appliedFilters) await this.update(this.appliedFilters); @@ -200,13 +184,9 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { }; // actions - /** - * @description This method is used to update the view lock - * @returns - */ lockView = async () => { const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + if (!workspaceSlug || !this.id || !this.service.lock) return; const view = await this.service.lock(workspaceSlug, this.id, projectId); if (!view) return; @@ -216,13 +196,9 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { }); }; - /** - * @description This method is used to remove the view lock - * @returns - */ unlockView = async () => { const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + if (!workspaceSlug || !this.id || !this.service.unlock) return; const view = await this.service.unlock(workspaceSlug, this.id, projectId); if (!view) return; @@ -232,13 +208,9 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { }); }; - /** - * @description This method is used to update the view favorite - * @returns - */ makeFavorite = async () => { const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + if (!workspaceSlug || !this.id || !this.service.makeFavorite) return; const view = await this.service.makeFavorite(workspaceSlug, this.id, projectId); if (!view) return; @@ -248,13 +220,9 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { }); }; - /** - * @description This method is used to remove the view favorite - * @returns - */ removeFavorite = async () => { const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + if (!workspaceSlug || !this.id || !this.service.removeFavorite) return; const view = await this.service.removeFavorite(workspaceSlug, this.id, projectId); if (!view) return; @@ -264,13 +232,9 @@ export class ViewsStore extends FiltersHelper implements TViewsStore { }); }; - /** - * @description This method is used to update the view - * @param viewData - */ update = async (viewData: Partial) => { const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + if (!workspaceSlug || !this.id) return; const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); if (!view) return;