From 3b0c75bee660bda66031794580a926d0fa4a0e93 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Tue, 28 Apr 2026 20:50:58 +0300 Subject: [PATCH] =?UTF-8?q?UI=20-=20=D0=9C=D0=95=D0=96=D0=9F=D0=A0=D0=9E?= =?UTF-8?q?=D0=95=D0=9A=D0=A2=D0=9D=D0=90=D0=AF=20=D0=9A=D0=9E=D0=9C=D0=9C?= =?UTF-8?q?=D0=A3=D0=9D=D0=98=D0=9A=D0=90=D0=A6=D0=98=D0=AF:=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BE=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9=20workspace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(projects)/project-shell-top-toolbar.tsx | 14 +-- .../web/app/(all)/[workspaceSlug]/layout.tsx | 2 + .../notifications-modal.tsx | 94 +++++++++++++++++++ .../notifications-modal.utils.ts | 50 ++++++++++ .../sidebar/sidebar-utility-rail.tsx | 10 +- .../workspace/sidebar/user-menu-item.tsx | 20 ++++ 6 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 plane-src/apps/web/core/components/workspace-notifications/notifications-modal.tsx create mode 100644 plane-src/apps/web/core/components/workspace-notifications/notifications-modal.utils.ts diff --git a/plane-src/apps/web/app/(all)/[workspaceSlug]/(projects)/project-shell-top-toolbar.tsx b/plane-src/apps/web/app/(all)/[workspaceSlug]/(projects)/project-shell-top-toolbar.tsx index 615afaf..8ed6465 100644 --- a/plane-src/apps/web/app/(all)/[workspaceSlug]/(projects)/project-shell-top-toolbar.tsx +++ b/plane-src/apps/web/app/(all)/[workspaceSlug]/(projects)/project-shell-top-toolbar.tsx @@ -36,6 +36,7 @@ import { import { SidebarProjectsListItem } from "@/components/workspace/sidebar/projects-list-item"; import { UserMenuRoot } from "@/components/workspace/sidebar/user-menu-root"; import { WorkspaceMenuRoot } from "@/components/workspace/sidebar/workspace-menu-root"; +import { openWorkspaceNotificationsModal } from "@/components/workspace-notifications/notifications-modal.utils"; import { getSidebarNavigationItemIcon } from "@/plane-web/components/workspace/sidebar/helper"; type TToolbarItem = { @@ -264,19 +265,20 @@ export const ProjectShellTopToolbar = observer(function ProjectShellTopToolbar() - openWorkspaceNotificationsModal()} > {totalNotifications > 0 && ( - - )} - + + )} + + diff --git a/plane-src/apps/web/core/components/workspace-notifications/notifications-modal.tsx b/plane-src/apps/web/core/components/workspace-notifications/notifications-modal.tsx new file mode 100644 index 0000000..58eee5c --- /dev/null +++ b/plane-src/apps/web/core/components/workspace-notifications/notifications-modal.tsx @@ -0,0 +1,94 @@ +"use client"; + +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import { useEffect, useState } from "react"; +import { observer } from "mobx-react"; +import { X } from "lucide-react"; +// plane imports +import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui"; +// hooks +import { useWorkspace } from "@/hooks/store/use-workspace"; +// local imports +import { NotificationsRoot } from "./root"; +import { NotificationsSidebarRoot } from "./sidebar"; +import { + closeWorkspaceNotificationsModal, + getWorkspaceNotificationsModalOpenFromSearch, + WORKSPACE_NOTIFICATIONS_MODAL_EVENT, +} from "./notifications-modal.utils"; + +const getInitialOpenState = () => { + if (typeof window === "undefined") return false; + + return getWorkspaceNotificationsModalOpenFromSearch(window.location.search); +}; + +export const WorkspaceNotificationsModal = observer(function WorkspaceNotificationsModal() { + const [isOpen, setIsOpen] = useState(getInitialOpenState); + const { currentWorkspace } = useWorkspace(); + + useEffect(() => { + const syncFromLocation = () => { + setIsOpen(getWorkspaceNotificationsModalOpenFromSearch(window.location.search)); + }; + + const handleModalEvent = (event: Event) => { + const detail = (event as CustomEvent<{ isOpen: boolean }>).detail; + + setIsOpen(detail.isOpen); + }; + + window.addEventListener(WORKSPACE_NOTIFICATIONS_MODAL_EVENT, handleModalEvent); + window.addEventListener("popstate", syncFromLocation); + + syncFromLocation(); + + return () => { + window.removeEventListener(WORKSPACE_NOTIFICATIONS_MODAL_EVENT, handleModalEvent); + window.removeEventListener("popstate", syncFromLocation); + }; + }, []); + + const handleClose = () => closeWorkspaceNotificationsModal(); + + return ( + +
+
+
+
Уведомления
+
{currentWorkspace?.name ?? "Workspace"}
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ ); +}); diff --git a/plane-src/apps/web/core/components/workspace-notifications/notifications-modal.utils.ts b/plane-src/apps/web/core/components/workspace-notifications/notifications-modal.utils.ts new file mode 100644 index 0000000..9723af2 --- /dev/null +++ b/plane-src/apps/web/core/components/workspace-notifications/notifications-modal.utils.ts @@ -0,0 +1,50 @@ +export const WORKSPACE_NOTIFICATIONS_MODAL_QUERY_KEY = "workspaceNotifications"; +export const WORKSPACE_NOTIFICATIONS_MODAL_EVENT = "nodedc:workspace-notifications-modal"; + +type TWorkspaceNotificationsModalEventDetail = { + isOpen: boolean; +}; + +const dispatchWorkspaceNotificationsModalEvent = (detail: TWorkspaceNotificationsModalEventDetail) => { + window.dispatchEvent( + new CustomEvent(WORKSPACE_NOTIFICATIONS_MODAL_EVENT, { detail }) + ); +}; + +export const getWorkspaceNotificationsModalOpenFromSearch = (search: string): boolean => { + const value = new URLSearchParams(search).get(WORKSPACE_NOTIFICATIONS_MODAL_QUERY_KEY); + + return value === "open"; +}; + +export const setWorkspaceNotificationsModalSearch = (replace = false) => { + if (typeof window === "undefined") return; + + const url = new URL(window.location.href); + url.searchParams.set(WORKSPACE_NOTIFICATIONS_MODAL_QUERY_KEY, "open"); + + window.history[replace ? "replaceState" : "pushState"](window.history.state, "", url); +}; + +export const clearWorkspaceNotificationsModalSearch = () => { + if (typeof window === "undefined") return; + + const url = new URL(window.location.href); + url.searchParams.delete(WORKSPACE_NOTIFICATIONS_MODAL_QUERY_KEY); + + window.history.replaceState(window.history.state, "", url); +}; + +export const openWorkspaceNotificationsModal = (replace = false) => { + if (typeof window === "undefined") return; + + setWorkspaceNotificationsModalSearch(replace); + dispatchWorkspaceNotificationsModalEvent({ isOpen: true }); +}; + +export const closeWorkspaceNotificationsModal = () => { + if (typeof window === "undefined") return; + + clearWorkspaceNotificationsModalSearch(); + dispatchWorkspaceNotificationsModalEvent({ isOpen: false }); +}; diff --git a/plane-src/apps/web/core/components/workspace/sidebar/sidebar-utility-rail.tsx b/plane-src/apps/web/core/components/workspace/sidebar/sidebar-utility-rail.tsx index d528a08..3598a5f 100644 --- a/plane-src/apps/web/core/components/workspace/sidebar/sidebar-utility-rail.tsx +++ b/plane-src/apps/web/core/components/workspace/sidebar/sidebar-utility-rail.tsx @@ -4,7 +4,6 @@ * See the LICENSE file for details. */ -import Link from "next/link"; import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; @@ -17,6 +16,7 @@ import { useCommandPalette } from "@/hooks/store/use-command-palette"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; import { UserMenuRoot } from "@/components/workspace/sidebar/user-menu-root"; +import { openWorkspaceNotificationsModal } from "@/components/workspace-notifications/notifications-modal.utils"; import { useWorkspaceNotifications } from "@/hooks/store/notifications"; export const SidebarUtilityRail = observer(function SidebarUtilityRail() { @@ -63,16 +63,18 @@ export const SidebarUtilityRail = observer(function SidebarUtilityRail() { )} - openWorkspaceNotificationsModal()} + aria-label="Уведомления" > {totalNotifications > 0 && ( )} Уведомления - +
diff --git a/plane-src/apps/web/core/components/workspace/sidebar/user-menu-item.tsx b/plane-src/apps/web/core/components/workspace/sidebar/user-menu-item.tsx index 02aa128..5cc4e6d 100644 --- a/plane-src/apps/web/core/components/workspace/sidebar/user-menu-item.tsx +++ b/plane-src/apps/web/core/components/workspace/sidebar/user-menu-item.tsx @@ -14,6 +14,7 @@ import type { EUserWorkspaceRoles } from "@plane/types"; // components import { SidebarNavItem } from "@/components/sidebar/sidebar-navigation"; import { NotificationAppSidebarOption } from "@/components/workspace-notifications/notification-app-sidebar-option"; +import { openWorkspaceNotificationsModal } from "@/components/workspace-notifications/notifications-modal.utils"; // hooks import { useAppTheme } from "@/hooks/store/use-app-theme"; import { useUserPermissions } from "@/hooks/store/user"; @@ -54,6 +55,25 @@ export const SidebarUserMenuItem = observer(function SidebarUserMenuItem(props: } }; + const handleNotificationsClick = () => { + handleLinkClick(); + openWorkspaceNotificationsModal(); + }; + + if (item.key === "notifications") { + return ( + + ); + } + return (