feat(nav): הסרת פילי-טאבים+תגי-ניווט, הורדת "פסיקה חסרה" מהתפריט (#3/#4) #276

Merged
chaim merged 1 commits from worktree-nav-pills-cleanup into main 2026-06-16 18:50:20 +00:00
2 changed files with 13 additions and 88 deletions

View File

@@ -9,8 +9,6 @@ import { LibrarySearchPanel } from "@/components/precedents/library-search-panel
import { HalachaReviewPanel } from "@/components/precedents/halacha-review-panel"; import { HalachaReviewPanel } from "@/components/precedents/halacha-review-panel";
import { PlansReviewPanel } from "@/components/precedents/plans-review-panel"; import { PlansReviewPanel } from "@/components/precedents/plans-review-panel";
import { LibraryStatsPanel } from "@/components/precedents/library-stats-panel"; import { LibraryStatsPanel } from "@/components/precedents/library-stats-panel";
import { useHalachotPending } from "@/lib/api/precedent-library";
import { usePlansPending } from "@/lib/api/plans";
import { useMissingPrecedents } from "@/lib/api/missing-precedents"; import { useMissingPrecedents } from "@/lib/api/missing-precedents";
/** /**
@@ -27,39 +25,6 @@ import { useMissingPrecedents } from "@/lib/api/missing-precedents";
* per-case precedent attacher (chair-attached quotes scoped to a case). * per-case precedent attacher (chair-attached quotes scoped to a case).
*/ */
/** Colored count pill riding on a tab trigger (mockup 07: warn for review
* queue, info for incoming). Returns null when the queue is empty. */
function CountPill({ n, tone }: { n: number; tone: "warn" | "info" }) {
if (!n) return null;
const cls =
tone === "warn"
? "bg-warn text-white"
: "bg-info text-white";
return (
<span
className={`ms-1.5 inline-flex items-center justify-center rounded-full px-1.5 min-w-[1.15rem] h-[1.15rem] text-[0.68rem] font-semibold tabular-nums ${cls}`}
>
{n}
</span>
);
}
function PendingPill() {
const { data } = useHalachotPending();
return <CountPill n={data?.count ?? 0} tone="warn" />;
}
function PlansPendingPill() {
const { data } = usePlansPending();
return <CountPill n={data?.count ?? 0} tone="warn" />;
}
function IncomingPill() {
// "פסיקה נכנסת" = open missing-precedents waiting for the chair to upload.
const { data } = useMissingPrecedents({ status: "open", limit: 1 });
return <CountPill n={data?.by_status?.open ?? 0} tone="info" />;
}
const PRECEDENT_TABS = new Set(["library", "search", "review", "plans", "incoming", "stats"]); const PRECEDENT_TABS = new Set(["library", "search", "review", "plans", "incoming", "stats"]);
export default function PrecedentsPage() { export default function PrecedentsPage() {
@@ -93,19 +58,16 @@ export default function PrecedentsPage() {
</div> </div>
{/* tabs as a dedicated row under the header — underline-style {/* tabs as a dedicated row under the header — underline-style
triggers with colored count pills (mockup 07). */} triggers; count pills removed (#3), the active underline alone
carries state (mockup 07). */}
<TabsList className="flex w-full justify-start gap-1 rounded-none border-0 border-b border-rule bg-transparent p-0 h-auto"> <TabsList className="flex w-full justify-start gap-1 rounded-none border-0 border-b border-rule bg-transparent p-0 h-auto">
{[ {[
{ value: "library", label: "ספרייה", pill: null }, { value: "library", label: "ספרייה" },
{ value: "search", label: "חיפוש בקורפוס", pill: null }, { value: "search", label: "חיפוש בקורפוס" },
{ value: "review", label: "תור הלכות", pill: <PendingPill /> }, { value: "review", label: "תור הלכות" },
{ value: "plans", label: "תכניות", pill: <PlansPendingPill /> }, { value: "plans", label: "תכניות" },
{ { value: "incoming", label: "פסיקה נכנסת" },
value: "incoming", { value: "stats", label: "סטטיסטיקה" },
label: "פסיקה נכנסת",
pill: <IncomingPill />,
},
{ value: "stats", label: "סטטיסטיקה", pill: null },
].map((t) => ( ].map((t) => (
<TabsTrigger <TabsTrigger
key={t.value} key={t.value}
@@ -113,7 +75,6 @@ export default function PrecedentsPage() {
className="rounded-none border-0 border-b-2 border-transparent bg-transparent px-4 py-2.5 -mb-px text-sm font-medium text-ink-muted shadow-none data-[state=active]:border-gold data-[state=active]:bg-transparent data-[state=active]:font-semibold data-[state=active]:text-navy data-[state=active]:shadow-none" className="rounded-none border-0 border-b-2 border-transparent bg-transparent px-4 py-2.5 -mb-px text-sm font-medium text-ink-muted shadow-none data-[state=active]:border-gold data-[state=active]:bg-transparent data-[state=active]:font-semibold data-[state=active]:text-navy data-[state=active]:shadow-none"
> >
{t.label} {t.label}
{t.pill}
</TabsTrigger> </TabsTrigger>
))} ))}
</TabsList> </TabsList>

View File

@@ -15,8 +15,6 @@ import {
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { GlobalSearch } from "@/components/global-search"; import { GlobalSearch } from "@/components/global-search";
import { headerSubtitle } from "@/components/header-context"; import { headerSubtitle } from "@/components/header-context";
import { useMissingPrecedentsOpenCount } from "@/lib/api/missing-precedents";
import { usePendingApprovals } from "@/lib/api/chair";
/** /**
* Ezer Mishpati navigation shell — two-row header. * Ezer Mishpati navigation shell — two-row header.
@@ -55,11 +53,12 @@ const KNOWLEDGE_MENUS: NavMenuDef[] = [
{ {
id: "precedents", id: "precedents",
label: "פסיקה", label: "פסיקה",
// #4: "פסיקה חסרה" (/missing-precedents) ירד מהתפריט — הוא כפול לטאב
// "פסיקה נכנסת" בתוך /precedents. הדף עצמו נשאר נגיש מתוך אותו טאב.
items: [ items: [
{ href: "/precedents", label: "ספריית פסיקה" }, { href: "/precedents", label: "ספריית פסיקה" },
{ href: "/digests", label: "יומונים" }, { href: "/digests", label: "יומונים" },
{ href: "/missing-precedents", label: "פסיקה חסרה" }, { href: "/graph", label: "מפת הקורפוס" },
{ href: "/graph", label: "מפת הקורפוס" },
], ],
}, },
{ {
@@ -260,7 +259,6 @@ function NavLink({ item, active }: { item: NavItem; active: boolean }) {
`} `}
> >
<span>{item.label}</span> <span>{item.label}</span>
{item.href === "/approvals" ? <ApprovalsBadge /> : null}
{active && ( {active && (
<span <span
className="absolute -bottom-[19px] inset-x-2 h-[2px] bg-gold" className="absolute -bottom-[19px] inset-x-2 h-[2px] bg-gold"
@@ -278,7 +276,6 @@ function NavLink({ item, active }: { item: NavItem; active: boolean }) {
* pending count stays visible without opening the menu. */ * pending count stays visible without opening the menu. */
function NavMenu({ menu, pathname }: { menu: NavMenuDef; pathname: string }) { function NavMenu({ menu, pathname }: { menu: NavMenuDef; pathname: string }) {
const menuActive = menu.items.some((i) => isActive(pathname, i.href)); const menuActive = menu.items.some((i) => isActive(pathname, i.href));
const hasMissingPrecedents = menu.items.some((i) => i.href === "/missing-precedents");
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger <DropdownMenuTrigger
@@ -293,7 +290,6 @@ function NavMenu({ menu, pathname }: { menu: NavMenuDef; pathname: string }) {
aria-label={`${menu.label} — תפריט`} aria-label={`${menu.label} — תפריט`}
> >
<span>{menu.label}</span> <span>{menu.label}</span>
{hasMissingPrecedents ? <MissingPrecedentsBadge /> : null}
<ChevronDown className="size-3" aria-hidden="true" /> <ChevronDown className="size-3" aria-hidden="true" />
{menuActive && ( {menuActive && (
<span <span
@@ -323,7 +319,6 @@ function NavMenu({ menu, pathname }: { menu: NavMenuDef; pathname: string }) {
className={`flex items-center cursor-pointer ${active ? "font-semibold" : ""}`} className={`flex items-center cursor-pointer ${active ? "font-semibold" : ""}`}
> >
<span>{item.label}</span> <span>{item.label}</span>
{item.href === "/missing-precedents" ? <MissingPrecedentsBadge /> : null}
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
</Fragment> </Fragment>
@@ -334,34 +329,3 @@ function NavMenu({ menu, pathname }: { menu: NavMenuDef; pathname: string }) {
); );
} }
/* 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. */
function ApprovalsBadge() {
const { data } = usePendingApprovals();
const total = data?.total_pending ?? 0;
if (!total) return null;
return (
<span
className="ms-1 inline-flex items-center justify-center min-w-[1.25rem] h-4 px-1 rounded-full bg-gold text-navy text-[0.65rem] font-semibold"
aria-label={`${total} פריטים ממתינים לאישורך`}
>
{total}
</span>
);
}
/* Small open-count badge next to "פסיקה חסרה" — only renders when >0
* so the nav stays quiet in normal operation. */
function MissingPrecedentsBadge() {
const { data: openCount } = useMissingPrecedentsOpenCount();
if (!openCount) return null;
return (
<span
className="ms-1 inline-flex items-center justify-center min-w-[1.25rem] h-4 px-1 rounded-full bg-gold text-navy text-[0.65rem] font-semibold"
aria-label={`${openCount} פסיקות חסרות פתוחות`}
>
{openCount}
</span>
);
}