Files
ApiCore/ApiCore-Frontend/src/app/(app)/sidebar.tsx
T
Nick SzaunigandClaude Opus 5.5 c01a38835a feat: platform backbone and field capture, first part (checkpoint)
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]>
2026-10-13 17:57:54 +02:00

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>
);
}