diff --git a/packages/types/src/view/base.d.ts b/packages/types/src/view/base.d.ts index 39e17b06b..c237bdcd7 100644 --- a/packages/types/src/view/base.d.ts +++ b/packages/types/src/view/base.d.ts @@ -7,7 +7,6 @@ import { export type TViewTypes = | "WORKSPACE_YOUR_VIEWS" | "WORKSPACE_VIEWS" - | "WORKSPACE_PROJECT_VIEWS" | "PROJECT_VIEWS" | "PROJECT_YOUR_VIEWS"; @@ -29,17 +28,20 @@ export type TView = { name: string | undefined; description: string | undefined; query: string | undefined; - filters: TViewFilters | undefined; - display_filters: TViewDisplayFilters | undefined; - display_properties: TViewDisplayProperties | undefined; + filters: TViewFilters; + display_filters: TViewDisplayFilters; + display_properties: TViewDisplayProperties; access: TViewAccess | undefined; owned_by: string | undefined; sort_order: number | undefined; - is_locked: boolean | undefined; - is_pinned: boolean | undefined; - is_favorite: boolean | undefined; + is_locked: boolean; + is_pinned: boolean; + is_favorite: boolean; created_by: string | undefined; updated_by: string | undefined; created_at: Date | undefined; updated_at: Date | undefined; + // local view variables + is_local_view: boolean; + is_create: boolean; }; diff --git a/packages/types/src/view/filter.d.ts b/packages/types/src/view/filter.d.ts index f28b1bda0..c7da2cd65 100644 --- a/packages/types/src/view/filter.d.ts +++ b/packages/types/src/view/filter.d.ts @@ -5,10 +5,53 @@ export type TViewLayouts = | "spreadsheet" | "gantt"; +export type TViewDisplayFiltersGrouped = + | "project" + | "state_detail.group" + | "state" + | "priority" + | "labels" + | "created_by" + | "assignees" + | "mentions" + | "modules" + | "cycles"; + +export type TViewDisplayFiltersOrderBy = + | "sort_order" + | "created_at" + | "-created_at" + | "updated_at" + | "-updated_at" + | "start_date" + | "-start_date" + | "target_date" + | "-target_date" + | "state__name" + | "-state__name" + | "priority" + | "-priority" + | "labels__name" + | "-labels__name" + | "assignees__first_name" + | "-assignees__first_name" + | "estimate_point" + | "-estimate_point" + | "link_count" + | "-link_count" + | "attachment_count" + | "-attachment_count" + | "sub_issues_count" + | "-sub_issues_count"; + +export type TViewDisplayFiltersType = "active" | "backlog"; + export type TViewCalendarLayouts = "month" | "week"; export type TViewFilters = { project: string[]; + module: string[]; + cycle: string[]; priority: string[]; state: string[]; state_group: string[]; @@ -23,10 +66,10 @@ export type TViewFilters = { export type TViewDisplayFilters = { layout: TViewLayouts; - group_by: string | undefined; - sub_group_by: string | undefined; - order_by: string; - type: string | undefined; + group_by: TViewDisplayFiltersGrouped | undefined; + sub_group_by: TViewDisplayFiltersGrouped | undefined; + order_by: TViewDisplayFiltersOrderBy | string; + type: TViewDisplayFiltersType | undefined; sub_issue: boolean; show_empty_groups: boolean; calendar: { @@ -52,19 +95,21 @@ export type TViewDisplayProperties = { }; export type TViewFilterProps = { - filters: TViewFilters | undefined; - display_filters: TViewDisplayFilters | undefined; - display_properties: TViewDisplayProperties | undefined; + filters: TViewFilters; + display_filters: TViewDisplayFilters; + display_properties: TViewDisplayProperties; }; export type TViewFilterPartialProps = { - filters: Partial | undefined; - display_filters: Partial | undefined; - display_properties: Partial | undefined; + filters: Partial; + display_filters: Partial; + display_properties: Partial; }; export type TViewFilterQueryParams = | "project" + | "module" + | "cycle" | "priority" | "state" | "state_group" diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index 85d01f8c9..2e0303a8e 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -1,4 +1,4 @@ -import { FC, } from "react"; +import { FC, useMemo } from "react"; import { useRouter } from "next/router"; // components import { IssuePeekOverview } from "components/issues"; diff --git a/web/components/view/all-issues-root.tsx b/web/components/view/all-issues-root.tsx new file mode 100644 index 000000000..337b991aa --- /dev/null +++ b/web/components/view/all-issues-root.tsx @@ -0,0 +1,166 @@ +import { FC, useEffect, useMemo, useState } from "react"; +import { observer } from "mobx-react-lite"; +import { CheckCircle } from "lucide-react"; +// hooks +import { useView, useViewDetail } from "hooks/store"; +import useToast from "hooks/use-toast"; +// components +import { ViewRoot, ViewCreateEdit, ViewFiltersRoot, ViewAppliedFiltersRoot, ViewLayoutRoot } from "."; +// ui +import { Spinner } from "@plane/ui"; +// constants +import { VIEW_TYPES } from "constants/view"; +// types +import { TViewOperations } from "./types"; +import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types"; + +type TAllIssuesViewRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; +}; + +export const AllIssuesViewRoot: FC = observer((props) => { + const { workspaceSlug, projectId, viewId } = props; + // states + const [viewType, setViewType] = useState(VIEW_TYPES.WORKSPACE_VIEWS); + const workspaceViewTabOptions = [ + { + key: VIEW_TYPES.WORKSPACE_YOUR_VIEWS, + title: "Your views", + onClick: () => VIEW_TYPES.WORKSPACE_YOUR_VIEWS != viewType && setViewType(VIEW_TYPES.WORKSPACE_YOUR_VIEWS), + }, + { + key: VIEW_TYPES.WORKSPACE_VIEWS, + title: "Workspace Views", + onClick: () => VIEW_TYPES.WORKSPACE_VIEWS != viewType && setViewType(VIEW_TYPES.WORKSPACE_VIEWS), + }, + ]; + // hooks + const viewStore = useView(workspaceSlug, projectId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const { setToastAlert } = useToast(); + + const viewOperations: TViewOperations = useMemo( + () => ({ + localViewCreate: (data) => viewStore?.localViewCreate(data), + clearLocalView: (viewId: string) => viewStore?.clearLocalView(viewId), + setFilters: (filters: Partial) => viewDetailStore?.setFilters(filters), + setDisplayFilters: (display_filters: Partial) => + viewDetailStore?.setDisplayFilters(display_filters), + setDisplayProperties: (display_properties: Partial) => + viewDetailStore?.setDisplayProperties(display_properties), + fetch: async () => await viewStore?.fetch(), + create: async (data: Partial) => { + try { + await viewStore?.create(data); + if (data.id) viewOperations.clearLocalView(data.id); + } catch { + setToastAlert({ title: "Error", message: "Error creating view", type: "error" }); + } + }, + }), + [viewStore, viewDetailStore, setToastAlert] + ); + + useEffect(() => { + if (workspaceSlug && viewId && viewType && viewStore) + viewStore?.fetch(viewStore?.viewIds.length > 0 ? "mutation-loader" : "init-loader"); + }, [workspaceSlug, viewId, viewType, viewStore]); + + return ( +
+
+
+
+ +
+
All Issues
+
+
+ {workspaceViewTabOptions.map((tab) => ( +
+ {tab.title} +
+ ))} +
+
+ + {viewStore?.loader && viewStore?.loader === "init-loader" ? ( +
+ +
+ ) : ( + <> + + + {/* */} + +
+
+ +
+ +
+ +
+ +
+ Filters +
+ +
+ Display Filters +
+ + {!viewDetailStore?.is_local_view && ( +
+ +
Edit
+
+
+ )} +
+ + )} +
+ ); +}); diff --git a/web/components/view/applied-filters/filter-item.tsx b/web/components/view/applied-filters/filter-item.tsx index 96de45c99..916265937 100644 --- a/web/components/view/applied-filters/filter-item.tsx +++ b/web/components/view/applied-filters/filter-item.tsx @@ -1,17 +1,49 @@ import { FC } from "react"; +import { User, X } from "lucide-react"; +// hooks +import { useViewDetail } from "hooks/store"; +// types +import { TViewFilters, TViewTypes } from "@plane/types"; -type TViewFiltersItem = { +type TViewAppliedFiltersItem = { workspaceSlug: string; projectId: string | undefined; - viewId: string | undefined; + viewId: string; + viewType: TViewTypes; + filterKey: keyof TViewFilters; + filterId: string; }; -export const ViewFiltersItem: FC = (props) => { - const { workspaceSlug, projectId, viewId } = props; +export const ViewAppliedFiltersItem: FC = (props) => { + const { workspaceSlug, projectId, viewId, viewType, filterKey, filterId } = props; + // hooks + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + + const removeFilterOption = () => { + const filters = viewDetailStore?.appliedFilters?.filters; + if (!filters) return; + const filterValues = filters[filterKey]; + const updatedFilterValues = filterValues.filter((value) => value !== filterId); + viewDetailStore?.setFilters({ [filterKey]: updatedFilterValues }); + }; return ( -
-
ViewFiltersItem
+
+
+ +
+
+ {filterKey} - {filterId} +
+
+ +
); }; diff --git a/web/components/view/applied-filters/filter.tsx b/web/components/view/applied-filters/filter.tsx index 55a032f71..a25f1b33d 100644 --- a/web/components/view/applied-filters/filter.tsx +++ b/web/components/view/applied-filters/filter.tsx @@ -4,46 +4,56 @@ import isEmpty from "lodash/isEmpty"; import { X } from "lucide-react"; // hooks import { useViewDetail } from "hooks/store"; +// components +import { ViewAppliedFiltersItem } from "./filter-item"; // helpers import { generateTitle } from "./helper"; // types -import { TFilters } from "@plane/types"; +import { TViewFilters, TViewTypes } from "@plane/types"; type TViewAppliedFilters = { workspaceSlug: string; projectId: string | undefined; viewId: string; - filterKey: keyof TFilters; + viewType: TViewTypes; + filterKey: keyof TViewFilters; }; export const ViewAppliedFilters: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, filterKey } = props; + const { workspaceSlug, projectId, viewId, viewType, filterKey } = props; - const view = useViewDetail("WORKSPACE", workspaceSlug, projectId, viewId); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const filterKeyValue = - view?.appliedFilters?.filters && !isEmpty(view?.appliedFilters?.filters) - ? view?.appliedFilters?.filters?.[filterKey] || undefined + viewDetailStore?.appliedFilters?.filters && !isEmpty(viewDetailStore?.appliedFilters?.filters) + ? viewDetailStore?.appliedFilters?.filters?.[filterKey] || undefined : undefined; - if (!filterKeyValue || filterKeyValue.length <= 0) return <>; - return ( -
-
{generateTitle(filterKey)}
-
- {/*
-
Icon
-
Title
-
Close
-
+ const clearFilter = () => viewDetailStore?.setFilters({ [filterKey]: [] }); -
-
Icon
-
Title
-
Close
-
*/} + if (!filterKeyValue || filterKeyValue.length <= -1) return <>; + return ( +
+
{generateTitle(filterKey)}
+
+ {["1", "2", "3", "4"].map((filterId) => ( + + ))}
-
+
diff --git a/web/components/view/applied-filters/root.tsx b/web/components/view/applied-filters/root.tsx index fbe2d2803..a2c4c3e54 100644 --- a/web/components/view/applied-filters/root.tsx +++ b/web/components/view/applied-filters/root.tsx @@ -1,45 +1,66 @@ import { FC } from "react"; import { observer } from "mobx-react-lite"; +import { X } from "lucide-react"; import isEmpty from "lodash/isEmpty"; // hooks import { useViewDetail } from "hooks/store"; // components import { ViewAppliedFilters } from "./filter"; // types -import { TFilters } from "@plane/types"; +import { TViewTypes, TViewFilters } from "@plane/types"; import { TViewOperations } from "../types"; type TViewAppliedFiltersRoot = { workspaceSlug: string; projectId: string | undefined; viewId: string; + viewType: TViewTypes; viewOperations: TViewOperations; }; export const ViewAppliedFiltersRoot: FC = observer((props) => { - const { workspaceSlug, projectId, viewId } = props; + const { workspaceSlug, projectId, viewId, viewType } = props; // hooks - const view = useViewDetail("WORKSPACE", workspaceSlug, projectId, viewId); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const filterKeys = - view?.appliedFilters && !isEmpty(view?.appliedFilters?.filters) - ? Object.keys(view?.appliedFilters?.filters) + viewDetailStore?.appliedFilters && !isEmpty(viewDetailStore?.appliedFilters?.filters) + ? Object.keys(viewDetailStore?.appliedFilters?.filters) : undefined; + const clearAllFilters = () => { + const clearedFilters: Partial> = {}; + filterKeys?.forEach((key) => { + const _key = key as keyof TViewFilters; + clearedFilters[_key] = []; + }); + viewDetailStore?.setFilters(clearedFilters); + }; + if (!filterKeys) return <>; return ( -
+
{filterKeys.map((key) => { - const filterKey = key as keyof TFilters; + const filterKey = key as keyof TViewFilters; return ( ); })} +
+
Clear All
+
+ +
+
); }); diff --git a/web/components/view/index.ts b/web/components/view/index.ts index e061452e0..a4c996395 100644 --- a/web/components/view/index.ts +++ b/web/components/view/index.ts @@ -1,10 +1,14 @@ -export * from "./root"; +export * from "./all-issues-root"; // views export * from "./views/root"; +export * from "./views/view-item"; export * from "./views/create-edit"; export * from "./views/create-edit-form"; +// layouts +export * from "./layout"; + // view filters export * from "./filters/root"; diff --git a/web/components/view/layout.tsx b/web/components/view/layout.tsx new file mode 100644 index 000000000..e3f9c5f4d --- /dev/null +++ b/web/components/view/layout.tsx @@ -0,0 +1,54 @@ +import { FC } from "react"; +import { observer } from "mobx-react-lite"; +import { LucideIcon, List, Kanban, Calendar, Sheet, GanttChartSquare } from "lucide-react"; +// hooks +import { useViewDetail } from "hooks/store"; +// ui +import { Tooltip } from "@plane/ui"; +// types +import { TViewTypes } from "@plane/types"; +import { TViewOperations } from "./types"; + +type TViewLayoutRoot = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + viewType: TViewTypes; + viewOperations: TViewOperations; +}; + +const LAYOUTS_DATA: { key: string; title: string; icon: LucideIcon }[] = [ + { key: "list", title: "List Layout", icon: List }, + { key: "kanban", title: "Kanban Layout", icon: Kanban }, + { key: "calendar", title: "Calendar Layout", icon: Calendar }, + { key: "spreadsheet", title: "Spreadsheet Layout", icon: Sheet }, + { key: "gantt", title: "Gantt Chart layout", icon: GanttChartSquare }, +]; + +export const ViewLayoutRoot: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; + // hooks + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + + return ( +
+ {LAYOUTS_DATA.map((layout) => ( + +
viewOperations.setDisplayFilters({ layout: layout.key })} + > + +
+
+ ))} +
+ ); +}); diff --git a/web/components/view/root.tsx b/web/components/view/root.tsx deleted file mode 100644 index 1f45bdcb0..000000000 --- a/web/components/view/root.tsx +++ /dev/null @@ -1,77 +0,0 @@ -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 index 1e4c52c99..147be8a1c 100644 --- a/web/components/view/types.d.ts +++ b/web/components/view/types.d.ts @@ -1,6 +1,11 @@ import { TView } from "@plane/types"; export type TViewOperations = { - create: (data: Partial) => void; - fetch: () => void; + localViewCreate: (data: TView) => void; + clearLocalView: (viewId: string) => void; + setFilters: (filters: Partial) => void; + setDisplayFilters: (display_filters: Partial) => void; + setDisplayProperties: (display_properties: Partial) => void; + fetch: () => Promise; + create: (data: Partial) => Promise; }; diff --git a/web/components/view/views/create-edit-form.tsx b/web/components/view/views/create-edit-form.tsx index fa47392e5..33700cdcf 100644 --- a/web/components/view/views/create-edit-form.tsx +++ b/web/components/view/views/create-edit-form.tsx @@ -1,23 +1,40 @@ import { FC, Fragment } from "react"; +import { observer } from "mobx-react-lite"; import { Dialog, Transition } from "@headlessui/react"; import { Trash2, Plus, X } from "lucide-react"; +// hooks +import { useViewDetail } from "hooks/store"; +// components +import { ViewAppliedFiltersRoot } from "../"; // ui import { Input, Button } from "@plane/ui"; // types +import { TView, TViewTypes } from "@plane/types"; import { TViewOperations } from "../types"; type TViewCreateEditForm = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + viewType: TViewTypes; + viewOperations: TViewOperations; modalToggle: boolean; - handleModalClose: () => void; - viewOperations?: TViewOperations; + onSubmit: (viewData: Partial) => void; }; -export const ViewCreateEditForm: FC = (props) => { - const { modalToggle, handleModalClose, viewOperations } = props; +export const ViewCreateEditForm: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, viewOperations, modalToggle, handleModalClose, onSubmit } = props; + // hooks + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); - const createView = () => { - viewOperations?.create({ name: "create" }); + const onContinue = async () => { + const payload: Partial = { + id: viewDetailStore?.id, + name: viewDetailStore?.name, + filters: viewDetailStore?.filters, + }; + onSubmit(payload); }; return ( @@ -48,23 +65,24 @@ export const ViewCreateEditForm: FC = (props) => { >
-
+ {/*
Project Identifier
-
-
Create|Edit View
+
*/} +
Create View
{ + viewDetailStore?.setName(e.target.value); + }} placeholder="What do you want to call this view?" className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400" autoFocus @@ -86,13 +104,23 @@ export const ViewCreateEditForm: FC = (props) => {
-
Applied Filters with each dropdown
+
+ +
- +
@@ -101,4 +129,4 @@ export const ViewCreateEditForm: FC = (props) => { ); -}; +}); diff --git a/web/components/view/views/create-edit.tsx b/web/components/view/views/create-edit.tsx index 90d9d6ff3..159790f00 100644 --- a/web/components/view/views/create-edit.tsx +++ b/web/components/view/views/create-edit.tsx @@ -1,12 +1,15 @@ -import { FC, useState } from "react"; +import { FC, ReactNode, useState } from "react"; +import { observer } from "mobx-react-lite"; import { Plus } from "lucide-react"; // ui import { Button } from "@plane/ui"; // components import { ViewCreateEditForm } from "./create-edit-form"; +// constants +import { viewLocalPayload } from "constants/view"; // types import { TViewOperations } from "../types"; -import { TViewTypes } from "@plane/types"; +import { TView, TViewFilters, TViewTypes } from "@plane/types"; type TViewCreateEdit = { workspaceSlug: string; @@ -14,29 +17,72 @@ type TViewCreateEdit = { viewId: string | undefined; viewType: TViewTypes; viewOperations: TViewOperations; + children?: ReactNode; }; -export const ViewCreateEdit: FC = (props) => { - const { workspaceSlug, projectId, viewId, viewOperations } = props; +export const ViewCreateEdit: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, viewOperations, children } = props; // states + const [currentViewId, setCurrentViewId] = useState(); + const [currentFilters, setCurrentFilters] = useState>({}); const [modalToggle, setModalToggle] = useState(false); - const handleModalOpen = () => setModalToggle(true); - const handleModalClose = () => setModalToggle(false); + const handleModalOpen = () => { + if (viewId === undefined) { + const viewPayload = viewLocalPayload; + setCurrentViewId(viewPayload.id); + viewOperations?.localViewCreate(viewPayload as TView); + } else { + setCurrentViewId(viewId); + } + setModalToggle(true); + }; - const createView = () => { - viewOperations?.create({ name: "create" }); + const handleModalClose = () => { + if (viewId === undefined) { + if (currentViewId) viewOperations?.clearLocalView(currentViewId); + } else { + } + setModalToggle(false); + setCurrentViewId(undefined); + setCurrentFilters({}); + }; + + const onSubmit = async (viewData: Partial) => { + if (!viewData?.name) return; + try { + await viewOperations.create(viewData); + handleModalClose(); + } catch (error) { + console.log(error); + } }; return ( <> - -
- + {currentViewId && ( + + )} + +
+ {children ? ( + children + ) : ( + + )}
); -}; +}); diff --git a/web/components/view/views/root.tsx b/web/components/view/views/root.tsx index adf8c0f59..a3f656f6c 100644 --- a/web/components/view/views/root.tsx +++ b/web/components/view/views/root.tsx @@ -1,5 +1,9 @@ import { FC } from "react"; -import { ChevronRight } from "lucide-react"; +import { observer } from "mobx-react-lite"; +// hooks +import { useView } from "hooks/store"; +// components +import { ViewItem, ViewCreateEdit } from "../"; // types import { TViewOperations } from "../types"; import { TViewTypes } from "@plane/types"; @@ -12,25 +16,36 @@ type TViewRoot = { viewOperations: TViewOperations; }; -export const ViewRoot: FC = (props) => { - const {} = props; +export const ViewRoot: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; + // hooks + const viewStore = useView(workspaceSlug, projectId, viewType); return ( -
- {/* header */} -
Workspace Views
- {/* divider */} -
-
-
- +
+ {viewStore?.viewIds && viewStore?.viewIds.length > 0 && ( +
+ {viewStore?.viewIds.map((_viewId) => ( + + ))}
-
- {/* views content */} -
-
Icon
-
Title
+ )} + +
+
); -}; +}); diff --git a/web/components/view/views/view-item.tsx b/web/components/view/views/view-item.tsx new file mode 100644 index 000000000..474bc29a2 --- /dev/null +++ b/web/components/view/views/view-item.tsx @@ -0,0 +1,46 @@ +import { FC } from "react"; +import Link from "next/link"; +import { observer } from "mobx-react-lite"; +// hooks +import { useView } from "hooks/store"; +// ui +import { PhotoFilterIcon } from "@plane/ui"; +// types +import { TViewTypes } from "@plane/types"; + +type TViewItem = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; + viewType: TViewTypes; + viewItemId: string; +}; + +export const ViewItem: FC = observer((props) => { + const { workspaceSlug, projectId, viewId, viewType, viewItemId } = props; + // hooks + const viewStore = useView(workspaceSlug, projectId, viewType); + + const view = viewStore?.viewById(viewItemId); + + if (!view) return <>; + return ( +
+ viewItemId === viewId && e.preventDefault()} + > +
+ +
+
+ {view?.name} +
+ +
+
+ ); +}); diff --git a/web/constants/view.ts b/web/constants/view.ts deleted file mode 100644 index cdce771ae..000000000 --- a/web/constants/view.ts +++ /dev/null @@ -1,47 +0,0 @@ -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/constants/view/filters.ts b/web/constants/view/filters.ts new file mode 100644 index 000000000..a11f2a514 --- /dev/null +++ b/web/constants/view/filters.ts @@ -0,0 +1,218 @@ +// types +import { TViewFilters, TViewDisplayFilters, TViewLayouts } from "@plane/types"; + +type TViewLayoutFilterProperties = { + filters: Partial[]; + display_filters: Partial[]; + extra_options: ("sub_issue" | "show_empty_groups")[]; + display_properties: boolean; +}; + +type TViewLayoutFilters = { + list: TViewLayoutFilterProperties; + kanban: TViewLayoutFilterProperties; + calendar: TViewLayoutFilterProperties; + spreadsheet: TViewLayoutFilterProperties; + gantt: TViewLayoutFilterProperties; +}; + +type TFilterPermissions = { + all: Omit & { + layouts: Omit[]; + }; + profile: Omit & { + layouts: Omit[]; + }; + project: TViewLayoutFilters & { + layouts: TViewLayouts[]; + }; + archived: Omit & { + layouts: Omit[]; + }; + draft: Omit & { + layouts: Omit[]; + }; +}; + +export const ALL_FILTER_PERMISSIONS: TFilterPermissions["all"] = { + layouts: ["spreadsheet"], + spreadsheet: { + filters: ["project", "priority", "state_group", "assignees", "created_by", "labels", "start_date", "target_date"], + display_filters: ["type"], + extra_options: [], + display_properties: true, + }, +}; + +export const PROFILE_FILTER_PERMISSIONS: TFilterPermissions["profile"] = { + layouts: ["list", "kanban"], + list: { + filters: ["priority", "state_group", "labels", "start_date", "target_date"], + display_filters: ["group_by", "order_by", "type"], + extra_options: [], + display_properties: true, + }, + kanban: { + filters: ["priority", "state_group", "labels", "start_date", "target_date"], + display_filters: ["group_by", "order_by", "type"], + extra_options: [], + display_properties: true, + }, +}; + +export const PROJECT_FILTER_PERMISSIONS: TFilterPermissions["project"] = { + layouts: ["list", "kanban", "spreadsheet", "calendar", "gantt"], + list: { + filters: [ + "priority", + "state", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "module", + "cycle", + ], + display_filters: ["group_by", "order_by", "type"], + extra_options: ["sub_issue", "show_empty_groups"], + display_properties: true, + }, + kanban: { + filters: [ + "priority", + "state", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "module", + "cycle", + ], + display_filters: ["group_by", "sub_group_by", "order_by", "type"], + extra_options: ["sub_issue", "show_empty_groups"], + display_properties: true, + }, + calendar: { + filters: [ + "priority", + "state", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "module", + "cycle", + ], + display_filters: ["type"], + extra_options: ["sub_issue"], + display_properties: true, + }, + spreadsheet: { + filters: [ + "priority", + "state", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "module", + "cycle", + ], + display_filters: ["order_by", "type"], + extra_options: [], + display_properties: true, + }, + + gantt: { + filters: [ + "priority", + "state", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "module", + "cycle", + ], + display_filters: ["order_by", "type"], + extra_options: ["sub_issue"], + display_properties: false, + }, +}; + +export const ARCHIVED_FILTER_PERMISSIONS: TFilterPermissions["archived"] = { + layouts: ["list"], + list: { + filters: [ + "priority", + "state", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "module", + "cycle", + ], + display_filters: ["group_by", "order_by"], + extra_options: [], + display_properties: true, + }, +}; + +export const DRAFT_FILTER_PERMISSIONS: TFilterPermissions["draft"] = { + layouts: ["list", "kanban"], + list: { + filters: [ + "priority", + "state", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "module", + "cycle", + ], + display_filters: ["group_by", "order_by", "type"], + extra_options: ["sub_issue", "show_empty_groups"], + display_properties: true, + }, + kanban: { + filters: [ + "priority", + "state", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "module", + "cycle", + ], + display_filters: ["group_by", "sub_group_by", "order_by", "type"], + extra_options: ["sub_issue", "show_empty_groups"], + display_properties: true, + }, +}; + +export const VIEW_DEFAULT_FILTER_PARAMETERS: TFilterPermissions = { + all: ALL_FILTER_PERMISSIONS, + profile: PROFILE_FILTER_PERMISSIONS, + project: PROJECT_FILTER_PERMISSIONS, + archived: ARCHIVED_FILTER_PERMISSIONS, + draft: DRAFT_FILTER_PERMISSIONS, +}; diff --git a/web/constants/view/index.ts b/web/constants/view/index.ts new file mode 100644 index 000000000..6476ca82f --- /dev/null +++ b/web/constants/view/index.ts @@ -0,0 +1,2 @@ +export * from "./root"; +export * from "./filters"; diff --git a/web/constants/view/root.ts b/web/constants/view/root.ts new file mode 100644 index 000000000..265d92f92 --- /dev/null +++ b/web/constants/view/root.ts @@ -0,0 +1,21 @@ +import { v4 as uuidV4 } from "uuid"; +// types +import { TViewTypes, TView } from "@plane/types"; + +export const VIEW_TYPES: Record = { + WORKSPACE_YOUR_VIEWS: "WORKSPACE_YOUR_VIEWS", + WORKSPACE_VIEWS: "WORKSPACE_VIEWS", + PROJECT_VIEWS: "PROJECT_VIEWS", + PROJECT_YOUR_VIEWS: "PROJECT_YOUR_VIEWS", +}; + +export const viewLocalPayload: Partial = { + id: uuidV4(), + name: "", + description: "", + filters: {}, + display_filters: {}, + display_properties: {}, + is_local_view: false, + is_create: true, +}; diff --git a/web/hooks/store/use-view-detail.tsx b/web/hooks/store/use-view-detail.tsx index e61c558e0..da4bc8b9d 100644 --- a/web/hooks/store/use-view-detail.tsx +++ b/web/hooks/store/use-view-detail.tsx @@ -15,17 +15,13 @@ export const useViewDetail = ( 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"); + if (!workspaceSlug || !viewId) return undefined; switch (viewType) { case "WORKSPACE_YOUR_VIEWS": - return context.view.workspaceViewStore.viewById(viewId); + return context.view.workspaceViewMeStore.viewById(viewId); case "WORKSPACE_VIEWS": - return context.view.workspaceViewMeStore.viewById(viewId); - case "WORKSPACE_PROJECT_VIEWS": - return context.view.workspaceViewMeStore.viewById(viewId); + return context.view.workspaceViewStore.viewById(viewId); case "PROJECT_YOUR_VIEWS": if (!projectId) throw new Error("useView hook must require projectId"); return context.view.projectViewMeStore.viewById(viewId); @@ -33,6 +29,6 @@ export const useViewDetail = ( 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"); + return undefined; } }; diff --git a/web/hooks/store/use-view.tsx b/web/hooks/store/use-view.tsx index 382a268b7..c6701a839 100644 --- a/web/hooks/store/use-view.tsx +++ b/web/hooks/store/use-view.tsx @@ -2,7 +2,7 @@ import { useContext } from "react"; // mobx store import { StoreContext } from "contexts/store-context"; // types -import { ViewRoot } from "store/view/view-root.store"; +import { ViewRootStore } from "store/view/view-root.store"; // types import { TViewTypes } from "@plane/types"; @@ -10,19 +10,17 @@ export const useView = ( workspaceSlug: string, projectId: string | undefined, viewType: TViewTypes | undefined -): ViewRoot => { +): ViewRootStore | undefined => { 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"); + if (!workspaceSlug || !viewType) return undefined; switch (viewType) { case "WORKSPACE_YOUR_VIEWS": - return context.view.workspaceViewStore; + return context.view.workspaceViewMeStore; case "WORKSPACE_VIEWS": - return context.view.workspaceViewMeStore; - case "WORKSPACE_PROJECT_VIEWS": - return context.view.workspaceViewMeStore; + return context.view.workspaceViewStore; case "PROJECT_YOUR_VIEWS": if (!projectId) throw new Error("useView hook must require projectId"); return context.view.projectViewMeStore; @@ -30,6 +28,6 @@ export const useView = ( if (!projectId) throw new Error("useView hook must require projectId"); return context.view.projectViewStore; default: - throw new Error("useView hook must require viewType"); + return undefined; } }; diff --git a/web/lib/app-provider.tsx b/web/lib/app-provider.tsx index 864c87f27..d820c4d4f 100644 --- a/web/lib/app-provider.tsx +++ b/web/lib/app-provider.tsx @@ -56,6 +56,7 @@ export const AppProvider: FC = observer((props) => { > {children} + {children} diff --git a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx index e89e2c70f..cb05f77c7 100644 --- a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx +++ b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx @@ -1,24 +1,34 @@ import { ReactElement } from "react"; +import { useRouter } from "next/router"; // layouts import { AppLayout } from "layouts/app-layout"; // components -import { GlobalViewsHeader } from "components/workspace"; -import { AllIssueLayoutRoot } from "components/issues"; -import { GlobalIssuesHeader } from "components/headers"; +// import { GlobalViewsHeader } from "components/workspace"; +// import { AllIssueLayoutRoot } from "components/issues"; +// import { GlobalIssuesHeader } from "components/headers"; +import { AllIssuesViewRoot } from "components/view"; // types import { NextPageWithLayout } from "lib/types"; -const GlobalViewIssuesPage: NextPageWithLayout = () => ( -
-
- - +const GlobalViewIssuesPage: NextPageWithLayout = () => { + const router = useRouter(); + const { workspaceSlug, globalViewId: viewId } = router.query; + + if (!workspaceSlug || !viewId) return <>; + return ( +
+
+ + {/* + */} +
-
-); + ); +}; GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; + // return }>{page}; + return }>{page}; }; export default GlobalViewIssuesPage; diff --git a/web/services/view/workspace_me.service.ts b/web/services/view/workspace_me.service.ts index 3c51c9411..bffcf821f 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/?type=workspace`) + return this.get(`/api/users/me/workspaces/${workspaceSlug}/views/`) .then((response) => response?.data) .catch((error) => { throw error?.response; diff --git a/web/store/view/helpers/filters_helpers.ts b/web/store/view/helpers/filters_helpers.ts index 0a180a7c1..6f2c3611e 100644 --- a/web/store/view/helpers/filters_helpers.ts +++ b/web/store/view/helpers/filters_helpers.ts @@ -11,17 +11,19 @@ import { export class FiltersHelper { // computed filters computedFilters = (filters: TViewFilters, defaultValues?: Partial): TViewFilters => ({ - project: filters?.project || defaultValues?.project || [], - priority: filters?.priority || defaultValues?.priority || [], - state: filters?.state || defaultValues?.state || [], - state_group: filters?.state_group || defaultValues?.state_group || [], - assignees: filters?.assignees || defaultValues?.assignees || [], - mentions: filters?.mentions || defaultValues?.mentions || [], - subscriber: filters?.subscriber || defaultValues?.subscriber || [], - created_by: filters?.created_by || defaultValues?.created_by || [], - labels: filters?.labels || defaultValues?.labels || [], - start_date: filters?.start_date || defaultValues?.start_date || [], - target_date: filters?.target_date || defaultValues?.target_date || [], + project: defaultValues?.project || filters?.project || [], + module: defaultValues?.module || filters?.module || [], + cycle: defaultValues?.cycle || filters?.cycle || [], + priority: defaultValues?.priority || filters?.priority || [], + state: defaultValues?.state || filters?.state || [], + state_group: defaultValues?.state_group || filters?.state_group || [], + assignees: defaultValues?.assignees || filters?.assignees || [], + mentions: defaultValues?.mentions || filters?.mentions || [], + subscriber: defaultValues?.subscriber || filters?.subscriber || [], + created_by: defaultValues?.created_by || filters?.created_by || [], + labels: defaultValues?.labels || filters?.labels || [], + start_date: defaultValues?.start_date || filters?.start_date || [], + target_date: defaultValues?.target_date || filters?.target_date || [], }); // computed display filters @@ -29,16 +31,16 @@ export class FiltersHelper { 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, - order_by: displayFilters?.order_by || defaultValues?.order_by || "sort_order", - type: displayFilters?.type || defaultValues?.type || undefined, - sub_issue: displayFilters?.sub_issue || defaultValues?.sub_issue || false, - show_empty_groups: displayFilters?.show_empty_groups || defaultValues?.show_empty_groups || false, + layout: defaultValues?.layout || displayFilters?.layout || "list", + group_by: defaultValues?.group_by || displayFilters?.group_by || undefined, + sub_group_by: defaultValues?.sub_group_by || displayFilters?.sub_group_by || undefined, + order_by: defaultValues?.order_by || displayFilters?.order_by || "sort_order", + type: defaultValues?.type || displayFilters?.type || undefined, + sub_issue: defaultValues?.sub_issue || displayFilters?.sub_issue || false, + show_empty_groups: defaultValues?.show_empty_groups || displayFilters?.show_empty_groups || false, calendar: { - show_weekends: displayFilters?.calendar?.show_weekends || defaultValues?.calendar?.show_weekends || false, - layout: displayFilters?.calendar?.layout || defaultValues?.calendar?.layout || "month", + show_weekends: defaultValues?.calendar?.show_weekends || displayFilters?.calendar?.show_weekends || false, + layout: defaultValues?.calendar?.layout || displayFilters?.calendar?.layout || "month", }, }); @@ -47,19 +49,19 @@ export class FiltersHelper { 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, - labels: displayProperties?.labels || defaultValues?.labels || true, - priority: displayProperties?.priority || defaultValues?.priority || true, - state: displayProperties?.state || defaultValues?.state || true, - sub_issue_count: displayProperties?.sub_issue_count || defaultValues?.sub_issue_count || true, - attachment_count: displayProperties?.attachment_count || defaultValues?.attachment_count || true, - link: displayProperties?.link || defaultValues?.link || true, - estimate: displayProperties?.estimate || defaultValues?.estimate || true, - key: displayProperties?.key || defaultValues?.key || true, - created_on: displayProperties?.created_on || defaultValues?.created_on || true, - updated_on: displayProperties?.updated_on || defaultValues?.updated_on || true, + assignee: defaultValues?.assignee || displayProperties?.assignee || true, + start_date: defaultValues?.start_date || displayProperties?.start_date || true, + due_date: defaultValues?.due_date || displayProperties?.due_date || true, + labels: defaultValues?.labels || displayProperties?.labels || true, + priority: defaultValues?.priority || displayProperties?.priority || true, + state: defaultValues?.state || displayProperties?.state || true, + sub_issue_count: defaultValues?.sub_issue_count || displayProperties?.sub_issue_count || true, + attachment_count: defaultValues?.attachment_count || displayProperties?.attachment_count || true, + link: defaultValues?.link || displayProperties?.link || true, + estimate: defaultValues?.estimate || displayProperties?.estimate || true, + key: defaultValues?.key || displayProperties?.key || true, + created_on: defaultValues?.created_on || displayProperties?.created_on || true, + updated_on: defaultValues?.updated_on || displayProperties?.updated_on || true, }); // compute filters and display_filters issue query parameters diff --git a/web/store/view/root.store.ts b/web/store/view/root.store.ts index c54fb55df..535010af6 100644 --- a/web/store/view/root.store.ts +++ b/web/store/view/root.store.ts @@ -1,5 +1,4 @@ // services - import { WorkspaceViewService, WorkspaceMeViewService, @@ -21,7 +20,6 @@ export class GlobalViewRootStore { // views root workspaceViewMeStore: ViewRootStore; workspaceViewStore: ViewRootStore; - workspaceViewProjectStore: ViewRootStore; projectViewStore: ViewRootStore; projectViewMeStore: ViewRootStore; @@ -32,10 +30,41 @@ export class GlobalViewRootStore { cycleUserViewStore?: userViewRootStore; constructor(private store: RootStore) { - // views root + const defaultViews: any[] = [ + { + id: "all-issues", + name: "All Issues", + filters: {}, + is_local_view: true, + }, + { + id: "assigned", + name: "Assigned", + filters: { + assignees: store.user?.currentUser?.id ? [store.user?.currentUser?.id] : [], + }, + is_local_view: true, + }, + { + id: "created", + name: "Created", + filters: { + created_by: store.user?.currentUser?.id ? [store.user?.currentUser?.id] : [], + }, + is_local_view: true, + }, + { + id: "subscribed", + name: "Subscribed", + filters: { + subscriber: store.user?.currentUser?.id ? [store.user?.currentUser?.id] : [], + }, + is_local_view: true, + }, + ]; + 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.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService(), defaultViews); this.projectViewStore = new ViewRootStore(this.store, new ProjectViewService()); this.projectViewMeStore = new ViewRootStore(this.store, new ProjectViewMeService()); diff --git a/web/store/view/user/view-root.store.ts b/web/store/view/user/view-root.store.ts index 2343cec55..7797f3b04 100644 --- a/web/store/view/user/view-root.store.ts +++ b/web/store/view/user/view-root.store.ts @@ -1,7 +1,6 @@ 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"; diff --git a/web/store/view/view-root.store.ts b/web/store/view/view-root.store.ts index 654b55f1d..7a172e691 100644 --- a/web/store/view/view-root.store.ts +++ b/web/store/view/view-root.store.ts @@ -7,33 +7,41 @@ import { ViewStore } from "./view.store"; import { TViewService } from "services/view/types"; import { TView } from "@plane/types"; -export type TLoader = "" | undefined; +export type TLoader = "init-loader" | "mutation-loader" | "submitting" | undefined; type TViewRootStore = { // observables + loader: TLoader; viewMap: Record; // computed viewIds: string[]; // helper actions viewById: (viewId: string) => ViewStore | undefined; // actions - fetch: () => Promise; + fetch: (_loader?: TLoader) => Promise; + localViewCreate: (view: TView) => Promise; + clearLocalView: (viewId: string) => Promise; create: (view: Partial) => Promise; remove: (viewId: string) => Promise; duplicate: (viewId: string) => Promise; }; export class ViewRootStore implements TViewRootStore { + // observables + loader: TLoader = "init-loader"; viewMap: Record = {}; - constructor(private store: RootStore, private service: TViewService) { + constructor(private store: RootStore, private service: TViewService, private defaultViews: TView[] = []) { makeObservable(this, { // observables - viewMap: observable.ref, + loader: observable.ref, + viewMap: observable, // computed viewIds: computed, // actions fetch: action, + localViewCreate: action, + clearLocalView: action, create: action, remove: action, duplicate: action, @@ -42,17 +50,26 @@ export class ViewRootStore implements TViewRootStore { // computed get viewIds() { - return Object.keys(this.viewMap); + const views = Object.values(this.viewMap); + return views.filter((view) => !view?.is_create).map((view) => view.id) as string[]; } // helper actions viewById = (viewId: string) => this.viewMap?.[viewId] || undefined; // actions - fetch = async () => { + fetch = async (_loader: TLoader = "init-loader") => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; + runInAction(() => { + if (this.defaultViews && this.defaultViews.length > 0) + this.defaultViews?.forEach((view) => { + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); + }); + }); + + this.loader = _loader; const views = await this.service.fetch(workspaceSlug, projectId); if (!views) return; @@ -60,6 +77,19 @@ export class ViewRootStore implements TViewRootStore { views.forEach((view) => { if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); }); + this.loader = undefined; + }); + }; + + localViewCreate = async (view: TView) => { + runInAction(() => { + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); + }); + }; + + clearLocalView = async (viewId: string) => { + runInAction(() => { + if (viewId) delete this.viewMap[viewId]; }); }; diff --git a/web/store/view/view.store.ts b/web/store/view/view.store.ts index 3e9197b08..d50d6a41e 100644 --- a/web/store/view/view.store.ts +++ b/web/store/view/view.store.ts @@ -26,9 +26,10 @@ export type TViewStore = TView & { appliedFilters: TViewFilterProps | undefined; appliedFiltersQueryParams: string | undefined; // helper actions - updateFilters: (filters: Partial) => void; - updateDisplayFilters: (display_filters: Partial) => void; - updateDisplayProperties: (display_properties: Partial) => void; + setName: (name: string) => void; + setFilters: (filters: Partial) => void; + setDisplayFilters: (display_filters: Partial) => void; + setDisplayProperties: (display_properties: Partial) => void; resetFilterChanges: () => void; saveFilterChanges: () => void; // actions @@ -46,19 +47,22 @@ export class ViewStore extends FiltersHelper implements TViewStore { name: string | undefined; description: string | undefined; query: string | undefined; - filters: TViewFilters | undefined; - display_filters: TViewDisplayFilters | undefined; - display_properties: TViewDisplayProperties | undefined; + filters: TViewFilters; + display_filters: TViewDisplayFilters; + display_properties: TViewDisplayProperties; access: TViewAccess | undefined; owned_by: string | undefined; sort_order: number | undefined; - is_locked: boolean | undefined; - is_pinned: boolean | undefined; - is_favorite: boolean | undefined; + is_locked: boolean = false; + is_pinned: boolean = false; + is_favorite: boolean = false; created_by: string | undefined; updated_by: string | undefined; created_at: Date | undefined; updated_at: Date | undefined; + // local variables + is_local_view: boolean = false; + is_create: boolean = false; loader: TLoader = undefined; filtersToUpdate: TViewFilterPartialProps = { @@ -75,11 +79,9 @@ export class ViewStore extends FiltersHelper implements TViewStore { this.name = _view.name; this.description = _view.description; this.query = _view.query; - 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.filters = this.computedFilters(_view.filters); + this.display_filters = this.computedDisplayFilters(_view.display_filters); + this.display_properties = this.computedDisplayProperties(_view.display_properties); this.access = _view.access; this.owned_by = _view.owned_by; this.sort_order = _view.sort_order; @@ -90,18 +92,42 @@ export class ViewStore extends FiltersHelper implements TViewStore { this.updated_by = _view.updated_by; 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; makeObservable(this, { // observables - loader: observable, - filtersToUpdate: observable.ref, + id: observable.ref, + workspace: observable.ref, + project: observable.ref, + name: observable.ref, + description: observable.ref, + query: observable.ref, + filters: observable, + display_filters: observable, + display_properties: observable, + access: observable.ref, + owned_by: observable.ref, + sort_order: observable.ref, + is_locked: observable.ref, + is_pinned: observable.ref, + is_favorite: observable.ref, + created_by: observable.ref, + updated_by: observable.ref, + created_at: observable.ref, + updated_at: observable.ref, + is_local_view: observable.ref, + is_create: observable.ref, + loader: observable.ref, + filtersToUpdate: observable, // computed appliedFilters: computed, appliedFiltersQueryParams: computed, // helper actions - updateFilters: action, - updateDisplayFilters: action, - updateDisplayProperties: action, + setName: action, + setFilters: action, + setDisplayFilters: action, + setDisplayProperties: action, resetFilterChanges: action, saveFilterChanges: action, // actions @@ -114,13 +140,12 @@ export class ViewStore extends FiltersHelper implements TViewStore { // 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, + 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 + ), }; } @@ -131,14 +156,23 @@ export class ViewStore extends FiltersHelper implements TViewStore { } // helper actions - updateFilters = (filters: Partial) => { + setName = (name: string) => { runInAction(() => { - this.loader = "submit"; - this.filtersToUpdate.filters = filters; + this.name = name; }); }; - updateDisplayFilters = async (display_filters: Partial) => { + setFilters = (filters: Partial) => { + runInAction(() => { + this.loader = "submit"; + Object.keys(filters).forEach((key) => { + const _key = key as keyof TViewFilters; + set(this.filtersToUpdate, ["filters", _key], filters[_key]); + }); + }); + }; + + setDisplayFilters = async (display_filters: Partial) => { const appliedFilters = this.appliedFilters; const layout = appliedFilters?.display_filters?.layout; @@ -146,7 +180,7 @@ export class ViewStore extends FiltersHelper implements TViewStore { 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 (group_by === undefined && display_filters.sub_group_by) 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"; @@ -155,14 +189,22 @@ export class ViewStore extends FiltersHelper implements TViewStore { runInAction(() => { this.loader = "submit"; - this.filtersToUpdate.display_filters = display_filters; + Object.keys(display_filters).forEach((key) => { + const _key = key as keyof TViewDisplayFilters; + set(this.filtersToUpdate, ["display_filters", _key], display_filters[_key]); + }); }); + + console.log("this.filtersToUpdate", this.filtersToUpdate?.display_filters?.layout); }; - updateDisplayProperties = async (display_properties: Partial) => { + setDisplayProperties = async (display_properties: Partial) => { runInAction(() => { this.loader = "submit"; - this.filtersToUpdate.display_properties = display_properties; + Object.keys(display_properties).forEach((key) => { + const _key = key as keyof TViewDisplayProperties; + set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]); + }); }); };