onLayoutChange(allLayouts)}
draggableHandle=".widget-drag-handle"
>
{widgets.map((widget) => (
l.i === widget.id),
minW: WIDGET_CONSTRAINTS[widget.widgetType].minW,
minH: WIDGET_CONSTRAINTS[widget.widgetType].minH,
}}>
))}
);
}
```
### NestJS Dashboard Controller
```typescript
// Source: NestJS Documentation Pattern + Existing Codebase Pattern
@Controller('dashboard')
export class DashboardController {
constructor(private dashboardService: DashboardService) {}
@Get('layout')
async getLayout(@Req() req: Request) {
const userId = req.user.id;
const tenantId = req.tenantId;
return this.dashboardService.getLayout(userId, tenantId);
}
@Put('layout')
async saveLayout(
@Req() req: Request,
@Body() dto: SaveLayoutDto,
) {
const userId = req.user.id;
const tenantId = req.tenantId;
return this.dashboardService.saveLayout(userId, tenantId, dto);
}
@Get('widgets')
async getWidgets(@Req() req: Request) {
return this.dashboardService.getWidgets(req.user.id);
}
@Post('widgets')
async addWidget(
@Req() req: Request,
@Body() dto: CreateWidgetDto,
) {
return this.dashboardService.addWidget(req.user.id, req.tenantId, dto);
}
@Patch('widgets/:id/config')
async updateWidgetConfig(
@Req() req: Request,
@Param('id') id: string,
@Body() dto: UpdateWidgetConfigDto,
) {
return this.dashboardService.updateWidgetConfig(id, req.user.id, dto);
}
@Delete('widgets/:id')
async removeWidget(
@Req() req: Request,
@Param('id') id: string,
) {
return this.dashboardService.removeWidget(id, req.user.id);
}
}
```
### Calendar Source Configuration
```typescript
// Prisma Schema fuer Calendar Sources
model CalendarSource {
id String @id @default(uuid())
userId String
tenantId String
name String
type String // 'caldav' | 'ics' | 'exchange'
url String
username String?
encryptedPassword String? // AES-256 verschluesselt
color String? @default("#3B82F6")
isVisible Boolean @default(true)
syncIntervalMin Int @default(15)
lastSyncAt DateTime?
lastSyncError String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([tenantId])
}
```
### Markdown Note Widget with Autosave
```typescript
// Source: @uiw/react-md-editor docs + Autosave Pattern
'use client';
import { useState, useCallback, useRef, useEffect } from 'react';
import MDEditor, { commands } from '@uiw/react-md-editor';
const DEBOUNCE_MS = 1500;
// Kompakte Toolbar (D-16)
const NOTE_COMMANDS = [
commands.bold,
commands.italic,
commands.strikethrough,
commands.divider,
commands.unorderedListCommand,
commands.checkedListCommand,
commands.divider,
commands.link,
commands.code,
];
export function NoteWidget({ instanceId, config }: WidgetProps) {
const [value, setValue] = useState(config.content as string || '');
const timerRef = useRef