fix: store updates

This commit is contained in:
gurusainath 2024-02-02 13:52:38 +05:30
parent 984f7ed6b8
commit d57d91e530
40 changed files with 1338 additions and 230 deletions

View File

@ -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, "public" = 0,
"private" = 1, "private" = 1,
"shared" = 2, "shared" = 2,
} }
export type TViewAccess = export type TViewAccess =
| EGlobalViewAccess.public | EViewAccess.public
| EGlobalViewAccess.private | EViewAccess.private
| EGlobalViewAccess.shared; | EViewAccess.shared;
export type TView = { export type TView = {
readonly id: string; id: string | undefined;
readonly workspace: string; workspace: string | undefined;
readonly project: string | undefined; project: string | undefined;
name: string; name: string | undefined;
description: string; description: string | undefined;
readonly query: string; query: string | undefined;
filters: TFilters; filters: TViewFilters | undefined;
display_filters: TDisplayFilters; display_filters: TViewDisplayFilters | undefined;
display_properties: TDisplayProperties; display_properties: TViewDisplayProperties | undefined;
readonly access: TViewAccess; access: TViewAccess | undefined;
readonly owned_by: string; owned_by: string | undefined;
readonly sort_order: number; sort_order: number | undefined;
readonly is_locked: boolean; is_locked: boolean | undefined;
readonly is_pinned: boolean; is_pinned: boolean | undefined;
readonly is_favorite: boolean; is_favorite: boolean | undefined;
readonly created_by: string; created_by: string | undefined;
readonly updated_by: string; updated_by: string | undefined;
readonly created_at: Date; created_at: Date | undefined;
readonly updated_at: Date; updated_at: Date | undefined;
}; };

View File

@ -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[]; project: string[];
priority: string[]; priority: string[];
state: string[]; state: string[];
@ -16,8 +21,8 @@ export type TFilters = {
target_date: string[]; target_date: string[];
}; };
export type TDisplayFilters = { export type TViewDisplayFilters = {
layout: TLayouts; layout: TViewLayouts;
group_by: string | undefined; group_by: string | undefined;
sub_group_by: string | undefined; sub_group_by: string | undefined;
order_by: string; order_by: string;
@ -26,11 +31,11 @@ export type TDisplayFilters = {
show_empty_groups: boolean; show_empty_groups: boolean;
calendar: { calendar: {
show_weekends: boolean; show_weekends: boolean;
layout: TCalendarLayouts; layout: TViewCalendarLayouts;
}; };
}; };
export type TDisplayProperties = { export type TViewDisplayProperties = {
assignee: boolean; assignee: boolean;
start_date: boolean; start_date: boolean;
due_date: boolean; due_date: boolean;
@ -46,19 +51,19 @@ export type TDisplayProperties = {
updated_on: boolean; updated_on: boolean;
}; };
export type TFilterProps = { export type TViewFilterProps = {
filters: TFilters; filters: TViewFilters | undefined;
display_filters: TDisplayFilters; display_filters: TViewDisplayFilters | undefined;
display_properties: TDisplayProperties; display_properties: TViewDisplayProperties | undefined;
}; };
export type TFilterPartialProps = { export type TViewFilterPartialProps = {
filters: Partial<TFilters>; filters: Partial<TViewFilters> | undefined;
display_filters: Partial<TDisplayFilters>; display_filters: Partial<TViewDisplayFilters> | undefined;
display_properties: Partial<TDisplayProperties>; display_properties: Partial<TViewDisplayProperties> | undefined;
}; };
export type TFilterQueryParams = export type TViewFilterQueryParams =
| "project" | "project"
| "priority" | "priority"
| "state" | "state"

View File

@ -1,2 +1,3 @@
export * from "./filter"; export * from "./filter";
export * from "./base"; export * from "./base";
export * from "./user-base";

21
packages/types/src/view/user-base.d.ts vendored Normal file
View File

@ -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;
};

View File

@ -1,4 +1,4 @@
import { FC, useMemo } from "react"; import { FC, } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// components // components
import { IssuePeekOverview } from "components/issues"; import { IssuePeekOverview } from "components/issues";

View File

@ -0,0 +1,17 @@
import { FC } from "react";
type TViewFiltersItem = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string | undefined;
};
export const ViewFiltersItem: FC<TViewFiltersItem> = (props) => {
const { workspaceSlug, projectId, viewId } = props;
return (
<div>
<div>ViewFiltersItem</div>
</div>
);
};

View File

@ -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<TViewAppliedFilters> = 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 (
<div key={filterKey} className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5">
<div className="flex-shrink-0 text-xs">{generateTitle(filterKey)}</div>
<div className="border border-red-500 relative flex items-center">
{/* <div className="relative flex items-center">
<div>Icon</div>
<div>Title</div>
<div>Close</div>
</div>
<div>
<div>Icon</div>
<div>Title</div>
<div>Close</div>
</div> */}
</div>
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 rounded-full cursor-pointer transition-all bg-custom-background-80 hover:bg-custom-background-90 text-custom-text-300 hover:text-custom-text-200">
<X size={10} />
</div>
</div>
);
});

View File

@ -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;
};

View File

@ -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<TViewAppliedFiltersRoot> = 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 (
<div className="relative flex items-center gap-2 flex-wrap border border-red-500 p-4">
{filterKeys.map((key) => {
const filterKey = key as keyof TFilters;
return (
<ViewAppliedFilters
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
filterKey={filterKey}
/>
);
})}
</div>
);
});

View File

@ -0,0 +1,17 @@
import { FC } from "react";
type TViewDisplayFiltersRoot = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string | undefined;
};
export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = (props) => {
const { workspaceSlug, projectId, viewId } = props;
return (
<div>
<div>ViewDisplayFiltersRoot</div>
</div>
);
};

View File

@ -0,0 +1,17 @@
import { FC } from "react";
type TViewDisplayPropertiesRoot = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string | undefined;
};
export const ViewDisplayPropertiesRoot: FC<TViewDisplayPropertiesRoot> = (props) => {
const { workspaceSlug, projectId, viewId } = props;
return (
<div>
<div>ViewDisplayPropertiesRoot</div>
</div>
);
};

View File

@ -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<TViewFiltersRoot> = (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 (
<div className="border border-red-500">
<div>ViewFiltersRoot</div>
</div>
);
};

View File

@ -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";

View File

@ -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<TWorkspaceViewRoot> = 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 (
<div className="relative w-full h-full border border-red-500">
<ViewCreateEdit
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewOperations={viewOperations}
/>
{/* <ViewRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewOperations={viewOperations}
/> */}
{/* <ViewFiltersRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={"61d6b507-ae5c-45d6-b169-da7162f016a0"}
viewOperations={viewOperations}
/>
<ViewAppliedFiltersRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={"61d6b507-ae5c-45d6-b169-da7162f016a0"}
viewOperations={viewOperations}
/> */}
</div>
);
});

6
web/components/view/types.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
import { TView } from "@plane/types";
export type TViewOperations = {
create: (data: Partial<TView>) => void;
fetch: () => void;
};

View File

@ -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<TViewCreateEditForm> = (props) => {
const { modalToggle, handleModalClose, viewOperations } = props;
const createView = () => {
viewOperations?.create({ name: "create" });
};
return (
<Transition.Root show={modalToggle} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleModalClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem] py-5 border-[0.1px] border-custom-border-100">
<div className="p-3 px-5 relative flex items-center gap-2">
<div className="relative rounded p-1.5 px-2 flex items-center gap-1 border border-custom-border-100 bg-custom-background-80">
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 overflow-hidden">
<Trash2 className="w-3.5 h-3.5" />
</div>
<div className="text-xs uppercase">Project Identifier</div>
</div>
<div className="">Create|Edit View</div>
</div>
<div className="p-3 px-5">
<Input
id="email"
name="email"
type="email"
// value={value}
// onChange={onChange}
// hasError={Boolean(errors.email)}
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
/>
</div>
<div className="p-3 px-5 relative flex justify-between items-center gap-2">
<div className="relative rounded p-1.5 px-2 flex items-center gap-1 border border-custom-border-100 bg-custom-background-80">
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 overflow-hidden">
<Plus className="w-3 h-3" />
</div>
<div className="text-xs">Filters</div>
</div>
<div className="relative rounded p-1.5 px-2 flex items-center gap-1 border border-dashed border-custom-border-100 bg-custom-background-80">
<div className="text-xs">Clear all filters</div>
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 overflow-hidden">
<X className="w-3 h-3" />
</div>
</div>
</div>
<div className="p-3 px-5 relative bg-custom-background-80">Applied Filters with each dropdown</div>
<div className="p-3 px-5 relative flex justify-end items-center gap-2">
<Button variant="neutral-primary" onClick={handleModalClose}>
Cancel
</Button>
<Button variant="primary">Create View</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};

View File

@ -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<TViewCreateEdit> = (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 (
<>
<ViewCreateEditForm modalToggle={modalToggle} handleModalClose={handleModalClose} />
<div className="border border-red-500 p-5">
<Button size="sm" className="flex justify-center items-center" onClick={createView}>
<Plus size={12} />
<span>New View</span>
</Button>
</div>
</>
);
};

View File

@ -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<TViewRoot> = (props) => {
const {} = props;
return (
<div className="border border-red-500 relative flex items-center gap-2">
{/* header */}
<div className="">Workspace Views</div>
{/* divider */}
<div className="relative w-[50px] h-[50px] flex justify-center items-center">
<div className="absolute top-0 bottom-0 border border-red-500 w-[0.5px]" />
<div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center rounded-full bg-red-500">
<ChevronRight size={12} />
</div>
</div>
{/* views content */}
<div className=" relative flex items-center">
<div>Icon</div>
<div>Title</div>
</div>
</div>
);
};

47
web/constants/view.ts Normal file
View File

@ -0,0 +1,47 @@
import { TViewTypes } from "@plane/types";
export const VIEW_TYPES: Record<TViewTypes, TViewTypes> = {
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",
],
},
};

View File

@ -21,3 +21,7 @@ export * from "./use-kanban-view";
export * from "./use-issue-detail"; export * from "./use-issue-detail";
export * from "./use-inbox"; export * from "./use-inbox";
export * from "./use-inbox-issues"; export * from "./use-inbox-issues";
// new store
export * from "./use-view";
export * from "./use-view-detail";

View File

@ -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");
}
};

View File

@ -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");
}
};

View File

@ -10,23 +10,24 @@ export class IssueFiltersService extends APIService {
} }
// // workspace issue filters // // workspace issue filters
// async fetchWorkspaceFilters(workspaceSlug: string): Promise<IIssueFiltersResponse> { async fetchWorkspaceFilters(workspaceSlug: string): Promise<IIssueFiltersResponse> {
// return this.get(`/api/workspaces/${workspaceSlug}/user-properties/`) return this.get(`/api/workspaces/${workspaceSlug}/user-properties/`)
// .then((response) => response?.data) .then((response) => response?.data)
// .catch((error) => { .catch((error) => {
// throw error?.response?.data; throw error?.response?.data;
// }); });
// } }
// async patchWorkspaceFilters(
// workspaceSlug: string, async patchWorkspaceFilters(
// data: Partial<IIssueFiltersResponse> workspaceSlug: string,
// ): Promise<IIssueFiltersResponse> { data: Partial<IIssueFiltersResponse>
// return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data) ): Promise<IIssueFiltersResponse> {
// .then((response) => response?.data) return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data)
// .catch((error) => { .then((response) => response?.data)
// throw error?.response?.data; .catch((error) => {
// }); throw error?.response?.data;
// } });
}
// project issue filters // project issue filters
async fetchProjectIssueFilters(workspaceSlug: string, projectId: string): Promise<IIssueFiltersResponse> { async fetchProjectIssueFilters(workspaceSlug: string, projectId: string): Promise<IIssueFiltersResponse> {

View File

@ -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";

View File

@ -91,7 +91,7 @@ export class ProjectViewService extends APIService implements TViewService {
projectId: string | undefined = undefined projectId: string | undefined = undefined
): Promise<TView | undefined> { ): Promise<TView | undefined> {
if (!projectId) return undefined; 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) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
@ -130,7 +130,7 @@ export class ProjectViewService extends APIService implements TViewService {
projectId: string | undefined = undefined projectId: string | undefined = undefined
): Promise<TView | undefined> { ): Promise<TView | undefined> {
if (!projectId) return undefined; 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) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;

View File

@ -91,7 +91,7 @@ export class ProjectViewMeService extends APIService implements TViewService {
projectId: string | undefined = undefined projectId: string | undefined = undefined
): Promise<TView | undefined> { ): Promise<TView | undefined> {
if (!projectId) return undefined; 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) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
@ -130,7 +130,7 @@ export class ProjectViewMeService extends APIService implements TViewService {
projectId: string | undefined = undefined projectId: string | undefined = undefined
): Promise<TView | undefined> { ): Promise<TView | undefined> {
if (!projectId) return undefined; 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) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;

View File

@ -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<TUserView | undefined>;
update: (
workspaceSlug: string,
data: Partial<TView>,
projectId?: string,
featureId?: string
) => Promise<TUserView | undefined>;
};
export type TViewService = { export type TViewService = {
fetch: (workspaceSlug: string, projectId?: string) => Promise<TView[] | undefined>; fetch: (workspaceSlug: string, projectId?: string) => Promise<TView[] | undefined>;
@ -10,10 +21,10 @@ export type TViewService = {
data: Partial<TView>, data: Partial<TView>,
projectId?: string projectId?: string
) => Promise<TView | undefined>; ) => Promise<TView | undefined>;
remove: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<void> | undefined; remove?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<void> | undefined;
lock: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>; lock?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>;
unlock: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>; unlock?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>;
duplicate: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>; duplicate?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>;
makeFavorite: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>; makeFavorite?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>;
removeFavorite: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>; removeFavorite?: (workspaceSlug: string, viewId: string, projectId?: string) => Promise<TView | undefined>;
}; };

View File

@ -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<TUserView | undefined> {
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<TViewFilterProps>,
projectId?: string,
cycleId?: string
): Promise<TUserView | undefined> {
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;
});
}
}

View File

@ -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<TUserView | undefined> {
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<TViewFilterProps>,
projectId?: string,
moduleId?: string
): Promise<TUserView | undefined> {
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;
});
}
}

View File

@ -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<TUserView | undefined> {
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<TViewFilterProps>,
projectId?: string
): Promise<TUserView | undefined> {
if (!projectId) return undefined;
return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}

View File

@ -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<TUserView | undefined> {
return this.get(`/api/workspaces/${workspaceSlug}/user-properties/`)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
async update(workspaceSlug: string, data: Partial<TViewFilterProps>): Promise<TUserView | undefined> {
return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
});
}
}

View File

@ -59,7 +59,7 @@ export class WorkspaceViewService extends APIService implements TViewService {
} }
async unlock(workspaceSlug: string, viewId: string): Promise<TView> { async unlock(workspaceSlug: string, viewId: string): Promise<TView> {
return this.post(`/api/workspaces/${workspaceSlug}/views/${viewId}/unlock/`) return this.delete(`/api/workspaces/${workspaceSlug}/views/${viewId}/lock/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
@ -83,7 +83,7 @@ export class WorkspaceViewService extends APIService implements TViewService {
} }
async removeFavorite(workspaceSlug: string, viewId: string): Promise<TView> { async removeFavorite(workspaceSlug: string, viewId: string): Promise<TView> {
return this.post(`/api/workspaces/${workspaceSlug}/views/${viewId}/unfavorite/`) return this.delete(`/api/workspaces/${workspaceSlug}/views/${viewId}/favorite/`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;

View File

@ -11,7 +11,7 @@ export class WorkspaceMeViewService extends APIService implements TViewService {
} }
async fetch(workspaceSlug: string): Promise<TView[]> { async fetch(workspaceSlug: string): Promise<TView[]> {
return this.get(`/api/users/me/workspaces/${workspaceSlug}/views/`) return this.get(`/api/users/me/workspaces/${workspaceSlug}/views/?type=workspace`)
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
@ -59,7 +59,7 @@ export class WorkspaceMeViewService extends APIService implements TViewService {
} }
async unlock(workspaceSlug: string, viewId: string): Promise<TView> { async unlock(workspaceSlug: string, viewId: string): Promise<TView> {
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) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;
@ -83,7 +83,7 @@ export class WorkspaceMeViewService extends APIService implements TViewService {
} }
async removeFavorite(workspaceSlug: string, viewId: string): Promise<TView> { async removeFavorite(workspaceSlug: string, viewId: string): Promise<TView> {
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) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
throw error?.response; throw error?.response;

View File

@ -17,10 +17,14 @@ import { IMentionStore, MentionStore } from "./mention.store";
import { DashboardStore, IDashboardStore } from "./dashboard.store"; import { DashboardStore, IDashboardStore } from "./dashboard.store";
import { IProjectPageStore, ProjectPageStore } from "./project-page.store"; import { IProjectPageStore, ProjectPageStore } from "./project-page.store";
import { ILabelStore, LabelStore } from "./label.store"; import { ILabelStore, LabelStore } from "./label.store";
// new stores
import { GlobalViewRootStore } from "./view/root.store";
enableStaticRendering(typeof window === "undefined"); enableStaticRendering(typeof window === "undefined");
export class RootStore { export class RootStore {
view: GlobalViewRootStore;
// old store structure
app: IAppRootStore; app: IAppRootStore;
user: IUserRootStore; user: IUserRootStore;
workspaceRoot: IWorkspaceRootStore; workspaceRoot: IWorkspaceRootStore;
@ -40,6 +44,8 @@ export class RootStore {
projectPages: IProjectPageStore; projectPages: IProjectPageStore;
constructor() { constructor() {
this.view = new GlobalViewRootStore(this);
// old store structure
this.app = new AppRootStore(this); this.app = new AppRootStore(this);
this.user = new UserRootStore(this); this.user = new UserRootStore(this);
this.workspaceRoot = new WorkspaceRootStore(this); this.workspaceRoot = new WorkspaceRootStore(this);

View File

@ -1,10 +1,16 @@
import isEmpty from "lodash/isEmpty"; import isEmpty from "lodash/isEmpty";
// types // types
import { TFilters, TDisplayFilters, TDisplayProperties, TFilterProps, TFilterQueryParams } from "@plane/types"; import {
TViewFilters,
TViewDisplayFilters,
TViewDisplayProperties,
TViewFilterProps,
TViewFilterQueryParams,
} from "@plane/types";
export class FiltersHelper { export class FiltersHelper {
// computed filters // computed filters
computedFilters = (filters: TFilters, defaultValues?: Partial<TFilters>): TFilters => ({ computedFilters = (filters: TViewFilters, defaultValues?: Partial<TViewFilters>): TViewFilters => ({
project: filters?.project || defaultValues?.project || [], project: filters?.project || defaultValues?.project || [],
priority: filters?.priority || defaultValues?.priority || [], priority: filters?.priority || defaultValues?.priority || [],
state: filters?.state || defaultValues?.state || [], state: filters?.state || defaultValues?.state || [],
@ -20,9 +26,9 @@ export class FiltersHelper {
// computed display filters // computed display filters
computedDisplayFilters = ( computedDisplayFilters = (
displayFilters: TDisplayFilters, displayFilters: TViewDisplayFilters,
defaultValues?: Partial<TDisplayFilters> defaultValues?: Partial<TViewDisplayFilters>
): TDisplayFilters => ({ ): TViewDisplayFilters => ({
layout: displayFilters?.layout || defaultValues?.layout || "list", layout: displayFilters?.layout || defaultValues?.layout || "list",
group_by: displayFilters?.group_by || defaultValues?.group_by || "none", group_by: displayFilters?.group_by || defaultValues?.group_by || "none",
sub_group_by: displayFilters?.sub_group_by || defaultValues?.sub_group_by || undefined, sub_group_by: displayFilters?.sub_group_by || defaultValues?.sub_group_by || undefined,
@ -38,9 +44,9 @@ export class FiltersHelper {
// computed display properties // computed display properties
computedDisplayProperties = ( computedDisplayProperties = (
displayProperties: TDisplayProperties, displayProperties: TViewDisplayProperties,
defaultValues?: Partial<TDisplayProperties> defaultValues?: Partial<TViewDisplayProperties>
): TDisplayProperties => ({ ): TViewDisplayProperties => ({
assignee: displayProperties?.assignee || defaultValues?.assignee || true, assignee: displayProperties?.assignee || defaultValues?.assignee || true,
start_date: displayProperties?.start_date || defaultValues?.start_date || true, start_date: displayProperties?.start_date || defaultValues?.start_date || true,
due_date: displayProperties?.due_date || defaultValues?.due_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 // compute filters and display_filters issue query parameters
computeAppliedFiltersQueryParameters = ( computeAppliedFiltersQueryParameters = (
filters: TFilterProps, filters: TViewFilterProps,
acceptableParamsByLayout: string[] acceptableParamsByLayout: string[]
): { params: any; query: string } => { ): { params: any; query: string } => {
const paramsObject: Partial<Record<TFilterQueryParams, string | boolean>> = {}; const paramsObject: Partial<Record<TViewFilterQueryParams, string | boolean>> = {};
let paramsString = ""; let paramsString = "";
const filteredParams: Partial<Record<TFilterQueryParams, undefined | string[] | boolean | string>> = { const filteredParams: Partial<Record<TViewFilterQueryParams, undefined | string[] | boolean | string>> = {
// issue filters // issue filters
priority: filters.filters?.priority || undefined, priority: filters.filters?.priority || undefined,
state_group: filters.filters?.state_group || undefined, state_group: filters.filters?.state_group || undefined,
@ -83,7 +89,7 @@ export class FiltersHelper {
}; };
Object.keys(filteredParams).forEach((key) => { Object.keys(filteredParams).forEach((key) => {
const _key = key as TFilterQueryParams; const _key = key as TViewFilterQueryParams;
const _value: string | boolean | string[] | undefined = filteredParams[_key]; const _value: string | boolean | string[] | undefined = filteredParams[_key];
if (_value != undefined && acceptableParamsByLayout.includes(_key)) if (_value != undefined && acceptableParamsByLayout.includes(_key))
paramsObject[_key] = Array.isArray(_value) ? _value.join(",") : _value; paramsObject[_key] = Array.isArray(_value) ? _value.join(",") : _value;
@ -92,7 +98,7 @@ export class FiltersHelper {
if (paramsObject && !isEmpty(paramsObject)) { if (paramsObject && !isEmpty(paramsObject)) {
paramsString = Object.keys(paramsObject) paramsString = Object.keys(paramsObject)
.map((key) => { .map((key) => {
const _key = key as TFilterQueryParams; const _key = key as TViewFilterQueryParams;
const _value: string | boolean | undefined = paramsObject[_key]; const _value: string | boolean | undefined = paramsObject[_key];
if (!undefined) return `${_key}=${_value}`; if (!undefined) return `${_key}=${_value}`;
}) })

View File

@ -1,23 +1,70 @@
// services // services
import { WorkspaceViewService } from "services/view/workspace.service";
import { WorkspaceMeViewService } from "services/view/workspace_me.service"; import {
import { ProjectViewService } from "services/view/project.service"; WorkspaceViewService,
import { ProjectViewMeService } from "services/view/project_me.service"; WorkspaceMeViewService,
ProjectViewService,
ProjectViewMeService,
WorkspaceFiltersService,
ProjectFiltersService,
ModuleFiltersService,
CycleFiltersService,
// LocalStorageFiltersService,
} from "services/view";
// stores // stores
import { ViewRoot } from "./view-root.store"; import { ViewRootStore } from "./view-root.store";
import { userViewRootStore } from "./user/view-root.store";
// types // types
import { RootStore } from "store/root.store"; import { RootStore } from "store/root.store";
export class ViewRootStore { export class GlobalViewRootStore {
workspaceViewStore: ViewRoot; // views root
workspaceViewMeStore: ViewRoot; workspaceViewMeStore: ViewRootStore;
projectViewStore: ViewRoot; workspaceViewStore: ViewRootStore;
projectViewMeStore: ViewRoot; workspaceViewProjectStore: ViewRootStore;
projectViewStore: ViewRootStore;
projectViewMeStore: ViewRootStore;
// user views root
workspaceUserViewStore?: userViewRootStore;
projectUserViewStore?: userViewRootStore;
moduleUserViewStore?: userViewRootStore;
cycleUserViewStore?: userViewRootStore;
constructor(private store: RootStore) { constructor(private store: RootStore) {
this.workspaceViewStore = new ViewRoot(this.store, new WorkspaceViewService()); // views root
this.workspaceViewMeStore = new ViewRoot(this.store, new WorkspaceMeViewService()); this.workspaceViewMeStore = new ViewRootStore(this.store, new WorkspaceMeViewService());
this.projectViewStore = new ViewRoot(this.store, new ProjectViewService()); this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService());
this.projectViewMeStore = new ViewRoot(this.store, new ProjectViewMeService()); 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());
} }
} }

View File

@ -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<string, UserViewStore>;
// computed
viewIds: string[];
// helper actions
viewById: (viewId: string) => UserViewStore | undefined;
// actions
fetch: () => Promise<void>;
};
export class userViewRootStore implements TUserViewRootStore {
// observables
viewMap: Record<string, UserViewStore> = {};
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)
);
});
};
}

View File

@ -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<TViewFilters>) => void;
updateDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
updateDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
resetFilterChanges: () => void;
saveFilterChanges: () => void;
// actions
update: (viewData: Partial<TUserView>) => Promise<void>;
};
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<TViewFilters>) => {
runInAction(() => {
this.loader = "submit";
this.filtersToUpdate.filters = filters;
});
};
updateDisplayFilters = async (display_filters: Partial<TViewDisplayFilters>) => {
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<TViewDisplayProperties>) => {
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<TViewFilterProps>) => {
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]);
});
});
};
}

View File

@ -1,20 +1,21 @@
// types
import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { action, computed, makeObservable, observable, runInAction } from "mobx";
import set from "lodash/set";
// stores // stores
import { RootStore } from "store/root.store"; import { RootStore } from "store/root.store";
import { ViewsStore } from "./view.store"; import { ViewStore } from "./view.store";
// types // types
import { TViewService } from "services/view/types"; import { TViewService } from "services/view/types";
import { TView } from "@plane/types"; import { TView } from "@plane/types";
import { set } from "lodash";
export type TLoader = "" | undefined; export type TLoader = "" | undefined;
type TViewRoot = { type TViewRootStore = {
// observables // observables
viewMap: Record<string, ViewsStore>; viewMap: Record<string, ViewStore>;
// computed // computed
viewIds: string[]; viewIds: string[];
// helper actions
viewById: (viewId: string) => ViewStore | undefined;
// actions // actions
fetch: () => Promise<void>; fetch: () => Promise<void>;
create: (view: Partial<TView>) => Promise<void>; create: (view: Partial<TView>) => Promise<void>;
@ -22,13 +23,13 @@ type TViewRoot = {
duplicate: (viewId: string) => Promise<void>; duplicate: (viewId: string) => Promise<void>;
}; };
export class ViewRoot implements TViewRoot { export class ViewRootStore implements TViewRootStore {
viewMap: Record<string, ViewsStore> = {}; viewMap: Record<string, ViewStore> = {};
constructor(private store: RootStore, private service: TViewService) { constructor(private store: RootStore, private service: TViewService) {
makeObservable(this, { makeObservable(this, {
// observables // observables
viewMap: observable, viewMap: observable.ref,
// computed // computed
viewIds: computed, viewIds: computed,
// actions // actions
@ -44,15 +45,10 @@ export class ViewRoot implements TViewRoot {
return Object.keys(this.viewMap); return Object.keys(this.viewMap);
} }
get views() { // helper actions
return Object.values(this.viewMap); viewById = (viewId: string) => this.viewMap?.[viewId] || undefined;
}
// actions // actions
/**
* @description This method is used to fetch all the views
* @returns
*/
fetch = async () => { fetch = async () => {
const { workspaceSlug, projectId } = this.store.app.router; const { workspaceSlug, projectId } = this.store.app.router;
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -62,16 +58,11 @@ export class ViewRoot implements TViewRoot {
runInAction(() => { runInAction(() => {
views.forEach((view) => { 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<TView>
* @returns
*/
create = async (data: Partial<TView>) => { create = async (data: Partial<TView>) => {
const { workspaceSlug, projectId } = this.store.app.router; const { workspaceSlug, projectId } = this.store.app.router;
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -80,40 +71,30 @@ export class ViewRoot implements TViewRoot {
if (!view) return; if (!view) return;
runInAction(() => { 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) => { remove = async (viewId: string) => {
const { workspaceSlug, projectId } = this.store.app.router; 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(() => { runInAction(() => {
delete this.viewMap[viewId]; delete this.viewMap[viewId];
}); });
}; };
/**
* @description This method is used to duplicate a view
* @param viewId: string
* @returns
*/
duplicate = async (viewId: string) => { duplicate = async (viewId: string) => {
const { workspaceSlug, projectId } = this.store.app.router; 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); const view = await this.service.duplicate(workspaceSlug, viewId, projectId);
if (!view) return; if (!view) return;
runInAction(() => { 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));
}); });
}; };
} }

View File

@ -6,29 +6,29 @@ import { RootStore } from "store/root.store";
import { TViewService } from "services/view/types"; import { TViewService } from "services/view/types";
import { import {
TView, TView,
TFilters, TViewFilters,
TDisplayFilters, TViewDisplayFilters,
TDisplayProperties, TViewDisplayProperties,
TFilterProps, TViewFilterProps,
TFilterPartialProps, TViewFilterPartialProps,
TViewAccess, TViewAccess,
} from "@plane/types"; } from "@plane/types";
// helpers // helpers
import { FiltersHelper } from "./filters_helpers"; import { FiltersHelper } from "./helpers/filters_helpers";
type TLoader = "submitting" | "submit" | undefined; type TLoader = "submitting" | "submit" | undefined;
export type TViewsStore = TView & { export type TViewStore = TView & {
// observables // observables
loader: TLoader; loader: TLoader;
filtersToUpdate: TFilterPartialProps; filtersToUpdate: TViewFilterPartialProps;
// computed // computed
appliedFilters: TFilterProps | undefined; appliedFilters: TViewFilterProps | undefined;
appliedFiltersQueryParams: string | undefined; appliedFiltersQueryParams: string | undefined;
// helper actions // helper actions
updateFilters: (filters: Partial<TFilters>) => void; updateFilters: (filters: Partial<TViewFilters>) => void;
updateDisplayFilters: (display_filters: Partial<TDisplayFilters>) => void; updateDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
updateDisplayProperties: (display_properties: Partial<TDisplayProperties>) => void; updateDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
resetFilterChanges: () => void; resetFilterChanges: () => void;
saveFilterChanges: () => void; saveFilterChanges: () => void;
// actions // actions
@ -39,29 +39,29 @@ export type TViewsStore = TView & {
update: (viewData: Partial<TView>) => Promise<void>; update: (viewData: Partial<TView>) => Promise<void>;
}; };
export class ViewsStore extends FiltersHelper implements TViewsStore { export class ViewStore extends FiltersHelper implements TViewStore {
id: string; id: string | undefined;
workspace: string; workspace: string | undefined;
project: string | undefined; project: string | undefined;
name: string; name: string | undefined;
description: string; description: string | undefined;
query: string; query: string | undefined;
filters: TFilters; filters: TViewFilters | undefined;
display_filters: TDisplayFilters; display_filters: TViewDisplayFilters | undefined;
display_properties: TDisplayProperties; display_properties: TViewDisplayProperties | undefined;
access: TViewAccess; access: TViewAccess | undefined;
owned_by: string; owned_by: string | undefined;
sort_order: number; sort_order: number | undefined;
is_locked: boolean; is_locked: boolean | undefined;
is_pinned: boolean; is_pinned: boolean | undefined;
is_favorite: boolean; is_favorite: boolean | undefined;
created_by: string; created_by: string | undefined;
updated_by: string; updated_by: string | undefined;
created_at: Date; created_at: Date | undefined;
updated_at: Date; updated_at: Date | undefined;
loader: TLoader = undefined; loader: TLoader = undefined;
filtersToUpdate: TFilterPartialProps = { filtersToUpdate: TViewFilterPartialProps = {
filters: {}, filters: {},
display_filters: {}, display_filters: {},
display_properties: {}, display_properties: {},
@ -75,9 +75,11 @@ export class ViewsStore extends FiltersHelper implements TViewsStore {
this.name = _view.name; this.name = _view.name;
this.description = _view.description; this.description = _view.description;
this.query = _view.query; this.query = _view.query;
this.filters = this.computedFilters(_view.filters); this.filters = _view.filters ? this.computedFilters(_view.filters) : undefined;
this.display_filters = this.computedDisplayFilters(_view.display_filters); this.display_filters = _view.display_filters ? this.computedDisplayFilters(_view.display_filters) : undefined;
this.display_properties = this.computedDisplayProperties(_view.display_properties); this.display_properties = _view.display_properties
? this.computedDisplayProperties(_view.display_properties)
: undefined;
this.access = _view.access; this.access = _view.access;
this.owned_by = _view.owned_by; this.owned_by = _view.owned_by;
this.sort_order = _view.sort_order; this.sort_order = _view.sort_order;
@ -112,51 +114,43 @@ export class ViewsStore extends FiltersHelper implements TViewsStore {
// computed // computed
get appliedFilters() { get appliedFilters() {
return { return {
filters: this.computedFilters(this.filters, this.filtersToUpdate.filters), filters: this.filters ? this.computedFilters(this.filters, this.filtersToUpdate.filters) : undefined,
display_filters: this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters), display_filters: this.display_filters
display_properties: this.computedDisplayProperties( ? this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters)
this.display_properties, : undefined,
this.filtersToUpdate.display_properties display_properties: this.display_properties
), ? this.computedDisplayProperties(this.display_properties, this.filtersToUpdate.display_properties)
: undefined,
}; };
} }
get appliedFiltersQueryParams() { get appliedFiltersQueryParams() {
const filters = this.appliedFilters; const filters = this.appliedFilters;
if (!filters) return undefined;
return this.computeAppliedFiltersQueryParameters(filters, [])?.query || undefined; return this.computeAppliedFiltersQueryParameters(filters, [])?.query || undefined;
} }
// helper actions // helper actions
/** updateFilters = (filters: Partial<TViewFilters>) => {
* @description This method is used to update the filters of the view
* @param filters: Partial<TFilters>
*/
updateFilters = (filters: Partial<TFilters>) => {
runInAction(() => { runInAction(() => {
this.loader = "submit"; this.loader = "submit";
this.filtersToUpdate.filters = filters; this.filtersToUpdate.filters = filters;
}); });
}; };
/** updateDisplayFilters = async (display_filters: Partial<TViewDisplayFilters>) => {
* @description This method is used to update the display filters of the view
* @param display_filters: Partial<TDisplayFilters>
*/
updateDisplayFilters = async (display_filters: Partial<TDisplayFilters>) => {
const appliedFilters = this.appliedFilters; const appliedFilters = this.appliedFilters;
const layout = appliedFilters.display_filters.layout; const layout = appliedFilters?.display_filters?.layout;
const sub_group_by = appliedFilters.display_filters.sub_group_by; const sub_group_by = appliedFilters?.display_filters?.sub_group_by;
const group_by = appliedFilters.display_filters.group_by; const group_by = appliedFilters?.display_filters?.group_by;
const sub_issue = appliedFilters.display_filters.sub_issue; 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 = undefined;
if (layout === "kanban") { if (layout === "kanban") {
if (sub_group_by === group_by) display_filters.group_by = undefined; if (sub_group_by === group_by) display_filters.group_by = undefined;
if (group_by === null) display_filters.group_by = "state"; if (group_by === null) display_filters.group_by = "state";
} }
if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false; if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false;
runInAction(() => { runInAction(() => {
@ -165,20 +159,13 @@ export class ViewsStore extends FiltersHelper implements TViewsStore {
}); });
}; };
/** updateDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => {
* @description This method is used to update the display properties of the view
* @param display_properties: Partial<TDisplayProperties>
*/
updateDisplayProperties = async (display_properties: Partial<TDisplayProperties>) => {
runInAction(() => { runInAction(() => {
this.loader = "submit"; this.loader = "submit";
this.filtersToUpdate.display_properties = display_properties; this.filtersToUpdate.display_properties = display_properties;
}); });
}; };
/**
* @description This method is used to reset the changes made to the filters
*/
resetFilterChanges = () => { resetFilterChanges = () => {
runInAction(() => { runInAction(() => {
this.loader = undefined; 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 () => { saveFilterChanges = async () => {
this.loader = "submitting"; this.loader = "submitting";
if (this.appliedFilters) await this.update(this.appliedFilters); if (this.appliedFilters) await this.update(this.appliedFilters);
@ -200,13 +184,9 @@ export class ViewsStore extends FiltersHelper implements TViewsStore {
}; };
// actions // actions
/**
* @description This method is used to update the view lock
* @returns
*/
lockView = async () => { lockView = async () => {
const { workspaceSlug, projectId } = this.store.app.router; 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); const view = await this.service.lock(workspaceSlug, this.id, projectId);
if (!view) return; 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 () => { unlockView = async () => {
const { workspaceSlug, projectId } = this.store.app.router; 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); const view = await this.service.unlock(workspaceSlug, this.id, projectId);
if (!view) return; 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 () => { makeFavorite = async () => {
const { workspaceSlug, projectId } = this.store.app.router; 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); const view = await this.service.makeFavorite(workspaceSlug, this.id, projectId);
if (!view) return; 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 () => { removeFavorite = async () => {
const { workspaceSlug, projectId } = this.store.app.router; 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); const view = await this.service.removeFavorite(workspaceSlug, this.id, projectId);
if (!view) return; 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<TView>) => { update = async (viewData: Partial<TView>) => {
const { workspaceSlug, projectId } = this.store.app.router; 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); const view = await this.service.update(workspaceSlug, this.id, viewData, projectId);
if (!view) return; if (!view) return;