Platform backbone (plan 2026-10-13-plattform-rueckgrat), reviewed tasks: - run roles api/worker/job/all with shared startup checks (Task 1) - schema and migration for DomainEvent, deliveries, JobRun, ThrottlerHit (Task 2) - audit, notifications and domain events written in the same transaction (Task 3) - dispatcher with SKIP LOCKED claims, retries and stale release (Task 4) - worker building blocks: tick, LISTEN/NOTIFY wake-up, health (Task 5) - worker process and entry points (Task 6) - throttler counters in Postgres (Task 19) - separate keys per purpose with key ring (Task 20) - public origin page without exact location (Task 22) - shop keys split into read and write (Task 23) - Kustomize base, components, overlay encry, CI validation (Task 26) Field capture T3 (plan 2026-10-13-t3-erfassung-am-stand), reviewed tasks: - brood frames, read endpoints, apiary overview and offline pack - network basics, offline queue v3, navigation sheets, form building blocks - new inspection with last-time hints and weather, varroa per day, treatment prefill, apiary page with round - inspection photo (Task 10) is implemented, review pending Not rolled out. Remaining tasks follow in later commits. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
84 lines
3.0 KiB
TypeScript
84 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { FileClock, Hexagon } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import type { AuthUser } from "@/lib/auth/auth-context";
|
|
import { isNavActive, type NavItem, visibleNavGroups } from "./nav-config";
|
|
|
|
function SidebarLink({ item, pathname }: { item: NavItem; pathname: string }) {
|
|
const Icon = item.icon;
|
|
const isActive = isNavActive(pathname, item.href);
|
|
return (
|
|
<Link
|
|
href={item.href}
|
|
aria-current={isActive ? "page" : undefined}
|
|
className={cn(
|
|
"group relative flex items-center gap-2.5 rounded-xl px-3 py-2 text-sm font-medium transition-all duration-150",
|
|
isActive
|
|
? "bg-primary text-primary-foreground shadow-[var(--shadow-soft)]"
|
|
: "text-muted-foreground hover:bg-accent/70 hover:text-foreground",
|
|
)}
|
|
>
|
|
<Icon
|
|
className={cn(
|
|
"size-4.5 shrink-0 transition-transform duration-150",
|
|
!isActive && "motion-full:group-hover:scale-110",
|
|
)}
|
|
/>
|
|
<span className="truncate">{item.label}</span>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export function Sidebar({ user }: { user: AuthUser }) {
|
|
const pathname = usePathname();
|
|
const groups = visibleNavGroups(user);
|
|
|
|
return (
|
|
// The page scrolls on the window; the sidebar stays put beside it and
|
|
// scrolls on its own when the nav is taller than the screen. It is a
|
|
// sibling of the page content, so its overflow cannot capture sticky
|
|
// elements there (StickySubmit).
|
|
<aside className="sticky top-0 z-10 hidden h-dvh w-64 shrink-0 flex-col gap-1 self-start overflow-y-auto bg-sidebar px-3 py-5 shadow-[var(--shadow-soft)] sm:flex">
|
|
<Link
|
|
href="/dashboard"
|
|
className="mb-5 flex items-center gap-2.5 px-2 font-heading text-lg font-semibold"
|
|
>
|
|
<span
|
|
className="flex size-8 items-center justify-center rounded-xl shadow-[var(--shadow-soft)]"
|
|
style={{ background: "var(--gradient-honey)" }}
|
|
>
|
|
<Hexagon
|
|
className="size-4.5 fill-white/25 text-white"
|
|
strokeWidth={2.25}
|
|
/>
|
|
</span>
|
|
ApiCore
|
|
</Link>
|
|
<nav className="flex flex-1 flex-col gap-5">
|
|
{groups.map((group, index) => (
|
|
<div
|
|
key={group.label ?? `group-${index}`}
|
|
className="flex flex-col gap-0.5"
|
|
>
|
|
{group.label ? (
|
|
<p className="px-3 pb-1 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase">
|
|
{group.label}
|
|
</p>
|
|
) : null}
|
|
{group.items.map((item) => (
|
|
<SidebarLink key={item.href} item={item} pathname={pathname} />
|
|
))}
|
|
</div>
|
|
))}
|
|
</nav>
|
|
<div className="flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-xs text-muted-foreground">
|
|
<FileClock className="size-3.5 shrink-0" />
|
|
<span className="truncate">v1.x</span>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|