feat: complete tOS project with HR, LEAN, Dashboard and Integrations modules
Full enterprise web operating system including: - Next.js 14 frontend with App Router, i18n (DE/EN), shadcn/ui - NestJS 10 backend with Prisma, JWT auth, Swagger docs - Keycloak 24 SSO with role-based access control - HR module (employees, time tracking, absences, org chart) - LEAN module (3S planning, morning meeting SQCDM, skill matrix) - Integrations module (PlentyONE, Zulip, Todoist, FreeScout, Nextcloud, ecoDMS, GembaDocs) - Dashboard with customizable drag & drop widget grid - Docker Compose infrastructure (PostgreSQL 16, Redis 7, Keycloak 24) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
204
.claude/agent-memory/frontend-specialist/component-patterns.md
Normal file
204
.claude/agent-memory/frontend-specialist/component-patterns.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Component Patterns - tOS Frontend
|
||||
|
||||
## Layout Component Pattern
|
||||
```tsx
|
||||
// src/components/layout/sidebar.tsx
|
||||
'use client';
|
||||
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useSidebarStore } from '@/stores/sidebar-store';
|
||||
|
||||
export function Sidebar({ locale }: { locale: string }) {
|
||||
const { isExpanded, toggleSidebar } = useSidebarStore();
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<motion.aside
|
||||
initial={false}
|
||||
animate={{ width: isExpanded ? 240 : 64 }}
|
||||
transition={{ duration: 0.2, ease: 'easeInOut' }}
|
||||
className="fixed left-0 top-0 z-40 h-screen border-r bg-sidebar"
|
||||
>
|
||||
{/* Content */}
|
||||
</motion.aside>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Page with Metadata Pattern
|
||||
```tsx
|
||||
// page.tsx (Server Component)
|
||||
import { Metadata } from 'next';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { ContentComponent } from './content-component';
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const t = await getTranslations('namespace');
|
||||
return { title: t('title') };
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return <ContentComponent />;
|
||||
}
|
||||
|
||||
// content-component.tsx (Client Component)
|
||||
'use client';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export function ContentComponent() {
|
||||
const t = useTranslations('namespace');
|
||||
return <div>{t('key')}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Zustand Store Pattern
|
||||
```tsx
|
||||
// src/stores/sidebar-store.ts
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
interface SidebarState {
|
||||
isExpanded: boolean;
|
||||
toggleSidebar: () => void;
|
||||
}
|
||||
|
||||
export const useSidebarStore = create<SidebarState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
isExpanded: true,
|
||||
toggleSidebar: () => set((state) => ({ isExpanded: !state.isExpanded })),
|
||||
}),
|
||||
{
|
||||
name: 'tos-sidebar-state',
|
||||
partialize: (state) => ({ isExpanded: state.isExpanded }),
|
||||
}
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Animation Pattern with Framer Motion
|
||||
```tsx
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
// Staggered list animation
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ staggerChildren: 0.1 }}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1, duration: 0.3, ease: 'easeOut' }}
|
||||
>
|
||||
{item.content}
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
```
|
||||
|
||||
## shadcn/ui Component with Variants
|
||||
```tsx
|
||||
// src/components/ui/button.tsx
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground',
|
||||
outline: 'border border-input bg-background hover:bg-accent',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
sm: 'h-8 px-3 text-xs',
|
||||
lg: 'h-10 px-8',
|
||||
icon: 'h-9 w-9',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
## Provider Composition Pattern
|
||||
```tsx
|
||||
// src/components/providers/index.tsx
|
||||
'use client';
|
||||
|
||||
export function Providers({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<SessionProvider>
|
||||
<QueryProvider>
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</QueryProvider>
|
||||
</SessionProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## i18n Usage Pattern
|
||||
```tsx
|
||||
// In components
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export function Component() {
|
||||
const t = useTranslations('namespace');
|
||||
return <span>{t('key', { param: 'value' })}</span>;
|
||||
}
|
||||
|
||||
// In server components
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function ServerComponent() {
|
||||
const t = await getTranslations('namespace');
|
||||
return <span>{t('key')}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
## Protected Route Layout Pattern
|
||||
```tsx
|
||||
// src/app/[locale]/(auth)/layout.tsx
|
||||
'use client';
|
||||
|
||||
export default function AuthLayout({ children, params: { locale } }) {
|
||||
const { isExpanded } = useSidebarStore();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="hidden lg:block">
|
||||
<Sidebar locale={locale} />
|
||||
</div>
|
||||
<MobileSidebar locale={locale} />
|
||||
|
||||
<div className={cn(
|
||||
'flex min-h-screen flex-col transition-[margin-left] duration-200',
|
||||
'ml-0',
|
||||
isExpanded ? 'lg:ml-[240px]' : 'lg:ml-[64px]'
|
||||
)}>
|
||||
<Header locale={locale} />
|
||||
<main className="flex-1 p-4 md:p-6 lg:p-8">
|
||||
<PageTransition>{children}</PageTransition>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user