"use client"; import type { ReactNode } from "react"; import { useState } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { AnimatePresence, motion } from "motion/react"; import { Logomark } from "@/components/logomark"; import { HexMark, HoneycombPattern } from "@/components/honeycomb"; import { AdminCommandPalette, type PaletteEntry } from "@/components/admin-command-palette"; import { useI18n } from "@/i18n/i18n-provider"; import type { Dictionary } from "@/i18n/dictionary-type"; import { ThemeToggle } from "@/components/theme-toggle"; import type { ThemeChoice } from "@/lib/theme"; /** Was an einer Seite gerade auf Bearbeitung wartet (Zaehler in der Seitenleiste). */ export type AdminBadges = Partial>; interface NavItem { href: string; label: string; } /** * Gruppiert statt einer langen Liste: 13 Eintraege lesen sich in vier * kurzen Bloecken schneller, und die taegliche Arbeit (Verkauf) steht oben. */ function getAdminNavGroups(t: Dictionary): { label: string | null; items: NavItem[] }[] { return [ { label: null, items: [{ href: "/admin", label: t.adminShell.navDashboard }] }, { label: t.adminShell.groupSales, items: [ { href: "/admin/bestellungen", label: t.adminShell.navOrders }, { href: "/admin/retouren", label: t.adminShell.navReturns }, { href: "/admin/widerrufe", label: t.adminShell.navWithdrawals }, { href: "/admin/gutscheine", label: t.adminShell.navVouchers }, { href: "/admin/versand", label: t.adminShell.navShipping }, ], }, { label: t.adminShell.groupRange, items: [ { href: "/admin/produkte", label: t.adminShell.navProducts }, { href: "/admin/ernten", label: t.adminShell.navHarvests }, { href: "/admin/chargen", label: t.adminShell.navBatches }, { href: "/admin/bewertungen", label: t.adminShell.navReviews }, ], }, { label: t.adminShell.groupCustomers, items: [ { href: "/admin/benutzer", label: t.adminShell.navUsers }, { href: "/admin/anfragen", label: t.adminShell.navBusinessInquiries }, ], }, { label: t.adminShell.groupSite, items: [ { href: "/admin/einstellungen", label: t.adminShell.navContent }, { href: "/admin/audit-log", label: t.adminShell.navAuditLog }, ], }, ]; } function getPaletteEntries(t: Dictionary): PaletteEntry[] { const pages = getAdminNavGroups(t).flatMap((group) => group.items.map((item) => ({ ...item, section: "pages" as const })) ); const actions: PaletteEntry[] = [ { href: "/admin/produkte/neu", label: t.adminShell.actionNewProduct, section: "actions" }, { href: "/admin/gutscheine/neu", label: t.adminShell.actionNewVoucher, section: "actions" }, { href: "/admin/benutzer/neu", label: t.adminShell.actionNewUser, section: "actions" }, { href: "/admin/versand/neu", label: t.adminShell.actionNewShippingTier, section: "actions" }, { href: "/shop", label: t.adminShell.actionViewShop, section: "actions" }, ]; return [...pages, ...actions]; } function isActiveHref(pathname: string, href: string): boolean { if (href === "/admin") return pathname === "/admin"; return pathname === href || pathname.startsWith(`${href}/`); } function NavLinks({ pathname, groups, badges, onNavigate, waitingLabel, }: { pathname: string; groups: ReturnType; badges: AdminBadges; onNavigate?: () => void; waitingLabel: (count: number) => string; }) { return ( <> {groups.map((group, groupIndex) => (
0 ? "mt-5" : ""}> {group.label ? (

{group.label}

) : null} {group.items.map((item) => { const active = isActiveHref(pathname, item.href); const count = badges[item.href] ?? 0; return ( {/* Aktive Seite als weich einblendende Flaeche, die beim Wechsel zur neuen Seite hinueberwandert. */} {active ? ( ) : null} {item.label} {count > 0 ? ( {waitingLabel(count)} ) : null} ); })}
))} ); } function Wordmark({ logoSrc }: { logoSrc: string | null }) { return ( WABENHAIN ADMIN ); } function PaletteTrigger({ onClick, label, shortcut }: { onClick: () => void; label: string; shortcut: string }) { return ( ); } interface AdminShellProps { adminName: string; /** Gewaehlte Darstellung (Cookie), siehe lib/theme.ts. */ themeChoice?: ThemeChoice; /** Logo fuer dunkle Flaechen, siehe lib/brand.ts. */ logoSrc?: string | null; badges?: AdminBadges; logoutAction: () => void | Promise; children: ReactNode; } /** * Eigener Arbeitsbereich fuer die Imkerei: gruppierte Seitenleiste mit * Zaehlern fuer alles, was gerade wartet, und einer Schnellsuche (Strg/Cmd+K). * Ab lg persistente Seitenleiste links, darunter ein Vollbild-Menue nach * demselben Muster wie die mobile Hauptnavigation in site-chrome.tsx. Die * Shop-Kopf- und -Fusszeile blendet das Root-Layout hier aus * (components/hide-on-admin.tsx). */ export function AdminShell({ adminName, logoSrc = null, themeChoice = "system", badges = {}, logoutAction, children, }: AdminShellProps) { const { t } = useI18n(); const groups = getAdminNavGroups(t); const pathname = usePathname(); const [mobileOpen, setMobileOpen] = useState(false); const [paletteOpen, setPaletteOpen] = useState(false); const paletteLabels = { placeholder: t.adminShell.palettePlaceholder, noResults: t.adminShell.paletteNoResults, pages: t.adminShell.paletteSectionPages, actions: t.adminShell.paletteSectionActions, dialog: t.adminShell.paletteOpen, }; const footer = (
{/* Der Shop-Kopf ist im Admin ausgeblendet (HideOnAdmin) — ohne diesen Link fuehrte kein Weg zurueck auf die Website. */}
{t.adminShell.backToWebsite}

{t.adminShell.loggedInAs(adminName)}

); return (
{/* Kein zweites
: das Root-Layout stellt bereits eines. */}
{children}
{mobileOpen ? (
{footer}
) : null}
); }