feat: global platform update

This commit is contained in:
ivannoskov
2026-01-07 14:17:43 +03:00
parent 25b194ad41
commit 3b6b97447b
22 changed files with 1536 additions and 309 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -26,9 +26,16 @@ import {
import { usePathname } from "next/navigation";
import Link from "next/link";
import * as React from "react";
import { cn } from "@/lib/utils";
export type NavGuideState = {
highlightedItem?: string;
expandedItems?: string[];
};
export function NavMain({
items,
guideState,
}: {
items: {
title: string;
@@ -40,10 +47,12 @@ export function NavMain({
url: string;
}[];
}[];
guideState?: NavGuideState;
}) {
const pathname = usePathname();
const { state } = useSidebar();
const isCollapsed = state === "collapsed";
const highlightedItem = guideState?.highlightedItem;
return (
<SidebarGroup>
@@ -53,6 +62,11 @@ export function NavMain({
const isActive = pathname === item.url;
const hasActiveSubmenu =
hasSubmenu && item.items!.some((sub) => pathname === sub.url);
const shouldForceOpen = guideState?.expandedItems?.includes(item.title);
const isGuideHighlight = highlightedItem === item.title;
const highlightedButtonClass = isGuideHighlight
? "ring-2 ring-primary/70"
: undefined;
// В свернутом состоянии показываем DropdownMenu для элементов с подменю
if (isCollapsed && hasSubmenu) {
@@ -63,6 +77,7 @@ export function NavMain({
<SidebarMenuButton
tooltip={item.title}
isActive={isActive || hasActiveSubmenu}
className={cn(highlightedButtonClass)}
>
{item.icon && <item.icon />}
<span>{item.title}</span>
@@ -99,9 +114,11 @@ export function NavMain({
// Обычное отображение для развернутого состояния
return (
<Collapsible
key={item.title}
key={`${item.title}-${shouldForceOpen ? "guide" : "default"}`}
asChild
defaultOpen={isActive || hasActiveSubmenu}
defaultOpen={
shouldForceOpen || isActive || hasActiveSubmenu
}
className="group/collapsible"
>
<SidebarMenuItem>
@@ -111,6 +128,7 @@ export function NavMain({
<SidebarMenuButton
tooltip={item.title}
isActive={isActive || hasActiveSubmenu}
className={cn(highlightedButtonClass)}
>
{item.icon && <item.icon />}
<span>{item.title}</span>
@@ -142,6 +160,7 @@ export function NavMain({
asChild
tooltip={item.title}
isActive={isActive}
className={cn(highlightedButtonClass)}
>
<Link href={item.url}>
{item.icon && <item.icon />}

View File

@@ -1,7 +1,6 @@
"use client";
import { LogOut, User } from "lucide-react";
import { useRouter } from "next/navigation";
import { Building2, Check, LogOut, Plus } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
@@ -19,19 +18,26 @@ import {
useSidebar,
} from "@/components/ui/sidebar";
import { useAuth } from "@/components/auth/auth-provider";
import type { Workspace } from "@/lib/types/api";
export function NavUser({
user,
workspaceSwitcher,
}: {
user: {
name: string;
email: string;
avatar: string;
};
workspaceSwitcher?: {
workspaces: Workspace[];
currentWorkspaceId?: string;
onSelect: (workspace: Workspace) => void;
onCreate?: () => void;
};
}) {
const { isMobile } = useSidebar();
const { logout } = useAuth();
const router = useRouter();
const handleLogout = () => {
logout();
@@ -91,6 +97,51 @@ export function NavUser({
</div>
</div>
</DropdownMenuLabel>
{workspaceSwitcher && (
<>
<DropdownMenuSeparator />
<DropdownMenuLabel className="text-xs text-muted-foreground">
Воркспейсы
</DropdownMenuLabel>
{workspaceSwitcher.workspaces.length > 0 ? (
workspaceSwitcher.workspaces.map((workspace) => (
<DropdownMenuItem
key={workspace.id}
onClick={() => workspaceSwitcher.onSelect(workspace)}
className="gap-2 p-2 text-sm"
>
<div className="flex size-6 items-center justify-center rounded-sm border">
<Building2 className="size-4 shrink-0" />
</div>
<span className="flex-1 truncate">{workspace.name}</span>
{workspaceSwitcher.currentWorkspaceId === workspace.id && (
<Check className="size-4 ml-auto" />
)}
</DropdownMenuItem>
))
) : (
<div className="px-2 py-1.5 text-sm text-muted-foreground">
Нет доступных воркспейсов
</div>
)}
{workspaceSwitcher.onCreate && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={workspaceSwitcher.onCreate}
className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
<Plus className="size-4" />
</div>
<span className="text-muted-foreground">
Создать воркспейс
</span>
</DropdownMenuItem>
</>
)}
</>
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />