import { Inbox, X } from "lucide-react"; import { useMemo, useState } from "react"; import { createPortal } from "react-dom"; import type { Client } from "../../entities/client/types"; import { PUBLIC_POOL_CLIENT, isPublicPoolClientId } from "../../entities/public-pool/constants"; import type { MeResponse, ProfileOption } from "../../shared/api/mockApi"; import { initials } from "../../shared/lib/format"; import { NodeDcProfileMenu, NodeDcSelect, type NodeDcSelectOption } from "../../shared/nodedc-ui"; export type LauncherAdminMode = "admin" | "platform"; export type LauncherNotificationKind = "nodedc" | "operational-core" | "engine"; export type LauncherNotificationStatus = "unread" | "read" | "archived"; export interface LauncherNotificationItem { id: string; kind: LauncherNotificationKind; title: string; description: string; meta?: string; displayStatus?: string; status: LauncherNotificationStatus; createdAt: string; updatedAt?: string | null; actionKind?: "engine-role-request"; requestId?: string; requestedServiceRole?: "viewer" | "member" | null; } type EngineRoleActionValue = "choose" | "viewer" | "member"; const engineRoleActionOptions: Array> = [ { value: "choose", label: "Изменить роль" }, { value: "viewer", label: "Гость" }, { value: "member", label: "Участник" }, ]; export function TopBar({ me, clients, profileOptions, activeProfileId, activeClientId, adminOpen, adminMode, onProfileChange, onClientChange, onOpenAdmin, onOpenPlatform, onOpenShowcase, onOpenProfileSettings, onLogout, brandLinkUrl = "/", notifications = [], onMarkNotificationsRead, onResolveEngineRoleRequest, }: { me: MeResponse; clients: Client[]; profileOptions: ProfileOption[]; activeProfileId: string; activeClientId: string; adminOpen: boolean; adminMode: LauncherAdminMode; onProfileChange: (userId: string) => void; onClientChange: (clientId: string) => void; onOpenAdmin: () => void; onOpenPlatform: () => void; onOpenShowcase: () => void; onOpenProfileSettings: () => void; onLogout?: () => void; brandLinkUrl?: string; notifications?: LauncherNotificationItem[]; onMarkNotificationsRead?: () => void | Promise; onResolveEngineRoleRequest?: (requestId: string, action: "approve" | "reject", serviceRole?: "viewer" | "member", deliveryId?: string) => void | Promise; }) { const [notificationsOpen, setNotificationsOpen] = useState(false); const [notificationFilter, setNotificationFilter] = useState("all"); const [notificationsOnlyNew, setNotificationsOnlyNew] = useState(false); const [resolvingNotificationIds, setResolvingNotificationIds] = useState>({}); const availableClientIds = new Set(me.memberships.map((membership) => membership.clientId)); const clientsWithPublicPool = [ ...clients, availableClientIds.has(PUBLIC_POOL_CLIENT.id) && !clients.some((client) => isPublicPoolClientId(client.id)) ? PUBLIC_POOL_CLIENT : null, ].filter((client): client is Client => Boolean(client)); const availableClients = clientsWithPublicPool.filter((client) => availableClientIds.has(client.id)); const activeClient = availableClients.find((client) => client.id === activeClientId); const clientOptions = availableClients.map((client) => ({ value: client.id, label: client.name, description: client.legalName ?? undefined, })); const canOpenPlatform = me.launcherRole === "root_admin"; const showLauncherNavigation = me.permissions.canOpenAdmin || canOpenPlatform; const unreadCount = notifications.filter((notification) => notification.status === "unread").length; const visibleNotifications = useMemo( () => notifications.filter((notification) => { if (notificationsOnlyNew && notification.status !== "unread") return false; return notificationFilter === "all" || notification.kind === notificationFilter; }), [notificationFilter, notifications, notificationsOnlyNew] ); function markNotificationsSeen() { if (unreadCount <= 0) return; void Promise.resolve(onMarkNotificationsRead?.()); } function closeNotifications() { markNotificationsSeen(); setNotificationsOpen(false); } function resolveEngineRoleNotification(requestId: string, action: "approve" | "reject", serviceRole?: "viewer" | "member", deliveryId?: string) { if (!onResolveEngineRoleRequest || resolvingNotificationIds[requestId]) return; setResolvingNotificationIds((current) => ({ ...current, [requestId]: true })); Promise.resolve(onResolveEngineRoleRequest(requestId, action, serviceRole, deliveryId)) .finally(() => { setResolvingNotificationIds((current) => { const { [requestId]: _done, ...rest } = current; return rest; }); }); } const notificationsModal = notificationsOpen && typeof document !== "undefined" ? createPortal(
event.stopPropagation()}>

Уведомления

NODE DC
setNotificationFilter("all")}> Все setNotificationFilter("nodedc")}> NODE.DC setNotificationFilter("operational-core")}> Operational Core setNotificationFilter("engine")}> Engine
{visibleNotifications.length === 0 ? (
Уведомлений нет История заявок NODE.DC, Operational Core и Engine появится здесь.
) : ( visibleNotifications.map((notification) => { const unread = notification.status === "unread"; return (
{notificationKindLabel(notification.kind)}
{notification.title}
{notification.description}
{notification.displayStatus || notificationStatusLabel(notification.status)} {notification.meta ? {notification.meta} : null}
{notification.actionKind === "engine-role-request" && notification.requestId && notification.status === "unread" && onResolveEngineRoleRequest ? (
{(() => { const requestBusy = Boolean(resolvingNotificationIds[notification.requestId]); return ( <> className="nodedc-notification-card__role-select-wrap" triggerClassName="nodedc-notification-card__role-select" menuClassName="nodedc-notification-card__role-menu" value="choose" options={engineRoleActionOptions} label="Изменить роль Engine" minMenuWidth={166} disabled={requestBusy} onChange={(value) => { if (value === "viewer" || value === "member") { resolveEngineRoleNotification(notification.requestId!, "approve", value, notification.id); } }} /> ); })()}
) : null}
); }) )}
, document.body ) : null; return (
{showLauncherNavigation ? ( <> onClientChange(clientId)} trigger={({ open, toggle, setTriggerRef }) => ( )} /> ) : null}
(
{ if (event.key === "Enter" || event.key === " ") { event.preventDefault(); toggle(); } }} > Профиль
)} />
{notificationsModal}
); } function NotificationFilterButton({ active, children, onClick, }: { active: boolean; children: string; onClick: () => void; }) { return ( ); } function notificationKindLabel(kind: LauncherNotificationKind): string { const labels: Record = { nodedc: "NODE.DC", "operational-core": "Operational Core", engine: "NODE.DC Engine", }; return labels[kind]; } function notificationStatusLabel(status: LauncherNotificationStatus): string { const labels: Record = { unread: "Входящее", read: "Прочитано", archived: "В архиве", }; return labels[status]; }