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
|
// local view variables
|
||||||
is_local_view: boolean;
|
is_local_view: boolean;
|
||||||
is_create: boolean;
|
is_create: boolean;
|
||||||
|
is_editable: boolean;
|
||||||
};
|
};
|
||||||
|
@ -48,7 +48,9 @@ export const ProfileSidebar = observer(() => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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 ? (
|
{userProjectsData ? (
|
||||||
<>
|
<>
|
||||||
<div className="relative h-32">
|
<div className="relative h-32">
|
||||||
|
@ -5,11 +5,11 @@ import { CheckCircle } from "lucide-react";
|
|||||||
import { useView, useViewDetail } from "hooks/store";
|
import { useView, useViewDetail } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import { ViewRoot, ViewCreateEdit, ViewFiltersRoot, ViewAppliedFiltersRoot, ViewLayoutRoot } from ".";
|
import { ViewRoot, ViewCreateEditForm, ViewAppliedFiltersRoot, ViewLayoutRoot } from ".";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
import { VIEW_TYPES } from "constants/view";
|
import { VIEW_TYPES, viewLocalPayload } from "constants/view";
|
||||||
// types
|
// types
|
||||||
import { TViewOperations } from "./types";
|
import { TViewOperations } from "./types";
|
||||||
import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types";
|
import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types";
|
||||||
@ -23,6 +23,7 @@ type TAllIssuesViewRoot = {
|
|||||||
export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, viewId } = props;
|
const { workspaceSlug, projectId, viewId } = props;
|
||||||
// states
|
// states
|
||||||
|
const [currentCreateEditViewId, setCurrentCreateEditViewId] = useState<string | undefined>(undefined);
|
||||||
const [viewType, setViewType] = useState(VIEW_TYPES.WORKSPACE_VIEWS);
|
const [viewType, setViewType] = useState(VIEW_TYPES.WORKSPACE_VIEWS);
|
||||||
const workspaceViewTabOptions = [
|
const workspaceViewTabOptions = [
|
||||||
{
|
{
|
||||||
@ -43,18 +44,45 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
|||||||
|
|
||||||
const viewOperations: TViewOperations = useMemo(
|
const viewOperations: TViewOperations = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
localViewCreate: (data) => viewStore?.localViewCreate(data),
|
setName: (name: string) => viewDetailStore?.setName(name),
|
||||||
clearLocalView: (viewId: string) => viewStore?.clearLocalView(viewId),
|
setDescription: (name: string) => viewDetailStore?.setDescription(name),
|
||||||
setFilters: (filters: Partial<TViewFilters>) => viewDetailStore?.setFilters(filters),
|
setFilters: (filters: Partial<TViewFilters>) => viewDetailStore?.setFilters(filters),
|
||||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) =>
|
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) =>
|
||||||
viewDetailStore?.setDisplayFilters(display_filters),
|
viewDetailStore?.setDisplayFilters(display_filters),
|
||||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) =>
|
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) =>
|
||||||
viewDetailStore?.setDisplayProperties(display_properties),
|
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(),
|
fetch: async () => await viewStore?.fetch(),
|
||||||
create: async (data: Partial<TView>) => {
|
create: async (data: Partial<TView>) => {
|
||||||
try {
|
try {
|
||||||
await viewStore?.create(data);
|
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 {
|
} catch {
|
||||||
setToastAlert({ title: "Error", message: "Error creating view", type: "error" });
|
setToastAlert({ title: "Error", message: "Error creating view", type: "error" });
|
||||||
}
|
}
|
||||||
@ -77,15 +105,15 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="font-medium">All Issues</div>
|
<div className="font-medium">All Issues</div>
|
||||||
</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) => (
|
{workspaceViewTabOptions.map((tab) => (
|
||||||
<div
|
<div
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
className={`p-4 py-1.5 rounded text-sm transition-all cursor-pointer font-medium
|
className={`p-4 py-1.5 rounded text-sm transition-all cursor-pointer font-medium
|
||||||
${
|
${
|
||||||
viewType === tab.key
|
viewType === tab.key
|
||||||
? "text-custom-text-100 bg-custom-background-90"
|
? "text-custom-text-100 bg-custom-background-100"
|
||||||
: "text-custom-text-200 bg-custom-background-80 hover:text-custom-text-100"
|
: "text-custom-text-300 bg-custom-background-80 hover:text-custom-text-100"
|
||||||
}`}
|
}`}
|
||||||
onClick={tab.onClick}
|
onClick={tab.onClick}
|
||||||
>
|
>
|
||||||
@ -101,6 +129,7 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
<div className="border-b border-custom-border-200">
|
||||||
<ViewRoot
|
<ViewRoot
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -108,16 +137,10 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
|||||||
viewType={viewType}
|
viewType={viewType}
|
||||||
viewOperations={viewOperations}
|
viewOperations={viewOperations}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
{/* <ViewFiltersRoot
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
viewId={viewId}
|
|
||||||
viewOperations={viewOperations}
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
<div className="p-5 border-b border-custom-border-200 relative flex gap-2">
|
<div className="p-5 border-b border-custom-border-200 relative flex gap-2">
|
||||||
<div className="w-full">
|
{/* <div className="w-full">
|
||||||
<ViewAppliedFiltersRoot
|
<ViewAppliedFiltersRoot
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -125,9 +148,9 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
|||||||
viewType={viewType}
|
viewType={viewType}
|
||||||
viewOperations={viewOperations}
|
viewOperations={viewOperations}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<div className="flex-shrink-0 h-full">
|
{/* <div className="flex-shrink-0 h-full">
|
||||||
<ViewLayoutRoot
|
<ViewLayoutRoot
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -135,32 +158,34 @@ export const AllIssuesViewRoot: FC<TAllIssuesViewRoot> = observer((props) => {
|
|||||||
viewType={viewType}
|
viewType={viewType}
|
||||||
viewOperations={viewOperations}
|
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
|
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
|
Display Filters
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{!viewDetailStore?.is_local_view && (
|
{/* {!viewDetailStore?.is_local_view && (
|
||||||
<div className="flex-shrink-0 h-full">
|
<div className="flex-shrink-0 h-full">
|
||||||
<ViewCreateEdit
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
viewId={viewId}
|
|
||||||
viewType={viewType}
|
|
||||||
viewOperations={viewOperations}
|
|
||||||
>
|
|
||||||
<div>Edit</div>
|
<div>Edit</div>
|
||||||
</ViewCreateEdit>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{currentCreateEditViewId != undefined && (
|
||||||
|
<ViewCreateEditForm
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
viewId={currentCreateEditViewId}
|
||||||
|
viewType={viewType}
|
||||||
|
viewOperations={viewOperations}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, Fragment } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import isEmpty from "lodash/isEmpty";
|
import isEmpty from "lodash/isEmpty";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
@ -33,13 +33,11 @@ export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
|||||||
|
|
||||||
if (!filterKeyValue || filterKeyValue.length <= -1) return <></>;
|
if (!filterKeyValue || filterKeyValue.length <= -1) return <></>;
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 py-1 min-h-[32px]">
|
||||||
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="flex-shrink-0 text-xs text-custom-text-200">{generateTitle(filterKey)}</div>
|
<div className="flex-shrink-0 text-xs text-custom-text-200">{generateTitle(filterKey)}</div>
|
||||||
<div className="relative flex items-center gap-1 flex-wrap">
|
<div className="relative flex items-center gap-1 flex-wrap">
|
||||||
{["1", "2", "3", "4"].map((filterId) => (
|
{["1", "2", "3", "4"].map((filterId) => (
|
||||||
|
<Fragment key={filterId}>
|
||||||
<ViewAppliedFiltersItem
|
<ViewAppliedFiltersItem
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -48,6 +46,7 @@ export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
|||||||
filterKey={filterKey}
|
filterKey={filterKey}
|
||||||
filterId={filterId}
|
filterId={filterId}
|
||||||
/>
|
/>
|
||||||
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import isEmpty from "lodash/isEmpty";
|
import isEmpty from "lodash/isEmpty";
|
||||||
// types
|
// types
|
||||||
import { TFilters } from "@plane/types";
|
import { TViewFilters } from "@plane/types";
|
||||||
|
|
||||||
type TComputedAppliedFilters = {
|
type TComputedAppliedFilters = {
|
||||||
key: string;
|
key: string;
|
||||||
@ -10,7 +10,7 @@ type TComputedAppliedFilters = {
|
|||||||
dropdownOptions?: { id: string; icon: ""; title: ""; component: ReactNode }[];
|
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) {
|
switch (key) {
|
||||||
case "project":
|
case "project":
|
||||||
return [];
|
return [];
|
||||||
@ -45,12 +45,12 @@ export const generateTitle = (title: string) =>
|
|||||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
export const constructAppliedFilters = (filters: TFilters): TComputedAppliedFilters => {
|
export const constructAppliedFilters = (filters: TViewFilters): TComputedAppliedFilters => {
|
||||||
const appliedFilters: TComputedAppliedFilters = [];
|
const appliedFilters: TComputedAppliedFilters = [];
|
||||||
|
|
||||||
if (filters && !isEmpty(filters)) {
|
if (filters && !isEmpty(filters)) {
|
||||||
Object.keys(filters).forEach((_filterKey) => {
|
Object.keys(filters).forEach((_filterKey) => {
|
||||||
const _key = _filterKey as keyof TFilters;
|
const _key = _filterKey as keyof TViewFilters;
|
||||||
const _value = filters[_key];
|
const _value = filters[_key];
|
||||||
|
|
||||||
if (_value && !isEmpty(_value)) {
|
if (_value && !isEmpty(_value)) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, Fragment } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import isEmpty from "lodash/isEmpty";
|
import isEmpty from "lodash/isEmpty";
|
||||||
@ -43,6 +43,7 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
|
|||||||
{filterKeys.map((key) => {
|
{filterKeys.map((key) => {
|
||||||
const filterKey = key as keyof TViewFilters;
|
const filterKey = key as keyof TViewFilters;
|
||||||
return (
|
return (
|
||||||
|
<Fragment key={filterKey}>
|
||||||
<ViewAppliedFilters
|
<ViewAppliedFilters
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -50,6 +51,7 @@ export const ViewAppliedFiltersRoot: FC<TViewAppliedFiltersRoot> = observer((pro
|
|||||||
viewType={viewType}
|
viewType={viewType}
|
||||||
filterKey={filterKey}
|
filterKey={filterKey}
|
||||||
/>
|
/>
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<div
|
<div
|
||||||
|
@ -3,7 +3,10 @@ export * from "./all-issues-root";
|
|||||||
// views
|
// views
|
||||||
export * from "./views/root";
|
export * from "./views/root";
|
||||||
export * from "./views/view-item";
|
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";
|
export * from "./views/create-edit-form";
|
||||||
|
|
||||||
// layouts
|
// 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";
|
import { TView } from "@plane/types";
|
||||||
|
|
||||||
export type TViewOperations = {
|
export type TViewOperations = {
|
||||||
localViewCreate: (data: TView) => void;
|
setName: (name: string) => void;
|
||||||
clearLocalView: (viewId: string) => void;
|
setDescription: (description: string) => void;
|
||||||
setFilters: (filters: Partial<TViewFilters>) => void;
|
setFilters: (filters: Partial<TViewFilters>) => void;
|
||||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||||
|
|
||||||
|
localViewCreateEdit: (viewId: string | undefined) => void;
|
||||||
|
localViewCreateEditClear: (viewId: string | undefined) => Promise<void>;
|
||||||
|
|
||||||
fetch: () => Promise<void>;
|
fetch: () => Promise<void>;
|
||||||
create: (data: Partial<TView>) => 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 { observer } from "mobx-react-lite";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Trash2, Plus, X } from "lucide-react";
|
import { Briefcase, Globe2, Plus, X } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useViewDetail } from "hooks/store";
|
import { useViewDetail, useProject } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { ViewAppliedFiltersRoot } from "../";
|
import { ViewAppliedFiltersRoot } from "../";
|
||||||
// ui
|
// ui
|
||||||
import { Input, Button } from "@plane/ui";
|
import { Input, Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { TView, TViewTypes } from "@plane/types";
|
import { TViewTypes } from "@plane/types";
|
||||||
import { TViewOperations } from "../types";
|
import { TViewOperations } from "../types";
|
||||||
|
|
||||||
type TViewCreateEditForm = {
|
type TViewCreateEditForm = {
|
||||||
@ -18,28 +18,50 @@ type TViewCreateEditForm = {
|
|||||||
viewId: string;
|
viewId: string;
|
||||||
viewType: TViewTypes;
|
viewType: TViewTypes;
|
||||||
viewOperations: TViewOperations;
|
viewOperations: TViewOperations;
|
||||||
modalToggle: boolean;
|
|
||||||
handleModalClose: () => void;
|
|
||||||
onSubmit: (viewData: Partial<TView>) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
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
|
// hooks
|
||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
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 onContinue = async () => {
|
||||||
const payload: Partial<TView> = {
|
setLoader(true);
|
||||||
id: viewDetailStore?.id,
|
if (viewDetailStore?.is_create) {
|
||||||
name: viewDetailStore?.name,
|
const payload = viewDetailStore?.filtersToUpdate;
|
||||||
filters: viewDetailStore?.filters,
|
await viewOperations.create(payload);
|
||||||
};
|
modalClose();
|
||||||
onSubmit(payload);
|
} 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 (
|
return (
|
||||||
<Transition.Root show={modalToggle} as={Fragment}>
|
<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
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
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">
|
<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="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">
|
{projectId && projectDetails ? (
|
||||||
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 overflow-hidden">
|
<div className="relative rounded p-1.5 px-2 flex items-center gap-1 border border-custom-border-100 bg-custom-background-80">
|
||||||
<Trash2 className="w-3.5 h-3.5" />
|
<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>
|
||||||
<div className="text-xs uppercase">Project Identifier</div>
|
<div className="text-xs uppercase font-medium">{projectDetails?.identifier || "Project"}</div>
|
||||||
</div> */}
|
</div>
|
||||||
<div className="">Create View</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>
|
||||||
|
|
||||||
<div className="p-3 px-5">
|
<div className="p-3 px-5">
|
||||||
@ -79,7 +110,7 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
|||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
type="text"
|
type="text"
|
||||||
value={viewDetailStore?.name || ""}
|
value={viewDetailStore?.filtersToUpdate?.name || ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
viewDetailStore?.setName(e.target.value);
|
viewDetailStore?.setName(e.target.value);
|
||||||
}}
|
}}
|
||||||
@ -90,13 +121,13 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 px-5 relative flex justify-between items-center gap-2">
|
<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">
|
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 overflow-hidden">
|
||||||
<Plus className="w-3 h-3" />
|
<Plus className="w-3 h-3" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs">Filters</div>
|
<div className="text-xs">Filters</div>
|
||||||
</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="text-xs">Clear all filters</div>
|
||||||
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 overflow-hidden">
|
<div className="flex-shrink-0 relative flex justify-center items-center w-4 h-4 overflow-hidden">
|
||||||
<X className="w-3 h-3" />
|
<X className="w-3 h-3" />
|
||||||
@ -115,11 +146,11 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 px-5 relative flex justify-end items-center gap-2">
|
<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
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" onClick={onContinue}>
|
<Button variant="primary" onClick={onContinue} disabled={loader}>
|
||||||
Create View
|
{loader ? `Saving...` : `${viewDetailStore?.is_create ? `Create` : `Update`} View`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</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 { observer } from "mobx-react-lite";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useView } from "hooks/store";
|
import { useView } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { ViewItem, ViewCreateEdit } from "../";
|
import { ViewItem, ViewDropdown } from "../";
|
||||||
|
// ui
|
||||||
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { TViewOperations } from "../types";
|
import { TViewOperations } from "../types";
|
||||||
import { TViewTypes } from "@plane/types";
|
import { TViewTypes } from "@plane/types";
|
||||||
@ -20,12 +23,47 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
|||||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
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 (
|
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 && (
|
{viewStore?.viewIds && viewStore?.viewIds.length > 0 && (
|
||||||
<div key={`views_list_${viewId}`} className="relative flex items-center w-full overflow-x-auto">
|
<div
|
||||||
{viewStore?.viewIds.map((_viewId) => (
|
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
|
<ViewItem
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
@ -33,18 +71,36 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
|||||||
viewType={viewType}
|
viewType={viewType}
|
||||||
viewItemId={_viewId}
|
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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex-shrink-0 my-auto pb-1">
|
<div className="flex-shrink-0 my-auto pb-1">
|
||||||
<ViewCreateEdit
|
<Button size="sm" prependIcon={<Plus />} onClick={() => viewOperations?.localViewCreateEdit(undefined)}>
|
||||||
workspaceSlug={workspaceSlug}
|
New View
|
||||||
projectId={projectId}
|
</Button>
|
||||||
viewId={undefined}
|
|
||||||
viewType={viewType}
|
|
||||||
viewOperations={viewOperations}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { FC } from "react";
|
import { FC, Fragment } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useView } from "hooks/store";
|
import { useView, useViewDetail } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { PhotoFilterIcon } from "@plane/ui";
|
import { PhotoFilterIcon, Tooltip } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { TViewTypes } from "@plane/types";
|
import { TViewTypes } from "@plane/types";
|
||||||
|
|
||||||
@ -19,27 +19,31 @@ type TViewItem = {
|
|||||||
export const ViewItem: FC<TViewItem> = observer((props) => {
|
export const ViewItem: FC<TViewItem> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, viewId, viewType, viewItemId } = props;
|
const { workspaceSlug, projectId, viewId, viewType, viewItemId } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const viewStore = useView(workspaceSlug, projectId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewItemId, viewType);
|
||||||
|
|
||||||
const view = viewStore?.viewById(viewItemId);
|
if (!viewDetailStore) return <></>;
|
||||||
|
|
||||||
if (!view) return <></>;
|
|
||||||
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
|
<Link
|
||||||
href={`/${workspaceSlug}/workspace-views/${viewItemId}`}
|
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`}
|
${viewItemId === viewId ? `text-custom-primary-100 bg-custom-primary-100/10` : `border-transparent`}
|
||||||
`}
|
`}
|
||||||
onClick={(e) => viewItemId === viewId && e.preventDefault()}
|
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" />
|
<PhotoFilterIcon className="w-3 h-3" />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full max-w-[80px] inline-block text-sm line-clamp-1 truncate overflow-hidden font-medium">
|
<div className="w-full max-w-[80px] inline-block text-sm line-clamp-1 truncate overflow-hidden font-medium">
|
||||||
{view?.name}
|
{viewDetailStore?.name}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
<div className={`border-b-2 ${viewItemId === viewId ? `border-custom-primary-100` : `border-transparent`}`} />
|
<div className={`border-b-2 ${viewItemId === viewId ? `border-custom-primary-100` : `border-transparent`}`} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -13,9 +13,9 @@ export const viewLocalPayload: Partial<TView> = {
|
|||||||
id: uuidV4(),
|
id: uuidV4(),
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
filters: {},
|
filters: undefined,
|
||||||
display_filters: {},
|
display_filters: undefined,
|
||||||
display_properties: {},
|
display_properties: undefined,
|
||||||
is_local_view: false,
|
is_local_view: false,
|
||||||
is_create: true,
|
is_create: true,
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,7 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
|
|||||||
<InstanceLayout>
|
<InstanceLayout>
|
||||||
<StoreWrapper>
|
<StoreWrapper>
|
||||||
<CrispWrapper user={currentUser}>
|
<CrispWrapper user={currentUser}>
|
||||||
<PostHogProvider
|
{/* <PostHogProvider
|
||||||
user={currentUser}
|
user={currentUser}
|
||||||
workspaceRole={currentWorkspaceRole}
|
workspaceRole={currentWorkspaceRole}
|
||||||
projectRole={currentProjectRole}
|
projectRole={currentProjectRole}
|
||||||
@ -55,7 +55,8 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
|
|||||||
posthogHost={envConfig?.posthog_host || null}
|
posthogHost={envConfig?.posthog_host || null}
|
||||||
>
|
>
|
||||||
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||||
</PostHogProvider>
|
</PostHogProvider> */}
|
||||||
|
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
|
||||||
</CrispWrapper>
|
</CrispWrapper>
|
||||||
</StoreWrapper>
|
</StoreWrapper>
|
||||||
</InstanceLayout>
|
</InstanceLayout>
|
||||||
|
@ -30,13 +30,7 @@ export class GlobalViewRootStore {
|
|||||||
cycleUserViewStore?: userViewRootStore;
|
cycleUserViewStore?: userViewRootStore;
|
||||||
|
|
||||||
constructor(private store: RootStore) {
|
constructor(private store: RootStore) {
|
||||||
const defaultViews: any[] = [
|
const workspaceViewMeStoreDefaultViews: any[] = [
|
||||||
{
|
|
||||||
id: "all-issues",
|
|
||||||
name: "All Issues",
|
|
||||||
filters: {},
|
|
||||||
is_local_view: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "assigned",
|
id: "assigned",
|
||||||
name: "Assigned",
|
name: "Assigned",
|
||||||
@ -62,9 +56,21 @@ export class GlobalViewRootStore {
|
|||||||
is_local_view: true,
|
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.workspaceViewMeStore = new ViewRootStore(
|
||||||
this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService(), defaultViews);
|
this.store,
|
||||||
|
new WorkspaceMeViewService(),
|
||||||
|
workspaceViewMeStoreDefaultViews
|
||||||
|
);
|
||||||
|
this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService(), workspaceViewStoreDefaultViews);
|
||||||
this.projectViewStore = new ViewRootStore(this.store, new ProjectViewService());
|
this.projectViewStore = new ViewRootStore(this.store, new ProjectViewService());
|
||||||
this.projectViewMeStore = new ViewRootStore(this.store, new ProjectViewMeService());
|
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);
|
const view = await this.service.fetch(this.workspaceSlug, this.projectId, this.featureId);
|
||||||
if (!view) return;
|
if (!view) return;
|
||||||
|
|
||||||
runInAction(() => {
|
// runInAction(() => {
|
||||||
if (this.workspaceSlug && view.id)
|
// if (this.workspaceSlug && view.id)
|
||||||
set(
|
// set(
|
||||||
this.viewMap,
|
// this.viewMap,
|
||||||
[view.id],
|
// [view.id],
|
||||||
new UserViewStore(view, this.service, this.workspaceSlug, this.projectId, this.featureId)
|
// new UserViewStore(view, this.service, this.workspaceSlug, this.projectId, this.featureId)
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,109 +1,155 @@
|
|||||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
|
// store
|
||||||
|
import { RootStore } from "store/root.store";
|
||||||
// types
|
// types
|
||||||
import { TUserViewService } from "services/view/types";
|
import { TViewService } from "services/view/types";
|
||||||
import {
|
import {
|
||||||
TUserView,
|
TView,
|
||||||
TViewFilters,
|
TViewFilters,
|
||||||
TViewDisplayFilters,
|
TViewDisplayFilters,
|
||||||
TViewDisplayProperties,
|
TViewDisplayProperties,
|
||||||
TViewFilterProps,
|
TViewFilterProps,
|
||||||
TViewFilterPartialProps,
|
TViewAccess,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { FiltersHelper } from "../helpers/filters_helpers";
|
import { FiltersHelper } from "../helpers/filters_helpers";
|
||||||
|
|
||||||
type TLoader = "submitting" | "submit" | undefined;
|
type TLoader = "submitting" | "submit" | undefined;
|
||||||
|
|
||||||
export type TUserViewStore = TUserView & {
|
export type TUserViewStore = TView & {
|
||||||
// observables
|
// observables
|
||||||
loader: TLoader;
|
loader: TLoader;
|
||||||
filtersToUpdate: TViewFilterPartialProps;
|
filtersToUpdate: Partial<TView>;
|
||||||
// computed
|
// computed
|
||||||
appliedFilters: TViewFilterProps | undefined;
|
appliedFilters: TViewFilterProps | undefined;
|
||||||
appliedFiltersQueryParams: string | undefined;
|
appliedFiltersQueryParams: string | undefined;
|
||||||
// helper actions
|
// helper actions
|
||||||
updateFilters: (filters: Partial<TViewFilters>) => void;
|
setName: (name: string) => void;
|
||||||
updateDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
setDescription: (description: string) => void;
|
||||||
updateDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
setFilters: (filters: Partial<TViewFilters>) => void;
|
||||||
resetFilterChanges: () => void;
|
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||||
saveFilterChanges: () => void;
|
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||||
|
resetChanges: () => void;
|
||||||
|
saveChanges: () => Promise<void>;
|
||||||
// actions
|
// 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 {
|
export class UserViewStore extends FiltersHelper implements TUserViewStore {
|
||||||
id: string | undefined;
|
id: string | undefined;
|
||||||
workspace: string | undefined;
|
workspace: string | undefined;
|
||||||
project: string | undefined;
|
project: string | undefined;
|
||||||
module: string | undefined;
|
name: string | undefined;
|
||||||
cycle: string | undefined;
|
description: string | undefined;
|
||||||
filters: TViewFilters | undefined;
|
query: string | undefined;
|
||||||
display_filters: TViewDisplayFilters | undefined;
|
filters: TViewFilters;
|
||||||
display_properties: TViewDisplayProperties | undefined;
|
display_filters: TViewDisplayFilters;
|
||||||
user: string | undefined;
|
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;
|
created_by: string | undefined;
|
||||||
updated_by: string | undefined;
|
updated_by: string | undefined;
|
||||||
created_at: Date | undefined;
|
created_at: Date | undefined;
|
||||||
updated_at: Date | undefined;
|
updated_at: Date | undefined;
|
||||||
|
is_local_view: boolean = false;
|
||||||
|
is_create: boolean = false;
|
||||||
|
is_editable: boolean = false;
|
||||||
loader: TLoader = undefined;
|
loader: TLoader = undefined;
|
||||||
filtersToUpdate: TViewFilterPartialProps = {
|
filtersToUpdate: Partial<TView> = {
|
||||||
filters: {},
|
name: "",
|
||||||
display_filters: {},
|
description: "",
|
||||||
display_properties: {},
|
filters: undefined,
|
||||||
|
display_filters: undefined,
|
||||||
|
display_properties: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(private store: RootStore, _view: TView, private service: TViewService) {
|
||||||
_view: TUserView,
|
|
||||||
private service: TUserViewService,
|
|
||||||
private workspaceSlug: string,
|
|
||||||
private projectId: string | undefined,
|
|
||||||
private featureId: string | undefined // moduleId/cycleId
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
this.id = _view.id;
|
this.id = _view.id;
|
||||||
this.workspace = _view.workspace;
|
this.workspace = _view.workspace;
|
||||||
this.project = _view.project;
|
this.project = _view.project;
|
||||||
this.filters = _view.filters ? this.computedFilters(_view.filters) : undefined;
|
this.name = _view.name;
|
||||||
this.display_filters = _view.display_filters ? this.computedDisplayFilters(_view.display_filters) : undefined;
|
this.description = _view.description;
|
||||||
this.display_properties = _view.display_properties
|
this.query = _view.query;
|
||||||
? this.computedDisplayProperties(_view.display_properties)
|
this.filters = this.computedFilters(_view.filters);
|
||||||
: undefined;
|
this.display_filters = this.computedDisplayFilters(_view.display_filters);
|
||||||
this.user = _view.user;
|
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.created_by = _view.created_by;
|
||||||
this.updated_by = _view.updated_by;
|
this.updated_by = _view.updated_by;
|
||||||
this.created_at = _view.created_at;
|
this.created_at = _view.created_at;
|
||||||
this.updated_at = _view.updated_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, {
|
makeObservable(this, {
|
||||||
// observables
|
// observables
|
||||||
loader: observable,
|
id: observable.ref,
|
||||||
filtersToUpdate: 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
|
// computed
|
||||||
appliedFilters: computed,
|
appliedFilters: computed,
|
||||||
appliedFiltersQueryParams: computed,
|
appliedFiltersQueryParams: computed,
|
||||||
// helper actions
|
// helper actions
|
||||||
updateFilters: action,
|
setName: action,
|
||||||
updateDisplayFilters: action,
|
setFilters: action,
|
||||||
updateDisplayProperties: action,
|
setDisplayFilters: action,
|
||||||
resetFilterChanges: action,
|
setDisplayProperties: action,
|
||||||
saveFilterChanges: action,
|
resetChanges: action,
|
||||||
|
saveChanges: action,
|
||||||
// actions
|
// actions
|
||||||
update: action,
|
update: action,
|
||||||
|
lockView: action,
|
||||||
|
unlockView: action,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
get appliedFilters() {
|
get appliedFilters() {
|
||||||
return {
|
return {
|
||||||
filters: this.filters ? this.computedFilters(this.filters, this.filtersToUpdate.filters) : undefined,
|
filters: this.computedFilters(this.filters, this.filtersToUpdate.filters),
|
||||||
display_filters: this.display_filters
|
display_filters: this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters),
|
||||||
? this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters)
|
display_properties: this.computedDisplayProperties(
|
||||||
: undefined,
|
this.display_properties,
|
||||||
display_properties: this.display_properties
|
this.filtersToUpdate.display_properties
|
||||||
? this.computedDisplayProperties(this.display_properties, this.filtersToUpdate.display_properties)
|
),
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,14 +160,29 @@ export class UserViewStore extends FiltersHelper implements TUserViewStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper actions
|
// helper actions
|
||||||
updateFilters = (filters: Partial<TViewFilters>) => {
|
setName = (name: string) => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = "submit";
|
this.filtersToUpdate.name = name;
|
||||||
this.filtersToUpdate.filters = filters;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
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 appliedFilters = this.appliedFilters;
|
||||||
|
|
||||||
const layout = appliedFilters?.display_filters?.layout;
|
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 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) 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";
|
||||||
@ -137,47 +198,130 @@ export class UserViewStore extends FiltersHelper implements TUserViewStore {
|
|||||||
if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false;
|
if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false;
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = "submit";
|
Object.keys(display_filters).forEach((key) => {
|
||||||
this.filtersToUpdate.display_filters = display_filters;
|
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(() => {
|
runInAction(() => {
|
||||||
this.loader = "submit";
|
Object.keys(display_properties).forEach((key) => {
|
||||||
this.filtersToUpdate.display_properties = display_properties;
|
const _key = key as keyof TViewDisplayProperties;
|
||||||
|
set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
resetFilterChanges = () => {
|
resetChanges = () => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
this.filtersToUpdate = {
|
this.filtersToUpdate = {
|
||||||
filters: {},
|
name: this.name,
|
||||||
display_filters: {},
|
description: this.description,
|
||||||
display_properties: {},
|
filters: this.filters,
|
||||||
|
display_filters: this.display_filters,
|
||||||
|
display_properties: this.display_properties,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
saveFilterChanges = async () => {
|
saveChanges = async () => {
|
||||||
|
try {
|
||||||
this.loader = "submitting";
|
this.loader = "submitting";
|
||||||
if (this.appliedFilters) await this.update(this.appliedFilters);
|
if (this.filtersToUpdate) await this.update(this.filtersToUpdate);
|
||||||
this.loader = undefined;
|
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
|
// actions
|
||||||
update = async (viewData: Partial<TViewFilterProps>) => {
|
lockView = async () => {
|
||||||
if (!this.workspaceSlug || !this.id) return;
|
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;
|
if (!view) return;
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
Object.keys(viewData).forEach((key) => {
|
Object.keys(viewData).forEach((key) => {
|
||||||
const _key = key as keyof TViewFilterProps;
|
const _key = key as keyof TView;
|
||||||
set(this, _key, viewData[_key]);
|
set(this, _key, viewData[_key]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
this.resetChanges();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
|
import sortBy from "lodash/sortBy";
|
||||||
|
import reverse from "lodash/reverse";
|
||||||
// stores
|
// stores
|
||||||
import { RootStore } from "store/root.store";
|
import { RootStore } from "store/root.store";
|
||||||
import { ViewStore } from "./view.store";
|
import { ViewStore } from "./view.store";
|
||||||
@ -18,9 +20,8 @@ type TViewRootStore = {
|
|||||||
// helper actions
|
// helper actions
|
||||||
viewById: (viewId: string) => ViewStore | undefined;
|
viewById: (viewId: string) => ViewStore | undefined;
|
||||||
// actions
|
// actions
|
||||||
fetch: (_loader?: TLoader) => Promise<void>;
|
|
||||||
localViewCreate: (view: TView) => Promise<void>;
|
localViewCreate: (view: TView) => Promise<void>;
|
||||||
clearLocalView: (viewId: string) => Promise<void>;
|
fetch: (_loader?: TLoader) => Promise<void>;
|
||||||
create: (view: Partial<TView>) => Promise<void>;
|
create: (view: Partial<TView>) => Promise<void>;
|
||||||
remove: (viewId: string) => Promise<void>;
|
remove: (viewId: string) => Promise<void>;
|
||||||
duplicate: (viewId: string) => Promise<void>;
|
duplicate: (viewId: string) => Promise<void>;
|
||||||
@ -39,9 +40,8 @@ export class ViewRootStore implements TViewRootStore {
|
|||||||
// computed
|
// computed
|
||||||
viewIds: computed,
|
viewIds: computed,
|
||||||
// actions
|
// actions
|
||||||
fetch: action,
|
|
||||||
localViewCreate: action,
|
localViewCreate: action,
|
||||||
clearLocalView: action,
|
fetch: action,
|
||||||
create: action,
|
create: action,
|
||||||
remove: action,
|
remove: action,
|
||||||
duplicate: action,
|
duplicate: action,
|
||||||
@ -51,19 +51,32 @@ export class ViewRootStore implements TViewRootStore {
|
|||||||
// computed
|
// computed
|
||||||
get viewIds() {
|
get viewIds() {
|
||||||
const views = Object.values(this.viewMap);
|
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
|
// helper actions
|
||||||
viewById = (viewId: string) => this.viewMap?.[viewId] || undefined;
|
viewById = (viewId: string) => this.viewMap?.[viewId] || undefined;
|
||||||
|
|
||||||
// actions
|
// 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") => {
|
fetch = async (_loader: TLoader = "init-loader") => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
if (this.defaultViews && this.defaultViews.length > 0)
|
if (this.defaultViews && this.defaultViews.length > 0)
|
||||||
|
runInAction(() => {
|
||||||
this.defaultViews?.forEach((view) => {
|
this.defaultViews?.forEach((view) => {
|
||||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
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;
|
this.loader = undefined;
|
||||||
});
|
});
|
||||||
};
|
} catch {}
|
||||||
|
|
||||||
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];
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
create = async (data: Partial<TView>) => {
|
create = async (data: Partial<TView>) => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
@ -103,20 +106,27 @@ export class ViewRootStore implements TViewRootStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
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) => {
|
remove = async (viewId: string) => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug || !viewId) return;
|
if (!workspaceSlug || !viewId) return;
|
||||||
|
|
||||||
|
if (this.viewMap?.[viewId] != undefined && !this.viewMap?.[viewId]?.is_create)
|
||||||
await this.service.remove?.(workspaceSlug, viewId, projectId);
|
await this.service.remove?.(workspaceSlug, viewId, projectId);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
delete this.viewMap[viewId];
|
delete this.viewMap[viewId];
|
||||||
});
|
});
|
||||||
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
duplicate = async (viewId: string) => {
|
duplicate = async (viewId: string) => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug || !this.service.duplicate) return;
|
if (!workspaceSlug || !this.service.duplicate) return;
|
||||||
|
|
||||||
@ -126,5 +136,6 @@ export class ViewRootStore implements TViewRootStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service));
|
||||||
});
|
});
|
||||||
|
} catch {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
TViewDisplayFilters,
|
TViewDisplayFilters,
|
||||||
TViewDisplayProperties,
|
TViewDisplayProperties,
|
||||||
TViewFilterProps,
|
TViewFilterProps,
|
||||||
TViewFilterPartialProps,
|
|
||||||
TViewAccess,
|
TViewAccess,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
@ -21,17 +20,18 @@ type TLoader = "submitting" | "submit" | undefined;
|
|||||||
export type TViewStore = TView & {
|
export type TViewStore = TView & {
|
||||||
// observables
|
// observables
|
||||||
loader: TLoader;
|
loader: TLoader;
|
||||||
filtersToUpdate: TViewFilterPartialProps;
|
filtersToUpdate: Partial<TView>;
|
||||||
// computed
|
// computed
|
||||||
appliedFilters: TViewFilterProps | undefined;
|
appliedFilters: TViewFilterProps | undefined;
|
||||||
appliedFiltersQueryParams: string | undefined;
|
appliedFiltersQueryParams: string | undefined;
|
||||||
// helper actions
|
// helper actions
|
||||||
setName: (name: string) => void;
|
setName: (name: string) => void;
|
||||||
|
setDescription: (description: string) => void;
|
||||||
setFilters: (filters: Partial<TViewFilters>) => void;
|
setFilters: (filters: Partial<TViewFilters>) => void;
|
||||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||||
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
setDisplayProperties: (display_properties: Partial<TViewDisplayProperties>) => void;
|
||||||
resetFilterChanges: () => void;
|
resetChanges: () => void;
|
||||||
saveFilterChanges: () => void;
|
saveChanges: () => Promise<void>;
|
||||||
// actions
|
// actions
|
||||||
lockView: () => Promise<void>;
|
lockView: () => Promise<void>;
|
||||||
unlockView: () => Promise<void>;
|
unlockView: () => Promise<void>;
|
||||||
@ -60,15 +60,16 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
updated_by: string | undefined;
|
updated_by: string | undefined;
|
||||||
created_at: Date | undefined;
|
created_at: Date | undefined;
|
||||||
updated_at: Date | undefined;
|
updated_at: Date | undefined;
|
||||||
// local variables
|
|
||||||
is_local_view: boolean = false;
|
is_local_view: boolean = false;
|
||||||
is_create: boolean = false;
|
is_create: boolean = false;
|
||||||
|
is_editable: boolean = false;
|
||||||
loader: TLoader = undefined;
|
loader: TLoader = undefined;
|
||||||
filtersToUpdate: TViewFilterPartialProps = {
|
filtersToUpdate: Partial<TView> = {
|
||||||
filters: {},
|
name: "",
|
||||||
display_filters: {},
|
description: "",
|
||||||
display_properties: {},
|
filters: undefined,
|
||||||
|
display_filters: undefined,
|
||||||
|
display_properties: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(private store: RootStore, _view: TView, private service: TViewService) {
|
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.updated_by = _view.updated_by;
|
||||||
this.created_at = _view.created_at;
|
this.created_at = _view.created_at;
|
||||||
this.updated_at = _view.updated_at;
|
this.updated_at = _view.updated_at;
|
||||||
|
|
||||||
this.is_local_view = _view.is_local_view;
|
this.is_local_view = _view.is_local_view;
|
||||||
this.is_create = _view.is_create;
|
this.is_create = _view.is_create;
|
||||||
|
this.is_editable = _view.is_editable;
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observables
|
// observables
|
||||||
@ -118,6 +121,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
updated_at: observable.ref,
|
updated_at: observable.ref,
|
||||||
is_local_view: observable.ref,
|
is_local_view: observable.ref,
|
||||||
is_create: observable.ref,
|
is_create: observable.ref,
|
||||||
|
is_editable: observable.ref,
|
||||||
loader: observable.ref,
|
loader: observable.ref,
|
||||||
filtersToUpdate: observable,
|
filtersToUpdate: observable,
|
||||||
// computed
|
// computed
|
||||||
@ -128,8 +132,8 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
setFilters: action,
|
setFilters: action,
|
||||||
setDisplayFilters: action,
|
setDisplayFilters: action,
|
||||||
setDisplayProperties: action,
|
setDisplayProperties: action,
|
||||||
resetFilterChanges: action,
|
resetChanges: action,
|
||||||
saveFilterChanges: action,
|
saveChanges: action,
|
||||||
// actions
|
// actions
|
||||||
update: action,
|
update: action,
|
||||||
lockView: action,
|
lockView: action,
|
||||||
@ -158,7 +162,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
// helper actions
|
// helper actions
|
||||||
setName = (name: string) => {
|
setName = (name: string) => {
|
||||||
runInAction(() => {
|
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;
|
if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false;
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = "submit";
|
|
||||||
Object.keys(display_filters).forEach((key) => {
|
Object.keys(display_filters).forEach((key) => {
|
||||||
const _key = key as keyof TViewDisplayFilters;
|
const _key = key as keyof TViewDisplayFilters;
|
||||||
set(this.filtersToUpdate, ["display_filters", _key], display_filters[_key]);
|
set(this.filtersToUpdate, ["display_filters", _key], display_filters[_key]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("this.filtersToUpdate", this.filtersToUpdate?.display_filters?.layout);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => {
|
setDisplayProperties = async (display_properties: Partial<TViewDisplayProperties>) => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = "submit";
|
|
||||||
Object.keys(display_properties).forEach((key) => {
|
Object.keys(display_properties).forEach((key) => {
|
||||||
const _key = key as keyof TViewDisplayProperties;
|
const _key = key as keyof TViewDisplayProperties;
|
||||||
set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]);
|
set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]);
|
||||||
@ -208,25 +214,36 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
resetFilterChanges = () => {
|
resetChanges = () => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
this.filtersToUpdate = {
|
this.filtersToUpdate = {
|
||||||
filters: {},
|
name: this.name,
|
||||||
display_filters: {},
|
description: this.description,
|
||||||
display_properties: {},
|
filters: this.filters,
|
||||||
|
display_filters: this.display_filters,
|
||||||
|
display_properties: this.display_properties,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
saveFilterChanges = async () => {
|
saveChanges = async () => {
|
||||||
|
try {
|
||||||
this.loader = "submitting";
|
this.loader = "submitting";
|
||||||
if (this.appliedFilters) await this.update(this.appliedFilters);
|
if (this.filtersToUpdate) await this.update(this.filtersToUpdate);
|
||||||
this.loader = undefined;
|
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
|
// actions
|
||||||
lockView = async () => {
|
lockView = async () => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug || !this.id || !this.service.lock) return;
|
if (!workspaceSlug || !this.id || !this.service.lock) return;
|
||||||
|
|
||||||
@ -236,9 +253,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.is_locked = view.is_locked;
|
this.is_locked = view.is_locked;
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
this.is_locked = this.is_locked;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
unlockView = async () => {
|
unlockView = async () => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug || !this.id || !this.service.unlock) return;
|
if (!workspaceSlug || !this.id || !this.service.unlock) return;
|
||||||
|
|
||||||
@ -248,9 +269,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.is_locked = view.is_locked;
|
this.is_locked = view.is_locked;
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
this.is_locked = this.is_locked;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
makeFavorite = async () => {
|
makeFavorite = async () => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug || !this.id || !this.service.makeFavorite) return;
|
if (!workspaceSlug || !this.id || !this.service.makeFavorite) return;
|
||||||
|
|
||||||
@ -260,9 +285,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.is_favorite = view.is_locked;
|
this.is_favorite = view.is_locked;
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
this.is_favorite = this.is_favorite;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
removeFavorite = async () => {
|
removeFavorite = async () => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug || !this.id || !this.service.removeFavorite) return;
|
if (!workspaceSlug || !this.id || !this.service.removeFavorite) return;
|
||||||
|
|
||||||
@ -272,9 +301,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.is_favorite = view.is_locked;
|
this.is_favorite = view.is_locked;
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
this.is_favorite = this.is_favorite;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
update = async (viewData: Partial<TView>) => {
|
update = async (viewData: Partial<TView>) => {
|
||||||
|
try {
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (!workspaceSlug || !this.id) return;
|
if (!workspaceSlug || !this.id) return;
|
||||||
|
|
||||||
@ -287,5 +320,8 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
set(this, _key, viewData[_key]);
|
set(this, _key, viewData[_key]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
this.resetChanges();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user