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:
325
.claude/agent-memory/frontend-specialist/MEMORY.md
Normal file
325
.claude/agent-memory/frontend-specialist/MEMORY.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# tOS Frontend - Agent Memory
|
||||
|
||||
## Project Overview
|
||||
- **Type**: Enterprise Web Dashboard (Next.js 14+ App Router)
|
||||
- **Location**: `/home/mehmed/Entwicklung/githubProjekte/tOS/apps/web/`
|
||||
- **Language**: Code/comments in English, UI in German (default) via i18n
|
||||
|
||||
## Tech Stack
|
||||
- Next.js 14+ with App Router
|
||||
- TypeScript (strict mode)
|
||||
- Tailwind CSS + shadcn/ui (new-york style)
|
||||
- next-themes for dark/light mode
|
||||
- next-intl for i18n (de default, en supported)
|
||||
- NextAuth with Keycloak provider
|
||||
- Framer Motion for animations
|
||||
- Zustand for client state (sidebar-store, dashboard-store)
|
||||
- TanStack Query for server state
|
||||
- TanStack Table for data tables
|
||||
- Recharts for chart components
|
||||
- dnd-kit for drag and drop
|
||||
- date-fns for date formatting
|
||||
- Lucide React for icons
|
||||
|
||||
## File Structure
|
||||
```
|
||||
apps/web/src/
|
||||
app/[locale]/ # Locale-based routing
|
||||
(auth)/ # Protected routes with sidebar layout
|
||||
dashboard/ # Widget-based dashboard
|
||||
settings/ # profile/, preferences/, security/, notifications/
|
||||
admin/ # users/, departments/, integrations/ - role protected
|
||||
lean/ # 3s-planning/, morning-meeting/, skill-matrix/
|
||||
hr/ # employees/, time-tracking/, absences/
|
||||
integrations/ # Overview + [type]/ dynamic routes
|
||||
login/ # Public login page
|
||||
components/
|
||||
ui/ # shadcn/ui + DataTable, Badge, Tabs, Switch, etc.
|
||||
layout/ # Sidebar (collapsible sub-nav), Header
|
||||
dashboard/ # Widget system (container, grid, registry)
|
||||
widgets/ # clock, welcome, quick-actions, stats, calendar, activity
|
||||
integrations/ # orders, chat, tasks, tickets, files, documents
|
||||
integrations/ # Shared: status-badge, integration-card, connection-test-button
|
||||
charts/ # bar-chart, line-chart, pie-chart, chart-container
|
||||
providers/ # SessionProvider, ThemeProvider, QueryProvider
|
||||
hooks/ # use-toast, use-media-query, use-mounted
|
||||
integrations/ # use-orders, use-messages, use-tasks, etc.
|
||||
lib/ # utils, api, auth, motion variants
|
||||
stores/ # sidebar-store, dashboard-store
|
||||
types/ # User, UserRole, Department, WidgetConfig, integrations.ts
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### 1. Server/Client Component Split
|
||||
- `page.tsx` = Server Component (metadata only)
|
||||
- `*-content.tsx` = Client Component (receives locale prop)
|
||||
|
||||
### 2. Widget System (Phase 2)
|
||||
- Registry pattern: `widget-registry.ts` defines all widget types
|
||||
- Drag & drop: dnd-kit with SortableContext
|
||||
- Persistence: Zustand store with localStorage
|
||||
- Each widget: WidgetContainer wrapper + specific content
|
||||
|
||||
### 3. Role-Based Navigation
|
||||
- NavItem has optional `requiredRoles: UserRole[]`
|
||||
- Sidebar filters items via `filterNavItems(items, userRoles)`
|
||||
- Roles: admin, manager, department_head, employee
|
||||
|
||||
### 4. DataTable Pattern
|
||||
- Generic component: `DataTable<TData, TValue>`
|
||||
- Use `DataTableColumnHeader` for sortable columns
|
||||
- Features: search, pagination, column visibility, row selection
|
||||
|
||||
### 5. Chart Components
|
||||
- Wrap Recharts in `ChartContainer` for consistent styling
|
||||
- Support loading states, empty states
|
||||
- Theme-aware colors via CSS variables
|
||||
|
||||
### 6. Integration Hooks (Phase 3)
|
||||
- TanStack Query with 30s staleTime
|
||||
- Mock data for development, TODO comments for API replacement
|
||||
- Query key factories: `ordersKeys`, `messagesKeys`, etc.
|
||||
- Optimistic updates for complete/toggle actions
|
||||
|
||||
### 7. Integration Widgets
|
||||
- Widget categories: `integrations` added to registry
|
||||
- requiredRoles: ['manager', 'admin'] for sensitive widgets
|
||||
- Each widget uses WidgetContainer + specific hook
|
||||
|
||||
## Sidebar Behavior
|
||||
- Expanded: 240px, Collapsed: 64px
|
||||
- Collapsible sub-navigation with ChevronDown animation
|
||||
- Tooltip shows sub-nav when collapsed
|
||||
|
||||
## i18n Keys
|
||||
- Flat structure with nested objects: `widgets.clock.name`
|
||||
- Use ASCII for German (oe, ae, ue) to avoid encoding issues
|
||||
|
||||
## Dependencies Added (Phase 2)
|
||||
- @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
|
||||
- @tanstack/react-table
|
||||
- @radix-ui/react-checkbox, collapsible, popover, select, tabs
|
||||
- recharts, date-fns
|
||||
|
||||
## Dependencies Added (Phase 3)
|
||||
- @radix-ui/react-switch (for Switch component)
|
||||
|
||||
## Integration Types (Phase 3+)
|
||||
- `plenty-one`: PlentyONE (e-commerce) - OrdersWidget
|
||||
- `zulip`: ZULIP (chat) - ChatWidget
|
||||
- `todoist`: Todoist (tasks) - TasksWidget
|
||||
- `freescout`: FreeScout (helpdesk) - TicketsWidget
|
||||
- `nextcloud`: Nextcloud (files) - FilesWidget
|
||||
- `ecodms`: ecoDMS (documents) - DocumentsWidget
|
||||
- `gembadocs`: GembaDocs (audits/compliance) - GembaDocsWidget
|
||||
|
||||
## LEAN Module Structure
|
||||
|
||||
### File Organization
|
||||
```
|
||||
apps/web/src/
|
||||
app/[locale]/(auth)/lean/
|
||||
page.tsx # LEAN overview
|
||||
s3-planning/
|
||||
page.tsx # S3 plans overview
|
||||
[departmentId]/page.tsx # Department detail view
|
||||
morning-meeting/
|
||||
page.tsx # Meetings overview + calendar
|
||||
morning-meeting-overview-content.tsx
|
||||
[departmentId]/
|
||||
page.tsx # Department SQCDM board
|
||||
department-meeting-content.tsx
|
||||
skill-matrix/
|
||||
page.tsx # Overview with department cards
|
||||
skill-matrix-overview-content.tsx
|
||||
[departmentId]/
|
||||
page.tsx # Department matrix grid + gap analysis
|
||||
department-skill-matrix-content.tsx
|
||||
components/lean/
|
||||
s3/ # S3 Planning components
|
||||
index.ts # Barrel export
|
||||
s3-status-cell.tsx # Colored week cell
|
||||
s3-status-modal.tsx # Status edit dialog
|
||||
s3-category-card.tsx # Category with week grid
|
||||
s3-progress-chart.tsx # Pie chart stats
|
||||
s3-plan-overview.tsx # Plans list with filters
|
||||
s3-department-view.tsx # Week calendar view
|
||||
morning-meeting/ # Morning Meeting components
|
||||
index.ts # Barrel export
|
||||
meeting-board.tsx # Main board with all 5 SQCDM columns
|
||||
sqcdm-column.tsx # Single S/Q/C/D/M column
|
||||
kpi-card.tsx # KPI with traffic light + trend
|
||||
meeting-timer.tsx # Timer with start/stop, countdown mode
|
||||
action-list.tsx # Action items container with filters
|
||||
action-item.tsx # Single action with status/assignee
|
||||
hooks/lean/
|
||||
index.ts # Barrel export
|
||||
use-s3-plans.ts # Plans CRUD + types
|
||||
use-s3-status.ts # Status updates
|
||||
use-meetings.ts # Meeting CRUD + topics/actions
|
||||
use-meeting-timer.ts # Timer logic (elapsed/countdown)
|
||||
```
|
||||
|
||||
### S3 Status Types & Colors
|
||||
- `NOT_APPLICABLE` (gray): Not started
|
||||
- `YELLOW`: In progress
|
||||
- `GREEN`: Completed
|
||||
- `RED`: Problem/Issue
|
||||
|
||||
### S3 Type Categories
|
||||
- `SEIRI`: Sort
|
||||
- `SEITON`: Set in Order
|
||||
- `SEISO`: Shine
|
||||
|
||||
### API Endpoints (Backend)
|
||||
- `GET /lean/s3/plans` - List with filters (year, month, departmentId)
|
||||
- `GET /lean/s3/plans/:id` - Single plan with categories/statuses
|
||||
- `PUT /lean/s3/status/:id` - Update status entry
|
||||
|
||||
### Skill Matrix Module
|
||||
```
|
||||
components/lean/skill-matrix/
|
||||
index.ts # Barrel export
|
||||
skill-level-badge.tsx # Level badge with color coding (0-4)
|
||||
skill-cell.tsx # Matrix cell with quick-edit popover
|
||||
skill-matrix-grid.tsx # Full grid (employees x skills)
|
||||
skill-gap-chart.tsx # Bar chart for gap analysis
|
||||
|
||||
hooks/lean/
|
||||
use-skills.ts # Skills CRUD + categories
|
||||
use-skill-matrix.ts # Matrix data + gap analysis
|
||||
```
|
||||
|
||||
### Skill Level Colors
|
||||
- `0` (gray): No knowledge
|
||||
- `1` (red): Basics
|
||||
- `2` (yellow): Independent
|
||||
- `3` (green): Expert
|
||||
- `4` (blue): Can train
|
||||
|
||||
### Skill Matrix API Endpoints (Backend)
|
||||
- `GET /lean/skills` - List with filters
|
||||
- `GET /lean/skills/department/:id` - Skills for department
|
||||
- `GET /lean/skill-matrix/:departmentId` - Full matrix
|
||||
- `GET /lean/skill-matrix/gaps/:departmentId` - Gap analysis
|
||||
- `POST /lean/skill-matrix/entries` - Create entry
|
||||
- `PUT /lean/skill-matrix/entries/:id` - Update entry
|
||||
- `POST /lean/skill-matrix/entries/bulk` - Bulk upsert
|
||||
|
||||
## HR Module (Phase 5)
|
||||
|
||||
### File Structure
|
||||
```
|
||||
apps/web/src/
|
||||
app/[locale]/(auth)/hr/
|
||||
page.tsx # HR overview with stats
|
||||
hr-overview-content.tsx # Client component
|
||||
employees/
|
||||
page.tsx # Employee list
|
||||
employees-content.tsx
|
||||
[id]/
|
||||
page.tsx # Employee details
|
||||
employee-detail-content.tsx
|
||||
new/
|
||||
page.tsx # New employee form
|
||||
new-employee-content.tsx
|
||||
org-chart/
|
||||
page.tsx # Organization chart
|
||||
org-chart-content.tsx
|
||||
components/hr/employees/
|
||||
index.ts # Barrel export
|
||||
employee-card.tsx # Quick overview card
|
||||
employee-list.tsx # DataTable with filters
|
||||
employee-form.tsx # Create/edit form
|
||||
org-chart.tsx # Hierarchical tree view
|
||||
hooks/hr/
|
||||
index.ts # Barrel export
|
||||
use-employees.ts # CRUD + types + mock data
|
||||
```
|
||||
|
||||
### Employee Types
|
||||
- `EmploymentStatus`: active, inactive, on_leave, terminated
|
||||
- `ContractType`: full_time, part_time, mini_job, intern, trainee, freelance
|
||||
- Full Employee interface with address, emergency contact
|
||||
|
||||
### Employee Status Colors
|
||||
- `active` (green)
|
||||
- `inactive` (gray)
|
||||
- `on_leave` (yellow)
|
||||
- `terminated` (red)
|
||||
|
||||
### i18n Keys (hr namespace)
|
||||
- `hr.title`, `hr.description`
|
||||
- `hr.employees.*` - List/detail pages
|
||||
- `hr.stats.*` - Dashboard statistics
|
||||
- `hr.employeeStatus.*` - Status translations
|
||||
- `hr.contractType.*` - Contract type translations
|
||||
- `hr.form.*` - Form sections
|
||||
- `hr.tabs.*` - Detail view tabs
|
||||
- `hr.toast.*` - Toast notifications
|
||||
|
||||
### Dependencies Added (Phase 5)
|
||||
- react-day-picker (for Calendar component)
|
||||
- react-hook-form + zod (already present)
|
||||
- @radix-ui/react-progress (for Progress component)
|
||||
|
||||
## HR Time Tracking Module (Phase 5)
|
||||
|
||||
### File Structure
|
||||
```
|
||||
apps/web/src/
|
||||
app/[locale]/(auth)/hr/
|
||||
time-tracking/
|
||||
page.tsx # Time tracking overview
|
||||
time-tracking-content.tsx # Time clock + entries
|
||||
[employeeId]/
|
||||
page.tsx # Employee time account
|
||||
employee-time-account-content.tsx
|
||||
absences/
|
||||
page.tsx # Absences overview
|
||||
absences-content.tsx # Balance + requests
|
||||
calendar/
|
||||
page.tsx # Team calendar view
|
||||
absence-calendar-content.tsx
|
||||
requests/
|
||||
page.tsx # Manager approval view
|
||||
absence-requests-content.tsx
|
||||
components/hr/
|
||||
time-tracking/
|
||||
index.ts # Barrel export
|
||||
time-clock.tsx # Web clock (clock in/out, breaks)
|
||||
time-entry-list.tsx # List of time entries
|
||||
time-entry-form.tsx # Correction request dialog
|
||||
time-summary.tsx # Monthly summary with progress
|
||||
absences/
|
||||
index.ts # Barrel export
|
||||
absence-calendar.tsx # Monthly calendar with absences
|
||||
absence-request-form.tsx # Create absence request dialog
|
||||
absence-card.tsx # Single absence with status
|
||||
absence-approval-list.tsx # Pending requests for managers
|
||||
vacation-balance.tsx # Vacation quota display
|
||||
hooks/hr/
|
||||
use-time-tracking.ts # Clock in/out, entries, summary
|
||||
use-absences.ts # Requests, balance, calendar
|
||||
types/hr.ts # TimeEntry, Absence, VacationBalance types
|
||||
```
|
||||
|
||||
### Time Tracking Types
|
||||
- `TimeEntryStatus`: CLOCKED_IN, ON_BREAK, CLOCKED_OUT
|
||||
- `TimeEntryType`: REGULAR, OVERTIME, CORRECTED
|
||||
- `TIME_STATUS_INFO`: Color mapping for status badges
|
||||
|
||||
### Absence Types
|
||||
- `AbsenceType`: VACATION, SICK, HOME_OFFICE, SPECIAL_LEAVE, UNPAID_LEAVE, TRAINING
|
||||
- `AbsenceRequestStatus`: PENDING, APPROVED, REJECTED, CANCELLED
|
||||
- `ABSENCE_TYPE_INFO`: Color + icon mapping
|
||||
- `ABSENCE_STATUS_INFO`: Status color mapping
|
||||
|
||||
### i18n Keys (hr namespace)
|
||||
- `hr.timeTracking.*` - Clock, entries, summary
|
||||
- `hr.absences.*` - Balance, requests, calendar, approvals
|
||||
|
||||
## Related Files
|
||||
- [shared-package.md](./shared-package.md) - @tos/shared integration guide & differences
|
||||
- [component-patterns.md](./component-patterns.md) - Code examples
|
||||
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>
|
||||
);
|
||||
}
|
||||
```
|
||||
69
.claude/agent-memory/frontend-specialist/shared-package.md
Normal file
69
.claude/agent-memory/frontend-specialist/shared-package.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# @tos/shared Package Integration
|
||||
|
||||
## Package Location
|
||||
`packages/shared/` - Built with tsup, outputs CJS + ESM + types to `dist/`
|
||||
|
||||
## Available Exports
|
||||
|
||||
### Utils (`@tos/shared` or `@tos/shared/utils`)
|
||||
- `formatDate(date, options?)` - German locale, 2-digit format (dd.MM.yyyy)
|
||||
- `formatTime(date, options?)` - German locale, HH:mm
|
||||
- `formatDateTime(date)` - Combined date + time
|
||||
- `calculateWorkingDays(start, end)` - Excludes weekends
|
||||
- `minutesToHoursAndMinutes(mins)` - Returns { hours, minutes }
|
||||
- `formatMinutesToTime(mins)` - Returns "HH:MM" string
|
||||
- `getInitials(firstName, lastName)` - Two-arg version, returns "FL"
|
||||
- `getFullName(firstName, lastName)` - Returns "First Last"
|
||||
- `slugify(str)` - URL-safe slug
|
||||
- `isDefined<T>(value)` - Type guard for non-null/undefined
|
||||
- `sleep(ms)` - Promise-based delay
|
||||
|
||||
### Constants (`@tos/shared`)
|
||||
- `DEFAULT_VACATION_DAYS`, `MIN_VACATION_DAYS`
|
||||
- `STANDARD_WORKING_HOURS_PER_WEEK/DAY`, `MAX_WORKING_HOURS_PER_DAY`
|
||||
- `DEFAULT_BREAK_MINUTES`, `REQUIRED_BREAK_MINUTES`, `EXTENDED_BREAK_MINUTES`
|
||||
- `SKILL_LEVELS`, `S3_CATEGORIES`, `SQCDM_CATEGORIES`
|
||||
- `HTTP_STATUS`, `DATE_FORMATS`
|
||||
- `API_VERSION`, `API_PREFIX`, `DEFAULT_PAGE_SIZE`, `MAX_PAGE_SIZE`
|
||||
- `PERMISSIONS`, `ROLE_PERMISSIONS`
|
||||
|
||||
### Types (`@tos/shared` or `@tos/shared/types`)
|
||||
- `User`, `UserRole`, `UserPreferences`, `DashboardWidgetConfig`, `NotificationPreferences`
|
||||
- `CreateUserDto`, `UpdateUserDto`
|
||||
- `Department`, `DepartmentCode`, `DepartmentWithStats`, `CreateDepartmentDto`, `UpdateDepartmentDto`
|
||||
- `Employee`, `ContractType`, `TimeEntry`, `TimeEntryType`, `Absence`, `AbsenceType`, `ApprovalStatus`
|
||||
- `AuthUser`, `TokenPayload`, `LoginRequest`, `AuthResponse`, `Permission`
|
||||
- Skill Matrix: `Skill`, `SkillMatrixEntry`, `SkillMatrix`, `SkillGapAnalysis`, etc.
|
||||
|
||||
## Integration Patterns
|
||||
|
||||
### Web App (apps/web)
|
||||
- `transpilePackages: ['@tos/shared']` in next.config.mjs (transpiles source directly)
|
||||
- `"@tos/shared": "workspace:*"` in package.json
|
||||
- Import directly: `import { getInitials } from '@tos/shared'`
|
||||
|
||||
### API App (apps/api)
|
||||
- `"@tos/shared": "workspace:*"` in package.json
|
||||
- Uses built dist (CJS) - requires `pnpm --filter @tos/shared build` first
|
||||
- Import: `import { calculateWorkingDays, DEFAULT_VACATION_DAYS } from '@tos/shared'`
|
||||
|
||||
## Key Differences: Local vs Shared
|
||||
|
||||
### getInitials
|
||||
- **Shared**: `getInitials(firstName: string, lastName: string)` - two args
|
||||
- **Web local** (`@/lib/utils`): `getInitials(name: string)` - single full name string, splits on space
|
||||
- Both coexist; use shared in components with firstName/lastName, local in header with full name
|
||||
|
||||
### Date Formatting
|
||||
- **Shared**: `formatDate()` uses `{ day: '2-digit', month: '2-digit', year: 'numeric' }`, hardcoded `de-DE`
|
||||
- **Web local**: `formatDate()` uses `{ year: 'numeric', month: 'long', day: 'numeric' }`, accepts locale param
|
||||
- **Web local**: `formatDateShort()` is equivalent to shared `formatDate()` but accepts locale param
|
||||
- NOT interchangeable - different output format
|
||||
|
||||
### sleep
|
||||
- Identical in both - web local now re-exports from `@tos/shared`
|
||||
|
||||
### ApprovalStatus / ContractType
|
||||
- **Shared**: UPPERCASE (`'PENDING'`, `'FULL_TIME'`)
|
||||
- **Web types**: lowercase (`'pending'`, `'full_time'`)
|
||||
- NOT interchangeable - different casing convention
|
||||
Reference in New Issue
Block a user