mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: updated empty states
This commit is contained in:
parent
c35d650de0
commit
430d9de722
@ -54,7 +54,7 @@ const navigation = (workspaceSlug: string, projectId: string) => [
|
||||
},
|
||||
{
|
||||
name: "Views",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/views`,
|
||||
href: `/${workspaceSlug}/projects/${projectId}/views/public`,
|
||||
Icon: PhotoFilterIcon,
|
||||
},
|
||||
{
|
||||
@ -147,7 +147,8 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div
|
||||
className={`group relative flex w-full items-center rounded-md px-2 py-1 text-custom-sidebar-text-10 hover:bg-custom-sidebar-background-80 ${snapshot?.isDragging ? "opacity-60" : ""
|
||||
className={`group relative flex w-full items-center rounded-md px-2 py-1 text-custom-sidebar-text-10 hover:bg-custom-sidebar-background-80 ${
|
||||
snapshot?.isDragging ? "opacity-60" : ""
|
||||
} ${isMenuActive ? "!bg-custom-sidebar-background-80" : ""}`}
|
||||
>
|
||||
{provided && (
|
||||
@ -157,8 +158,10 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`absolute -left-2.5 top-1/2 hidden -translate-y-1/2 rounded p-0.5 text-custom-sidebar-text-400 ${isCollapsed ? "" : "group-hover:!flex"
|
||||
} ${project.sort_order === null ? "cursor-not-allowed opacity-60" : ""} ${isMenuActive ? "!flex" : ""
|
||||
className={`absolute -left-2.5 top-1/2 hidden -translate-y-1/2 rounded p-0.5 text-custom-sidebar-text-400 ${
|
||||
isCollapsed ? "" : "group-hover:!flex"
|
||||
} ${project.sort_order === null ? "cursor-not-allowed opacity-60" : ""} ${
|
||||
isMenuActive ? "!flex" : ""
|
||||
}`}
|
||||
{...provided?.dragHandleProps}
|
||||
>
|
||||
@ -170,11 +173,13 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
<Tooltip tooltipContent={`${project.name}`} position="right" className="ml-2" disabled={!isCollapsed}>
|
||||
<Disclosure.Button
|
||||
as="div"
|
||||
className={`flex flex-grow cursor-pointer select-none items-center truncate text-left text-sm font-medium ${isCollapsed ? "justify-center" : `justify-between`
|
||||
className={`flex flex-grow cursor-pointer select-none items-center truncate text-left text-sm font-medium ${
|
||||
isCollapsed ? "justify-center" : `justify-between`
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex w-full flex-grow items-center gap-x-2 truncate ${isCollapsed ? "justify-center" : ""
|
||||
className={`flex w-full flex-grow items-center gap-x-2 truncate ${
|
||||
isCollapsed ? "justify-center" : ""
|
||||
}`}
|
||||
>
|
||||
{project.emoji ? (
|
||||
@ -195,7 +200,8 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<ChevronDown
|
||||
className={`hidden h-4 w-4 flex-shrink-0 ${open ? "rotate-180" : ""} ${isMenuActive ? "!block" : ""
|
||||
className={`hidden h-4 w-4 flex-shrink-0 ${open ? "rotate-180" : ""} ${
|
||||
isMenuActive ? "!block" : ""
|
||||
} mb-0.5 text-custom-sidebar-text-400 duration-300 group-hover:!block`}
|
||||
/>
|
||||
)}
|
||||
@ -320,7 +326,8 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
disabled={!isCollapsed}
|
||||
>
|
||||
<div
|
||||
className={`group flex items-center gap-2.5 rounded-md px-2 py-1.5 text-xs font-medium outline-none ${router.asPath.includes(item.href)
|
||||
className={`group flex items-center gap-2.5 rounded-md px-2 py-1.5 text-xs font-medium outline-none ${
|
||||
router.asPath.includes(item.href)
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||
} ${isCollapsed ? "justify-center" : ""}`}
|
||||
|
@ -47,8 +47,8 @@ export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = observer((pro
|
||||
}, [viewDetailStore, viewPageType]);
|
||||
|
||||
return (
|
||||
<div className="space-y-1 divide-y divide-custom-border-300">
|
||||
<div className="relative py-1 first:pt-0">
|
||||
<div className="space-y-1 divide-y divide-custom-border-300 [&>div]:first:pt-0 [&>div]:last:pb-0">
|
||||
<div className="relative py-1">
|
||||
<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
|
||||
@ -95,8 +95,8 @@ export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = observer((pro
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="pt-1 pb-0">
|
||||
{filtersExtraProperties.map((option) => (
|
||||
<div className="py-1">
|
||||
<DisplayFilterExtraOptions
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@ -104,8 +104,8 @@ export const ViewDisplayFiltersRoot: FC<TViewDisplayFiltersRoot> = observer((pro
|
||||
viewType={viewType}
|
||||
filterKey={option as TViewDisplayFiltersExtraOptions}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ import { Briefcase, CheckCircle, ChevronRight } from "lucide-react";
|
||||
import { useProject } from "hooks/store";
|
||||
// types
|
||||
import { TViewTypes } from "@plane/types";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
type TViewHeader = {
|
||||
projectId: string | undefined;
|
||||
@ -29,8 +30,14 @@ export const ViewHeader: FC<TViewHeader> = (props) => {
|
||||
{projectDetails && (
|
||||
<Fragment>
|
||||
<div className="relative flex items-center gap-2 overflow-hidden">
|
||||
<div className="flex-shrink-0 w-6 h-6 rounded relative flex justify-center items-center bg-custom-background-80">
|
||||
{projectDetails?.icon_prop ? projectDetails?.icon_prop.toString() : <Briefcase size={12} />}
|
||||
<div className="flex-shrink-0 w-6 h-6 rounded relative flex justify-center items-center bg-custom-background-80 text-sm">
|
||||
{projectDetails?.emoji ? (
|
||||
renderEmoji(projectDetails?.emoji)
|
||||
) : projectDetails?.icon_prop ? (
|
||||
renderEmoji(projectDetails?.icon_prop)
|
||||
) : (
|
||||
<Briefcase size={12} />
|
||||
)}
|
||||
</div>
|
||||
<div className="font-medium inline-block whitespace-nowrap overflow-hidden truncate line-clamp-1 text-sm">
|
||||
{projectDetails?.name ? projectDetails?.name : "Project Issues"}
|
||||
|
@ -10,6 +10,37 @@ import {
|
||||
TViewDisplayFiltersExtraOptions,
|
||||
} from "@plane/types";
|
||||
|
||||
// global variables
|
||||
export enum EViewPageType {
|
||||
ALL = "all",
|
||||
PROFILE = "profile",
|
||||
PROJECT = "project",
|
||||
ARCHIVED = "archived",
|
||||
DRAFT = "draft",
|
||||
}
|
||||
|
||||
export enum ELocalViews {
|
||||
ALL_ISSUES = "all-issues",
|
||||
ASSIGNED = "assigned",
|
||||
CREATED = "created",
|
||||
SUBSCRIBED = "subscribed",
|
||||
}
|
||||
|
||||
export enum EFilterTypes {
|
||||
FILTERS = "filters",
|
||||
DISPLAY_FILTERS = "display_filters",
|
||||
DISPLAY_PROPERTIES = "display_properties",
|
||||
KANBAN_FILTERS = "kanban_filters",
|
||||
}
|
||||
|
||||
export enum EViewLayouts {
|
||||
LIST = "list",
|
||||
KANBAN = "kanban",
|
||||
CALENDAR = "calendar",
|
||||
SPREADSHEET = "spreadsheet",
|
||||
GANTT = "gantt",
|
||||
}
|
||||
|
||||
// filters constants
|
||||
export const STATE_GROUP_PROPERTY: Record<TStateGroups, { label: string; color: string }> = {
|
||||
backlog: { label: "Backlog", color: "#d9d9d9" },
|
||||
@ -67,26 +98,10 @@ export const EXTRA_OPTIONS_PROPERTY: Record<TViewDisplayFiltersExtraOptions, { l
|
||||
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>[];
|
||||
display_filters: Partial<keyof TViewDisplayFilters>[];
|
||||
extra_options: ("sub_issue" | "show_empty_groups")[];
|
||||
extra_options: TViewDisplayFiltersExtraOptions[];
|
||||
display_properties: boolean;
|
||||
readonlyFilters?: Partial<keyof TViewFilters>[];
|
||||
};
|
||||
@ -112,10 +127,8 @@ const ALL_FILTER_PERMISSIONS: TFilterPermissions["all"] = {
|
||||
layouts: [EViewLayouts.SPREADSHEET],
|
||||
[EViewLayouts.SPREADSHEET]: {
|
||||
filters: ["project", "priority", "state_group", "assignees", "created_by", "labels", "start_date", "target_date"],
|
||||
// display_filters: ["type"],
|
||||
// extra_options: [],
|
||||
display_filters: ["group_by", "sub_group_by", "order_by", "type"],
|
||||
extra_options: ["sub_issue", "show_empty_groups"],
|
||||
display_filters: ["type"],
|
||||
extra_options: [],
|
||||
display_properties: true,
|
||||
},
|
||||
};
|
||||
|
@ -40,7 +40,6 @@ const ProjectPrivateViewPage: NextPageWithLayout = () => {
|
||||
viewType={VIEW_TYPES.PROJECT_PRIVATE_VIEWS}
|
||||
viewPageType={EViewPageType.PROJECT}
|
||||
baseRoute={`/${workspaceSlug?.toString()}/projects/${projectId}/views/private`}
|
||||
workspaceViewTabOptions={workspaceViewTabOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,20 +1,114 @@
|
||||
import { ReactElement } from "react";
|
||||
import { Fragment, ReactElement, useEffect, useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { useTheme } from "next-themes";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
// hooks
|
||||
import { useUser, useView } from "hooks/store";
|
||||
// layouts
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
||||
import { ViewHeader } from "components/view";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// types
|
||||
import { NextPageWithLayout } from "lib/types";
|
||||
// constants
|
||||
import { VIEW_TYPES } from "constants/view";
|
||||
import { VIEW_EMPTY_STATE_DETAILS } from "constants/empty-state";
|
||||
|
||||
const ProjectPrivateViewPage: NextPageWithLayout = () => {
|
||||
const ProjectPublicViewPage: NextPageWithLayout = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, viewId } = router.query;
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug?.toString(), projectId?.toString(), VIEW_TYPES.PROJECT_PUBLIC_VIEWS);
|
||||
const { currentUser } = useUser();
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
if (!workspaceSlug || !projectId || !viewId) return <></>;
|
||||
return <div />;
|
||||
};
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "views", isLightMode);
|
||||
|
||||
ProjectPrivateViewPage.getLayout = function getLayout(page: ReactElement) {
|
||||
useSWR(
|
||||
workspaceSlug && projectId
|
||||
? `PROJECT_VIEWS_${VIEW_TYPES.PROJECT_PUBLIC_VIEWS}_${workspaceSlug.toString()}_${projectId.toString()}`
|
||||
: null,
|
||||
async () => {
|
||||
await viewStore?.fetch();
|
||||
console.log("viewStore", viewStore?.viewIds);
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceSlug && projectId && viewStore?.viewIds && viewStore?.viewIds.length > 0) {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/views/public/${viewStore?.viewIds[0]}`);
|
||||
}
|
||||
}, [workspaceSlug, projectId, viewStore?.viewIds, router]);
|
||||
|
||||
const workspaceViewTabOptions = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: VIEW_TYPES.PROJECT_PRIVATE_VIEWS,
|
||||
title: "Private",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/views/private`,
|
||||
},
|
||||
{
|
||||
key: VIEW_TYPES.PROJECT_PUBLIC_VIEWS,
|
||||
title: "Public",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/views/public`,
|
||||
},
|
||||
],
|
||||
[workspaceSlug, projectId]
|
||||
);
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
return (
|
||||
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||
{viewStore?.loader === "init-loader" ? (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{viewStore?.viewIds && viewStore?.viewIds?.length <= 0 && (
|
||||
<Fragment>
|
||||
<div className="flex-shrink-0 px-5 pt-4 pb-4 border-b border-custom-border-200">
|
||||
<ViewHeader
|
||||
projectId={projectId.toString()}
|
||||
viewType={VIEW_TYPES.PROJECT_PRIVATE_VIEWS}
|
||||
titleIcon={<CheckCircle size={12} />}
|
||||
title="Views"
|
||||
workspaceViewTabOptions={workspaceViewTabOptions}
|
||||
/>
|
||||
</div>
|
||||
<div className="relative w-full h-full flex justify-center items-center overflow-hidden overflow-y-auto">
|
||||
<EmptyState
|
||||
title={VIEW_EMPTY_STATE_DETAILS["project-views"].title}
|
||||
description={VIEW_EMPTY_STATE_DETAILS["project-views"].description}
|
||||
image={EmptyStateImagePath}
|
||||
comicBox={{
|
||||
title: VIEW_EMPTY_STATE_DETAILS["project-views"].comicBox.title,
|
||||
description: VIEW_EMPTY_STATE_DETAILS["project-views"].comicBox.description,
|
||||
}}
|
||||
primaryButton={{
|
||||
text: VIEW_EMPTY_STATE_DETAILS["project-views"].primaryButton.text,
|
||||
onClick: () => {},
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
ProjectPublicViewPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <AppLayout header={<></>}>{page}</AppLayout>;
|
||||
};
|
||||
|
||||
export default ProjectPrivateViewPage;
|
||||
export default ProjectPublicViewPage;
|
||||
|
@ -40,7 +40,6 @@ const ProjectPublicViewPage: NextPageWithLayout = () => {
|
||||
viewType={VIEW_TYPES.PROJECT_PUBLIC_VIEWS}
|
||||
viewPageType={EViewPageType.PROJECT}
|
||||
baseRoute={`/${workspaceSlug?.toString()}/views/public`}
|
||||
workspaceViewTabOptions={workspaceViewTabOptions}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full h-full overflow-hidden">Issues render</div>
|
||||
|
@ -0,0 +1,114 @@
|
||||
import { Fragment, ReactElement, useEffect, useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { useTheme } from "next-themes";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
// hooks
|
||||
import { useUser, useView } from "hooks/store";
|
||||
// layouts
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
|
||||
import { ViewHeader } from "components/view";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// types
|
||||
import { NextPageWithLayout } from "lib/types";
|
||||
// constants
|
||||
import { VIEW_TYPES } from "constants/view";
|
||||
import { VIEW_EMPTY_STATE_DETAILS } from "constants/empty-state";
|
||||
|
||||
const ProjectPublicViewPage: NextPageWithLayout = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// hooks
|
||||
const viewStore = useView(workspaceSlug?.toString(), projectId?.toString(), VIEW_TYPES.PROJECT_PUBLIC_VIEWS);
|
||||
const { currentUser } = useUser();
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
|
||||
const EmptyStateImagePath = getEmptyStateImagePath("onboarding", "views", isLightMode);
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId
|
||||
? `PROJECT_VIEWS_${VIEW_TYPES.PROJECT_PUBLIC_VIEWS}_${workspaceSlug.toString()}_${projectId.toString()}`
|
||||
: null,
|
||||
async () => {
|
||||
await viewStore?.fetch();
|
||||
console.log("viewStore", viewStore?.viewIds);
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceSlug && projectId && viewStore?.viewIds && viewStore?.viewIds.length > 0) {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/views/public/${viewStore?.viewIds[0]}`);
|
||||
}
|
||||
}, [workspaceSlug, projectId, viewStore?.viewIds, router]);
|
||||
|
||||
const workspaceViewTabOptions = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: VIEW_TYPES.PROJECT_PRIVATE_VIEWS,
|
||||
title: "Private",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/views/private`,
|
||||
},
|
||||
{
|
||||
key: VIEW_TYPES.PROJECT_PUBLIC_VIEWS,
|
||||
title: "Public",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/views/public`,
|
||||
},
|
||||
],
|
||||
[workspaceSlug, projectId]
|
||||
);
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
return (
|
||||
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||
{viewStore?.loader === "init-loader" ? (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{viewStore?.viewIds && viewStore?.viewIds?.length <= 0 && (
|
||||
<Fragment>
|
||||
<div className="flex-shrink-0 px-5 pt-4 pb-4 border-b border-custom-border-200">
|
||||
<ViewHeader
|
||||
projectId={projectId.toString()}
|
||||
viewType={VIEW_TYPES.PROJECT_PUBLIC_VIEWS}
|
||||
titleIcon={<CheckCircle size={12} />}
|
||||
title="Views"
|
||||
workspaceViewTabOptions={workspaceViewTabOptions}
|
||||
/>
|
||||
</div>
|
||||
<div className="relative w-full h-full flex justify-center items-center overflow-hidden overflow-y-auto">
|
||||
<EmptyState
|
||||
title={VIEW_EMPTY_STATE_DETAILS["project-views"].title}
|
||||
description={VIEW_EMPTY_STATE_DETAILS["project-views"].description}
|
||||
image={EmptyStateImagePath}
|
||||
comicBox={{
|
||||
title: VIEW_EMPTY_STATE_DETAILS["project-views"].comicBox.title,
|
||||
description: VIEW_EMPTY_STATE_DETAILS["project-views"].comicBox.description,
|
||||
}}
|
||||
primaryButton={{
|
||||
text: VIEW_EMPTY_STATE_DETAILS["project-views"].primaryButton.text,
|
||||
onClick: () => {},
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
ProjectPublicViewPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <AppLayout header={<></>}>{page}</AppLayout>;
|
||||
};
|
||||
|
||||
export default ProjectPublicViewPage;
|
@ -8,7 +8,7 @@ import { GlobalViewRoot, ViewHeader } from "components/view";
|
||||
// types
|
||||
import { NextPageWithLayout } from "lib/types";
|
||||
// constants
|
||||
import { EViewPageType, VIEW_TYPES } from "constants/view";
|
||||
import { ELocalViews, EViewPageType, VIEW_TYPES } from "constants/view";
|
||||
|
||||
const WorkspacePublicViewPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
@ -19,12 +19,12 @@ const WorkspacePublicViewPage: NextPageWithLayout = () => {
|
||||
{
|
||||
key: VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS,
|
||||
title: "Private",
|
||||
href: `/${workspaceSlug}/views/private/assigned`,
|
||||
href: `/${workspaceSlug}/views/private/${ELocalViews.ASSIGNED}`,
|
||||
},
|
||||
{
|
||||
key: VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS,
|
||||
title: "Public",
|
||||
href: `/${workspaceSlug}/views/public/all-issues`,
|
||||
href: `/${workspaceSlug}/views/public/${ELocalViews.ALL_ISSUES}`,
|
||||
},
|
||||
],
|
||||
[workspaceSlug]
|
||||
|
7
web/services/view/types.d.ts
vendored
7
web/services/view/types.d.ts
vendored
@ -3,12 +3,7 @@ import { TView, TUserView } from "@plane/types";
|
||||
export type TUserViewService = {
|
||||
// featureId represents moduleId/cycleId
|
||||
fetch: (workspaceSlug: string, projectId?: string, featureId?: string) => Promise<TUserView | undefined>;
|
||||
update: (
|
||||
workspaceSlug: string,
|
||||
data: Partial<TView>,
|
||||
projectId?: string,
|
||||
featureId?: string
|
||||
) => Promise<TUserView | undefined>;
|
||||
update: (workspaceSlug: string, data: any, projectId?: string, featureId?: string) => Promise<TUserView | undefined>;
|
||||
};
|
||||
|
||||
export type TViewService = {
|
||||
|
@ -123,37 +123,37 @@ 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
|
||||
let view: TView | undefined = undefined;
|
||||
|
||||
// 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;
|
||||
const currentView = { ...this.viewById(viewId) };
|
||||
if (!currentView) return;
|
||||
|
||||
runInAction(() => {
|
||||
view = currentView;
|
||||
view.filters = userView.filters;
|
||||
view.display_filters = userView.display_filters;
|
||||
view.display_properties = userView.display_properties;
|
||||
});
|
||||
} else {
|
||||
const view = await this.service.fetchById(workspaceSlug, viewId, projectId);
|
||||
if (!view) return;
|
||||
const currentView = await this.service.fetchById(workspaceSlug, viewId, projectId);
|
||||
if (!currentView) return;
|
||||
|
||||
view = currentView;
|
||||
view?.display_filters && (view.display_filters = userView.display_filters);
|
||||
view?.display_properties && (view.display_properties = userView.display_properties);
|
||||
}
|
||||
|
||||
if (!view) return;
|
||||
runInAction(() => {
|
||||
if (view.id)
|
||||
if (view?.id)
|
||||
set(
|
||||
this.viewMap,
|
||||
[view.id],
|
||||
new ViewStore(this.store, view, this.service, this.userService, this.viewPageType)
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,12 @@ import {
|
||||
// helpers
|
||||
import { FiltersHelper } from "./helpers/filters_helpers";
|
||||
// constants
|
||||
import { EViewLayouts, EViewPageType, viewDefaultFilterParametersByViewTypeAndLayout } from "constants/view";
|
||||
import {
|
||||
EViewLayouts,
|
||||
EViewPageType,
|
||||
EFilterTypes,
|
||||
viewDefaultFilterParametersByViewTypeAndLayout,
|
||||
} from "constants/view";
|
||||
|
||||
type TLoader = "updating" | undefined;
|
||||
|
||||
@ -160,6 +165,9 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
unlockView: action,
|
||||
makeFavorite: action,
|
||||
removeFavorite: action,
|
||||
updateUserFilters: action,
|
||||
updateUserDisplayFilters: action,
|
||||
updateUserDisplayProperties: action,
|
||||
});
|
||||
}
|
||||
|
||||
@ -187,7 +195,7 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
const requiredFilterProperties = viewDefaultFilterParametersByViewTypeAndLayout(
|
||||
this.viewPageType,
|
||||
layout,
|
||||
"filters"
|
||||
EFilterTypes.FILTERS
|
||||
);
|
||||
|
||||
return this.computeAppliedFiltersQueryParameters(appliedFilters, requiredFilterProperties)?.query || undefined;
|
||||
@ -231,9 +239,9 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
setFilters = (filterKey: keyof TViewFilters | undefined = undefined, filterValue: "clear_all" | string) => {
|
||||
runInAction(() => {
|
||||
if (filterKey === undefined) {
|
||||
if (filterValue === "clear_all") set(this.filtersToUpdate, ["filters"], {});
|
||||
if (filterValue === "clear_all") set(this.filtersToUpdate, [EFilterTypes.FILTERS], {});
|
||||
} else
|
||||
update(this.filtersToUpdate, ["filters", filterKey], (_values = []) => {
|
||||
update(this.filtersToUpdate, [EFilterTypes.FILTERS, filterKey], (_values = []) => {
|
||||
if (filterValue === "clear_all") return [];
|
||||
if (_values.includes(filterValue)) return pull(_values, filterValue);
|
||||
return concat(_values, filterValue);
|
||||
@ -259,21 +267,25 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
runInAction(() => {
|
||||
Object.keys(display_filters).forEach((key) => {
|
||||
const _key = key as keyof TViewDisplayFilters;
|
||||
set(this.filtersToUpdate, ["display_filters", _key], display_filters[_key]);
|
||||
set(this.filtersToUpdate, [EFilterTypes.DISPLAY_FILTERS, _key], display_filters[_key]);
|
||||
});
|
||||
});
|
||||
|
||||
// update display properties globally
|
||||
|
||||
// updating display properties locally for kanban filters
|
||||
// update display filters globally
|
||||
this.updateUserDisplayFilters({ [EFilterTypes.DISPLAY_FILTERS]: this.filtersToUpdate.display_filters });
|
||||
};
|
||||
|
||||
setDisplayProperties = async (displayPropertyKey: keyof TViewDisplayProperties) => {
|
||||
runInAction(() => {
|
||||
update(this.filtersToUpdate, ["display_properties", displayPropertyKey], (_value: boolean = true) => !_value);
|
||||
update(
|
||||
this.filtersToUpdate,
|
||||
[EFilterTypes.DISPLAY_PROPERTIES, displayPropertyKey],
|
||||
(_value: boolean = true) => !_value
|
||||
);
|
||||
});
|
||||
|
||||
// update display properties globally
|
||||
this.updateUserDisplayProperties({ [EFilterTypes.DISPLAY_PROPERTIES]: this.filtersToUpdate.display_properties });
|
||||
};
|
||||
|
||||
setIsEditable = (is_editable: boolean) => {
|
||||
@ -297,7 +309,12 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
|
||||
saveChanges = async () => {
|
||||
try {
|
||||
if (this.filtersToUpdate) await this.update(this.filtersToUpdate);
|
||||
if (!this.id) return;
|
||||
|
||||
if (["all-issues", "assigned", "created", "subscribed"].includes(this.id)) {
|
||||
const payload = this.filtersToUpdate.filters;
|
||||
await this.updateUserFilters({ [EFilterTypes.FILTERS]: payload });
|
||||
} else await this.update(this.filtersToUpdate);
|
||||
} catch {
|
||||
Object.keys(this.filtersToUpdate).forEach((key) => {
|
||||
const _key = key as keyof TUpdateView;
|
||||
@ -394,4 +411,61 @@ export class ViewStore extends FiltersHelper implements TViewStore {
|
||||
this.is_favorite = this.is_favorite;
|
||||
}
|
||||
};
|
||||
|
||||
// updating the user specific filters, display filters, and display properties
|
||||
updateUserFilters = async (filters: { [EFilterTypes.FILTERS]: TViewFilters }) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const userView = await this.userService.update(workspaceSlug, filters, projectId);
|
||||
if (!userView) return;
|
||||
|
||||
runInAction(() => {
|
||||
this.filters = userView.filters;
|
||||
});
|
||||
} catch {
|
||||
runInAction(() => {
|
||||
this.filters = this.filters;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateUserDisplayFilters = async (display_filters: { [EFilterTypes.DISPLAY_FILTERS]: TViewDisplayFilters }) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const userView = await this.userService.update(workspaceSlug, display_filters, projectId);
|
||||
if (!userView) return;
|
||||
|
||||
runInAction(() => {
|
||||
this.display_filters = userView.display_filters;
|
||||
});
|
||||
} catch {
|
||||
runInAction(() => {
|
||||
this.display_filters = this.display_filters;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateUserDisplayProperties = async (display_properties: {
|
||||
[EFilterTypes.DISPLAY_PROPERTIES]: TViewDisplayProperties;
|
||||
}) => {
|
||||
try {
|
||||
const { workspaceSlug, projectId } = this.store.app.router;
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const userView = await this.userService.update(workspaceSlug, display_properties, projectId);
|
||||
if (!userView) return;
|
||||
|
||||
runInAction(() => {
|
||||
this.display_properties = userView.display_properties;
|
||||
});
|
||||
} catch {
|
||||
runInAction(() => {
|
||||
this.display_properties = this.display_properties;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user