mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: updated filters and filter components
This commit is contained in:
parent
8ae1e3cff2
commit
46a6a599a1
8
packages/types/src/view/base.d.ts
vendored
8
packages/types/src/view/base.d.ts
vendored
@ -46,3 +46,11 @@ export type TView = {
|
|||||||
is_create: boolean;
|
is_create: boolean;
|
||||||
is_editable: boolean;
|
is_editable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TUpdateView = {
|
||||||
|
name: string | undefined;
|
||||||
|
description: string | undefined;
|
||||||
|
filters: TViewFilters;
|
||||||
|
display_filters: TViewDisplayFilters;
|
||||||
|
display_properties: TViewDisplayProperties;
|
||||||
|
};
|
||||||
|
@ -30,7 +30,7 @@ export const ViewAppliedFiltersItem: FC<TViewAppliedFiltersItem> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`filter_value_${filterKey}_${propertyId}`}
|
key={`filter_value_${filterKey}_${propertyId}`}
|
||||||
className="bg-custom-background-80 rounded relative flex items-center gap-1.5 p-1.5 py-1"
|
className="bg-custom-background-80 rounded relative flex items-center gap-1.5 p-1"
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center overflow-hidden">
|
<div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center overflow-hidden">
|
||||||
{propertyDetail?.icon || <ImagePlus size={14} />}
|
{propertyDetail?.icon || <ImagePlus size={14} />}
|
||||||
|
@ -37,7 +37,7 @@ export const ViewAppliedFilters: FC<TViewAppliedFilters> = observer((props) => {
|
|||||||
|
|
||||||
if (!propertyValues || propertyValues.length <= 0) return <></>;
|
if (!propertyValues || propertyValues.length <= 0) return <></>;
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1.5 px-2 min-h-[32px]">
|
<div className="relative flex items-center gap-2 border border-custom-border-300 rounded p-1 px-2 min-h-[32px]">
|
||||||
<div className="flex-shrink-0 text-xs text-custom-text-200 capitalize">{filterKey.replaceAll("_", " ")}</div>
|
<div className="flex-shrink-0 text-xs text-custom-text-200 capitalize">{filterKey.replaceAll("_", " ")}</div>
|
||||||
<div className="relative flex items-center gap-1.5 flex-wrap">
|
<div className="relative flex items-center gap-1.5 flex-wrap">
|
||||||
{propertyVisibleCount && propertyValues.length >= propertyVisibleCount ? (
|
{propertyVisibleCount && propertyValues.length >= propertyVisibleCount ? (
|
||||||
|
@ -87,7 +87,7 @@ export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = obser
|
|||||||
) : (
|
) : (
|
||||||
<Tooltip tooltipContent={"Display"} position="bottom">
|
<Tooltip tooltipContent={"Display"} position="bottom">
|
||||||
<div
|
<div
|
||||||
className={`relative flex items-center gap-1 h-7 rounded px-2 transition-all
|
className={`relative flex items-center gap-1 h-8 rounded px-2 transition-all
|
||||||
${
|
${
|
||||||
displayDropdownText
|
displayDropdownText
|
||||||
? `border border-custom-border-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80`
|
? `border border-custom-border-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80`
|
||||||
|
@ -89,7 +89,7 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
|
|||||||
) : (
|
) : (
|
||||||
<Tooltip tooltipContent={"Filters"} position="bottom">
|
<Tooltip tooltipContent={"Filters"} position="bottom">
|
||||||
<div
|
<div
|
||||||
className={`relative flex items-center gap-1 h-7 rounded px-2 transition-all
|
className={`relative flex items-center gap-1 h-8 rounded px-2 transition-all
|
||||||
${
|
${
|
||||||
displayDropdownText
|
displayDropdownText
|
||||||
? `border border-custom-border-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80`
|
? `border border-custom-border-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80`
|
||||||
|
124
web/components/view/filters/edit-dropdown.tsx
Normal file
124
web/components/view/filters/edit-dropdown.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { FC, Fragment, useMemo, useRef, useState } from "react";
|
||||||
|
import { ChevronDown, ChevronUp, RotateCcw } from "lucide-react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { usePopper } from "react-popper";
|
||||||
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
|
// hooks
|
||||||
|
import { useViewDetail } from "hooks/store";
|
||||||
|
// ui
|
||||||
|
import { PhotoFilterIcon } from "@plane/ui";
|
||||||
|
// types
|
||||||
|
import { TViewTypes } from "@plane/types";
|
||||||
|
import { TViewFilterEditDropdownOptions, TViewOperations } from "../types";
|
||||||
|
|
||||||
|
type TViewFiltersEditDropdown = {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string | undefined;
|
||||||
|
viewId: string;
|
||||||
|
viewType: TViewTypes;
|
||||||
|
viewOperations: TViewOperations;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewFiltersEditDropdown: FC<TViewFiltersEditDropdown> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||||
|
// hooks
|
||||||
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
|
// 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-end",
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: "preventOverflow",
|
||||||
|
options: {
|
||||||
|
padding: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "offset",
|
||||||
|
options: {
|
||||||
|
offset: [0, 10],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// dropdown options
|
||||||
|
const dropdownOptions: TViewFilterEditDropdownOptions[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
icon: PhotoFilterIcon,
|
||||||
|
key: "save_as_new",
|
||||||
|
label: "Save as new view",
|
||||||
|
onClick: () => viewOperations.update(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: RotateCcw,
|
||||||
|
key: "reset_changes",
|
||||||
|
label: "Reset changes",
|
||||||
|
onClick: () => viewOperations.resetChanges(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[viewOperations]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!viewDetailStore?.isFiltersUpdateEnabled) return <></>;
|
||||||
|
return (
|
||||||
|
<Menu as="div" className="relative flex-shrink-0" ref={dropdownRef}>
|
||||||
|
<div className=" relative flex items-center rounded h-8 transition-all cursor-pointer bg-custom-primary-100/20 text-custom-primary-100">
|
||||||
|
<button
|
||||||
|
className="text-sm px-3 font-medium h-full border-r border-white/50 flex justify-center items-center rounded-l transition-all hover:bg-custom-primary-100/30"
|
||||||
|
disabled={viewDetailStore?.loader === "updating"}
|
||||||
|
onClick={() => viewOperations.update()}
|
||||||
|
>
|
||||||
|
{viewDetailStore?.loader === "updating" ? "updating..." : "Update"}
|
||||||
|
</button>
|
||||||
|
<Menu.Button
|
||||||
|
as="div"
|
||||||
|
className="flex-shrink-0 px-1.5 hover:bg-custom-primary-100/30 h-full flex justify-center items-center rounded-r transition-all outline-none"
|
||||||
|
ref={setReferenceElement}
|
||||||
|
>
|
||||||
|
{({ open }) => (!open ? <ChevronDown size={16} /> : <ChevronUp size={16} />)}
|
||||||
|
</Menu.Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items
|
||||||
|
className="absolute right-0 z-20 mt-1.5 flex w-52 flex-col rounded border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 text-xs shadow-lg outline-none p-1"
|
||||||
|
ref={setPopperElement}
|
||||||
|
style={styles.popper}
|
||||||
|
{...attributes.popper}
|
||||||
|
>
|
||||||
|
{dropdownOptions &&
|
||||||
|
dropdownOptions.length > 0 &&
|
||||||
|
dropdownOptions.map((option) => (
|
||||||
|
<Menu.Item
|
||||||
|
key={option.key}
|
||||||
|
as="button"
|
||||||
|
type="button"
|
||||||
|
className="relative flex items-center gap-2 p-1 py-1.5 rounded transition-all hover:bg-custom-background-80 text-custom-text-200 hover:text-custom-text-100 cursor-pointer"
|
||||||
|
onClick={option.onClick}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center">
|
||||||
|
<option.icon size={12} className="w-3 h-3" />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs whitespace-nowrap">{option.label}</div>
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
});
|
@ -17,6 +17,7 @@ export * from "./filters/root";
|
|||||||
export * from "./filters/filter-item-root";
|
export * from "./filters/filter-item-root";
|
||||||
export * from "./filters/filter-item";
|
export * from "./filters/filter-item";
|
||||||
export * from "./filters/filter-selection";
|
export * from "./filters/filter-selection";
|
||||||
|
export * from "./filters/edit-dropdown";
|
||||||
|
|
||||||
// view display filters
|
// view display filters
|
||||||
export * from "./display-filters/dropdown";
|
export * from "./display-filters/dropdown";
|
||||||
|
@ -31,12 +31,12 @@ export const ViewLayoutRoot: FC<TViewLayoutRoot> = observer((props) => {
|
|||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex gap-0.5 items-center bg-custom-background-80 rounded p-1 h-7 shadow-custom-shadow-2xs">
|
<div className="relative flex gap-0.5 items-center bg-custom-background-80 rounded p-1 shadow-custom-shadow-2xs">
|
||||||
{LAYOUTS_DATA.map((layout) => (
|
{LAYOUTS_DATA.map((layout) => (
|
||||||
<Fragment key={layout.key}>
|
<Fragment key={layout.key}>
|
||||||
<Tooltip tooltipContent={layout.title} position="bottom">
|
<Tooltip tooltipContent={layout.title} position="bottom">
|
||||||
<div
|
<div
|
||||||
className={`relative h-[24px] w-7 flex justify-center items-center overflow-hidden rounded transition-all cursor-pointer
|
className={`relative h-6 w-7 flex justify-center items-center overflow-hidden rounded transition-all cursor-pointer
|
||||||
${
|
${
|
||||||
viewDetailStore?.filtersToUpdate?.display_filters?.layout === layout.key
|
viewDetailStore?.filtersToUpdate?.display_filters?.layout === layout.key
|
||||||
? `bg-custom-background-100 shadow-custom-shadow-2xs`
|
? `bg-custom-background-100 shadow-custom-shadow-2xs`
|
||||||
|
@ -9,13 +9,14 @@ import useToast from "hooks/use-toast";
|
|||||||
import {
|
import {
|
||||||
ViewRoot,
|
ViewRoot,
|
||||||
ViewCreateEditForm,
|
ViewCreateEditForm,
|
||||||
|
ViewEditDropdown,
|
||||||
ViewLayoutRoot,
|
ViewLayoutRoot,
|
||||||
ViewFiltersDropdown,
|
ViewFiltersDropdown,
|
||||||
|
ViewFiltersEditDropdown,
|
||||||
ViewDisplayFiltersDropdown,
|
ViewDisplayFiltersDropdown,
|
||||||
ViewAppliedFiltersRoot,
|
ViewAppliedFiltersRoot,
|
||||||
ViewDuplicateConfirmationModal,
|
ViewDuplicateConfirmationModal,
|
||||||
ViewDeleteConfirmationModal,
|
ViewDeleteConfirmationModal,
|
||||||
ViewEditDropdown,
|
|
||||||
} from ".";
|
} from ".";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
@ -53,7 +54,7 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
|||||||
const handleViewOperationsToggle = (type: TViewOperationsToggle["type"], viewId: string | undefined) =>
|
const handleViewOperationsToggle = (type: TViewOperationsToggle["type"], viewId: string | undefined) =>
|
||||||
setViewOperationsToggle({ type, viewId });
|
setViewOperationsToggle({ type, viewId });
|
||||||
|
|
||||||
const viewDetailCreateStore = useViewDetail(
|
const viewDetailCreateEditStore = useViewDetail(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
projectId,
|
projectId,
|
||||||
viewOperationsToggle?.viewId || viewId,
|
viewOperationsToggle?.viewId || viewId,
|
||||||
@ -62,55 +63,112 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
|||||||
|
|
||||||
const viewOperations: TViewOperations = useMemo(
|
const viewOperations: TViewOperations = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
setName: (name: string) => viewDetailStore?.setName(name),
|
|
||||||
setDescription: (name: string) => viewDetailStore?.setDescription(name),
|
|
||||||
setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => {
|
|
||||||
if (viewOperationsToggle.type && ["CREATE", "EDIT"].includes(viewOperationsToggle.type))
|
|
||||||
viewDetailCreateStore?.setFilters(filterKey, filterValue);
|
|
||||||
else viewDetailStore?.setFilters(filterKey, filterValue);
|
|
||||||
},
|
|
||||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) =>
|
|
||||||
viewDetailStore?.setDisplayFilters(display_filters),
|
|
||||||
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) =>
|
|
||||||
viewDetailStore?.setDisplayProperties(displayPropertyKey),
|
|
||||||
localViewCreateEdit: (viewId: string | undefined) => {
|
localViewCreateEdit: (viewId: string | undefined) => {
|
||||||
if (viewId === undefined) {
|
if (viewId === undefined) {
|
||||||
const viewPayload = viewLocalPayload;
|
const viewPayload = viewLocalPayload;
|
||||||
handleViewOperationsToggle("CREATE", viewPayload.id);
|
handleViewOperationsToggle("CREATE", viewPayload.id);
|
||||||
viewStore?.localViewCreate(viewPayload as TView);
|
viewStore?.localViewCreate(viewPayload as TView);
|
||||||
} else handleViewOperationsToggle("EDIT", viewId);
|
} else {
|
||||||
|
handleViewOperationsToggle("EDIT", viewId);
|
||||||
|
viewDetailCreateEditStore?.setIsEditable(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
localViewCreateEditClear: async (viewId: string | undefined) => {
|
localViewCreateEditClear: async (viewId: string | undefined) => {
|
||||||
if (viewId) viewStore?.remove(viewId);
|
if (viewDetailCreateEditStore?.is_create && viewId) viewStore?.remove(viewId);
|
||||||
handleViewOperationsToggle(undefined, undefined);
|
handleViewOperationsToggle(undefined, undefined);
|
||||||
},
|
},
|
||||||
fetch: async () => await viewStore?.fetch(),
|
resetChanges: () => viewDetailStore?.resetChanges(),
|
||||||
|
|
||||||
|
setName: (name: string) => {
|
||||||
|
if (viewOperationsToggle.type && ["CREATE", "EDIT"].includes(viewOperationsToggle.type))
|
||||||
|
viewDetailCreateEditStore?.setName(name);
|
||||||
|
else viewDetailStore?.setName(name);
|
||||||
|
},
|
||||||
|
setDescription: (name: string) => {
|
||||||
|
if (viewOperationsToggle.type && ["CREATE", "EDIT"].includes(viewOperationsToggle.type))
|
||||||
|
viewDetailCreateEditStore?.setDescription(name);
|
||||||
|
else viewDetailStore?.setDescription(name);
|
||||||
|
},
|
||||||
|
setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => {
|
||||||
|
if (viewOperationsToggle.type && ["CREATE", "EDIT"].includes(viewOperationsToggle.type))
|
||||||
|
viewDetailCreateEditStore?.setFilters(filterKey, filterValue);
|
||||||
|
else viewDetailStore?.setFilters(filterKey, filterValue);
|
||||||
|
},
|
||||||
|
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => {
|
||||||
|
if (viewOperationsToggle.type && ["CREATE", "EDIT"].includes(viewOperationsToggle.type))
|
||||||
|
viewDetailCreateEditStore?.setDisplayFilters(display_filters);
|
||||||
|
else viewDetailStore?.setDisplayFilters(display_filters);
|
||||||
|
},
|
||||||
|
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => {
|
||||||
|
if (viewOperationsToggle.type && ["CREATE", "EDIT"].includes(viewOperationsToggle.type))
|
||||||
|
viewDetailCreateEditStore?.setDisplayProperties(displayPropertyKey);
|
||||||
|
else viewDetailStore?.setDisplayProperties(displayPropertyKey);
|
||||||
|
},
|
||||||
|
|
||||||
|
fetch: async () => {
|
||||||
|
try {
|
||||||
|
await viewStore?.fetch();
|
||||||
|
} catch {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Something went wrong. Please try again later or contact the support team.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
create: async (data: Partial<TView>) => {
|
create: async (data: Partial<TView>) => {
|
||||||
try {
|
try {
|
||||||
await viewStore?.create(data);
|
await viewStore?.create(data);
|
||||||
handleViewOperationsToggle(undefined, undefined);
|
handleViewOperationsToggle(undefined, undefined);
|
||||||
|
setToastAlert({
|
||||||
|
type: "success",
|
||||||
|
title: "Success!",
|
||||||
|
message: "View created successfully.",
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
setToastAlert({ title: "Error", message: "Error creating view", type: "error" });
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Something went wrong. Please try again later or contact the support team.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
remove: async (viewId: string) => {
|
remove: async (viewId: string) => {
|
||||||
try {
|
try {
|
||||||
await viewStore?.remove(viewId);
|
await viewStore?.remove(viewId);
|
||||||
handleViewOperationsToggle(undefined, undefined);
|
handleViewOperationsToggle(undefined, undefined);
|
||||||
|
setToastAlert({
|
||||||
|
type: "success",
|
||||||
|
title: "Success!",
|
||||||
|
message: "View removed successfully.",
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
setToastAlert({ title: "Error", message: "Error removing view", type: "error" });
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Something went wrong. Please try again later or contact the support team.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update: async () => {
|
update: async () => {
|
||||||
try {
|
try {
|
||||||
await viewDetailStore?.saveChanges();
|
await viewDetailStore?.saveChanges();
|
||||||
handleViewOperationsToggle(undefined, undefined);
|
handleViewOperationsToggle(undefined, undefined);
|
||||||
|
setToastAlert({
|
||||||
|
type: "success",
|
||||||
|
title: "Success!",
|
||||||
|
message: "View updated successfully.",
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
setToastAlert({ title: "Error", message: "Error updating view", type: "error" });
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Something went wrong. Please try again later or contact the support team.",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[viewStore, viewDetailStore, setToastAlert, viewOperationsToggle, viewDetailCreateStore]
|
[viewStore, viewDetailStore, setToastAlert, viewOperationsToggle, viewDetailCreateEditStore]
|
||||||
);
|
);
|
||||||
|
|
||||||
// fetch all views
|
// fetch all views
|
||||||
@ -222,13 +280,9 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative flex items-center gap-1 rounded px-2 h-7 transition-all hover:bg-custom-background-80 cursor-pointer">
|
<ViewEditDropdown viewId={viewId} viewOperations={viewOperations} />
|
||||||
<div className="w-4 h-4 relative flex justify-center items-center overflow-hidden">
|
|
||||||
<Pencil size={12} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ViewEditDropdown
|
<ViewFiltersEditDropdown
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
|
25
web/components/view/types.d.ts
vendored
25
web/components/view/types.d.ts
vendored
@ -1,17 +1,36 @@
|
|||||||
|
import { LucideIcon } from "lucide-react";
|
||||||
|
// types
|
||||||
import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types";
|
import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types";
|
||||||
|
|
||||||
export type TViewOperations = {
|
export type TViewOperations = {
|
||||||
|
localViewCreateEdit: (viewId: string | undefined) => void;
|
||||||
|
localViewCreateEditClear: (viewId: string | undefined) => Promise<void>;
|
||||||
|
resetChanges: () => void;
|
||||||
|
|
||||||
setName: (name: string) => void;
|
setName: (name: string) => void;
|
||||||
setDescription: (description: string) => void;
|
setDescription: (description: string) => void;
|
||||||
setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => void;
|
setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => void;
|
||||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||||
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void;
|
setDisplayProperties: (displayPropertyKey: keyof 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>;
|
update: () => Promise<void>;
|
||||||
remove: (viewId: string) => Promise<void>;
|
remove: (viewId: string) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// view and view filter edit dropdowns
|
||||||
|
export type TViewEditDropdownOptions = {
|
||||||
|
icon: LucideIcon;
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
children: TViewEditDropdownOptions[] | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TViewFilterEditDropdownOptions = {
|
||||||
|
icon: LucideIcon | any;
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
@ -23,7 +23,7 @@ type TViewCreateEditForm = {
|
|||||||
export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
const currentViewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
// states
|
// states
|
||||||
const [modalToggle, setModalToggle] = useState(false);
|
const [modalToggle, setModalToggle] = useState(false);
|
||||||
@ -43,12 +43,12 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
|||||||
|
|
||||||
const onContinue = async () => {
|
const onContinue = async () => {
|
||||||
setLoader(true);
|
setLoader(true);
|
||||||
if (viewDetailStore?.is_create) {
|
if (currentViewDetailStore?.is_create) {
|
||||||
const payload = viewDetailStore?.filtersToUpdate;
|
const payload = currentViewDetailStore?.filtersToUpdate;
|
||||||
await viewOperations.create(payload);
|
await viewOperations.create(payload);
|
||||||
modalClose();
|
modalClose();
|
||||||
} else {
|
} else {
|
||||||
const payload = viewDetailStore?.filtersToUpdate;
|
const payload = currentViewDetailStore?.filtersToUpdate;
|
||||||
if (!payload) return;
|
if (!payload) return;
|
||||||
await viewOperations.update();
|
await viewOperations.update();
|
||||||
modalClose();
|
modalClose();
|
||||||
@ -58,7 +58,7 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
|||||||
|
|
||||||
const projectDetails = projectId ? getProjectById(projectId) : undefined;
|
const projectDetails = projectId ? getProjectById(projectId) : undefined;
|
||||||
|
|
||||||
if (!viewDetailStore?.id) return <></>;
|
if (!currentViewDetailStore?.id) return <></>;
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={modalToggle} as={Fragment}>
|
<Transition.Root show={modalToggle} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-20" onClose={modalClose}>
|
<Dialog as="div" className="relative z-20" onClose={modalClose}>
|
||||||
@ -110,9 +110,9 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
|||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
type="text"
|
type="text"
|
||||||
value={viewDetailStore?.filtersToUpdate?.name || ""}
|
value={currentViewDetailStore?.filtersToUpdate?.name || ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
viewDetailStore?.setName(e.target.value);
|
viewOperations?.setName(e.target.value);
|
||||||
}}
|
}}
|
||||||
placeholder="What do you want to call this view?"
|
placeholder="What do you want to call this view?"
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
||||||
@ -165,7 +165,7 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" onClick={onContinue} disabled={loader}>
|
<Button variant="primary" onClick={onContinue} disabled={loader}>
|
||||||
{loader ? `Saving...` : `${viewDetailStore?.is_create ? `Create` : `Update`} View`}
|
{loader ? `Saving...` : `${currentViewDetailStore?.is_create ? `Create` : `Update`} View`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
|
@ -1,41 +1,143 @@
|
|||||||
import { FC } from "react";
|
import { FC, Fragment, useMemo, useRef, useState } from "react";
|
||||||
import { ChevronDown } from "lucide-react";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
import { useViewDetail } from "hooks/store";
|
import { usePopper } from "react-popper";
|
||||||
// components
|
import { Copy, Eye, Globe2, Link2, Pencil, Trash } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { TViewTypes } from "@plane/types";
|
import { TViewEditDropdownOptions, TViewOperations } from "../types";
|
||||||
import { TViewOperations } from "../types";
|
|
||||||
|
|
||||||
type TViewEditDropdown = {
|
type TViewEditDropdown = {
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string | undefined;
|
|
||||||
viewId: string;
|
viewId: string;
|
||||||
viewType: TViewTypes;
|
|
||||||
viewOperations: TViewOperations;
|
viewOperations: TViewOperations;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewEditDropdown: FC<TViewEditDropdown> = observer((props) => {
|
export const ViewEditDropdown: FC<TViewEditDropdown> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
|
const { viewId, viewOperations } = props;
|
||||||
// hooks
|
// refs
|
||||||
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
|
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-end",
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: "preventOverflow",
|
||||||
|
options: {
|
||||||
|
padding: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "offset",
|
||||||
|
options: {
|
||||||
|
offset: [0, 10],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// dropdown options
|
||||||
|
const dropdownOptions: TViewEditDropdownOptions[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
icon: Pencil,
|
||||||
|
key: "rename",
|
||||||
|
label: "Rename",
|
||||||
|
onClick: () => viewOperations.localViewCreateEdit(viewId),
|
||||||
|
children: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Eye,
|
||||||
|
key: "accessability",
|
||||||
|
label: "Change Accessability",
|
||||||
|
onClick: () => {},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
icon: Eye,
|
||||||
|
key: "private",
|
||||||
|
label: "Private",
|
||||||
|
onClick: () => viewOperations.create({}),
|
||||||
|
children: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Globe2,
|
||||||
|
key: "public",
|
||||||
|
label: "Public",
|
||||||
|
onClick: () => viewOperations.create({}),
|
||||||
|
children: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Copy,
|
||||||
|
key: "duplicate",
|
||||||
|
label: "Duplicate view",
|
||||||
|
onClick: () => viewOperations.remove(viewId),
|
||||||
|
children: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Link2,
|
||||||
|
key: "copy_link",
|
||||||
|
label: "Copy view link",
|
||||||
|
onClick: () => viewOperations.remove(viewId),
|
||||||
|
children: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Trash,
|
||||||
|
key: "delete",
|
||||||
|
label: "Delete view",
|
||||||
|
onClick: () => viewOperations.remove(viewId),
|
||||||
|
children: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[viewOperations, viewId]
|
||||||
|
);
|
||||||
|
|
||||||
if (!viewDetailStore?.isFiltersUpdateEnabled) return <></>;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Menu as="div" className="relative flex-shrink-0" ref={dropdownRef}>
|
||||||
<div className=" relative flex items-center rounded h-7 transition-all cursor-pointer bg-custom-primary-100/20 text-custom-primary-100">
|
<Menu.Button
|
||||||
<button
|
className="relative flex items-center gap-1 rounded px-2 h-8 transition-all hover:bg-custom-background-80 cursor-pointer outline-none"
|
||||||
className="text-sm px-3 font-medium h-full border-r border-white/50 flex justify-center items-center rounded-l transition-all hover:bg-custom-primary-100/30"
|
ref={setReferenceElement}
|
||||||
disabled={viewDetailStore?.loader === "filters_submitting"}
|
>
|
||||||
onClick={() => viewOperations.update()}
|
<div className="w-4 h-4 relative flex justify-center items-center overflow-hidden">
|
||||||
>
|
<Pencil size={12} />
|
||||||
Update
|
|
||||||
</button>
|
|
||||||
<div className="flex-shrink-0 px-1.5 hover:bg-custom-primary-100/30 h-full flex justify-center items-center rounded-r transition-all">
|
|
||||||
<ChevronDown size={16} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Menu.Button>
|
||||||
</>
|
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items
|
||||||
|
className="absolute right-0 z-20 mt-1.5 flex w-52 flex-col rounded border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 text-xs shadow-lg outline-none p-1"
|
||||||
|
ref={setPopperElement}
|
||||||
|
style={styles.popper}
|
||||||
|
{...attributes.popper}
|
||||||
|
>
|
||||||
|
{dropdownOptions &&
|
||||||
|
dropdownOptions.length > 0 &&
|
||||||
|
dropdownOptions.map((option) => (
|
||||||
|
<Menu.Item
|
||||||
|
key={option.key}
|
||||||
|
as="button"
|
||||||
|
type="button"
|
||||||
|
className="relative w-full flex items-center gap-2 p-1 py-1.5 rounded transition-all hover:bg-custom-background-80 text-custom-text-200 hover:text-custom-text-100 cursor-pointer"
|
||||||
|
onClick={option.onClick}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0 w-4 h-4 relative flex justify-center items-center">
|
||||||
|
<option.icon size={12} />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs whitespace-nowrap">{option.label}</div>
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -31,22 +31,16 @@ export const ViewRoot: FC<TViewRoot> = observer((props) => {
|
|||||||
const handleViewTabsVisibility = () => {
|
const handleViewTabsVisibility = () => {
|
||||||
const tabContainer = document.getElementById("tab-container");
|
const tabContainer = document.getElementById("tab-container");
|
||||||
const tabItemViewMore = document.getElementById("tab-item-view-more");
|
const tabItemViewMore = document.getElementById("tab-item-view-more");
|
||||||
const itemWidth = 128;
|
const itemWidth = 116;
|
||||||
if (!tabContainer || !tabItemViewMore) return;
|
if (!tabContainer || !tabItemViewMore) return;
|
||||||
|
|
||||||
const containerWidth = tabContainer.clientWidth;
|
const containerWidth = tabContainer.clientWidth;
|
||||||
const itemViewMoreLeftOffset = tabItemViewMore.offsetLeft + (tabItemViewMore.clientWidth + 10);
|
const itemViewMoreLeftOffset = tabItemViewMore.offsetLeft + (tabItemViewMore.clientWidth + 10);
|
||||||
const itemViewMoreRightOffset = containerWidth - itemViewMoreLeftOffset;
|
const itemViewMoreRightOffset = containerWidth - itemViewMoreLeftOffset;
|
||||||
|
|
||||||
if (itemViewMoreLeftOffset > containerWidth) {
|
const itemsToRenderLeft = Math.floor(itemViewMoreLeftOffset / itemWidth) || 0;
|
||||||
const itemsToRender = Math.floor(containerWidth / itemWidth);
|
const itemsToRenderRight = Math.floor(itemViewMoreRightOffset / itemWidth) || 0;
|
||||||
setItemsToRenderViewCount(itemsToRender);
|
setItemsToRenderViewCount(itemsToRenderLeft + itemsToRenderRight);
|
||||||
}
|
|
||||||
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());
|
window.addEventListener("resize", () => handleViewTabsVisibility());
|
||||||
|
@ -28,16 +28,12 @@ export const ViewItem: FC<TViewItem> = observer((props) => {
|
|||||||
<Tooltip tooltipContent={viewDetailStore?.name} position="top">
|
<Tooltip tooltipContent={viewDetailStore?.name} position="top">
|
||||||
<Link
|
<Link
|
||||||
href={`${baseRoute}/${viewItemId}`}
|
href={`${baseRoute}/${viewItemId}`}
|
||||||
className={`cursor-pointer relative p-2 px-2.5 flex justify-center items-center gap-1 rounded transition-all hover:bg-custom-background-80
|
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
|
||||||
${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
|
<div className={`flex-shrink-0 rounded-sm relative w-3 h-3 flex justify-center items-center overflow-hidden`}>
|
||||||
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">
|
||||||
|
@ -4,12 +4,14 @@ import update from "lodash/update";
|
|||||||
import concat from "lodash/concat";
|
import concat from "lodash/concat";
|
||||||
import pull from "lodash/pull";
|
import pull from "lodash/pull";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "store/root.store";
|
import { RootStore } from "store/root.store";
|
||||||
// types
|
// types
|
||||||
import { TUserViewService, TViewService } from "services/view/types";
|
import { TUserViewService, TViewService } from "services/view/types";
|
||||||
import {
|
import {
|
||||||
TView,
|
TView,
|
||||||
|
TUpdateView,
|
||||||
TViewFilters,
|
TViewFilters,
|
||||||
TViewDisplayFilters,
|
TViewDisplayFilters,
|
||||||
TViewDisplayProperties,
|
TViewDisplayProperties,
|
||||||
@ -19,12 +21,12 @@ import {
|
|||||||
// helpers
|
// helpers
|
||||||
import { FiltersHelper } from "./helpers/filters_helpers";
|
import { FiltersHelper } from "./helpers/filters_helpers";
|
||||||
|
|
||||||
type TLoader = "filters_submit" | "filters_submitting" | "update" | "updating" | undefined;
|
type TLoader = "updating" | undefined;
|
||||||
|
|
||||||
export type TViewStore = TView & {
|
export type TViewStore = TView & {
|
||||||
// observables
|
// observables
|
||||||
loader: TLoader;
|
loader: TLoader;
|
||||||
filtersToUpdate: Partial<TView>;
|
filtersToUpdate: TUpdateView;
|
||||||
// computed
|
// computed
|
||||||
appliedFilters: TViewFilterProps | undefined;
|
appliedFilters: TViewFilterProps | undefined;
|
||||||
appliedFiltersQueryParams: string | undefined;
|
appliedFiltersQueryParams: string | undefined;
|
||||||
@ -36,10 +38,11 @@ export type TViewStore = TView & {
|
|||||||
setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => void;
|
setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => void;
|
||||||
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
setDisplayFilters: (display_filters: Partial<TViewDisplayFilters>) => void;
|
||||||
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void;
|
setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void;
|
||||||
|
setIsEditable: (id_editable: boolean) => void;
|
||||||
resetChanges: () => void;
|
resetChanges: () => void;
|
||||||
saveChanges: () => Promise<void>;
|
saveChanges: () => Promise<void>;
|
||||||
// actions
|
// actions
|
||||||
update: (viewData: Partial<TView>) => Promise<void>;
|
update: (viewData: TUpdateView) => Promise<void>;
|
||||||
lockView: () => Promise<void>;
|
lockView: () => Promise<void>;
|
||||||
unlockView: () => Promise<void>;
|
unlockView: () => Promise<void>;
|
||||||
makeFavorite: () => Promise<void>;
|
makeFavorite: () => Promise<void>;
|
||||||
@ -70,13 +73,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
is_create: boolean = false;
|
is_create: boolean = false;
|
||||||
is_editable: boolean = false;
|
is_editable: boolean = false;
|
||||||
loader: TLoader = undefined;
|
loader: TLoader = undefined;
|
||||||
filtersToUpdate: Partial<TView> = {
|
filtersToUpdate: TUpdateView;
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
filters: undefined,
|
|
||||||
display_filters: undefined,
|
|
||||||
display_properties: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: RootStore,
|
private store: RootStore,
|
||||||
@ -108,6 +105,8 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
this.is_create = _view.is_create;
|
this.is_create = _view.is_create;
|
||||||
this.is_editable = _view.is_editable;
|
this.is_editable = _view.is_editable;
|
||||||
this.filtersToUpdate = {
|
this.filtersToUpdate = {
|
||||||
|
name: this.name,
|
||||||
|
description: this.description,
|
||||||
filters: this.computedFilters(_view.filters),
|
filters: this.computedFilters(_view.filters),
|
||||||
display_filters: this.computedDisplayFilters(_view.display_filters),
|
display_filters: this.computedDisplayFilters(_view.display_filters),
|
||||||
display_properties: this.computedDisplayProperties(_view.display_properties),
|
display_properties: this.computedDisplayProperties(_view.display_properties),
|
||||||
@ -149,6 +148,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
setFilters: action,
|
setFilters: action,
|
||||||
setDisplayFilters: action,
|
setDisplayFilters: action,
|
||||||
setDisplayProperties: action,
|
setDisplayProperties: action,
|
||||||
|
setIsEditable: action,
|
||||||
resetChanges: action,
|
resetChanges: action,
|
||||||
saveChanges: action,
|
saveChanges: action,
|
||||||
// actions
|
// actions
|
||||||
@ -189,12 +189,13 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isFiltersUpdateEnabled() {
|
get isFiltersUpdateEnabled() {
|
||||||
const filters = this.filters;
|
const _filters = this.filters;
|
||||||
const appliedFilters = this.appliedFilters?.filters;
|
const _appliedFilters = this.appliedFilters?.filters;
|
||||||
|
|
||||||
let isFiltersUpdateEnabled = false;
|
let isFiltersUpdateEnabled = false;
|
||||||
Object.keys(appliedFilters).forEach((key) => {
|
Object.keys(_appliedFilters).forEach((key) => {
|
||||||
const _key = key as keyof TViewFilters;
|
const _key = key as keyof TViewFilters;
|
||||||
if (!isEqual(appliedFilters[_key].slice().sort(), filters[_key].slice().sort())) isFiltersUpdateEnabled = true;
|
if (!isEqual(_appliedFilters[_key].slice().sort(), _filters[_key].slice().sort())) isFiltersUpdateEnabled = true;
|
||||||
});
|
});
|
||||||
return isFiltersUpdateEnabled;
|
return isFiltersUpdateEnabled;
|
||||||
}
|
}
|
||||||
@ -254,14 +255,21 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setIsEditable = (is_editable: boolean) => {
|
||||||
|
runInAction(() => {
|
||||||
|
this.is_editable = is_editable;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
resetChanges = () => {
|
resetChanges = () => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
const _view = cloneDeep(this);
|
||||||
this.filtersToUpdate = {
|
this.filtersToUpdate = {
|
||||||
name: this.name,
|
name: _view.name,
|
||||||
description: this.description,
|
description: _view.description,
|
||||||
filters: this.filters,
|
filters: _view.filters,
|
||||||
display_filters: this.display_filters,
|
display_filters: _view.display_filters,
|
||||||
display_properties: this.display_properties,
|
display_properties: _view.display_properties,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -271,15 +279,19 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
if (this.filtersToUpdate) await this.update(this.filtersToUpdate);
|
if (this.filtersToUpdate) await this.update(this.filtersToUpdate);
|
||||||
} catch {
|
} catch {
|
||||||
Object.keys(this.filtersToUpdate).forEach((key) => {
|
Object.keys(this.filtersToUpdate).forEach((key) => {
|
||||||
const _key = key as keyof TView;
|
const _key = key as keyof TUpdateView;
|
||||||
set(this, _key, this.filtersToUpdate[_key]);
|
set(this, _key, this.filtersToUpdate[_key]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
update = async (viewData: Partial<TView>) => {
|
update = async (viewData: TUpdateView) => {
|
||||||
try {
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.loader = "updating";
|
||||||
|
});
|
||||||
|
|
||||||
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,10 +299,11 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
|||||||
if (!view) return;
|
if (!view) return;
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
Object.keys(viewData).forEach((key) => {
|
Object.keys(view).forEach((key) => {
|
||||||
const _key = key as keyof TView;
|
const _key = key as keyof TView;
|
||||||
set(this, _key, viewData[_key]);
|
set(this, _key, view[_key]);
|
||||||
});
|
});
|
||||||
|
this.loader = undefined;
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
this.resetChanges();
|
this.resetChanges();
|
||||||
|
Loading…
Reference in New Issue
Block a user