Merge pull request 'feat(nav): קיבוץ הניווט העליון בתפריטים נפתחים (פסיקה/סגנון)' (#147) from worktree-nav-tidy into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 58s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 58s
This commit was merged in pull request #147.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { Fragment, type ReactNode } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { ChevronDown, Settings } from "lucide-react";
|
||||
@@ -22,7 +22,7 @@ import { usePendingApprovals } from "@/lib/api/chair";
|
||||
* Ezer Mishpati navigation shell — two-row header.
|
||||
*
|
||||
* Row 1 (brand): logo + dynamic context subtitle · global search · agent boards
|
||||
* Row 2 (nav): work group · knowledge group · admin dropdown
|
||||
* Row 2 (nav): work links · knowledge dropdowns (פסיקה/סגנון) · admin dropdown
|
||||
*
|
||||
* Editorial/judicial aesthetic preserved:
|
||||
* - Navy background with a gold hairline rule (border-b-3)
|
||||
@@ -33,27 +33,37 @@ import { usePendingApprovals } from "@/lib/api/chair";
|
||||
* sighted users see where they are.
|
||||
*/
|
||||
|
||||
type NavItem = { href: string; label: string };
|
||||
type NavGroup = { id: string; items: NavItem[] };
|
||||
// `groupLabel`, when present, renders a separator + sub-heading inside a
|
||||
// dropdown *before* this item — used to set off secondary tools.
|
||||
type NavItem = { href: string; label: string; groupLabel?: string };
|
||||
type NavMenuDef = { id: string; label: string; items: NavItem[] };
|
||||
|
||||
const NAV_GROUPS: NavGroup[] = [
|
||||
{
|
||||
id: "work",
|
||||
items: [
|
||||
// Work cluster — direct links, the daily hubs.
|
||||
const WORK_LINKS: NavItem[] = [
|
||||
{ href: "/", label: "בית" },
|
||||
{ href: "/approvals", label: "מרכז אישורים" },
|
||||
{ href: "/feedback", label: "הערות יו״ר" },
|
||||
{ href: "/archive", label: "ארכיון" },
|
||||
];
|
||||
|
||||
// Knowledge cluster — grouped under dropdowns so the bar stays scannable.
|
||||
// All routes are preserved; only their placement in the nav changes.
|
||||
const KNOWLEDGE_MENUS: NavMenuDef[] = [
|
||||
{
|
||||
id: "precedents",
|
||||
label: "פסיקה",
|
||||
items: [
|
||||
{ href: "/precedents", label: "ספריית פסיקה" },
|
||||
{ href: "/digests", label: "יומונים" },
|
||||
{ href: "/missing-precedents", label: "פסיקה חסרה" },
|
||||
{ href: "/graph", label: "מפת הקורפוס", groupLabel: "ניתוח וכיול" },
|
||||
{ href: "/goldset", label: "מדגם-זהב" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "knowledge",
|
||||
id: "style",
|
||||
label: "סגנון",
|
||||
items: [
|
||||
{ href: "/precedents", label: "ספריית פסיקה" },
|
||||
{ href: "/graph", label: "מפת הקורפוס" },
|
||||
{ href: "/digests", label: "יומונים" },
|
||||
{ href: "/missing-precedents", label: "פסיקה חסרה" },
|
||||
{ href: "/goldset", label: "מדגם-זהב" },
|
||||
{ href: "/training", label: "אימון סגנון" },
|
||||
{ href: "/methodology", label: "מתודולוגיה" },
|
||||
],
|
||||
@@ -162,21 +172,17 @@ export function AppShell({ children }: { children: ReactNode }) {
|
||||
{/* ─── Row 2 — section nav ─── */}
|
||||
<div className="flex items-center gap-3 px-10 pt-1 pb-[18px]">
|
||||
<nav className="flex items-center gap-3" aria-label="ניווט ראשי">
|
||||
{NAV_GROUPS.map((group, idx) => (
|
||||
<div key={group.id} className="flex items-center">
|
||||
{idx > 0 && (
|
||||
<span
|
||||
className="mx-2 h-4 w-px bg-parchment/20"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center gap-1">
|
||||
{group.items.map((item) => (
|
||||
{WORK_LINKS.map((item) => (
|
||||
<NavLink key={item.href} item={item} active={isActive(pathname, item.href)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<span className="mx-2 h-4 w-px bg-parchment/20" aria-hidden="true" />
|
||||
<div className="flex items-center gap-1">
|
||||
{KNOWLEDGE_MENUS.map((menu) => (
|
||||
<NavMenu key={menu.id} menu={menu} pathname={pathname} />
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<DropdownMenu>
|
||||
@@ -249,7 +255,6 @@ function NavLink({ item, active }: { item: NavItem; active: boolean }) {
|
||||
`}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
{item.href === "/missing-precedents" ? <MissingPrecedentsBadge /> : null}
|
||||
{item.href === "/approvals" ? <ApprovalsBadge /> : null}
|
||||
{active && (
|
||||
<span
|
||||
@@ -261,6 +266,69 @@ function NavLink({ item, active }: { item: NavItem; active: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
/* Knowledge-cluster dropdown (פסיקה / סגנון). The trigger mirrors the admin
|
||||
* dropdown styling and lights up — gold underline included — when any child
|
||||
* route is active, so the active section reads even while the menu is closed.
|
||||
* The "פסיקה חסרה" open-count badge surfaces on the trigger too, so the
|
||||
* pending count stays visible without opening the menu. */
|
||||
function NavMenu({ menu, pathname }: { menu: NavMenuDef; pathname: string }) {
|
||||
const menuActive = menu.items.some((i) => isActive(pathname, i.href));
|
||||
const hasMissingPrecedents = menu.items.some((i) => i.href === "/missing-precedents");
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
className={`
|
||||
relative flex items-center gap-1 px-3 py-1.5 rounded text-sm
|
||||
transition-colors outline-none focus-visible:ring-2 focus-visible:ring-gold/60
|
||||
${menuActive
|
||||
? "text-parchment font-semibold bg-navy-soft/80"
|
||||
: "text-parchment/80 hover:text-parchment hover:bg-navy-soft/60"}
|
||||
data-[state=open]:bg-navy-soft/80 data-[state=open]:text-parchment
|
||||
`}
|
||||
aria-label={`${menu.label} — תפריט`}
|
||||
>
|
||||
<span>{menu.label}</span>
|
||||
{hasMissingPrecedents ? <MissingPrecedentsBadge /> : null}
|
||||
<ChevronDown className="size-3" aria-hidden="true" />
|
||||
{menuActive && (
|
||||
<span
|
||||
className="absolute -bottom-[19px] inset-x-2 h-[2px] bg-gold"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end" sideOffset={10} className="min-w-[200px]">
|
||||
{menu.items.map((item) => {
|
||||
const active = isActive(pathname, item.href);
|
||||
return (
|
||||
<Fragment key={item.href}>
|
||||
{item.groupLabel && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
||||
{item.groupLabel}
|
||||
</DropdownMenuLabel>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem asChild>
|
||||
<Link
|
||||
href={item.href}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={`flex items-center cursor-pointer ${active ? "font-semibold" : ""}`}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
{item.href === "/missing-precedents" ? <MissingPrecedentsBadge /> : null}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
/* Total pending-approvals badge next to "מרכז אישורים" — Dafna's outstanding
|
||||
* human-gate items (halachot + missing precedents + feedback + qa_failed).
|
||||
* Renders only when >0 so the nav stays quiet when everything is cleared. */
|
||||
|
||||
Reference in New Issue
Block a user