internal frontend implementation with keycloak authentication #16
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Bilanzbuchhalter
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
internal_frontend/app/demo/kanzlei/rechtsanwalt/page.tsx
Normal file
7
internal_frontend/app/demo/kanzlei/rechtsanwalt/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Rechtsanwalt
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
internal_frontend/app/demo/kanzlei/steuer/page.tsx
Normal file
7
internal_frontend/app/demo/kanzlei/steuer/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Steuer
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
internal_frontend/app/demo/settings/page.tsx
Normal file
10
internal_frontend/app/demo/settings/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import {SidebarGroupLabel} from "@/components/ui/sidebar";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SidebarGroupLabel>Documents</SidebarGroupLabel>
|
||||||
|
Settings
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import type {Metadata} from "next";
|
import type {Metadata} from "next";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import {ThemeProvider} from "@/components/theme-provider";
|
import {ThemeProvider} from "@/components/theme-provider";
|
||||||
import {SidebarProvider, SidebarTrigger} from "@/components/ui/sidebar"
|
import {SidebarInset, SidebarProvider, SidebarTrigger} from "@/components/ui/sidebar"
|
||||||
import {AppSidebar} from "@/components/app-sidebar"
|
import {AppSidebar} from "@/components/app-sidebar"
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import {Separator} from "@/components/ui/separator";
|
||||||
|
import {DynamicBreadcrumb} from "@/components/dynamic-breadcrumb";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Internal | Rhein Software",
|
title: "Internal | Rhein Software",
|
||||||
@@ -16,7 +18,6 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<html lang="de" suppressHydrationWarning>
|
<html lang="de" suppressHydrationWarning>
|
||||||
<body>
|
<body>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
@@ -25,10 +26,29 @@ export default function RootLayout({
|
|||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<SidebarProvider>
|
<SidebarProvider
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||||
|
"--header-height": "calc(var(--spacing) * 12)",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
<AppSidebar/>
|
<AppSidebar/>
|
||||||
<main>
|
<main>
|
||||||
<SidebarTrigger/>
|
<SidebarInset>
|
||||||
|
<header
|
||||||
|
className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
||||||
|
<div className="flex items-center gap-2 px-4">
|
||||||
|
<SidebarTrigger className="-ml-1"/>
|
||||||
|
<Separator
|
||||||
|
orientation="vertical"
|
||||||
|
className="mr-2 data-[orientation=vertical]:h-4"
|
||||||
|
/>
|
||||||
|
<DynamicBreadcrumb/>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</SidebarInset>
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
@@ -36,4 +56,4 @@ export default function RootLayout({
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -116,17 +116,17 @@ export function AppSidebar() {
|
|||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<SidebarMenuSub className="ml-4 border-l border-border pl-4 flex flex-col gap-y-1">
|
<SidebarMenuSub className="ml-4 border-l border-border pl-4 flex flex-col gap-y-1">
|
||||||
<SidebarMenuSubItem>
|
<SidebarMenuSubItem>
|
||||||
<SidebarMenuSubButton href="/kanzlei/steuer">
|
<SidebarMenuSubButton href="/demo/kanzlei/steuer">
|
||||||
Steuer
|
Steuer
|
||||||
</SidebarMenuSubButton>
|
</SidebarMenuSubButton>
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
<SidebarMenuSubItem>
|
<SidebarMenuSubItem>
|
||||||
<SidebarMenuSubButton href="/kanzlei/rechtsanwalt">
|
<SidebarMenuSubButton href="/demo/kanzlei/rechtsanwalt">
|
||||||
Rechtsanwalt
|
Rechtsanwalt
|
||||||
</SidebarMenuSubButton>
|
</SidebarMenuSubButton>
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
<SidebarMenuSubItem>
|
<SidebarMenuSubItem>
|
||||||
<SidebarMenuSubButton href="/kanzlei/bilanzbuchhalter">
|
<SidebarMenuSubButton href="/demo/kanzlei/bilanzbuchhalter">
|
||||||
Bilanzbuchhalter
|
Bilanzbuchhalter
|
||||||
</SidebarMenuSubButton>
|
</SidebarMenuSubButton>
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
|
|||||||
42
internal_frontend/components/dynamic-breadcrumb.tsx
Normal file
42
internal_frontend/components/dynamic-breadcrumb.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// components/dynamic-breadcrumb.tsx
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import {usePathname} from 'next/navigation';
|
||||||
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
} from "@/components/ui/breadcrumb";
|
||||||
|
import React from 'react';
|
||||||
|
import {getBreadcrumbs} from "@/utils/BreadcrumbUtils";
|
||||||
|
|
||||||
|
export function DynamicBreadcrumb() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const breadcrumbs = getBreadcrumbs(pathname);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
{breadcrumbs.map((breadcrumb, index) => (
|
||||||
|
<React.Fragment key={breadcrumb.href}>
|
||||||
|
<BreadcrumbItem className="hidden md:block">
|
||||||
|
{breadcrumb.isCurrentPage ? (
|
||||||
|
<BreadcrumbPage>{breadcrumb.label}</BreadcrumbPage>
|
||||||
|
) : (
|
||||||
|
<BreadcrumbLink href={breadcrumb.href}>
|
||||||
|
{breadcrumb.label}
|
||||||
|
</BreadcrumbLink>
|
||||||
|
)}
|
||||||
|
</BreadcrumbItem>
|
||||||
|
{index < breadcrumbs.length - 1 && (
|
||||||
|
<BreadcrumbSeparator className="hidden md:block"/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
internal_frontend/components/ui/breadcrumb.tsx
Normal file
109
internal_frontend/components/ui/breadcrumb.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
||||||
|
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
|
return (
|
||||||
|
<ol
|
||||||
|
data-slot="breadcrumb-list"
|
||||||
|
className={cn(
|
||||||
|
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
data-slot="breadcrumb-item"
|
||||||
|
className={cn("inline-flex items-center gap-1.5", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbLink({
|
||||||
|
asChild,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"a"> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}) {
|
||||||
|
const Comp = asChild ? Slot : "a"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="breadcrumb-link"
|
||||||
|
className={cn("hover:text-foreground transition-colors", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="breadcrumb-page"
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
className={cn("text-foreground font-normal", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbSeparator({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"li">) {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
data-slot="breadcrumb-separator"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("[&>svg]:size-3.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children ?? <ChevronRight />}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbEllipsis({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="breadcrumb-ellipsis"
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("flex size-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="size-4" />
|
||||||
|
<span className="sr-only">More</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
BreadcrumbEllipsis,
|
||||||
|
}
|
||||||
8
internal_frontend/lib/breadcrumb-map.ts
Normal file
8
internal_frontend/lib/breadcrumb-map.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// lib/breadcrumb-map.ts
|
||||||
|
export const breadcrumbMap: Record<string, string> = {
|
||||||
|
'dashboard': 'Dashboard',
|
||||||
|
'settings': 'Settings',
|
||||||
|
'demo': 'Demo',
|
||||||
|
'users': 'User Management',
|
||||||
|
// Add more mappings as needed
|
||||||
|
};
|
||||||
28
internal_frontend/utils/BreadcrumbUtils.ts
Normal file
28
internal_frontend/utils/BreadcrumbUtils.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// utils/getBreadcrumbs.ts
|
||||||
|
import {breadcrumbMap} from '@/lib/breadcrumb-map';
|
||||||
|
|
||||||
|
export type Breadcrumb = {
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
|
isCurrentPage?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getBreadcrumbs(path: string): Breadcrumb[] {
|
||||||
|
const pathSegments = path.split('/').filter(Boolean);
|
||||||
|
|
||||||
|
return pathSegments.map((segment, index) => {
|
||||||
|
const href = `/${pathSegments.slice(0, index + 1).join('/')}`;
|
||||||
|
// Use the mapping if it exists, otherwise format the segment
|
||||||
|
const label = breadcrumbMap[segment.toLowerCase()] ||
|
||||||
|
segment
|
||||||
|
.split('-')
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
href,
|
||||||
|
isCurrentPage: index === pathSegments.length - 1
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user