feat(web): IA-3 navigation, legacy redirects and top-bar inbox (FE-1)
Sidebar becomes 5 groups (overview / resources / subscriptions / organize / system). Pending items move to a top-bar inbox with badge and grouped dropdown. Engine panel, schedules and identity pages redirect to the task page, its plan tab and media overview views; the download plan becomes a second tab of the download panel. nav.test and smoke cover the new routes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4bb2583f6c
commit
738d4ce752
18 files changed
+647
-1104
No files matched your search
@@ -3,12 +3,12 @@ import { ChevronLeft, ChevronRight, Clapperboard, LoaderCircle, Menu } from "luc
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { Link, Outlet, useLocation } from "react-router";
|
||||
import { useSession } from "~/api/auth.ts";
|
||||
import { useDecisionSummary } from "~/api/decisions.ts";
|
||||
import { AgentDrawer, AgentDrawerTrigger } from "~/components/agent/agent-drawer.tsx";
|
||||
import { AccountMenu } from "~/components/console/account-menu.tsx";
|
||||
import { AgentLauncher, AgentNavEntry } from "~/components/console/agent-launcher.tsx";
|
||||
import { ErrorState } from "~/components/console/empty-state.tsx";
|
||||
import { findNavItem, NAV_GROUPS, type NavGroup, type NavItem } from "~/components/console/nav.ts";
|
||||
import { InboxBell } from "~/components/console/inbox-bell.tsx";
|
||||
import { findNavItem, NAV_GROUPS, type NavGroup, pageTitle } from "~/components/console/nav.ts";
|
||||
import PrismLeak from "~/components/prism-leak.tsx";
|
||||
import { ThemeToggle } from "~/components/theme-toggle.tsx";
|
||||
import { Button } from "~/components/ui/button.tsx";
|
||||
@@ -18,31 +18,10 @@ import { cn } from "~/lib/utils.ts";
|
||||
|
||||
const PORTAL_NAME = "媒体自动化";
|
||||
|
||||
/** 侧栏角标:待处理未决数 */
|
||||
function useNavBadges(): Record<NonNullable<NavItem["badge"]>, number> {
|
||||
const summary = useDecisionSummary();
|
||||
return { decisions: summary.data?.total ?? 0 };
|
||||
}
|
||||
|
||||
function NavBadge({ n, className }: { n: number; className?: string }) {
|
||||
if (!n) return null;
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"min-w-5 rounded-full bg-warning/15 px-1.5 text-center text-micro leading-5 font-semibold text-warning tabular-nums",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{n > 99 ? "99+" : n}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── 展开态导航:选中项由一枚玻璃滑块(seg-thumb)滑到激活链接背后 ───
|
||||
function ExpandedNav({ groups, onNavigate }: { groups: NavGroup[]; onNavigate?: () => void }) {
|
||||
const location = useLocation();
|
||||
const activeTo = findNavItem(location.pathname)?.to;
|
||||
const badges = useNavBadges();
|
||||
const navRef = useRef<HTMLElement>(null);
|
||||
const [pill, setPill] = useState<{ x: number; y: number; w: number; h: number } | null>(null);
|
||||
|
||||
@@ -102,7 +81,6 @@ function ExpandedNav({ groups, onNavigate }: { groups: NavGroup[]; onNavigate?:
|
||||
>
|
||||
<item.icon className={cn("h-4 w-4 shrink-0", active && "text-primary")} />
|
||||
<span className="flex-1 truncate">{item.label}</span>
|
||||
{item.badge ? <NavBadge n={badges[item.badge]} /> : null}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
@@ -117,7 +95,6 @@ function ExpandedNav({ groups, onNavigate }: { groups: NavGroup[]; onNavigate?:
|
||||
function CollapsedNav() {
|
||||
const location = useLocation();
|
||||
const activeTo = findNavItem(location.pathname)?.to;
|
||||
const badges = useNavBadges();
|
||||
return (
|
||||
<nav className="flex flex-1 flex-col items-center gap-1 overflow-y-auto py-4">
|
||||
<AgentNavEntry collapsed />
|
||||
@@ -126,12 +103,11 @@ function CollapsedNav() {
|
||||
{gi > 0 ? <span aria-hidden className="my-1 h-px w-6 bg-border" /> : null}
|
||||
{group.items.map((item) => {
|
||||
const active = item.to === activeTo;
|
||||
const n = item.badge ? badges[item.badge] : 0;
|
||||
return (
|
||||
<Link
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
title={n ? `${item.label}(${n})` : item.label}
|
||||
title={item.label}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 items-center justify-center rounded-xl transition-colors duration-micro",
|
||||
@@ -141,9 +117,6 @@ function CollapsedNav() {
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-[18px] w-[18px]" />
|
||||
{n ? (
|
||||
<span aria-hidden className="absolute top-1.5 right-1.5 h-1.5 w-1.5 rounded-full bg-warning" />
|
||||
) : null}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
@@ -200,7 +173,7 @@ function ConsoleShell({ session }: { session: SessionInfo }) {
|
||||
const location = useLocation();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [sheetOpen, setSheetOpen] = useState(false);
|
||||
const activeLabel = findNavItem(location.pathname)?.label ?? PORTAL_NAME;
|
||||
const activeLabel = pageTitle(location.pathname) ?? PORTAL_NAME;
|
||||
|
||||
return (
|
||||
<div className="app-scrollbars relative isolate flex min-h-screen overflow-x-hidden bg-background">
|
||||
@@ -261,6 +234,7 @@ function ConsoleShell({ session }: { session: SessionInfo }) {
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{/* 桌面有侧栏主入口与右下角悬浮按钮,顶栏小按钮只在窄屏保留 */}
|
||||
<InboxBell />
|
||||
<AgentDrawerTrigger className="xl:hidden" />
|
||||
<ThemeToggle />
|
||||
<AccountMenu session={session} />
|
||||
|
||||
Reference in new issue
Block a user