new clients functionality

This commit is contained in:
Rajpreet0 2026-09-27 18:57:56 +02:00
parent 9dbacf4d45
commit 5edd697056
25 changed files with 1268 additions and 56 deletions

View file

@ -1,13 +1,14 @@
import {
import {
FileTextIcon,
FoldersIcon,
InfoIcon,
KeyIcon,
LayoutDashboardIcon,
LucideIcon,
SettingsIcon,
UserRoundIcon,
UsersRoundIcon
}
LayoutDashboardIcon,
LucideIcon,
SettingsIcon,
UserRoundIcon,
UsersRoundIcon
}
from "lucide-react";
export type MenuItem = {
@ -60,4 +61,36 @@ export const clientMenu: MenuItem[] = [
url: "/client/storage",
icon: FoldersIcon
}
]
]
export function getClientDetailMenu(clientId: string): MenuItem[] {
const base = `/admin/clients/${clientId}`
return [
{
title: "Info",
url: base,
icon: InfoIcon
},
{
title: "Stammdaten",
url: `${base}/data`,
icon: UserRoundIcon
},
{
title: "Rechnungen",
url: `${base}/billing`,
icon: FileTextIcon
},
{
title: "Passwörter",
url: `${base}/passwords`,
icon: KeyIcon
},
{
title: "Dokumente",
url: `${base}/storage`,
icon: FoldersIcon
}
]
}

View file

@ -11,8 +11,10 @@ import {
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Session } from "../../types/user";
import { adminMenu, clientMenu } from "../../data/sidebar-menu-content";
import { ClientsMenuItem } from "./clients-menu-item";
import { LogoutButton } from "./logout-button";
import { SidebarMenuLink } from "./sidebar-menu-link";
import { ThemeToggle } from "./theme-toggle";
import UserBadge from "./user-badge";
export function AppSidebar({ session }: { session: Session }) {
@ -37,21 +39,28 @@ export function AppSidebar({ session }: { session: Session }) {
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu className="gap-1.5">
{menu.map((item) => (
<SidebarMenuLink
key={item.url}
title={item.title}
url={item.url}
icon={<item.icon className="size-4.5" />}
/>
))}
{menu.map((item) =>
item.url === "/admin/clients" ? (
<ClientsMenuItem key={item.url} />
) : (
<SidebarMenuLink
key={item.url}
title={item.title}
url={item.url}
icon={<item.icon className="size-4.5" />}
/>
)
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter className="gap-3 p-4">
<p className="truncate text-xs text-muted-foreground">{session.email}</p>
<LogoutButton />
<div className="flex items-center gap-2">
<LogoutButton />
<ThemeToggle />
</div>
</SidebarFooter>
<SidebarRail />
</Sidebar>

View file

@ -0,0 +1,92 @@
"use client"
import Link from "next/link";
import { usePathname } from "next/navigation";
import { ChevronRightIcon, UsersRoundIcon } from "lucide-react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
useSidebar,
} from "@/components/ui/sidebar";
import { getClientDetailMenu } from "@/modules/shared/data/sidebar-menu-content";
export function ClientsMenuItem() {
const pathname = usePathname();
const { setOpenMobile } = useSidebar();
const detailMatch = pathname.match(/^\/admin\/clients\/([^/]+)/);
const clientId = detailMatch?.[1];
if (!clientId) {
const isActive = pathname === "/admin/clients";
return (
<SidebarMenuItem>
<SidebarMenuButton
render={<Link href="/admin/clients" onClick={() => setOpenMobile(false)} />}
isActive={isActive}
className="h-10 text-[0.95rem]"
>
<UsersRoundIcon className="size-4.5" />
<span>Clients</span>
</SidebarMenuButton>
</SidebarMenuItem>
);
}
const menu = getClientDetailMenu(clientId);
return (
<Collapsible defaultOpen>
<SidebarMenuItem>
<div className="relative">
<SidebarMenuButton
render={<Link href="/admin/clients" onClick={() => setOpenMobile(false)} />}
className="h-10 pr-9 text-[0.95rem]"
>
<UsersRoundIcon className="size-4.5" />
<span>Clients</span>
</SidebarMenuButton>
<CollapsibleTrigger
render={
<button
type="button"
aria-label="Client-Menü umschalten"
className="group absolute top-1/2 right-1.5 -translate-y-1/2 rounded-md p-1.5 hover:bg-sidebar-accent"
/>
}
>
<ChevronRightIcon className="size-4 transition-transform group-data-panel-open:rotate-90" />
</CollapsibleTrigger>
</div>
<CollapsibleContent>
<SidebarMenuSub>
{menu.map((item) => {
const isActive = pathname === item.url;
return (
<SidebarMenuSubItem key={item.url}>
<SidebarMenuSubButton
render={<Link href={item.url} onClick={() => setOpenMobile(false)} />}
isActive={isActive}
>
<item.icon className="size-4" />
<span>{item.title}</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
);
})}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
);
}

View file

@ -0,0 +1,38 @@
"use client"
import { useSyncExternalStore } from "react";
import { useTheme } from "next-themes";
import { MoonIcon, SunIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
function subscribe() {
return () => {};
}
function useMounted() {
return useSyncExternalStore(subscribe, () => true, () => false);
}
export function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme();
const mounted = useMounted();
if (!mounted) {
return <Button variant="outline" size="icon" className="shrink-0" disabled />;
}
const isDark = resolvedTheme === "dark";
return (
<Button
type="button"
variant="outline"
size="icon"
className="shrink-0 cursor-pointer"
onClick={() => setTheme(isDark ? "light" : "dark")}
aria-label={isDark ? "Zu hellem Modus wechseln" : "Zu dunklem Modus wechseln"}
>
{isDark ? <SunIcon /> : <MoonIcon />}
</Button>
)
}