mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: implemented view create and edit operations and updated the store
This commit is contained in:
parent
bc694bb742
commit
f05b8de91d
1
packages/types/src/view/base.d.ts
vendored
1
packages/types/src/view/base.d.ts
vendored
@ -44,4 +44,5 @@ export type TView = {
|
||||
// local view variables
|
||||
is_local_view: boolean;
|
||||
is_create: boolean;
|
||||
is_editable: boolean;
|
||||
};
|
||||
|
@ -48,7 +48,9 @@ export const ProfileSidebar = observer(() => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full flex-shrink-0 overflow-y-auto shadow-custom-shadow-sm md:h-full md:w-80 border-l border-custom-border-100">
|
||||
<div
|
||||
className={`w-full flex-shrink-0 overflow-y-auto shadow-custom-shadow-sm md:h-full md:w-80 border-l border-custom-border-100`}
|
||||
>
|
||||
{userProjectsData ? (
|
||||
<>
|
||||
<div className="relative h-32">
|
||||
|
@ -5,11 +5,11 @@ import { CheckCircle } from "lucide-react";
|
||||
import { useView, useViewDetail } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { ViewRoot, ViewCreateEdit, ViewFiltersRoot, ViewAppliedFiltersRoot, ViewLayoutRoot } from ".";
|
||||
import { ViewRoot, ViewCreateEditForm, ViewAppliedFiltersRoot, ViewLayoutRoot } from ".";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// constants
|
||||
import { VIEW_TYPES } from "constants/view";
|
||||
import { VIEW_TYPES, viewLocalPayload } from "constants/view";
|
||||
// types
|
||||
import { TViewOperations } from "./types";
|
||||
import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types";
|
||||
@ -23,6 +23,7 @@ type TAllIssuesViewRoot = {
|
||||
export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId } = props;
|
||||
// states
|
||||
const [currentCreateEditViewId, setCurrentCreateEditViewId] = useState<string | undefined>(undefined);
|
||||
const [viewType, setViewType] = useState(VIEW_TYPES.WORKSPACE_VIEWS);
|
||||
const workspaceViewTabOptions = [
|
||||
{
|
||||
@ -43,18 +44,45 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
|
||||
const viewOperations: TViewOperations = useMemo(
|
||||
() => ({
|
||||
localViewCreate: (data) => viewStore?.localViewCreate(data),
|
||||
clearLocalView: (viewId: string) => viewStore?.clearLocalView(viewId),
|
||||
setName: (name: string) => viewDetailStore?.setName(name),
|
||||
setDescription: (name: string) => viewDetailStore?.setDescription(name),
|
||||
setFilters: (filters: Partial<TViewFilters>) => viewDetailStore?.setFilters(filters),
|
||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) =>
|
||||
viewDetailStore?.setDisplayFilters(display_filters),
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) =>
|
||||
viewDetailStore?.setDisplayProperties(display_properties),
|
||||
localViewCreateEdit: (viewId: string | undefined) => {
|
||||
if (viewId === undefined) {
|
||||
const viewPayload = viewLocalPayload;
|
||||
setCurrentCreateEditViewId(viewPayload.id);
|
||||
viewStore?.localViewCreate(viewPayload as TView);
|
||||
} else setCurrentCreateEditViewId(viewId);
|
||||
},
|
||||
localViewCreateEditClear: async (viewId: string | undefined) => {
|
||||
console.log("viewId", viewId);
|
||||
if (viewId) viewStore?.remove(viewId);
|
||||
setCurrentCreateEditViewId(undefined);
|
||||
},
|
||||
fetch: async () => await viewStore?.fetch(),
|
||||
create: async (data: Partial<TView>) => {
|
||||
try {
|
||||
await viewStore?.create(data);
|
||||
if (data.id) viewOperations.clearLocalView(data.id);
|
||||
setCurrentCreateEditViewId(undefined);
|
||||
} catch {
|
||||
setToastAlert({ title: "Error", message: "Error creating view", type: "error" });
|
||||
}
|
||||
},
|
||||
update: async () => {
|
||||
try {
|
||||
await viewDetailStore?.saveChanges();
|
||||
setCurrentCreateEditViewId(undefined);
|
||||
} catch {
|
||||
setToastAlert({ title: "Error", message: "Error creating view", type: "error" });
|
||||
}
|
||||
},
|
||||
remove: async (viewId: string) => {
|
||||
try {
|
||||
await viewStore?.remove(viewId);
|
||||
} catch {
|
||||
setToastAlert({ title: "Error", message: "Error creating view", type: "error" });
|
||||
}
|
||||
@ -77,15 +105,15 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
</div>
|
||||
<div className="font-medium">All Issues</div>
|
||||
</div>
|
||||
<div className="relative inline-flex items-center rounded border border-custom-border-300 bg-custom-background-80">
|
||||
<div className="relative inline-flex items-center rounded border border-custom-border-200 bg-custom-background-80">
|
||||
{workspaceViewTabOptions.map((tab) => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={`p-4 py-1.5 rounded text-sm transition-all cursor-pointer font-medium
|
||||
${
|
||||
viewType === tab.key
|
||||
? "text-custom-text-100 bg-custom-background-90"
|
||||
: "text-custom-text-200 bg-custom-background-80 hover:text-custom-text-100"
|
||||
? "text-custom-text-100 bg-custom-background-100"
|
||||
: "text-custom-text-300 bg-custom-background-80 hover:text-custom-text-100"
|
||||
}`}
|
||||
onClick={tab.onClick}
|
||||
>
|
||||
@ -101,6 +129,7 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="border-b border-custom-border-200">
|
||||
<ViewRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@ -108,16 +137,10 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
|
||||
{/* <ViewFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewOperations={viewOperations}
|
||||
/> */}
|
||||
</div>
|
||||
|
||||
<div className="p-5 border-b border-custom-border-200 relative flex gap-2">
|
||||
<div className="w-full">
|
||||
{/* <div className="w-full">
|
||||
<ViewAppliedFiltersRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@ -125,9 +148,9 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex-shrink-0 h-full">
|
||||
{/* <div className="flex-shrink-0 h-full">
|
||||
<ViewLayoutRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@ -135,32 +158,34 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex-shrink-0 relative w-7 h-7 overflow-hidden border border-red-500 rounded flex justify-center items-center">
|
||||
{/* <div className="flex-shrink-0 relative w-7 h-7 overflow-hidden border border-red-500 rounded flex justify-center items-center">
|
||||
Filters
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex-shrink-0 relative w-7 h-7 overflow-hidden border border-red-500 rounded flex justify-center items-center">
|
||||
{/* <div className="flex-shrink-0 relative w-7 h-7 overflow-hidden border border-red-500 rounded flex justify-center items-center">
|
||||
Display Filters
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{!viewDetailStore?.is_local_view && (
|
||||
{/* {!viewDetailStore?.is_local_view && (
|
||||
<div className="flex-shrink-0 h-full">
|
||||
<ViewCreateEdit
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
>
|
||||
<div>Edit</div>
|
||||
</ViewCreateEdit>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentCreateEditViewId != undefined && (
|
||||
<ViewCreateEditForm
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={currentCreateEditViewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC } from "react";
|
||||
import { FC, Fragment } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { X } from "lucide-react";
|
||||
@ -33,13 +33,11 @@ export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
||||
|
||||
if (!filterKeyValue || filterKeyValue.length <= -1) return <></>;
|
||||
return (
|
||||
<div
|
||||
key={filterKey}
|
||||
className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 py-1 min-h-[32px]"
|
||||
>
|
||||
<div className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 py-1 min-h-[32px]">
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-200">{generateTitle(filterKey)}</div>
|
||||
<div className="relative flex items-center gap-1 flex-wrap">
|
||||
{["1", "2", "3", "4"].map((filterId) => (
|
||||
<Fragment key={filterId}>
|
||||
<ViewAppliedFiltersItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@ -48,6 +46,7 @@ export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
||||
filterKey={filterKey}
|
||||
filterId={filterId}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ReactNode } from "react";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
// types
|
||||
import { TFilters } from "@plane/types";
|
||||
import { TViewFilters } from "@plane/types";
|
||||
|
||||
type TComputedAppliedFilters = {
|
||||
key: string;
|
||||
@ -10,7 +10,7 @@ type TComputedAppliedFilters = {
|
||||
dropdownOptions?: { id: string; icon: ""; title: ""; component: ReactNode }[];
|
||||
}[];
|
||||
|
||||
export const filterOptions = (key: keyof TFilters, selectedFilters: string[]) => {
|
||||
export const filterOptions = (key: keyof TViewFilters, selectedFilters: string[]) => {
|
||||
switch (key) {
|
||||
case "project":
|
||||
return [];
|
||||
@ -45,12 +45,12 @@ export const generateTitle = (title: string) =>
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
|
||||
export const constructAppliedFilters = (filters: TFilters): TComputedAppliedFilters => {
|
||||
export const constructAppliedFilters = (filters: TViewFilters): TComputedAppliedFilters => {
|
||||
const appliedFilters: TComputedAppliedFilters = [];
|
||||
|
||||
if (filters && !isEmpty(filters)) {
|
||||
Object.keys(filters).forEach((_filterKey) => {
|
||||
const _key = _filterKey as keyof TFilters;
|
||||
const _key = _filterKey as keyof TViewFilters;
|
||||
const _value = filters[_key];
|
||||
|
||||
if (_value && !isEmpty(_value)) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC } from "react";
|
||||
import { FC, Fragment } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
@ -43,6 +43,7 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
|
||||
{filterKeys.map((key) => {
|
||||
const filterKey = key as keyof TViewFilters;
|
||||
return (
|
||||
<Fragment key={filterKey}>
|
||||
<ViewAppliedFilters
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@ -50,6 +51,7 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
|
||||
viewType={viewType}
|
||||
filterKey={filterKey}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<div
|
||||
|
@ -3,7 +3,10 @@ export * from "./all-issues-root";
|
||||
// views
|
||||
export * from "./views/root";
|
||||
export * from "./views/view-item";
|
||||
export * from "./views/create-edit";
|
||||
|
||||
export * from "./views/dropdown/root";
|
||||
export * from "./views/dropdown/dropdown-item";
|
||||
|
||||
export * from "./views/create-edit-form";
|
||||
|
||||
// layouts
|
||||
|
10
web/components/view/types.d.ts
vendored
10
web/components/view/types.d.ts
vendored
@ -1,11 +1,17 @@
|
||||
import { TView } from "@plane/types";
|
||||
|
||||
export type TViewOperations = {
|
||||
localViewCreate: (data: TView) => void;
|
||||
clearLocalView: (viewId: string) => void;
|
||||
setName: (name: string) => void;
|
||||
setDescription: (description: string) => void;
|
||||
setFilters: (filters: Partial<TViewFilters>) => void;
|
||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||
|
||||
localViewCreateEdit: (viewId: string | undefined) => void;
|
||||
localViewCreateEditClear: (viewId: string | undefined) => Promise<void>;
|
||||
|
||||
fetch: () => Promise<void>;
|
||||
create: (data: Partial<TView>) => Promise<void>;
|
||||
update: () => Promise<void>;
|
||||
remove: (viewId: string) => Promise<void>;
|
||||
};
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { FC, Fragment } from "react";
|
||||
import { FC, Fragment, useCallback, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Trash2, Plus, X } from "lucide-react";
|
||||
import { Briefcase, Globe2, Plus, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
import { useViewDetail, useProject } from "hooks/store";
|
||||
// components
|
||||
import { ViewAppliedFiltersRoot } from "../";
|
||||
// ui
|
||||
import { Input, Button } from "@plane/ui";
|
||||
// types
|
||||
import { TView, TViewTypes } from "@plane/types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "../types";
|
||||
|
||||
type TViewCreateEditForm = {
|
||||
@ -18,28 +18,50 @@ type TViewCreateEditForm = {
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
modalToggle: boolean;
|
||||
handleModalClose: () => void;
|
||||
onSubmit: (viewData: Partial<TView>) => void;
|
||||
};
|
||||
|
||||
export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations, modalToggle, handleModalClose, onSubmit } = props;
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
const { getProjectById } = useProject();
|
||||
// states
|
||||
const [modalToggle, setModalToggle] = useState(false);
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
const modalOpen = useCallback(() => setModalToggle(true), [setModalToggle]);
|
||||
const modalClose = useCallback(() => {
|
||||
setModalToggle(false);
|
||||
setTimeout(() => {
|
||||
viewOperations.localViewCreateEditClear(viewId);
|
||||
}, 200);
|
||||
}, [viewId, setModalToggle, viewOperations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewId) modalOpen();
|
||||
}, [viewId, modalOpen, modalClose]);
|
||||
|
||||
const onContinue = async () => {
|
||||
const payload: Partial<TView> = {
|
||||
id: viewDetailStore?.id,
|
||||
name: viewDetailStore?.name,
|
||||
filters: viewDetailStore?.filters,
|
||||
};
|
||||
onSubmit(payload);
|
||||
setLoader(true);
|
||||
if (viewDetailStore?.is_create) {
|
||||
const payload = viewDetailStore?.filtersToUpdate;
|
||||
await viewOperations.create(payload);
|
||||
modalClose();
|
||||
} else {
|
||||
const payload = viewDetailStore?.filtersToUpdate;
|
||||
if (!payload) return;
|
||||
await viewOperations.update();
|
||||
modalClose();
|
||||
}
|
||||
setLoader(false);
|
||||
};
|
||||
|
||||
const projectDetails = projectId ? getProjectById(projectId) : undefined;
|
||||
|
||||
if (!viewDetailStore?.id) return <></>;
|
||||
return (
|
||||
<Transition.Root show={modalToggle} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={handleModalClose}>
|
||||
<Dialog as="div" className="relative z-20" onClose={modalClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
@ -65,13 +87,22 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
>
|
||||
<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" />
|
||||
{projectId && projectDetails ? (
|
||||
<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-5 h-5 overflow-hidden">
|
||||
<Briefcase className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
<div className="text-xs uppercase">Project Identifier</div>
|
||||
</div> */}
|
||||
<div className="">Create View</div>
|
||||
<div className="text-xs uppercase font-medium">{projectDetails?.identifier || "Project"}</div>
|
||||
</div>
|
||||
) : (
|
||||
<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-5 h-5 overflow-hidden">
|
||||
<Globe2 className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
<div className="text-xs uppercase font-medium">Workspace</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="font-medium text-lg">Save View</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 px-5">
|
||||
@ -79,7 +110,7 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
value={viewDetailStore?.name || ""}
|
||||
value={viewDetailStore?.filtersToUpdate?.name || ""}
|
||||
onChange={(e) => {
|
||||
viewDetailStore?.setName(e.target.value);
|
||||
}}
|
||||
@ -90,13 +121,13 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
</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="cursor-pointer 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="cursor-pointer 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" />
|
||||
@ -115,11 +146,11 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||
</div>
|
||||
|
||||
<div className="p-3 px-5 relative flex justify-end items-center gap-2">
|
||||
<Button variant="neutral-primary" onClick={handleModalClose}>
|
||||
<Button variant="neutral-primary" onClick={modalClose} disabled={loader}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" onClick={onContinue}>
|
||||
Create View
|
||||
<Button variant="primary" onClick={onContinue} disabled={loader}>
|
||||
{loader ? `Saving...` : `${viewDetailStore?.is_create ? `Create` : `Update`} View`}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
|
@ -1,88 +0,0 @@
|
||||
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 { TView, TViewFilters, TViewTypes } from "@plane/types";
|
||||
|
||||
type TViewCreateEdit = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string | undefined;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const ViewCreateEdit: FC<TViewCreateEdit> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations, children } = props;
|
||||
// states
|
||||
const [currentViewId, setCurrentViewId] = useState<string>();
|
||||
const [currentFilters, setCurrentFilters] = useState<Partial<TViewFilters>>({});
|
||||
const [modalToggle, setModalToggle] = useState(false);
|
||||
|
||||
const handleModalOpen = () => {
|
||||
if (viewId === undefined) {
|
||||
const viewPayload = viewLocalPayload;
|
||||
setCurrentViewId(viewPayload.id);
|
||||
viewOperations?.localViewCreate(viewPayload as TView);
|
||||
} else {
|
||||
setCurrentViewId(viewId);
|
||||
}
|
||||
setModalToggle(true);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
if (viewId === undefined) {
|
||||
if (currentViewId) viewOperations?.clearLocalView(currentViewId);
|
||||
} else {
|
||||
}
|
||||
setModalToggle(false);
|
||||
setCurrentViewId(undefined);
|
||||
setCurrentFilters({});
|
||||
};
|
||||
|
||||
const onSubmit = async (viewData: Partial<TView>) => {
|
||||
if (!viewData?.name) return;
|
||||
try {
|
||||
await viewOperations.create(viewData);
|
||||
handleModalClose();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentViewId && (
|
||||
<ViewCreateEditForm
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={currentViewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
onSubmit={onSubmit}
|
||||
modalToggle={modalToggle}
|
||||
handleModalClose={handleModalClose}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="inline-block" onClick={handleModalOpen}>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<Button size="sm" className="flex justify-center items-center">
|
||||
<Plus size={12} />
|
||||
<span>New View</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
74
web/components/view/views/dropdown/dropdown-item.tsx
Normal file
74
web/components/view/views/dropdown/dropdown-item.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { FC } from "react";
|
||||
import Link from "next/link";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { GripVertical, MoreVertical } from "lucide-react";
|
||||
// hooks
|
||||
import { useViewDetail } from "hooks/store";
|
||||
// ui
|
||||
import { PhotoFilterIcon, Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
type TViewDropdownItem = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string;
|
||||
viewType: TViewTypes;
|
||||
currentViewId: string | undefined;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export const ViewDropdownItem: FC<TViewDropdownItem> = (props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, currentViewId, searchQuery } = props;
|
||||
// hooks
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||
|
||||
const isDragEnabled = false;
|
||||
const isEditable = !viewDetailStore?.is_local_view || false;
|
||||
|
||||
if (!viewDetailStore) return <></>;
|
||||
if (!searchQuery || (searchQuery && viewDetailStore?.name?.toLowerCase().includes(searchQuery.toLowerCase())))
|
||||
return (
|
||||
<Combobox.Option
|
||||
value={undefined}
|
||||
className={`w-full px-1 pl-2 py-1.5 truncate flex items-center justify-between gap-1 rounded cursor-pointer select-none group
|
||||
${currentViewId === viewDetailStore?.id ? `bg-custom-primary-100/10` : `hover:bg-custom-background-80`}
|
||||
`}
|
||||
>
|
||||
<Tooltip tooltipContent={viewDetailStore?.name} position="left">
|
||||
<div className="relative w-full flex items-center gap-1 overflow-hidden">
|
||||
{isDragEnabled && (
|
||||
<div className="flex-shrink-0 w-5 h-5 relative rounded flex justify-center items-center hover:bg-custom-background-100">
|
||||
<GripVertical className="w-3.5 h-3.5 text-custom-text-200 group-hover:text-custom-text-100" />
|
||||
</div>
|
||||
)}
|
||||
<Link
|
||||
href={`/${workspaceSlug}/workspace-views/${viewDetailStore?.id}`}
|
||||
className={`w-full h-full overflow-hidden relative flex items-center gap-1
|
||||
${
|
||||
currentViewId === viewDetailStore?.id
|
||||
? `text-custom-text-100`
|
||||
: `text-custom-text-200 group-hover:text-custom-text-100`
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="flex-shrink-0 w-5 h-5 relative flex justify-center items-center">
|
||||
<PhotoFilterIcon className="w-3 h-3 " />
|
||||
</div>
|
||||
|
||||
<div className="w-full line-clamp-1 truncate overflow-hidden inline-block whitespace-nowrap text-sm font-medium">
|
||||
{viewDetailStore?.name}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{isEditable && (
|
||||
<div className="flex-shrink-0 w-5 h-5 relative rounded flex justify-center items-center hover:bg-custom-background-100">
|
||||
<MoreVertical className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
</div>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
);
|
||||
return <></>;
|
||||
};
|
125
web/components/view/views/dropdown/root.tsx
Normal file
125
web/components/view/views/dropdown/root.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import { FC, Fragment, ReactNode, useRef, useState } from "react";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Plus, Search } from "lucide-react";
|
||||
// hooks
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
import { useView } from "hooks/store";
|
||||
// components
|
||||
import { ViewDropdownItem } from "../../";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
import { TViewOperations } from "../../types";
|
||||
|
||||
type TViewDropdown = {
|
||||
workspaceSlug: string;
|
||||
projectId: string | undefined;
|
||||
viewId: string | undefined;
|
||||
viewType: TViewTypes;
|
||||
viewOperations: TViewOperations;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const ViewDropdown: FC<TViewDropdown> = (props) => {
|
||||
const { workspaceSlug, projectId, viewId: currentViewId, viewType, viewOperations, children } = props;
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
// states
|
||||
const [dropdownToggle, setDropdownToggle] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
// refs
|
||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||
// popper-js refs
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
// popper-js init
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
padding: 12,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const handleDropdownOpen = () => setDropdownToggle(true);
|
||||
const handleDropdownClose = () => setDropdownToggle(false);
|
||||
const handleDropdownToggle = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (!dropdownToggle) handleDropdownOpen();
|
||||
else handleDropdownClose();
|
||||
};
|
||||
|
||||
useOutsideClickDetector(dropdownRef, handleDropdownClose);
|
||||
|
||||
return (
|
||||
<Combobox as="div" ref={dropdownRef}>
|
||||
<Combobox.Button as={Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={"block h-full w-full outline-none"}
|
||||
onClick={handleDropdownToggle}
|
||||
>
|
||||
{children ? (
|
||||
<span className="relative inline-block">{children}</span>
|
||||
) : (
|
||||
<span className="whitespace-nowrap">More...</span>
|
||||
)}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
|
||||
{dropdownToggle && (
|
||||
<Combobox.Options className="fixed z-10" static>
|
||||
<div
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
className="my-1 w-64 p-2 space-y-2 rounded bg-custom-background-100 border-[0.5px] border-custom-border-300 shadow-custom-shadow-rg focus:outline-none"
|
||||
>
|
||||
<div className="relative p-0.5 px-2 text-sm flex items-center gap-2 rounded border border-custom-border-100 bg-custom-background-90">
|
||||
<Search className="h-3 w-3 text-custom-text-300" strokeWidth={1.5} />
|
||||
<Combobox.Input
|
||||
className="w-full bg-transparent py-1 text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search for a view..."
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="max-h-60 space-y-0.5 overflow-y-scroll">
|
||||
{viewStore?.viewIds &&
|
||||
viewStore?.viewIds.length > 0 &&
|
||||
viewStore?.viewIds.map((viewId) => (
|
||||
<Fragment key={viewId}>
|
||||
<ViewDropdownItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
currentViewId={currentViewId}
|
||||
searchQuery={query}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="relative flex justify-center items-center gap-1 rounded p-1 py-1.5 transition-all border border-custom-border-200 bg-custom-background-90 hover:bg-custom-background-80 text-custom-text-300 hover:text-custom-text-200 cursor-pointer"
|
||||
onClick={() => viewOperations?.localViewCreateEdit(undefined)}
|
||||
>
|
||||
<Plus className="w-3 h-3" />
|
||||
<div className="text-sm">New view</div>
|
||||
</div>
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
)}
|
||||
</Combobox>
|
||||
);
|
||||
};
|
@ -1,9 +1,12 @@
|
||||
import { FC } from "react";
|
||||
import { FC, Fragment, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useView } from "hooks/store";
|
||||
// components
|
||||
import { ViewItem, ViewCreateEdit } from "../";
|
||||
import { ViewItem, ViewDropdown } from "../";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import { TViewOperations } from "../types";
|
||||
import { TViewTypes } from "@plane/types";
|
||||
@ -20,12 +23,47 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
// state
|
||||
const [itemsToRenderViewsCount, setItemsToRenderViewCount] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
const handleViewTabsVisibility = () => {
|
||||
const tabContainer = document.getElementById("tab-container");
|
||||
const tabItemViewMore = document.getElementById("tab-item-view-more");
|
||||
const itemWidth = 124;
|
||||
if (!tabContainer || !tabItemViewMore) return;
|
||||
|
||||
const containerWidth = tabContainer.clientWidth;
|
||||
const itemViewMoreLeftOffset = tabItemViewMore.offsetLeft;
|
||||
const itemViewMoreRightOffset = containerWidth - itemViewMoreLeftOffset;
|
||||
|
||||
if (itemViewMoreLeftOffset + (tabItemViewMore.clientWidth + 10) > containerWidth) {
|
||||
const itemsToRender = Math.floor(containerWidth / itemWidth);
|
||||
setItemsToRenderViewCount(itemsToRender);
|
||||
}
|
||||
if (itemViewMoreRightOffset > itemWidth + 10) {
|
||||
const itemsToRenderLeft = Math.floor(itemViewMoreLeftOffset / itemWidth) || 0;
|
||||
const itemsToRenderRight = Math.floor(itemViewMoreRightOffset / itemWidth) || 0;
|
||||
setItemsToRenderViewCount(itemsToRenderLeft + itemsToRenderRight);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("resize", () => handleViewTabsVisibility());
|
||||
handleViewTabsVisibility();
|
||||
|
||||
return () => window.removeEventListener("resize", () => handleViewTabsVisibility());
|
||||
}, [viewStore?.viewIds]);
|
||||
|
||||
return (
|
||||
<div className="border-b border-custom-border-100 relative flex px-5 gap-2">
|
||||
<div className="relative flex justify-between px-5 gap-2">
|
||||
{viewStore?.viewIds && viewStore?.viewIds.length > 0 && (
|
||||
<div key={`views_list_${viewId}`} className="relative flex items-center w-full overflow-x-auto">
|
||||
{viewStore?.viewIds.map((_viewId) => (
|
||||
<div
|
||||
key={`views_list_${viewId}`}
|
||||
id="tab-container"
|
||||
className="relative flex items-center w-full overflow-hidden"
|
||||
>
|
||||
{viewStore?.viewIds?.slice(0, itemsToRenderViewsCount || viewStore?.viewIds.length).map((_viewId) => (
|
||||
<Fragment key={_viewId}>
|
||||
<ViewItem
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@ -33,18 +71,36 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
||||
viewType={viewType}
|
||||
viewItemId={_viewId}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
<div id="tab-item-view-more" className="min-w-[90px]">
|
||||
{viewStore?.viewIds.length <= (itemsToRenderViewsCount || viewStore?.viewIds.length) ? null : (
|
||||
<ViewDropdown
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={viewId}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
>
|
||||
<div className="text-sm font-semibold mb-1 p-2 px-2.5 text-custom-text-200 cursor-pointer hover:bg-custom-background-80 whitespace-nowrap rounded relative flex items-center gap-1">
|
||||
<span>
|
||||
<Plus size={12} />
|
||||
</span>
|
||||
<span>
|
||||
{viewStore?.viewIds.length - (itemsToRenderViewsCount || viewStore?.viewIds.length)} More...
|
||||
</span>
|
||||
</div>
|
||||
</ViewDropdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-shrink-0 my-auto pb-1">
|
||||
<ViewCreateEdit
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
viewId={undefined}
|
||||
viewType={viewType}
|
||||
viewOperations={viewOperations}
|
||||
/>
|
||||
<Button size="sm" prependIcon={<Plus />} onClick={() => viewOperations?.localViewCreateEdit(undefined)}>
|
||||
New View
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import { FC, Fragment } from "react";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useView } from "hooks/store";
|
||||
import { useView, useViewDetail } from "hooks/store";
|
||||
// ui
|
||||
import { PhotoFilterIcon } from "@plane/ui";
|
||||
import { PhotoFilterIcon, Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
|
||||
@ -19,27 +19,31 @@ type TViewItem = {
|
||||
export const ViewItem: FC<TViewItem> = observer((props) => {
|
||||
const { workspaceSlug, projectId, viewId, viewType, viewItemId } = props;
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewItemId, viewType);
|
||||
|
||||
const view = viewStore?.viewById(viewItemId);
|
||||
|
||||
if (!view) return <></>;
|
||||
if (!viewDetailStore) return <></>;
|
||||
return (
|
||||
<div key={viewItemId} className="space-y-0.5 relative h-full flex flex-col justify-between">
|
||||
<div className="space-y-0.5 relative h-full flex flex-col justify-between">
|
||||
<Tooltip tooltipContent={viewDetailStore?.name} position="top">
|
||||
<Link
|
||||
href={`/${workspaceSlug}/workspace-views/${viewItemId}`}
|
||||
className={`cursor-pointer relative p-2 px-2.5 flex justify-center items-center gap-1.5 rounded transition-all hover:bg-custom-background-80
|
||||
className={`cursor-pointer relative p-2 px-2.5 flex justify-center items-center gap-1 rounded transition-all hover:bg-custom-background-80
|
||||
${viewItemId === viewId ? `text-custom-primary-100 bg-custom-primary-100/10` : `border-transparent`}
|
||||
`}
|
||||
onClick={(e) => viewItemId === viewId && e.preventDefault()}
|
||||
>
|
||||
<div className="flex-shrink-0 bg-custom-background-80 rounded-sm relative w-5 h-5 flex justify-center items-center overflow-hidden">
|
||||
<div
|
||||
className={`flex-shrink-0 rounded-sm relative w-5 h-5 flex justify-center items-center overflow-hidden
|
||||
${viewItemId === viewId ? `bg-transparent` : `bg-custom-background-80`}
|
||||
`}
|
||||
>
|
||||
<PhotoFilterIcon className="w-3 h-3" />
|
||||
</div>
|
||||
<div className="w-full max-w-[80px] inline-block text-sm line-clamp-1 truncate overflow-hidden font-medium">
|
||||
{view?.name}
|
||||
{viewDetailStore?.name}
|
||||
</div>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<div className={`border-b-2 ${viewItemId === viewId ? `border-custom-primary-100` : `border-transparent`}`} />
|
||||
</div>
|
||||
);
|
||||
|
@ -13,9 +13,9 @@ export const viewLocalPayload: Partial<TView> = {
|
||||
id: uuidV4(),
|
||||
name: "",
|
||||
description: "",
|
||||
filters: {},
|
||||
display_filters: {},
|
||||
display_properties: {},
|
||||
filters: undefined,
|
||||
display_filters: undefined,
|
||||
display_properties: undefined,
|
||||
is_local_view: false,
|
||||
is_create: true,
|
||||
};
|
||||
|
@ -47,7 +47,7 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
|
||||
<InstanceLayout>
|
||||
<StoreWrapper>
|
||||
<CrispWrapper user={currentUser}>
|
||||
<PostHogProvider
|
||||
{/* <PostHogProvider
|
||||
user={currentUser}
|
||||
workspaceRole={currentWorkspaceRole}
|
||||
projectRole={currentProjectRole}
|
||||
@ -55,7 +55,8 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
|
||||
posthogHost={envConfig?.posthog_host || null}
|
||||
>
|
||||
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||
</PostHogProvider>
|
||||
</PostHogProvider> */}
|
||||
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||
</CrispWrapper>
|
||||
</StoreWrapper>
|
||||
</InstanceLayout>
|
||||
|
@ -30,13 +30,7 @@ export class GlobalViewRootStore {
|
||||
cycleUserViewStore?: userViewRootStore;
|
||||
|
||||
constructor(private store: RootStore) {
|
||||
const defaultViews: any[] = [
|
||||
{
|
||||
id: "all-issues",
|
||||
name: "All Issues",
|
||||
filters: {},
|
||||
is_local_view: true,
|
||||
},
|
||||
const workspaceViewMeStoreDefaultViews: any[] = [
|
||||
{
|
||||
id: "assigned",
|
||||
name: "Assigned",
|
||||
@ -62,9 +56,21 @@ export class GlobalViewRootStore {
|
||||
is_local_view: true,
|
||||
},
|
||||
];
|
||||
const workspaceViewStoreDefaultViews: any[] = [
|
||||
{
|
||||
id: "all-issues",
|
||||
name: "All Issues",
|
||||
filters: {},
|
||||
is_local_view: true,
|
||||
},
|
||||
];
|
||||
|
||||
this.workspaceViewMeStore = new ViewRootStore(this.store, new WorkspaceMeViewService());
|
||||
this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService(), defaultViews);
|
||||
this.workspaceViewMeStore = new ViewRootStore(
|
||||
this.store,
|
||||
new WorkspaceMeViewService(),
|
||||
workspaceViewMeStoreDefaultViews
|
||||
);
|
||||
this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService(), workspaceViewStoreDefaultViews);
|
||||
this.projectViewStore = new ViewRootStore(this.store, new ProjectViewService());
|
||||
this.projectViewMeStore = new ViewRootStore(this.store, new ProjectViewMeService());
|
||||
|
||||
|
@ -51,13 +51,13 @@ export class userViewRootStore implements TUserViewRootStore {
|
||||
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)
|
||||
);
|
||||
});
|
||||
// runInAction(() => {
|
||||
// if (this.workspaceSlug && view.id)
|
||||
// set(
|
||||
// this.viewMap,
|
||||
// [view.id],
|
||||
// new UserViewStore(view, this.service, this.workspaceSlug, this.projectId, this.featureId)
|
||||
// );
|
||||
// });
|
||||
};
|
||||
}
|
||||
|
@ -1,109 +1,155 @@
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import set from "lodash/set";
|
||||
// store
|
||||
import { RootStore } from "store/root.store";
|
||||
// types
|
||||
import { TUserViewService } from "services/view/types";
|
||||
import { TViewService } from "services/view/types";
|
||||
import {
|
||||
TUserView,
|
||||
TView,
|
||||
TViewFilters,
|
||||
TViewDisplayFilters,
|
||||
TViewDisplayProperties,
|
||||
TViewFilterProps,
|
||||
TViewFilterPartialProps,
|
||||
TViewAccess,
|
||||
} from "@plane/types";
|
||||
// helpers
|
||||
import { FiltersHelper } from "../helpers/filters_helpers";
|
||||
|
||||
type TLoader = "submitting" | "submit" | undefined;
|
||||
|
||||
export type TUserViewStore = TUserView & {
|
||||
export type TUserViewStore = TView & {
|
||||
// observables
|
||||
loader: TLoader;
|
||||
filtersToUpdate: TViewFilterPartialProps;
|
||||
filtersToUpdate: Partial<TView>;
|
||||
// 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;
|
||||
setName: (name: string) => void;
|
||||
setDescription: (description: string) => void;
|
||||
setFilters: (filters: Partial<TViewFilters>) => void;
|
||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||
resetChanges: () => void;
|
||||
saveChanges: () => Promise<void>;
|
||||
// actions
|
||||
update: (viewData: Partial<TUserView>) => Promise<void>;
|
||||
lockView: () => Promise<void>;
|
||||
unlockView: () => Promise<void>;
|
||||
makeFavorite: () => Promise<void>;
|
||||
removeFavorite: () => Promise<void>;
|
||||
update: (viewData: Partial<TView>) => 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;
|
||||
name: string | undefined;
|
||||
description: string | undefined;
|
||||
query: string | undefined;
|
||||
filters: TViewFilters;
|
||||
display_filters: TViewDisplayFilters;
|
||||
display_properties: TViewDisplayProperties;
|
||||
access: TViewAccess | undefined;
|
||||
owned_by: string | undefined;
|
||||
sort_order: number | 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;
|
||||
|
||||
is_local_view: boolean = false;
|
||||
is_create: boolean = false;
|
||||
is_editable: boolean = false;
|
||||
loader: TLoader = undefined;
|
||||
filtersToUpdate: TViewFilterPartialProps = {
|
||||
filters: {},
|
||||
display_filters: {},
|
||||
display_properties: {},
|
||||
filtersToUpdate: Partial<TView> = {
|
||||
name: "",
|
||||
description: "",
|
||||
filters: undefined,
|
||||
display_filters: undefined,
|
||||
display_properties: undefined,
|
||||
};
|
||||
|
||||
constructor(
|
||||
_view: TUserView,
|
||||
private service: TUserViewService,
|
||||
private workspaceSlug: string,
|
||||
private projectId: string | undefined,
|
||||
private featureId: string | undefined // moduleId/cycleId
|
||||
) {
|
||||
constructor(private store: RootStore, _view: TView, private service: TViewService) {
|
||||
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.name = _view.name;
|
||||
this.description = _view.description;
|
||||
this.query = _view.query;
|
||||
this.filters = this.computedFilters(_view.filters);
|
||||
this.display_filters = this.computedDisplayFilters(_view.display_filters);
|
||||
this.display_properties = this.computedDisplayProperties(_view.display_properties);
|
||||
this.access = _view.access;
|
||||
this.owned_by = _view.owned_by;
|
||||
this.sort_order = _view.sort_order;
|
||||
this.is_locked = _view.is_locked;
|
||||
this.is_pinned = _view.is_pinned;
|
||||
this.is_favorite = _view.is_favorite;
|
||||
this.created_by = _view.created_by;
|
||||
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;
|
||||
this.is_editable = _view.is_editable;
|
||||
|
||||
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,
|
||||
is_editable: observable.ref,
|
||||
loader: observable.ref,
|
||||
filtersToUpdate: observable,
|
||||
// computed
|
||||
appliedFilters: computed,
|
||||
appliedFiltersQueryParams: computed,
|
||||
// helper actions
|
||||
updateFilters: action,
|
||||
updateDisplayFilters: action,
|
||||
updateDisplayProperties: action,
|
||||
resetFilterChanges: action,
|
||||
saveFilterChanges: action,
|
||||
setName: action,
|
||||
setFilters: action,
|
||||
setDisplayFilters: action,
|
||||
setDisplayProperties: action,
|
||||
resetChanges: action,
|
||||
saveChanges: action,
|
||||
// actions
|
||||
update: action,
|
||||
lockView: action,
|
||||
unlockView: 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,
|
||||
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
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ -114,14 +160,29 @@ export class UserViewStore extends FiltersHelper implements TUserViewStore {
|
||||
}
|
||||
|
||||
// helper actions
|
||||
updateFilters = (filters: Partial<TViewFilters>) => {
|
||||
setName = (name: string) => {
|
||||
runInAction(() => {
|
||||
this.loader = "submit";
|
||||
this.filtersToUpdate.filters = filters;
|
||||
this.filtersToUpdate.name = name;
|
||||
});
|
||||
};
|
||||
|
||||
updateDisplayFilters = async (display_filters: Partial<TViewDisplayFilters>) => {
|
||||
setDescription = (description: string) => {
|
||||
runInAction(() => {
|
||||
this.filtersToUpdate.description = description;
|
||||
});
|
||||
};
|
||||
|
||||
setFilters = (filters: Partial<TViewFilters>) => {
|
||||
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<TViewDisplayFilters>) => {
|
||||
const appliedFilters = this.appliedFilters;
|
||||
|
||||
const layout = appliedFilters?.display_filters?.layout;
|
||||
@ -129,7 +190,7 @@ export class UserViewStore extends FiltersHelper implements TUserViewStore {
|
||||
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";
|
||||
@ -137,47 +198,130 @@ export class UserViewStore extends FiltersHelper implements TUserViewStore {
|
||||
if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false;
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
updateDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => {
|
||||
setDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => {
|
||||
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]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
resetFilterChanges = () => {
|
||||
resetChanges = () => {
|
||||
runInAction(() => {
|
||||
this.loader = undefined;
|
||||
this.filtersToUpdate = {
|
||||
filters: {},
|
||||
display_filters: {},
|
||||
display_properties: {},
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
filters: this.filters,
|
||||
display_filters: this.display_filters,
|
||||
display_properties: this.display_properties,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
saveFilterChanges = async () => {
|
||||
saveChanges = async () => {
|
||||
try {
|
||||
this.loader = "submitting";
|
||||
if (this.appliedFilters) await this.update(this.appliedFilters);
|
||||
if (this.filtersToUpdate) await this.update(this.filtersToUpdate);
|
||||
this.loader = undefined;
|
||||
} catch {
|
||||
this.loader = undefined;
|
||||
Object.keys(this.filtersToUpdate).forEach((key) => {
|
||||
const _key = key as keyof TView;
|
||||
set(this, _key, this.filtersToUpdate[_key]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// actions
|
||||
update = async (viewData: Partial<TViewFilterProps>) => {
|
||||
if (!this.workspaceSlug || !this.id) return;
|
||||
lockView = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id || !this.service.lock) return;
|
||||
|
||||
const view = await this.service.update(this.workspaceSlug, viewData, this.projectId, this.featureId);
|
||||
const view = await this.service.lock(workspaceSlug, this.id, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
this.is_locked = view.is_locked;
|
||||
});
|
||||
} catch {
|
||||
this.is_locked = this.is_locked;
|
||||
}
|
||||
};
|
||||
|
||||
unlockView = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id || !this.service.unlock) return;
|
||||
|
||||
const view = await this.service.unlock(workspaceSlug, this.id, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
this.is_locked = view.is_locked;
|
||||
});
|
||||
} catch {
|
||||
this.is_locked = this.is_locked;
|
||||
}
|
||||
};
|
||||
|
||||
makeFavorite = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id || !this.service.makeFavorite) return;
|
||||
|
||||
const view = await this.service.makeFavorite(workspaceSlug, this.id, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
this.is_favorite = view.is_locked;
|
||||
});
|
||||
} catch {
|
||||
this.is_favorite = this.is_favorite;
|
||||
}
|
||||
};
|
||||
|
||||
removeFavorite = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id || !this.service.removeFavorite) return;
|
||||
|
||||
const view = await this.service.removeFavorite(workspaceSlug, this.id, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
this.is_favorite = view.is_locked;
|
||||
});
|
||||
} catch {
|
||||
this.is_favorite = this.is_favorite;
|
||||
}
|
||||
};
|
||||
|
||||
update = async (viewData: Partial<TView>) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id) return;
|
||||
|
||||
const view = await this.service.update(workspaceSlug, this.id, viewData, projectId);
|
||||
if (!view) return;
|
||||
|
||||
runInAction(() => {
|
||||
Object.keys(viewData).forEach((key) => {
|
||||
const _key = key as keyof TViewFilterProps;
|
||||
const _key = key as keyof TView;
|
||||
set(this, _key, viewData[_key]);
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
this.resetChanges();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import set from "lodash/set";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import reverse from "lodash/reverse";
|
||||
// stores
|
||||
import { RootStore } from "store/root.store";
|
||||
import { ViewStore } from "./view.store";
|
||||
@ -18,9 +20,8 @@ type TViewRootStore = {
|
||||
// helper actions
|
||||
viewById: (viewId: string) => ViewStore | undefined;
|
||||
// actions
|
||||
fetch: (_loader?: TLoader) => Promise<void>;
|
||||
localViewCreate: (view: TView) => Promise<void>;
|
||||
clearLocalView: (viewId: string) => Promise<void>;
|
||||
fetch: (_loader?: TLoader) => Promise<void>;
|
||||
create: (view: Partial<TView>) => Promise<void>;
|
||||
remove: (viewId: string) => Promise<void>;
|
||||
duplicate: (viewId: string) => Promise<void>;
|
||||
@ -39,9 +40,8 @@ export class ViewRootStore implements TViewRootStore {
|
||||
// computed
|
||||
viewIds: computed,
|
||||
// actions
|
||||
fetch: action,
|
||||
localViewCreate: action,
|
||||
clearLocalView: action,
|
||||
fetch: action,
|
||||
create: action,
|
||||
remove: action,
|
||||
duplicate: action,
|
||||
@ -51,19 +51,32 @@ export class ViewRootStore implements TViewRootStore {
|
||||
// computed
|
||||
get viewIds() {
|
||||
const views = Object.values(this.viewMap);
|
||||
return views.filter((view) => !view?.is_create).map((view) => view.id) as string[];
|
||||
const localViews = views.filter((view) => view.is_local_view);
|
||||
let apiViews = views.filter((view) => !view.is_local_view && !view.is_create);
|
||||
apiViews = reverse(sortBy(apiViews, "sort_order"));
|
||||
|
||||
const _viewIds = [...localViews.map((view) => view.id), ...apiViews.map((view) => view.id)];
|
||||
|
||||
return _viewIds as string[];
|
||||
}
|
||||
|
||||
// helper actions
|
||||
viewById = (viewId: string) => this.viewMap?.[viewId] || undefined;
|
||||
|
||||
// actions
|
||||
localViewCreate = async (view: TView) => {
|
||||
runInAction(() => {
|
||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
||||
});
|
||||
};
|
||||
|
||||
fetch = async (_loader: TLoader = "init-loader") => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
runInAction(() => {
|
||||
if (this.defaultViews && this.defaultViews.length > 0)
|
||||
runInAction(() => {
|
||||
this.defaultViews?.forEach((view) => {
|
||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
||||
});
|
||||
@ -79,21 +92,11 @@ export class ViewRootStore implements TViewRootStore {
|
||||
});
|
||||
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];
|
||||
});
|
||||
} catch {}
|
||||
};
|
||||
|
||||
create = async (data: Partial<TView>) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
@ -103,20 +106,27 @@ export class ViewRootStore implements TViewRootStore {
|
||||
runInAction(() => {
|
||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
||||
});
|
||||
|
||||
if (data.id) this.remove(data.id);
|
||||
} catch {}
|
||||
};
|
||||
|
||||
remove = async (viewId: string) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !viewId) return;
|
||||
|
||||
if (this.viewMap?.[viewId] != undefined && !this.viewMap?.[viewId]?.is_create)
|
||||
await this.service.remove?.(workspaceSlug, viewId, projectId);
|
||||
|
||||
runInAction(() => {
|
||||
delete this.viewMap[viewId];
|
||||
});
|
||||
} catch {}
|
||||
};
|
||||
|
||||
duplicate = async (viewId: string) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.service.duplicate) return;
|
||||
|
||||
@ -126,5 +136,6 @@ export class ViewRootStore implements TViewRootStore {
|
||||
runInAction(() => {
|
||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
||||
});
|
||||
} catch {}
|
||||
};
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
TViewDisplayFilters,
|
||||
TViewDisplayProperties,
|
||||
TViewFilterProps,
|
||||
TViewFilterPartialProps,
|
||||
TViewAccess,
|
||||
} from "@plane/types";
|
||||
// helpers
|
||||
@ -21,17 +20,18 @@ type TLoader = "submitting" | "submit" | undefined;
|
||||
export type TViewStore = TView & {
|
||||
// observables
|
||||
loader: TLoader;
|
||||
filtersToUpdate: TViewFilterPartialProps;
|
||||
filtersToUpdate: Partial<TView>;
|
||||
// computed
|
||||
appliedFilters: TViewFilterProps | undefined;
|
||||
appliedFiltersQueryParams: string | undefined;
|
||||
// helper actions
|
||||
setName: (name: string) => void;
|
||||
setDescription: (description: string) => void;
|
||||
setFilters: (filters: Partial<TViewFilters>) => void;
|
||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||
resetFilterChanges: () => void;
|
||||
saveFilterChanges: () => void;
|
||||
resetChanges: () => void;
|
||||
saveChanges: () => Promise<void>;
|
||||
// actions
|
||||
lockView: () => Promise<void>;
|
||||
unlockView: () => Promise<void>;
|
||||
@ -60,15 +60,16 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
updated_by: string | undefined;
|
||||
created_at: Date | undefined;
|
||||
updated_at: Date | undefined;
|
||||
// local variables
|
||||
is_local_view: boolean = false;
|
||||
is_create: boolean = false;
|
||||
|
||||
is_editable: boolean = false;
|
||||
loader: TLoader = undefined;
|
||||
filtersToUpdate: TViewFilterPartialProps = {
|
||||
filters: {},
|
||||
display_filters: {},
|
||||
display_properties: {},
|
||||
filtersToUpdate: Partial<TView> = {
|
||||
name: "",
|
||||
description: "",
|
||||
filters: undefined,
|
||||
display_filters: undefined,
|
||||
display_properties: undefined,
|
||||
};
|
||||
|
||||
constructor(private store: RootStore, _view: TView, private service: TViewService) {
|
||||
@ -92,8 +93,10 @@ 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;
|
||||
this.is_editable = _view.is_editable;
|
||||
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
@ -118,6 +121,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
updated_at: observable.ref,
|
||||
is_local_view: observable.ref,
|
||||
is_create: observable.ref,
|
||||
is_editable: observable.ref,
|
||||
loader: observable.ref,
|
||||
filtersToUpdate: observable,
|
||||
// computed
|
||||
@ -128,8 +132,8 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
setFilters: action,
|
||||
setDisplayFilters: action,
|
||||
setDisplayProperties: action,
|
||||
resetFilterChanges: action,
|
||||
saveFilterChanges: action,
|
||||
resetChanges: action,
|
||||
saveChanges: action,
|
||||
// actions
|
||||
update: action,
|
||||
lockView: action,
|
||||
@ -158,7 +162,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
// helper actions
|
||||
setName = (name: string) => {
|
||||
runInAction(() => {
|
||||
this.name = name;
|
||||
this.filtersToUpdate.name = name;
|
||||
});
|
||||
};
|
||||
|
||||
setDescription = (description: string) => {
|
||||
runInAction(() => {
|
||||
this.filtersToUpdate.description = description;
|
||||
});
|
||||
};
|
||||
|
||||
@ -188,19 +198,15 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false;
|
||||
|
||||
runInAction(() => {
|
||||
this.loader = "submit";
|
||||
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);
|
||||
};
|
||||
|
||||
setDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => {
|
||||
runInAction(() => {
|
||||
this.loader = "submit";
|
||||
Object.keys(display_properties).forEach((key) => {
|
||||
const _key = key as keyof TViewDisplayProperties;
|
||||
set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]);
|
||||
@ -208,25 +214,36 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
});
|
||||
};
|
||||
|
||||
resetFilterChanges = () => {
|
||||
resetChanges = () => {
|
||||
runInAction(() => {
|
||||
this.loader = undefined;
|
||||
this.filtersToUpdate = {
|
||||
filters: {},
|
||||
display_filters: {},
|
||||
display_properties: {},
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
filters: this.filters,
|
||||
display_filters: this.display_filters,
|
||||
display_properties: this.display_properties,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
saveFilterChanges = async () => {
|
||||
saveChanges = async () => {
|
||||
try {
|
||||
this.loader = "submitting";
|
||||
if (this.appliedFilters) await this.update(this.appliedFilters);
|
||||
if (this.filtersToUpdate) await this.update(this.filtersToUpdate);
|
||||
this.loader = undefined;
|
||||
} catch {
|
||||
this.loader = undefined;
|
||||
Object.keys(this.filtersToUpdate).forEach((key) => {
|
||||
const _key = key as keyof TView;
|
||||
set(this, _key, this.filtersToUpdate[_key]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// actions
|
||||
lockView = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id || !this.service.lock) return;
|
||||
|
||||
@ -236,9 +253,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
runInAction(() => {
|
||||
this.is_locked = view.is_locked;
|
||||
});
|
||||
} catch {
|
||||
this.is_locked = this.is_locked;
|
||||
}
|
||||
};
|
||||
|
||||
unlockView = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id || !this.service.unlock) return;
|
||||
|
||||
@ -248,9 +269,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
runInAction(() => {
|
||||
this.is_locked = view.is_locked;
|
||||
});
|
||||
} catch {
|
||||
this.is_locked = this.is_locked;
|
||||
}
|
||||
};
|
||||
|
||||
makeFavorite = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id || !this.service.makeFavorite) return;
|
||||
|
||||
@ -260,9 +285,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
runInAction(() => {
|
||||
this.is_favorite = view.is_locked;
|
||||
});
|
||||
} catch {
|
||||
this.is_favorite = this.is_favorite;
|
||||
}
|
||||
};
|
||||
|
||||
removeFavorite = async () => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id || !this.service.removeFavorite) return;
|
||||
|
||||
@ -272,9 +301,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
runInAction(() => {
|
||||
this.is_favorite = view.is_locked;
|
||||
});
|
||||
} catch {
|
||||
this.is_favorite = this.is_favorite;
|
||||
}
|
||||
};
|
||||
|
||||
update = async (viewData: Partial<TView>) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug || !this.id) return;
|
||||
|
||||
@ -287,5 +320,8 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
set(this, _key, viewData[_key]);
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
this.resetChanges();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user