chore: store changes on the filters and display filters

This commit is contained in:
gurusainath 2024-02-13 18:41:34 +05:30
parent 02380c730d
commit a5fd6f0e8a
27 changed files with 717 additions and 298 deletions

View File

@ -1,9 +1,17 @@
declare enum EViewLayouts {
LIST = "list",
KANBAN = "kanban",
CALENDAR = "calendar",
SPREADSHEET = "spreadsheet",
GANTT = "gantt",
}
export type TViewLayouts =
| "list"
| "kanban"
| "calendar"
| "spreadsheet"
| "gantt";
| EViewLayouts.LIST
| EViewLayouts.KANBAN
| EViewLayouts.CALENDAR
| EViewLayouts.SPREADSHEET
| EViewLayouts.GANTT;
export type TViewDisplayFiltersGrouped =
| "project"

View File

@ -154,7 +154,9 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label="Issues" icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />} />}
link={
<BreadcrumbLink label="Issues" icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />} />
}
/>
</Breadcrumbs>
</div>
@ -203,7 +205,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
</div>
{currentProjectDetails?.inbox_view && inboxDetails && (
<Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxDetails?.id}`}>
<span className="hidden md:block" >
<span className="hidden md:block">
<Button variant="neutral-primary" size="sm" className="relative">
Inbox
{inboxDetails?.pending_issue_count > 0 && (
@ -218,7 +220,12 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
)}
{canUserCreateIssue && (
<>
<Button className="hidden md:block" onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
<Button
className="hidden md:block"
onClick={() => setAnalyticsModal(true)}
variant="neutral-primary"
size="sm"
>
Analytics
</Button>
<Button

View File

@ -0,0 +1,72 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useViewDetail, useViewFilter } from "hooks/store";
// components
import { ViewDisplayFiltersItem, ViewDisplayFilterSelection } from "..";
// types
import { TViewDisplayFilters, TViewTypes } from "@plane/types";
type TViewDisplayFiltersItemRoot = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
filterKey: keyof TViewDisplayFilters;
};
export const ViewDisplayFiltersItemRoot: FC<TViewDisplayFiltersItemRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, filterKey } = props;
// hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
const filterPropertyIds = viewFilterHelper?.displayFilterIdsWithKey(filterKey) || [];
const handlePropertySelection = (_propertyId: string) => {
viewDetailStore?.setDisplayFilters({ [filterKey]: _propertyId });
};
const renderElement = (_propertyId: string) =>
filterKey === "group_by"
? viewDetailStore?.appliedFilters?.display_filters?.["sub_group_by"] !== _propertyId
? true
: false
: filterKey === "sub_group_by"
? viewDetailStore?.appliedFilters?.display_filters?.["group_by"] !== _propertyId
? true
: false
: true;
if (filterPropertyIds.length <= 0)
return <div className="text-xs italic py-1 text-custom-text-300">No items are available.</div>;
return (
<div className="space-y-0.5">
{filterPropertyIds.map(
(propertyId) =>
renderElement(propertyId) && (
<button
key={`filterKey_${propertyId}`}
className="relative w-full flex items-center overflow-hidden gap-2.5 cursor-pointer p-1 py-1.5 rounded hover:bg-custom-background-80 transition-all group"
onClick={() => handlePropertySelection(propertyId)}
>
<ViewDisplayFilterSelection
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
filterKey={filterKey}
propertyId={propertyId}
/>
<ViewDisplayFiltersItem
workspaceSlug={workspaceSlug}
projectId={projectId}
filterKey={filterKey}
propertyId={propertyId}
/>
</button>
)
)}
</div>
);
});

View File

@ -0,0 +1,27 @@
import { FC, Fragment } from "react";
// hooks
import { useViewFilter } from "hooks/store";
// types
import { TViewDisplayFilters } from "@plane/types";
type TViewDisplayFiltersItem = {
workspaceSlug: string;
projectId: string | undefined;
filterKey: keyof TViewDisplayFilters;
propertyId: string;
};
export const ViewDisplayFiltersItem: FC<TViewDisplayFiltersItem> = (props) => {
const { workspaceSlug, projectId, filterKey, propertyId } = props;
// hooks
const viewFilterHelper = useViewFilter(workspaceSlug, projectId);
const propertyDetail = viewFilterHelper?.displayPropertyDetails(filterKey, propertyId) || undefined;
if (!propertyDetail) return <></>;
return (
<div className="text-xs block truncate line-clamp-1 text-custom-text-200 group-hover:text-custom-text-100">
{propertyDetail?.label || propertyId}
</div>
);
};

View File

@ -0,0 +1,38 @@
import { FC } from "react";
import { Check } from "lucide-react";
import { observer } from "mobx-react-lite";
// hooks
import { useViewDetail } from "hooks/store";
// types
import { TViewDisplayFilters, TViewTypes } from "@plane/types";
type TViewDisplayFilterSelection = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
filterKey: keyof TViewDisplayFilters;
propertyId: string;
};
export const ViewDisplayFilterSelection: FC<TViewDisplayFilterSelection> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, filterKey, propertyId } = props;
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
const propertyIds = viewDetailStore?.appliedFilters?.display_filters?.[filterKey] || undefined;
const isSelected = propertyIds === propertyId || false;
return (
<div
className={`flex-shrink-0 w-3 h-3 flex justify-center items-center border rounded-full text-bold ${
isSelected
? "border-custom-primary-100 bg-custom-primary-100"
: "border-custom-border-400 bg-custom-background-100"
}`}
>
{isSelected && <Check size={14} />}
</div>
);
});

View File

@ -7,28 +7,34 @@ import { MonitorDot } from "lucide-react";
// hooks
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components
import { ViewDisplayPropertiesRoot } from "../";
import { ViewDisplayFiltersRoot } from "../";
// ui
import { Tooltip } from "@plane/ui";
// types
import { TViewTypes } from "@plane/types";
// constants
import { EViewPageType } from "constants/view";
type TViewDisplayFiltersDropdown = {
type NewType = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewPageType: EViewPageType;
children?: ReactNode;
displayDropdownText?: boolean;
dropdownPlacement?: Placement;
};
type TViewDisplayFiltersDropdown = NewType;
export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = observer((props) => {
const {
workspaceSlug,
projectId,
viewId,
viewType,
viewPageType,
children,
displayDropdownText = true,
dropdownPlacement = "bottom-start",
@ -110,18 +116,14 @@ export const ViewDisplayFiltersDropdown: FC<TViewDisplayFiltersDropdown> = obser
{...attributes.popper}
className="my-1 w-72 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="max-h-96 space-y-1 overflow-y-scroll">
<div className="space-y-2">
<div className="text-sm font-medium text-custom-text-200">Properties</div>
<ViewDisplayPropertiesRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
/>
</div>
<div className="border border-red-500">Content</div>
<div className="max-h-[500px] space-y-0.5 overflow-y-scroll mb-2">
<ViewDisplayFiltersRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewPageType={viewPageType}
/>
</div>
</div>
</Combobox.Options>

View File

@ -1,17 +1,101 @@
import { FC } from "react";
import { FC, useState } from "react";
import { observer } from "mobx-react-lite";
import { ChevronUp, ChevronDown } from "lucide-react";
import filter from "lodash/filter";
import concat from "lodash/concat";
import uniq from "lodash/uniq";
// hooks
import { useViewDetail } from "hooks/store";
// components
import { ViewDisplayPropertiesRoot, ViewDisplayFiltersItemRoot } from "../";
// types
import { TViewDisplayFilters, TViewTypes } from "@plane/types";
// constants
import { EViewPageType, viewDefaultFilterParametersByViewTypeAndLayout } from "constants/view";
type TViewDisplayFiltersRoot = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewPageType: EViewPageType;
};
export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = (props) => {
const { workspaceSlug, projectId, viewId } = props;
export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, viewPageType } = props;
// hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
// state
const [filterVisibility, setFilterVisibility] = useState<(Partial<keyof TViewDisplayFilters> | "display_property")[]>(
[]
);
const handleFilterVisibility = (key: keyof TViewDisplayFilters | "display_property") => {
setFilterVisibility((prevData = []) => {
if (prevData.includes(key)) return filter(prevData, (item) => item !== key);
return uniq(concat(prevData, [key]));
});
};
const layout = viewDetailStore?.appliedFilters?.display_filters?.layout;
const filtersProperties = layout
? viewDefaultFilterParametersByViewTypeAndLayout(viewPageType, layout, "display_filters")
: [];
return (
<div>
<div>ViewDisplayFiltersRoot</div>
<div className="space-y-1 divide-y divide-custom-border-300">
<div className="relative py-1 first:pt-0">
<div className="sticky top-0 z-20 flex justify-between items-center gap-2 bg-custom-background-100 select-none">
<div className="font-medium text-xs text-custom-text-300 capitalize py-1">Properties</div>
<div
className="flex-shrink-0 relative overflow-hidden w-5 h-5 rounded flex justify-center items-center cursor-pointer hover:bg-custom-background-80"
onClick={() => handleFilterVisibility("display_property")}
>
{!filterVisibility.includes("display_property") ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
</div>
</div>
{!filterVisibility.includes("display_property") && (
<div className="py-1">
<ViewDisplayPropertiesRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
/>
</div>
)}
</div>
{filtersProperties.map((filterKey) => (
<div key={filterKey} className="relative py-1 last:pb-0">
<div className="sticky top-0 z-20 flex justify-between items-center gap-2 bg-custom-background-100 select-none">
<div className="font-medium text-xs text-custom-text-300 capitalize py-1">
{filterKey.replaceAll("_", " ")}
</div>
<div
className="flex-shrink-0 relative overflow-hidden w-5 h-5 rounded flex justify-center items-center cursor-pointer hover:bg-custom-background-80"
onClick={() => handleFilterVisibility(filterKey)}
>
{!filterVisibility.includes(filterKey) ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
</div>
</div>
{!filterVisibility.includes(filterKey) && (
<ViewDisplayFiltersItemRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
filterKey={filterKey}
/>
)}
</div>
))}
{/* extra options */}
<div>
<div>Show sub issues</div>
<div>Show Empty groups</div>
</div>
</div>
);
};
});

View File

@ -12,12 +12,14 @@ import { ViewFiltersRoot } from "../";
import { Tooltip } from "@plane/ui";
// types
import { TViewTypes } from "@plane/types";
import { EViewPageType } from "constants/view";
type TViewFiltersDropdown = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewPageType: EViewPageType;
children?: ReactNode;
displayDropdownText?: boolean;
dropdownPlacement?: Placement;
@ -29,6 +31,7 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
projectId,
viewId,
viewType,
viewPageType,
children,
displayDropdownText = true,
dropdownPlacement = "bottom-start",
@ -130,6 +133,7 @@ export const ViewFiltersDropdown: FC<TViewFiltersDropdown> = observer((props) =>
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewPageType={viewPageType}
dateCustomFilterToggle={dateCustomFilterToggle}
setDateCustomFilterToggle={setDateCustomFilterToggle}
/>

View File

@ -50,14 +50,14 @@ export const ViewFiltersEditDropdown: FC<TViewFiltersEditDropdown> = observer((p
// dropdown options
const dropdownOptions: TViewFilterEditDropdownOptions[] = useMemo(
() => [
// {
// icon: PhotoFilterIcon,
// key: "save_as_new",
// label: "Save as new view",
// onClick: () => {
// viewOperations.localViewCreateEdit(undefined, viewDetailStore?.filtersToUpdate);
// },
// },
{
icon: PhotoFilterIcon,
key: "save_as_new",
label: "Save as new view",
onClick: () => {
viewOperations.localViewCreateEdit(undefined, viewDetailStore?.filtersToUpdate);
},
},
{
icon: RotateCcw,
key: "reset_changes",

View File

@ -10,19 +10,28 @@ import { useViewDetail } from "hooks/store";
import { ViewFiltersItemRoot } from "../";
// types
import { TViewFilters, TViewTypes } from "@plane/types";
import { VIEW_DEFAULT_FILTER_PARAMETERS } from "constants/view";
import { EViewPageType, viewDefaultFilterParametersByViewTypeAndLayout } from "constants/view";
type TViewFiltersRoot = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewPageType: EViewPageType;
dateCustomFilterToggle: string | undefined;
setDateCustomFilterToggle: (value: string | undefined) => void;
};
export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, dateCustomFilterToggle, setDateCustomFilterToggle } = props;
const {
workspaceSlug,
projectId,
viewId,
viewType,
viewPageType,
dateCustomFilterToggle,
setDateCustomFilterToggle,
} = props;
// hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
// state
@ -34,9 +43,11 @@ export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
});
};
const layout = viewDetailStore?.appliedFilters?.display_filters?.layout || "spreadsheet";
const layout = viewDetailStore?.appliedFilters?.display_filters?.layout;
const filtersProperties = VIEW_DEFAULT_FILTER_PARAMETERS?.["all"]?.["spreadsheet"]?.filters || [];
const filtersProperties = layout
? viewDefaultFilterParametersByViewTypeAndLayout(viewPageType, layout, "filters")
: [];
if (!layout || filtersProperties.length <= 0) return <></>;
return (
@ -45,7 +56,7 @@ export const ViewFiltersRoot: FC<TViewFiltersRoot> = observer((props) => {
<div key={filterKey} className="relative py-1 first:pt-0 last:pb-0">
<div className="sticky top-0 z-20 flex justify-between items-center gap-2 bg-custom-background-100 select-none">
<div className="font-medium text-xs text-custom-text-300 capitalize py-1">
{filterKey.replace("_", " ")}
{filterKey.replaceAll("_", " ")}
</div>
<div
className="flex-shrink-0 relative overflow-hidden w-5 h-5 rounded flex justify-center items-center cursor-pointer hover:bg-custom-background-80"

View File

@ -22,6 +22,9 @@ export * from "./filters/edit-dropdown";
// view display filters
export * from "./display-filters/dropdown";
export * from "./display-filters/root";
export * from "./display-filters/display-filter-item-root";
export * from "./display-filters/display-filter-item";
export * from "./display-filters/display-filter-selection";
// view display properties
export * from "./display-properties/root";

View File

@ -6,49 +6,57 @@ import { useViewDetail } from "hooks/store";
// ui
import { Tooltip } from "@plane/ui";
// types
import { TViewLayouts, TViewTypes } from "@plane/types";
import { TViewTypes } from "@plane/types";
// constants
import { EViewLayouts, EViewPageType, viewPageDefaultLayoutsByPageType } from "constants/view";
type TViewLayoutRoot = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewPageType: EViewPageType;
};
const LAYOUTS_DATA: { key: TViewLayouts; title: string; icon: LucideIcon }[] = [
{ key: "list", title: "List Layout", icon: List },
{ key: "kanban", title: "Kanban Layout", icon: Kanban },
{ key: "calendar", title: "Calendar Layout", icon: Calendar },
{ key: "spreadsheet", title: "Spreadsheet Layout", icon: Sheet },
{ key: "gantt", title: "Gantt Chart layout", icon: GanttChartSquare },
const LAYOUTS_DATA: { key: EViewLayouts; title: string; icon: LucideIcon }[] = [
{ key: EViewLayouts.LIST, title: "List Layout", icon: List },
{ key: EViewLayouts.KANBAN, title: "Kanban Layout", icon: Kanban },
{ key: EViewLayouts.CALENDAR, title: "Calendar Layout", icon: Calendar },
{ key: EViewLayouts.SPREADSHEET, title: "Spreadsheet Layout", icon: Sheet },
{ key: EViewLayouts.GANTT, title: "Gantt Chart layout", icon: GanttChartSquare },
];
export const ViewLayoutRoot: FC<TViewLayoutRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType } = props;
const { workspaceSlug, projectId, viewId, viewType, viewPageType } = props;
// hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
if (!viewDetailStore) return <></>;
const validLayouts = viewPageDefaultLayoutsByPageType(viewPageType);
if (!viewDetailStore || validLayouts.length <= 1) return <></>;
return (
<div className="relative flex gap-0.5 items-center bg-custom-background-80 rounded p-1 shadow-custom-shadow-2xs">
{LAYOUTS_DATA.map((layout) => (
<Fragment key={layout.key}>
<Tooltip tooltipContent={layout.title} position="bottom">
<div
className={`relative h-6 w-7 flex justify-center items-center overflow-hidden rounded transition-all cursor-pointer
{LAYOUTS_DATA.map((layout) => {
if (!validLayouts.includes(layout.key)) return <Fragment key={layout.key} />;
return (
<Fragment key={layout.key}>
<Tooltip tooltipContent={layout.title} position="bottom">
<div
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
? `bg-custom-background-100 shadow-custom-shadow-2xs`
: `hover:bg-custom-background-100`
}
`}
onClick={() => viewDetailStore.setDisplayFilters({ layout: layout.key })}
>
<layout.icon size={12} />
</div>
</Tooltip>
</Fragment>
))}
onClick={() => viewDetailStore.setDisplayFilters({ layout: layout.key })}
>
<layout.icon size={12} />
</div>
</Tooltip>
</Fragment>
);
})}
</div>
);
});

View File

@ -23,7 +23,7 @@ import {
// ui
import { Spinner } from "@plane/ui";
// constants
import { viewLocalPayload } from "constants/view";
import { EViewPageType, viewLocalPayload } from "constants/view";
// types
import { TViewOperations } from "./types";
import { TView, TViewTypes } from "@plane/types";
@ -33,6 +33,7 @@ type TGlobalViewRoot = {
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewPageType: EViewPageType;
baseRoute: string;
workspaceViewTabOptions: { key: TViewTypes; title: string; href: string }[];
};
@ -43,7 +44,7 @@ type TViewOperationsToggle = {
};
export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, baseRoute, workspaceViewTabOptions } = props;
const { workspaceSlug, projectId, viewId, viewType, viewPageType, baseRoute, workspaceViewTabOptions } = props;
// hooks
const viewStore = useView(workspaceSlug, projectId, viewType);
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
@ -232,7 +233,13 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
</div>
<div className="flex-shrink-0">
<ViewLayoutRoot workspaceSlug={workspaceSlug} projectId={projectId} viewId={viewId} viewType={viewType} />
<ViewLayoutRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewPageType={viewPageType}
/>
</div>
<div className="flex-shrink-0">
@ -241,6 +248,7 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewPageType={viewPageType}
displayDropdownText={false}
/>
</div>
@ -251,24 +259,29 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewPageType={viewPageType}
displayDropdownText={false}
/>
</div>
<ViewEditDropdown
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewOperations={viewOperations}
/>
<div className="flex-shrink-0">
<ViewEditDropdown
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewOperations={viewOperations}
/>
</div>
<ViewFiltersEditDropdown
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewOperations={viewOperations}
/>
<div className="flex-shrink-0">
<ViewFiltersEditDropdown
workspaceSlug={workspaceSlug}
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewOperations={viewOperations}
/>
</div>
</div>
</>
)}
@ -282,6 +295,7 @@ export const GlobalViewRoot: FC<TGlobalViewRoot> = observer((props) => {
projectId={projectId}
viewId={viewOperationsToggle.viewId}
viewType={viewType}
viewPageType={viewPageType}
viewOperations={viewOperations}
/>
)}

View File

@ -11,17 +11,20 @@ import { Input, Button } from "@plane/ui";
// types
import { TViewTypes } from "@plane/types";
import { TViewOperations } from "../types";
// constants
import { EViewPageType } from "constants/view";
type TViewCreateEditForm = {
workspaceSlug: string;
projectId: string | undefined;
viewId: string;
viewType: TViewTypes;
viewPageType: EViewPageType;
viewOperations: TViewOperations;
};
export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props;
const { workspaceSlug, projectId, viewId, viewType, viewPageType, viewOperations } = props;
// hooks
const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType);
const { getProjectById } = useProject();
@ -126,6 +129,7 @@ export const ViewCreateEditForm: FC<TViewCreateEditForm> = observer((props) => {
projectId={projectId}
viewId={viewId}
viewType={viewType}
viewPageType={viewPageType}
dropdownPlacement="right"
>
<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">

View File

@ -49,49 +49,49 @@ export const ViewEditDropdown: FC<TViewEditDropdown> = observer((props) => {
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,
// },
{
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]
);

View File

@ -449,4 +449,4 @@ export const groupReactionEmojis = (reactions: any) => {
}
return _groupedEmojis;
};
};

View File

@ -1,40 +1,24 @@
// types
import { TStateGroups, TIssuePriorities, TViewFilters, TViewDisplayFilters, TViewLayouts } from "@plane/types";
import {
TStateGroups,
TIssuePriorities,
TViewFilters,
TViewDisplayFilters,
TViewDisplayFiltersGrouped,
TViewDisplayFiltersOrderBy,
TViewDisplayFiltersType,
} from "@plane/types";
// filters constants
export const STATE_GROUP_PROPERTY: {
[key in TStateGroups]: {
label: string;
color: string;
};
} = {
backlog: {
label: "Backlog",
color: "#d9d9d9",
},
unstarted: {
label: "Unstarted",
color: "#3f76ff",
},
started: {
label: "Started",
color: "#f59e0b",
},
completed: {
label: "Completed",
color: "#16a34a",
},
cancelled: {
label: "Canceled",
color: "#dc2626",
},
export const STATE_GROUP_PROPERTY: Record<TStateGroups, { label: string; color: string }> = {
backlog: { label: "Backlog", color: "#d9d9d9" },
unstarted: { label: "Unstarted", color: "#3f76ff" },
started: { label: "Started", color: "#f59e0b" },
completed: { label: "Completed", color: "#16a34a" },
cancelled: { label: "Canceled", color: "#dc2626" },
};
export const PRIORITIES_PROPERTY: {
[key in TIssuePriorities]: {
label: string;
};
} = {
export const PRIORITIES_PROPERTY: Record<TIssuePriorities, { label: string }> = {
urgent: { label: "Urgent" },
high: { label: "High" },
medium: { label: "Medium" },
@ -42,11 +26,7 @@ export const PRIORITIES_PROPERTY: {
none: { label: "None" },
};
export const DATE_PROPERTY: {
[key in string]: {
label: string;
};
} = {
export const DATE_PROPERTY: Record<string, { label: string }> = {
"1_weeks;after;fromnow": { label: "1 week from now" },
"2_weeks;after;fromnow": { label: "2 weeks from now" },
"1_months;after;fromnow": { label: "1 month from now" },
@ -55,77 +35,98 @@ export const DATE_PROPERTY: {
};
// display filter constants
export const GROUP_BY_PROPERTY: Partial<Record<TViewDisplayFiltersGrouped | "null", { label: string }>> = {
state: { label: "states" },
priority: { label: "Priority" },
labels: { label: "labels" },
assignees: { label: "Assignees" },
created_by: { label: "Created By" },
cycles: { label: "Cycles" },
modules: { label: "Modules" },
null: { label: "None" },
};
// layout, filter, display filter and display properties permissions for views
type TViewLayoutFilterProperties = {
export const ORDER_BY_PROPERTY: Partial<Record<TViewDisplayFiltersOrderBy, Record<string, string>>> = {
sort_order: { label: "Manual" },
"-created_at": { label: "Last Created" },
"-updated_at": { label: "Last Updated" },
start_date: { label: "Start Date" },
target_date: { label: "Due Date" },
"-priority": { label: "Priority" },
};
export const TYPE_PROPERTY: Record<TViewDisplayFiltersType | "null", { label: string }> = {
null: { label: "All" },
active: { label: "Active issues" },
backlog: { label: "Backlog issues" },
};
export const EXTRA_OPTIONS_PROPERTY: Record<string, { label: string }> = {
sub_issue: { label: "Sub Issues" },
show_empty_groups: { label: "Show Empty Groups" },
};
export enum EViewPageType {
ALL = "all",
PROFILE = "profile",
PROJECT = "project",
ARCHIVED = "archived",
DRAFT = "draft",
}
export enum EViewLayouts {
LIST = "list",
KANBAN = "kanban",
CALENDAR = "calendar",
SPREADSHEET = "spreadsheet",
GANTT = "gantt",
}
export type TViewLayoutFilterProperties = {
filters: Partial<keyof TViewFilters>[];
readonlyFilters?: Partial<keyof TViewFilters>[];
display_filters: Partial<keyof TViewDisplayFilters>[];
extra_options: ("sub_issue" | "show_empty_groups")[];
display_properties: boolean;
readonlyFilters?: Partial<keyof TViewFilters>[];
};
type TViewLayoutFilters = {
list: TViewLayoutFilterProperties;
kanban: TViewLayoutFilterProperties;
calendar: TViewLayoutFilterProperties;
spreadsheet: TViewLayoutFilterProperties;
gantt: TViewLayoutFilterProperties;
export type TViewLayoutFilters = {
layouts: Partial<EViewLayouts>[];
[EViewLayouts.LIST]: TViewLayoutFilterProperties;
[EViewLayouts.KANBAN]: TViewLayoutFilterProperties;
[EViewLayouts.CALENDAR]: TViewLayoutFilterProperties;
[EViewLayouts.SPREADSHEET]: TViewLayoutFilterProperties;
[EViewLayouts.GANTT]: TViewLayoutFilterProperties;
};
type TFilterPermissions = {
all: Omit<TViewLayoutFilters, "list" | "kanban" | "calendar" | "gantt"> & {
layouts: Omit<TViewLayouts, "list" | "kanban" | "calendar" | "gantt">[];
};
profile: Omit<TViewLayoutFilters, "spreadsheet" | "calendar" | "gantt"> & {
layouts: Omit<TViewLayouts, "spreadsheet" | "calendar" | "gantt">[];
};
project: TViewLayoutFilters & {
layouts: TViewLayouts[];
};
archived: Omit<TViewLayoutFilters, "kanban" | "spreadsheet" | "calendar" | "gantt"> & {
layouts: Omit<TViewLayouts, "kanban" | "spreadsheet" | "calendar" | "gantt">[];
};
draft: Omit<TViewLayoutFilters, "spreadsheet" | "calendar" | "gantt"> & {
layouts: Omit<TViewLayouts, "kanban" | "spreadsheet" | "calendar" | "gantt">[];
};
export type TFilterPermissions = {
[EViewPageType.ALL]: Partial<TViewLayoutFilters>;
[EViewPageType.PROFILE]: Partial<TViewLayoutFilters>;
[EViewPageType.PROJECT]: TViewLayoutFilters;
[EViewPageType.ARCHIVED]: Partial<TViewLayoutFilters>;
[EViewPageType.DRAFT]: Partial<TViewLayoutFilters>;
};
const ALL_FILTER_PERMISSIONS: TFilterPermissions["all"] = {
layouts: ["spreadsheet"],
spreadsheet: {
// filters: ["project", "priority", "state_group", "assignees", "created_by", "labels", "start_date", "target_date"],
filters: [
"project",
"module",
"cycle",
"priority",
"state",
"state_group",
"assignees",
"mentions",
"subscriber",
"created_by",
"labels",
"start_date",
"target_date",
],
// display_filters: ["type"],
display_filters: ["group_by", "sub_group_by", "order_by", "type"],
extra_options: [],
layouts: [EViewLayouts.SPREADSHEET],
[EViewLayouts.SPREADSHEET]: {
filters: ["project", "priority", "state_group", "assignees", "created_by", "labels", "start_date", "target_date"],
display_filters: ["type"],
// extra_options: [],
extra_options: ["sub_issue", "show_empty_groups"],
display_properties: true,
},
};
const PROFILE_FILTER_PERMISSIONS: TFilterPermissions["profile"] = {
layouts: ["list", "kanban"],
list: {
layouts: [EViewLayouts.LIST, EViewLayouts.KANBAN],
[EViewLayouts.LIST]: {
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
display_filters: ["group_by", "order_by", "type"],
extra_options: [],
display_properties: true,
},
kanban: {
[EViewLayouts.KANBAN]: {
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
display_filters: ["group_by", "order_by", "type"],
extra_options: [],
@ -134,8 +135,14 @@ const PROFILE_FILTER_PERMISSIONS: TFilterPermissions["profile"] = {
};
const PROJECT_FILTER_PERMISSIONS: TFilterPermissions["project"] = {
layouts: ["list", "kanban", "spreadsheet", "calendar", "gantt"],
list: {
layouts: [
EViewLayouts.LIST,
EViewLayouts.KANBAN,
EViewLayouts.CALENDAR,
EViewLayouts.SPREADSHEET,
EViewLayouts.GANTT,
],
[EViewLayouts.LIST]: {
filters: [
"priority",
"state",
@ -152,7 +159,7 @@ const PROJECT_FILTER_PERMISSIONS: TFilterPermissions["project"] = {
extra_options: ["sub_issue", "show_empty_groups"],
display_properties: true,
},
kanban: {
[EViewLayouts.KANBAN]: {
filters: [
"priority",
"state",
@ -169,7 +176,7 @@ const PROJECT_FILTER_PERMISSIONS: TFilterPermissions["project"] = {
extra_options: ["sub_issue", "show_empty_groups"],
display_properties: true,
},
calendar: {
[EViewLayouts.CALENDAR]: {
filters: [
"priority",
"state",
@ -186,7 +193,7 @@ const PROJECT_FILTER_PERMISSIONS: TFilterPermissions["project"] = {
extra_options: ["sub_issue"],
display_properties: true,
},
spreadsheet: {
[EViewLayouts.SPREADSHEET]: {
filters: [
"priority",
"state",
@ -203,8 +210,7 @@ const PROJECT_FILTER_PERMISSIONS: TFilterPermissions["project"] = {
extra_options: [],
display_properties: true,
},
gantt: {
[EViewLayouts.GANTT]: {
filters: [
"priority",
"state",
@ -224,8 +230,8 @@ const PROJECT_FILTER_PERMISSIONS: TFilterPermissions["project"] = {
};
const ARCHIVED_FILTER_PERMISSIONS: TFilterPermissions["archived"] = {
layouts: ["list"],
list: {
layouts: [EViewLayouts.LIST],
[EViewLayouts.LIST]: {
filters: [
"priority",
"state",
@ -245,8 +251,8 @@ const ARCHIVED_FILTER_PERMISSIONS: TFilterPermissions["archived"] = {
};
const DRAFT_FILTER_PERMISSIONS: TFilterPermissions["draft"] = {
layouts: ["list", "kanban"],
list: {
layouts: [EViewLayouts.LIST, EViewLayouts.KANBAN],
[EViewLayouts.LIST]: {
filters: [
"priority",
"state",
@ -263,7 +269,7 @@ const DRAFT_FILTER_PERMISSIONS: TFilterPermissions["draft"] = {
extra_options: ["sub_issue", "show_empty_groups"],
display_properties: true,
},
kanban: {
[EViewLayouts.KANBAN]: {
filters: [
"priority",
"state",
@ -283,9 +289,19 @@ const DRAFT_FILTER_PERMISSIONS: TFilterPermissions["draft"] = {
};
export const VIEW_DEFAULT_FILTER_PARAMETERS: TFilterPermissions = {
all: ALL_FILTER_PERMISSIONS,
profile: PROFILE_FILTER_PERMISSIONS,
project: PROJECT_FILTER_PERMISSIONS,
archived: ARCHIVED_FILTER_PERMISSIONS,
draft: DRAFT_FILTER_PERMISSIONS,
[EViewPageType.ALL]: ALL_FILTER_PERMISSIONS,
[EViewPageType.PROFILE]: PROFILE_FILTER_PERMISSIONS,
[EViewPageType.PROJECT]: PROJECT_FILTER_PERMISSIONS,
[EViewPageType.ARCHIVED]: ARCHIVED_FILTER_PERMISSIONS,
[EViewPageType.DRAFT]: DRAFT_FILTER_PERMISSIONS,
};
export const viewPageDefaultLayoutsByPageType = (_viewPageType: EViewPageType) =>
VIEW_DEFAULT_FILTER_PARAMETERS?.[_viewPageType]?.layouts || [];
export const viewDefaultFilterParametersByViewTypeAndLayout = <K extends keyof TViewLayoutFilterProperties>(
_viewPageType: EViewPageType,
_layout: EViewLayouts,
property: K
): TViewLayoutFilterProperties[K] =>
VIEW_DEFAULT_FILTER_PARAMETERS?.[_viewPageType]?.[_layout]?.[property] as TViewLayoutFilterProperties[K];

View File

@ -13,9 +13,24 @@ import {
StateGroupIcon,
} from "@plane/ui";
// types
import { TIssuePriorities, TStateGroups, TViewFilters } from "@plane/types";
import {
TIssuePriorities,
TStateGroups,
TViewFilters,
TViewDisplayFilters,
TViewDisplayFiltersGrouped,
TViewDisplayFiltersOrderBy,
TViewDisplayFiltersType,
} from "@plane/types";
// constants
import { STATE_GROUP_PROPERTY, PRIORITIES_PROPERTY, DATE_PROPERTY } from "constants/view/filters";
import {
STATE_GROUP_PROPERTY,
PRIORITIES_PROPERTY,
DATE_PROPERTY,
GROUP_BY_PROPERTY,
ORDER_BY_PROPERTY,
TYPE_PROPERTY,
} from "constants/view/filters";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
import { renderFormattedDate } from "helpers/date-time.helper";
@ -30,6 +45,11 @@ type TFilterPropertyDefaultDetails = {
label: string;
};
type TDisplayFilterPropertyDetails = {
icon: ReactNode;
label: string;
};
export const useViewFilter = (workspaceSlug: string, projectId: string | undefined) => {
const { projectMap, getProjectById } = useProject();
const { getProjectModuleIds, getModuleById } = useModule();
@ -326,9 +346,68 @@ export const useViewFilter = (workspaceSlug: string, projectId: string | undefin
}
};
const displayFilterIdsWithKey = (displayFilterKey: keyof TViewDisplayFilters): string[] | undefined => {
if (!displayFilterKey) return undefined;
switch (displayFilterKey) {
case "group_by":
return Object.keys(GROUP_BY_PROPERTY) || undefined;
case "sub_group_by":
return Object.keys(GROUP_BY_PROPERTY) || undefined;
case "order_by":
return Object.keys(ORDER_BY_PROPERTY) || undefined;
case "type":
return Object.keys(TYPE_PROPERTY) || undefined;
default:
return undefined;
}
};
const displayPropertyDetails = (
displayFilterKey: keyof TViewDisplayFilters,
propertyId: string
): TDisplayFilterPropertyDetails | undefined => {
if (!displayFilterKey) return undefined;
switch (displayFilterKey) {
case "group_by":
const groupBy = GROUP_BY_PROPERTY?.[propertyId as TViewDisplayFiltersGrouped | "null"];
if (!groupBy) return undefined;
return {
icon: undefined,
label: groupBy.label,
};
case "sub_group_by":
const subGroupBy = GROUP_BY_PROPERTY?.[propertyId as TViewDisplayFiltersGrouped | "null"];
if (!subGroupBy) return undefined;
return {
icon: undefined,
label: subGroupBy.label,
};
case "order_by":
const orderBy = ORDER_BY_PROPERTY?.[propertyId as TViewDisplayFiltersOrderBy];
if (!orderBy) return undefined;
return {
icon: undefined,
label: orderBy.label,
};
case "type":
const type = TYPE_PROPERTY?.[propertyId as TViewDisplayFiltersType | "null"];
if (!type) return undefined;
return {
icon: undefined,
label: type.label,
};
default:
return undefined;
}
};
return {
filterIdsWithKey,
propertyDefaultDetails,
propertyDetails,
displayFilterIdsWithKey,
displayPropertyDetails,
};
};

View File

@ -7,7 +7,7 @@ import { GlobalViewRoot } from "components/view";
// types
import { NextPageWithLayout } from "lib/types";
// constants
import { VIEW_TYPES } from "constants/view";
import { EViewPageType, VIEW_TYPES } from "constants/view";
const ProjectPrivateViewPage: NextPageWithLayout = () => {
const router = useRouter();
@ -38,6 +38,7 @@ const ProjectPrivateViewPage: NextPageWithLayout = () => {
projectId={projectId.toString()}
viewId={viewId.toString()}
viewType={VIEW_TYPES.PROJECT_PRIVATE_VIEWS}
viewPageType={EViewPageType.PROJECT}
baseRoute={`/${workspaceSlug?.toString()}/projects/${projectId}/views/private`}
workspaceViewTabOptions={workspaceViewTabOptions}
/>

View File

@ -1,49 +1,16 @@
import { ReactElement, useMemo } from "react";
import { ReactElement } from "react";
import { useRouter } from "next/router";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { GlobalViewRoot } from "components/view";
// types
import { NextPageWithLayout } from "lib/types";
// constants
import { VIEW_TYPES } from "constants/view";
const ProjectPrivateViewPage: NextPageWithLayout = () => {
const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query;
const workspaceViewTabOptions = useMemo(
() => [
{
key: VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS,
title: "Private",
href: `/${workspaceSlug}/projects/${projectId}/views/private`,
},
{
key: VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS,
title: "Public",
href: `/${workspaceSlug}/projects/${projectId}/views/public`,
},
],
[workspaceSlug, projectId]
);
if (!workspaceSlug || !projectId || !viewId) return <></>;
return (
<div className="h-full overflow-hidden bg-custom-background-100">
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
<GlobalViewRoot
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
viewId={viewId.toString()}
viewType={VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS}
baseRoute={`/${workspaceSlug?.toString()}/projects/${projectId}/views/private`}
workspaceViewTabOptions={workspaceViewTabOptions}
/>
</div>
</div>
);
return <div />;
};
ProjectPrivateViewPage.getLayout = function getLayout(page: ReactElement) {

View File

@ -7,7 +7,7 @@ import { GlobalViewRoot } from "components/view";
// types
import { NextPageWithLayout } from "lib/types";
// constants
import { VIEW_TYPES } from "constants/view";
import { EViewPageType, VIEW_TYPES } from "constants/view";
const ProjectPublicViewPage: NextPageWithLayout = () => {
const router = useRouter();
@ -38,6 +38,7 @@ const ProjectPublicViewPage: NextPageWithLayout = () => {
projectId={undefined}
viewId={viewId.toString()}
viewType={VIEW_TYPES.PROJECT_PUBLIC_VIEWS}
viewPageType={EViewPageType.PROJECT}
baseRoute={`/${workspaceSlug?.toString()}/views/public`}
workspaceViewTabOptions={workspaceViewTabOptions}
/>

View File

@ -7,7 +7,7 @@ import { GlobalViewRoot } from "components/view";
// types
import { NextPageWithLayout } from "lib/types";
// constants
import { VIEW_TYPES } from "constants/view";
import { EViewPageType, VIEW_TYPES } from "constants/view";
const WorkspacePrivateViewPage: NextPageWithLayout = () => {
const router = useRouter();
@ -38,6 +38,7 @@ const WorkspacePrivateViewPage: NextPageWithLayout = () => {
projectId={undefined}
viewId={viewId.toString()}
viewType={VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS}
viewPageType={EViewPageType.ALL}
baseRoute={`/${workspaceSlug?.toString()}/views/private`}
workspaceViewTabOptions={workspaceViewTabOptions}
/>

View File

@ -7,7 +7,7 @@ import { GlobalViewRoot } from "components/view";
// types
import { NextPageWithLayout } from "lib/types";
// constants
import { VIEW_TYPES } from "constants/view";
import { EViewPageType, VIEW_TYPES } from "constants/view";
const WorkspacePublicViewPage: NextPageWithLayout = () => {
const router = useRouter();
@ -38,6 +38,7 @@ const WorkspacePublicViewPage: NextPageWithLayout = () => {
projectId={undefined}
viewId={viewId.toString()}
viewType={VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS}
viewPageType={EViewPageType.ALL}
baseRoute={`/${workspaceSlug?.toString()}/views/public`}
workspaceViewTabOptions={workspaceViewTabOptions}
/>

View File

@ -8,6 +8,8 @@ import {
TViewFilterProps,
TViewFilterQueryParams,
} from "@plane/types";
// constants
import { EViewPageType, viewPageDefaultLayoutsByPageType } from "constants/view";
export class FiltersHelper {
// computed filters
@ -29,21 +31,26 @@ export class FiltersHelper {
// computed display filters
computedDisplayFilters = (
viewPageType: EViewPageType,
displayFilters: TViewDisplayFilters,
defaultValues?: Partial<TViewDisplayFilters>
): TViewDisplayFilters => ({
layout: defaultValues?.layout || displayFilters?.layout || "list",
group_by: defaultValues?.group_by || displayFilters?.group_by || undefined,
sub_group_by: defaultValues?.sub_group_by || displayFilters?.sub_group_by || undefined,
order_by: defaultValues?.order_by || displayFilters?.order_by || "sort_order",
type: defaultValues?.type || displayFilters?.type || undefined,
sub_issue: defaultValues?.sub_issue || displayFilters?.sub_issue || false,
show_empty_groups: defaultValues?.show_empty_groups || displayFilters?.show_empty_groups || false,
calendar: {
show_weekends: defaultValues?.calendar?.show_weekends || displayFilters?.calendar?.show_weekends || false,
layout: defaultValues?.calendar?.layout || displayFilters?.calendar?.layout || "month",
},
});
): TViewDisplayFilters => {
const viewPageDefaultLayout = viewPageDefaultLayoutsByPageType(viewPageType)?.[0] || "list";
return {
layout: defaultValues?.layout || displayFilters?.layout || viewPageDefaultLayout,
group_by: defaultValues?.group_by || displayFilters?.group_by || undefined,
sub_group_by: defaultValues?.sub_group_by || displayFilters?.sub_group_by || undefined,
order_by: defaultValues?.order_by || displayFilters?.order_by || "sort_order",
type: defaultValues?.type || displayFilters?.type || undefined,
sub_issue: defaultValues?.sub_issue || displayFilters?.sub_issue || false,
show_empty_groups: defaultValues?.show_empty_groups || displayFilters?.show_empty_groups || false,
calendar: {
show_weekends: defaultValues?.calendar?.show_weekends || displayFilters?.calendar?.show_weekends || false,
layout: defaultValues?.calendar?.layout || displayFilters?.calendar?.layout || "month",
},
};
};
// computed display properties
computedDisplayProperties = (

View File

@ -11,6 +11,8 @@ import {
} from "services/view";
// types
import { RootStore } from "store/root.store";
// constants
import { EViewPageType } from "constants/view";
export class GlobalViewRootStore {
workspacePrivateViewStore: ViewRootStore;
@ -59,25 +61,29 @@ export class GlobalViewRootStore {
this.store,
workspacePrivateDefaultViews,
new WorkspacePrivateViewService(),
new WorkspaceFiltersService()
new WorkspaceFiltersService(),
EViewPageType.ALL
);
this.workspacePublicViewStore = new ViewRootStore(
this.store,
workspacePublicDefaultViews,
new WorkspacePublicViewService(),
new WorkspaceFiltersService()
new WorkspaceFiltersService(),
EViewPageType.ALL
);
this.projectPrivateViewStore = new ViewRootStore(
this.store,
undefined,
new ProjectPrivateViewService(),
new ProjectFiltersService()
new ProjectFiltersService(),
EViewPageType.PROJECT
);
this.projectPublicViewStore = new ViewRootStore(
this.store,
undefined,
new ProjectPublicViewService(),
new ProjectFiltersService()
new ProjectFiltersService(),
EViewPageType.PROJECT
);
}
}

View File

@ -9,6 +9,8 @@ import { ViewStore } from "./view.store";
// types
import { TUserViewService, TViewService } from "services/view/types";
import { TView } from "@plane/types";
// constants
import { EViewPageType } from "constants/view";
export type TLoader = "init-loader" | "mutation-loader" | "submitting" | undefined;
@ -37,7 +39,8 @@ export class ViewRootStore implements TViewRootStore {
private store: RootStore,
private defaultViews: TView[] = [],
private service: TViewService,
private userService: TUserViewService
private userService: TUserViewService,
private viewPageType: EViewPageType
) {
makeObservable(this, {
// observables
@ -70,7 +73,12 @@ export class ViewRootStore implements TViewRootStore {
// actions
localViewCreate = async (view: TView) => {
runInAction(() => {
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service, this.userService));
if (view.id)
set(
this.viewMap,
[view.id],
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
);
});
};
@ -84,7 +92,12 @@ export class ViewRootStore implements TViewRootStore {
if (this.defaultViews && this.defaultViews.length > 0)
runInAction(() => {
this.defaultViews?.forEach((view) => {
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service, this.userService));
if (view.id)
set(
this.viewMap,
[view.id],
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
);
});
});
@ -93,7 +106,12 @@ export class ViewRootStore implements TViewRootStore {
runInAction(() => {
views.forEach((view) => {
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service, this.userService));
if (view.id)
set(
this.viewMap,
[view.id],
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
);
});
this.loader = undefined;
});
@ -105,9 +123,13 @@ export class ViewRootStore implements TViewRootStore {
const { workspaceSlug, projectId } = this.store.app.router;
if (!workspaceSlug || !viewId) return;
// fetching display properties and display_filters
const userView = await this.userService.fetch(workspaceSlug, projectId);
if (!userView) return;
// fetching kanban display filters from local
// fetching display filters from local and from the view
if (["all-issues", "assigned", "created", "subscribed"].includes(viewId)) {
const view = { ...this.viewById(viewId) };
if (!view) return;
@ -124,7 +146,12 @@ export class ViewRootStore implements TViewRootStore {
view?.display_properties && (view.display_properties = userView.display_properties);
runInAction(() => {
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service, this.userService));
if (view.id)
set(
this.viewMap,
[view.id],
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
);
});
}
} catch {}
@ -139,7 +166,12 @@ export class ViewRootStore implements TViewRootStore {
if (!view) return;
runInAction(() => {
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service, this.userService));
if (view.id)
set(
this.viewMap,
[view.id],
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
);
});
if (data.id) this.remove(data.id);
@ -169,7 +201,12 @@ export class ViewRootStore implements TViewRootStore {
if (!view) return;
runInAction(() => {
if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service, this.userService));
if (view.id)
set(
this.viewMap,
[view.id],
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
);
});
} catch {}
};

View File

@ -20,6 +20,8 @@ import {
} from "@plane/types";
// helpers
import { FiltersHelper } from "./helpers/filters_helpers";
// constants
import { EViewPageType, viewDefaultFilterParametersByViewTypeAndLayout } from "constants/view";
type TLoader = "updating" | undefined;
@ -79,7 +81,8 @@ export class ViewStore extends FiltersHelper implements TViewStore {
private store: RootStore,
_view: TView,
private service: TViewService,
private userService: TUserViewService
private userService: TUserViewService,
private viewPageType: EViewPageType
) {
super();
this.id = _view.id;
@ -89,7 +92,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
this.description = _view.description;
this.query = _view.query;
this.filters = this.computedFilters(_view.filters);
this.display_filters = this.computedDisplayFilters(_view.display_filters);
this.display_filters = this.computedDisplayFilters(this.viewPageType, _view.display_filters);
this.display_properties = this.computedDisplayProperties(_view.display_properties);
this.access = _view.access;
this.owned_by = _view.owned_by;
@ -108,7 +111,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
name: this.name,
description: this.description,
filters: this.computedFilters(_view.filters),
display_filters: this.computedDisplayFilters(_view.display_filters),
display_filters: this.computedDisplayFilters(this.viewPageType, _view.display_filters),
display_properties: this.computedDisplayProperties(_view.display_properties),
};
@ -164,7 +167,11 @@ export class ViewStore extends FiltersHelper implements TViewStore {
get appliedFilters() {
return {
filters: this.computedFilters(this.filters, this.filtersToUpdate.filters),
display_filters: this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters),
display_filters: this.computedDisplayFilters(
this.viewPageType,
this.display_filters,
this.filtersToUpdate.display_filters
),
display_properties: this.computedDisplayProperties(
this.display_properties,
this.filtersToUpdate.display_properties
@ -173,9 +180,17 @@ export class ViewStore extends FiltersHelper implements TViewStore {
}
get appliedFiltersQueryParams() {
const filters = this.appliedFilters;
if (!filters) return undefined;
return this.computeAppliedFiltersQueryParameters(filters, [])?.query || undefined;
const appliedFilters = this.appliedFilters;
if (!appliedFilters) return undefined;
const layout = appliedFilters?.display_filters?.layout;
const requiredFilterProperties = viewDefaultFilterParametersByViewTypeAndLayout(
this.viewPageType,
layout,
"filters"
);
return this.computeAppliedFiltersQueryParameters(appliedFilters, requiredFilterProperties)?.query || undefined;
}
get isFiltersApplied() {
@ -247,12 +262,18 @@ export class ViewStore extends FiltersHelper implements TViewStore {
set(this.filtersToUpdate, ["display_filters", _key], display_filters[_key]);
});
});
// update display properties globally
// updating display properties locally for kanban filters
};
setDisplayProperties = async (displayPropertyKey: keyof TViewDisplayProperties) => {
runInAction(() => {
update(this.filtersToUpdate, ["display_properties", displayPropertyKey], (_value: boolean = true) => !_value);
});
// update display properties globally
};
setIsEditable = (is_editable: boolean) => {