26 KiB
Refactor Nyla UI - Analyse und Implementation Plan
Datum: 23. Januar 2026
Executive Summary
Die Seiten Chat Playground, Workflows, Automations, Prompts, Files und Connections im neuen UI (frontend_nyla) sind inkomplett implementiert. Sie nutzen:
- Eigene, vereinfachte Table-Komponenten statt des etablierten
FormGenerator-Systems - Fehlende Dashboard- und Chat-Elemente (PlaygroundPage)
- Keine RBAC-Berechtigungsprüfung
- Keine Backend-Pagination/Sorting/Filtering
- Keine standardisierten CRUD-Modals
Design-Prinzipien für die Implementation
WICHTIG: Das bestehende Look & Feel von Nyla MUSS beibehalten werden!
- Keine neuen Styles oder Design-Patterns einführen
- Bestehende CSS-Module und Komponenten-Styles wiederverwenden
- Nur fehlende Logik ergänzen, nicht das visuelle Design ändern
- Neue Komponenten müssen sich nahtlos in das bestehende Design einfügen
1. IST-Analyse
1.1 Chat Playground (PlaygroundPage.tsx)
Aktueller Zustand:
Die PlaygroundPage ist extrem vereinfacht und hat fast alle Funktionen aus dem alten UI (frontend_agents) verloren.
Was FEHLT (Vergleich zu frontend_agents/public/htmlparts/part_workflow.html):
| Element | Alt (frontend_agents) | Neu (frontend_nyla) | Ziel |
|---|---|---|---|
| Layout | 70/30 Spalten (Chat/Dashboard) | Single-Column, kein Dashboard | Resizable Spalten mit Drag-Divider |
| Dashboard | Hierarchisches Dashboard mit Rounds → Operations → Logs | ❌ Komplett fehlt | Logik portieren, Nyla-Styles |
| Progress-Tracking | Progress-Bars pro Operation | ❌ Komplett fehlt | Logik portieren, Nyla-Styles |
| Collapse/Expand | Für Rounds, Operations, Logs | ❌ Komplett fehlt | Logik portieren |
| Prompt-Auswahl | Dropdown mit Prompts aus API | ❌ Fehlt | Bestehende DropdownSelect nutzen |
| Workflow-Modus | Dropdown mit Enum aus Backend | ❌ Fehlt | Bestehende DropdownSelect nutzen |
| Datei-Upload | Button + Drag & Drop, mehrere Dateien | ❌ Fehlt | Bestehende DragDropOverlay nutzen |
| Voice Recording | Mikrofon-Button mit STT | ❌ Fehlt | Logik portieren, Nyla Button-Styles |
| Stop/Reset Buttons | Stop, Reset, Refresh Tokens | Nur Läuft... Button | Bestehende Button-Komponenten |
| Data Statistics | Sent/Received/Time/Tokens | ❌ Fehlt | Logik portieren, Nyla-Styles |
| File Preview Modal | Vorschau, Download, Copy, TTS | ❌ Fehlt | Bestehende Popup-Komponente nutzen |
| Unified Content Area | Kombinierte Logs + Messages chronologisch | Getrennte Listen | Bestehende Log/Messages nutzen |
| Auto-Scroll | Automatisches Scrollen | ❌ Fehlt | Bestehende AutoScroll nutzen |
| Empty State | Mit Link zur Einführung | Einfacher Text | Nyla-Styles |
Vorhandene Hooks im frontend_nyla/src/hooks/playground/:
- useDashboardInputForm.ts
- useDashboardLogTree.ts
- useWorkflowLifecycle.ts
- useWorkflowOperations.ts
- useWorkflowPolling.ts
- useWorkflows.ts
- playgroundUtils.ts
Problem: Die Hooks sind vorhanden, aber die UI-Komponenten nutzen sie nicht vollständig!
1.2 Workflows, Automations, Prompts, Files, Connections
Aktueller Zustand:
Diese Seiten haben eigene, einfache Table-Implementierungen statt FormGeneratorTable.
Vergleich:
| Feature | AdminUsersPage (korrekt) | WorkflowsPage (falsch) |
|---|---|---|
FormGeneratorTable |
✅ Verwendet | ❌ Eigene <table> |
FormGeneratorForm |
✅ Für Create/Edit Modals | ❌ Keine Modals |
attributes vom Backend |
✅ Über Hook | ❌ Hardcoded Columns |
columns aus attributes |
✅ Dynamisch generiert | ❌ Hardcoded |
| Backend-Pagination | ✅ Via hookData | ❌ Client-seitig |
| Backend-Sorting | ✅ Via hookData | ❌ Client-seitig |
| Backend-Filtering | ✅ Via hookData | ❌ Client-seitig |
| Inline-Editing | ✅ Für Boolean-Felder | ❌ Nicht unterstützt |
| RBAC Permissions | ✅ permissions?.create !== 'n' |
❌ Keine Prüfung |
| Action Buttons | ✅ Standard + Custom | ❌ Nur Delete |
| Loading State | ✅ Overlay | ❌ Einfacher Text |
| Empty State | ✅ Mit Icon und CTA | ❌ Einfacher Text |
| Error State | ✅ Mit Retry-Button | ❌ Einfacher Text |
2. Vorhandene Ressourcen im frontend_nyla
2.1 FormGenerator-System (vollständig implementiert)
Komponenten:
FormGeneratorTable- Tabelle mit Pagination, Sorting, FilteringFormGeneratorForm- Backend-driven FormulareFormGeneratorList- ListenansichtFormGeneratorControls- Such- und Filter-Controls- Action Buttons: Edit, Delete, View, Copy, Custom
Features:
- FK-Resolution (Foreign Keys anzeigen als Labels)
- TextMultilingual-Support
- Inline-Editing für Boolean-Felder
- Column-Resizing mit LocalStorage-Persistenz
- Multi-Column-Sorting
2.2 Vorhandene Hooks
Workflows:
useWorkflows.ts-useUserWorkflows(),useWorkflowOperations()
Automations:
useAutomations.ts-useAutomations(),useAutomationOperations()
Prompts:
usePrompts.ts-usePrompts(),usePromptOperations()
Files:
useFiles.ts-useUserFiles(),useFileOperations()
Connections:
useConnections.ts-useConnections()(vollständig)
Playground-spezifisch:
hooks/playground/useDashboardInputForm.tshooks/playground/useDashboardLogTree.tshooks/playground/useWorkflowLifecycle.tshooks/playground/useWorkflowOperations.tshooks/playground/useWorkflowPolling.ts
2.3 Bestehende Nyla UI-Komponenten (WIEDERVERWENDEN!)
WICHTIG: Diese Komponenten definieren das Nyla Look & Feel und sollten bei der Implementation wiederverwendet werden!
UiComponents (in components/UiComponents/):
Button/- Standard-Buttons, CreateButton, UploadButtonTextField/- Input-FelderDropdownSelect/- Select-KomponenteDragDropOverlay/- Für File-Upload mit Drag & DropLog/- Log-Darstellung mit LogMessageMessages/- Chat-NachrichtenPopup/- Modal-DialogeToast/- NotificationsTabs/- Tab-NavigationAutoScroll/- Auto-Scroll-Funktionalität
Diese Komponenten-Styles übernehmen:
- Farben (CSS-Variablen)
- Typografie
- Abstände/Padding
- Border-Radien
- Hover-/Focus-States
3. Implementation Plan
Phase 1: Chat Playground (Priorität HOCH)
3.1.1 Layout-Refactoring
Datei: frontend_nyla/src/pages/workflows/PlaygroundPage.tsx
Design-Prinzip: Bestehendes Nyla Look & Feel beibehalten, nur fehlende Logik ergänzen!
- Resizable Spalten-Layout mit Drag-Divider:
Statt eines fixen 70/30-Layouts wird ein dynamisch anpassbares Layout implementiert:
- Default: 70% Chat / 30% Dashboard
- Benutzer kann die Trennlinie mit der Maus verschieben
- Min/Max-Grenzen: Chat min 40%, Dashboard min 20%
- Spaltenbreite wird in LocalStorage persistiert
import { useResizablePanels } from '../../hooks/useResizablePanels';
const PlaygroundPage: React.FC = () => {
// Hook für resizable panels mit LocalStorage-Persistenz
const {
leftWidth, // in Prozent (default 70)
isDragging,
handleMouseDown, // Auf Divider anwenden
} = useResizablePanels({
storageKey: 'playground-panel-width',
defaultLeftWidth: 70,
minLeftWidth: 40,
maxLeftWidth: 80,
});
return (
<div className={styles.playgroundContainer}>
<div className={styles.chatSection}>
<div className={styles.chatColumns}>
{/* Links - Chat Messages (dynamische Breite) */}
<div
className={styles.chatLeft}
style={{ width: `${leftWidth}%` }}
>
<UnifiedContentArea />
</div>
{/* Resizable Divider */}
<div
className={`${styles.resizeDivider} ${isDragging ? styles.dragging : ''}`}
onMouseDown={handleMouseDown}
>
<div className={styles.dividerHandle} />
</div>
{/* Rechts - Dashboard (dynamische Breite) */}
<div
className={styles.chatRight}
style={{ width: `${100 - leftWidth}%` }}
>
<WorkflowDashboard />
</div>
</div>
</div>
<div className={styles.workflowFooter}>
<UserInputArea />
<DataStatistics />
</div>
</div>
);
};
3.1.1.1 Hook: useResizablePanels
Zu erstellen: frontend_nyla/src/hooks/useResizablePanels.ts
import { useState, useCallback, useEffect, useRef } from 'react';
interface UseResizablePanelsOptions {
storageKey: string;
defaultLeftWidth: number; // in %
minLeftWidth: number; // in %
maxLeftWidth: number; // in %
}
export const useResizablePanels = ({
storageKey,
defaultLeftWidth,
minLeftWidth,
maxLeftWidth,
}: UseResizablePanelsOptions) => {
// Initialer Wert aus LocalStorage oder Default
const [leftWidth, setLeftWidth] = useState<number>(() => {
const stored = localStorage.getItem(storageKey);
if (stored) {
const parsed = parseFloat(stored);
if (!isNaN(parsed) && parsed >= minLeftWidth && parsed <= maxLeftWidth) {
return parsed;
}
}
return defaultLeftWidth;
});
const [isDragging, setIsDragging] = useState(false);
const containerRef = useRef<HTMLElement | null>(null);
// Mouse-Event-Handler
const handleMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
setIsDragging(true);
containerRef.current = (e.target as HTMLElement).closest('.chatColumns');
}, []);
useEffect(() => {
if (!isDragging) return;
const handleMouseMove = (e: MouseEvent) => {
if (!containerRef.current) return;
const containerRect = containerRef.current.getBoundingClientRect();
const newLeftWidth = ((e.clientX - containerRect.left) / containerRect.width) * 100;
// Clamp zwischen min und max
const clampedWidth = Math.max(minLeftWidth, Math.min(maxLeftWidth, newLeftWidth));
setLeftWidth(clampedWidth);
};
const handleMouseUp = () => {
setIsDragging(false);
// In LocalStorage speichern
localStorage.setItem(storageKey, leftWidth.toString());
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, leftWidth, storageKey, minLeftWidth, maxLeftWidth]);
return {
leftWidth,
isDragging,
handleMouseDown,
setLeftWidth,
};
};
3.1.1.2 CSS für Resizable Divider
In Playground.module.css hinzufügen:
/* Resizable Divider - passend zum Nyla Design */
.resizeDivider {
width: 6px;
cursor: col-resize;
background-color: transparent;
position: relative;
flex-shrink: 0;
z-index: 10;
transition: background-color 0.15s ease;
}
.resizeDivider:hover,
.resizeDivider.dragging {
background-color: var(--color-border-hover, rgba(255, 255, 255, 0.1));
}
.dividerHandle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 4px;
height: 40px;
border-radius: 2px;
background-color: var(--color-text-muted, rgba(255, 255, 255, 0.3));
opacity: 0;
transition: opacity 0.15s ease;
}
.resizeDivider:hover .dividerHandle,
.resizeDivider.dragging .dividerHandle {
opacity: 1;
}
/* Verhindert Text-Selektion während Drag */
.chatColumns.dragging {
user-select: none;
}
3.1.2 Neue Komponenten erstellen
Design-Prinzip: Alle neuen Komponenten müssen das bestehende Nyla Look & Feel nutzen!
- Bestehende CSS-Variablen verwenden (aus
:rootoder Theme)- Vorhandene Button-/Input-Styles wiederverwenden
- Keine neuen Farbschemata oder Typografie einführen
- Komponenten-Styles in Module-CSS kapseln
Zu erstellen in frontend_nyla/src/components/Playground/:
| Komponente | Beschreibung | Logik basiert auf | Styles |
|---|---|---|---|
WorkflowDashboard.tsx |
Hierarchisches Dashboard | workflowUiRendererDashboard.js |
Nyla-Styles |
UnifiedContentArea.tsx |
Kombinierte Logs + Messages | workflowUiRenderer.js |
Nyla-Styles |
UserInputArea.tsx |
Prompt-Auswahl, Input, Buttons | part_workflow.html |
Bestehende Nyla Input-Styles |
DataStatistics.tsx |
Sent/Received/Time/Tokens | part_workflow.html |
Nyla-Styles |
FilePreviewModal.tsx |
Datei-Vorschau | Existiert teilweise | Bestehende Modal-Styles |
WorkflowControls.tsx |
Stop, Reset, Start Buttons | workflowUiControls.js |
Bestehende Button-Styles |
Wichtig: Diese Komponenten sollen die Logik aus dem alten frontend_agents übernehmen,
aber das visuelle Design von Nyla beibehalten. Das heisst:
- Hooks und State-Management aus
frontend_agentsportieren - CSS-Styles aus den bestehenden Nyla-Komponenten verwenden/erweitern
3.1.3 Dashboard Implementation
State-Struktur:
interface DashboardLogTree {
rounds: Map<number, {
operations: Map<string, Operation>;
rootOperations: string[];
expanded: boolean;
isCompleted: boolean;
}>;
operations: Map<string, {
logs: Map<string, LogEntry>;
parentId: string | null;
expanded: boolean;
latestProgress: number | null;
latestStatus: string | null;
roundNumber: number;
}>;
logExpandedStates: Map<string, boolean>;
currentRound: number | null;
}
Verwendung von existierendem Hook:
import { useDashboardLogTree } from '../../hooks/playground/useDashboardLogTree';
const {
dashboardLogTree,
processDashboardLogs,
toggleRoundExpanded,
toggleOperationExpanded,
clearDashboard,
} = useDashboardLogTree();
3.1.4 Input-Bereich Implementation
Neue Elemente:
- Prompt-Dropdown (aus
usePrompts()) - Workflow-Modus-Dropdown (aus
/api/options/workflow.mode) - File-Upload mit Drag & Drop
- Voice-Recording-Button
- Stop/Reset/Start-Buttons
Phase 2-6: Data-Seiten (Workflows, Automations, Prompts, Files, Connections)
Design-Prinzip für alle Data-Seiten:
FormGeneratorTableundFormGeneratorFormverwenden (bereits Nyla-konform)- Bestehende Page-Header-Styles aus Admin-Seiten übernehmen
- Keine neuen Layouts/Designs einführen
- Nur fehlende Hooks/Logik ergänzen
Phase 2: Workflows Page (Priorität HOCH)
3.2.1 Hook erweitern
Datei: frontend_nyla/src/hooks/useWorkflows.ts
Erweitern um:
export const useWorkflowsAdmin = () => {
const { data, loading, error, refetch } = useApi<WorkflowResponse>('/api/workflows/');
// Attributes vom Backend
const { data: attributesResponse } = useApi<AttributesResponse>('/api/attributes/ChatWorkflow');
return {
data: data?.items || [],
attributes: attributesResponse?.attributes || [],
columns: generateColumns(attributesResponse?.attributes),
permissions: data?.permissions,
pagination: data?.pagination,
loading,
error,
refetch,
// Operations
handleDelete: async (id: string) => { /* ... */ },
handleInlineUpdate: async (id: string, data: Partial<Workflow>) => { /* ... */ },
};
};
3.2.2 Page umschreiben
Datei: frontend_nyla/src/pages/workflows/WorkflowsPage.tsx
Nach Pattern von AdminUsersPage.tsx:
import { FormGeneratorTable } from '../../components/FormGenerator';
import { useWorkflowsAdmin } from '../../hooks/useWorkflows';
export const WorkflowsPage: React.FC = () => {
const {
data: workflows,
attributes,
columns,
permissions,
pagination,
loading,
error,
refetch,
handleDelete,
handleInlineUpdate,
} = useWorkflowsAdmin();
return (
<div className={styles.page}>
<PageHeader title="Workflows" onRefresh={refetch} />
<FormGeneratorTable
data={workflows}
columns={columns}
loading={loading}
pagination={true}
actionButtons={[
{ type: 'edit', onAction: handleContinue },
{ type: 'delete' },
]}
customActions={[
{
id: 'continue',
icon: <FaPlay />,
onClick: handleContinue,
title: 'Workflow fortsetzen',
}
]}
onDelete={handleDelete}
hookData={{
refetch,
permissions,
pagination,
handleDelete,
handleInlineUpdate,
}}
/>
</div>
);
};
Phase 3: Automations Page
Analog zu Workflows Page:
- Hook erweitern:
useAutomationsAdmin() - Page umschreiben mit
FormGeneratorTable - Custom Actions: Execute, Toggle Active
Phase 4: Prompts Page
Spezifische Anpassungen:
- Hook erweitern:
usePromptsAdmin() - Page umschreiben mit
FormGeneratorTable - Create/Edit Modal mit
FormGeneratorForm - Content-Feld als Textarea
Phase 5: Files Page
Spezifische Anpassungen:
- Hook erweitern:
useFilesAdmin() - Page umschreiben mit
FormGeneratorTable - Custom Actions: Download, Preview
- Upload-Button mit
UploadButton-Komponente
Phase 6: Connections Page
Spezifische Anpassungen:
- Hook erweitern:
useConnectionsAdmin() - Page umschreiben mit
FormGeneratorTable - Custom Actions: Connect, Refresh Token
- Provider-spezifische Buttons (Google, Microsoft)
4. Detaillierte Änderungsliste
4.1 Zu erstellende Dateien
frontend_nyla/src/
├── hooks/
│ └── useResizablePanels.ts # NEU: Hook für resizable panels
├── components/
│ └── Playground/
│ ├── index.ts
│ ├── Playground.module.css # NEU: Layout-Styles inkl. Divider
│ ├── WorkflowDashboard/
│ │ ├── index.ts
│ │ ├── WorkflowDashboard.tsx # Logik aus workflowUiRendererDashboard.js
│ │ ├── WorkflowDashboard.module.css
│ │ ├── DashboardRound.tsx
│ │ ├── DashboardOperation.tsx
│ │ └── DashboardProgressBar.tsx
│ ├── UnifiedContentArea/
│ │ ├── index.ts
│ │ ├── UnifiedContentArea.tsx # Logik aus workflowUiRenderer.js
│ │ ├── UnifiedContentArea.module.css
│ │ ├── MessageItem.tsx
│ │ └── LogItem.tsx
│ ├── UserInputArea/
│ │ ├── index.ts
│ │ ├── UserInputArea.tsx # Logik aus workflow.js + part_workflow.html
│ │ ├── UserInputArea.module.css
│ │ ├── PromptSelector.tsx
│ │ ├── WorkflowModeSelector.tsx
│ │ └── VoiceRecordButton.tsx
│ ├── WorkflowControls/
│ │ ├── index.ts
│ │ ├── WorkflowControls.tsx # Logik aus workflowUiControls.js
│ │ └── WorkflowControls.module.css
│ └── DataStatistics/
│ ├── index.ts
│ ├── DataStatistics.tsx
│ └── DataStatistics.module.css
Hinweis: Alle
.module.cssDateien sollen bestehende Nyla CSS-Variablen nutzen und sich nahtlos ins bestehende Design einfügen!
4.2 Zu ändernde Dateien
| Datei | Änderung |
|---|---|
pages/workflows/PlaygroundPage.tsx |
Kompletter Rewrite mit neuen Komponenten |
pages/workflows/WorkflowsPage.tsx |
Umschreiben auf FormGeneratorTable |
pages/workflows/AutomationsPage.tsx |
Umschreiben auf FormGeneratorTable |
pages/basedata/PromptsPage.tsx |
Umschreiben auf FormGeneratorTable |
pages/basedata/FilesPage.tsx |
Umschreiben auf FormGeneratorTable |
pages/basedata/ConnectionsPage.tsx |
Umschreiben auf FormGeneratorTable |
hooks/useWorkflows.ts |
Admin-Hook hinzufügen |
hooks/useAutomations.ts |
Admin-Hook hinzufügen |
hooks/usePrompts.ts |
Admin-Hook hinzufügen |
hooks/useFiles.ts |
Admin-Hook hinzufügen |
hooks/useConnections.ts |
Admin-Hook hinzufügen |
4.3 CSS-Anpassungen
Neue CSS-Module:
Playground.module.css- HauptlayoutWorkflowDashboard.module.css- Dashboard-StylesUnifiedContentArea.module.css- Content-Styles
Bestehende zu aktualisieren:
WorkflowPages.module.css- Für alle Workflow-Seiten
5. API-Endpunkte (bereits vorhanden)
| Endpunkt | Verwendung |
|---|---|
/api/workflows/ |
Liste, CRUD |
/api/workflows/{id} |
Einzelner Workflow |
/api/workflows/{id}/chat-data |
Unified Chat-Daten |
/api/automations/ |
Liste, CRUD |
/api/prompts/ |
Liste, CRUD |
/api/files/ |
Liste, CRUD |
/api/connections/ |
Liste, CRUD |
/api/attributes/{EntityType} |
Attribute-Definitionen |
/api/options/workflow.mode |
Workflow-Modus-Enum |
6. Migrations-Checkliste
Chat Playground
useResizablePanelsHook erstellen- Resizable Spalten-Layout implementieren (mit Drag-Divider)
- WorkflowDashboard-Komponente erstellen (Logik aus frontend_agents, Nyla-Styles)
- UnifiedContentArea-Komponente erstellen (Logik aus frontend_agents, Nyla-Styles)
- UserInputArea mit allen Features erstellen (Nyla Input-Styles)
- DataStatistics-Komponente erstellen (Nyla-Styles)
- Polling integrieren (bestehende Hooks nutzen)
- File-Upload mit Drag & Drop (bestehende DragDropOverlay nutzen)
- Voice-Recording integrieren
- LocalStorage-Persistenz für Panel-Breite
Workflows Page
- Hook
useWorkflowsAdminerstellen - Page auf FormGeneratorTable umstellen
- Action Buttons konfigurieren
- RBAC-Permissions implementieren
Automations Page
- Hook
useAutomationsAdminerstellen - Page auf FormGeneratorTable umstellen
- Toggle Active implementieren
- Execute-Action implementieren
Prompts Page
- Hook
usePromptsAdminerstellen - Page auf FormGeneratorTable umstellen
- Create/Edit Modals mit FormGeneratorForm
- Content-Textarea korrekt anzeigen
Files Page
- Hook
useFilesAdminerstellen - Page auf FormGeneratorTable umstellen
- Download/Preview Actions
- Upload-Button integrieren
Connections Page
- Hook
useConnectionsAdminerstellen - Page auf FormGeneratorTable umstellen
- Connect/Refresh Actions
- Provider-spezifische Buttons
7. Schätzung Aufwand
| Phase | Aufwand | Priorität |
|---|---|---|
| Phase 1: Chat Playground | Hoch | KRITISCH |
| Phase 2: Workflows Page | Mittel | Hoch |
| Phase 3: Automations Page | Mittel | Hoch |
| Phase 4: Prompts Page | Niedrig | Mittel |
| Phase 5: Files Page | Niedrig | Mittel |
| Phase 6: Connections Page | Niedrig | Mittel |
8. Anhang: Pattern-Referenz
Standard-Page-Struktur
import React, { useState, useMemo } from 'react';
import { FormGeneratorTable, FormGeneratorForm } from '../../components/FormGenerator';
import { useEntityAdmin, useEntityOperations } from '../../hooks/useEntity';
import styles from './Page.module.css';
interface Entity {
id: string;
[key: string]: any;
}
export const EntityPage: React.FC = () => {
// Hooks
const {
data,
attributes,
columns,
permissions,
pagination,
loading,
error,
refetch,
fetchById,
handleInlineUpdate,
updateOptimistically,
} = useEntityAdmin();
const {
handleCreate,
handleUpdate,
handleDelete,
} = useEntityOperations();
// State
const [showCreateModal, setShowCreateModal] = useState(false);
const [editingItem, setEditingItem] = useState<Entity | null>(null);
// Permissions
const canCreate = permissions?.create !== 'n';
const canUpdate = permissions?.update !== 'n';
const canDelete = permissions?.delete !== 'n';
// Form Attributes
const formAttributes = useMemo(() => {
return (attributes || []).filter(attr => !['id'].includes(attr.name));
}, [attributes]);
// Handlers
const handleEditClick = async (item: Entity) => {
const fullItem = await fetchById(item.id);
if (fullItem) setEditingItem(fullItem);
};
const handleCreateSubmit = async (data: Partial<Entity>) => {
const result = await handleCreate(data);
if (result.success) {
setShowCreateModal(false);
refetch();
}
};
const handleEditSubmit = async (data: Partial<Entity>) => {
if (!editingItem) return;
const result = await handleUpdate(editingItem.id, data);
if (result.success) {
setEditingItem(null);
refetch();
}
};
// Render
return (
<div className={styles.page}>
<PageHeader
title="Entities"
onRefresh={refetch}
onCreate={canCreate ? () => setShowCreateModal(true) : undefined}
/>
<FormGeneratorTable
data={data}
columns={columns}
loading={loading}
pagination={true}
actionButtons={[
...(canUpdate ? [{ type: 'edit', onAction: handleEditClick }] : []),
...(canDelete ? [{ type: 'delete' }] : []),
]}
onDelete={handleDelete}
hookData={{
refetch,
permissions,
pagination,
handleDelete,
handleInlineUpdate,
updateOptimistically,
}}
/>
{/* Create Modal */}
{showCreateModal && (
<Modal onClose={() => setShowCreateModal(false)}>
<FormGeneratorForm
attributes={formAttributes}
mode="create"
onSubmit={handleCreateSubmit}
onCancel={() => setShowCreateModal(false)}
/>
</Modal>
)}
{/* Edit Modal */}
{editingItem && (
<Modal onClose={() => setEditingItem(null)}>
<FormGeneratorForm
attributes={formAttributes}
data={editingItem}
mode="edit"
onSubmit={handleEditSubmit}
onCancel={() => setEditingItem(null)}
/>
</Modal>
)}
</div>
);
};
9. Design-Konsistenz-Checklist
Bei jeder Implementation prüfen:
- CSS-Variablen: Werden bestehende Nyla CSS-Variablen verwendet? (Farben, Abstände, etc.)
- Komponenten: Werden bestehende UiComponents wiederverwendet wo möglich?
- Styles: Sind neue Styles konsistent mit bestehendem Nyla-Design?
- Keine Neuerungen: Wurden keine neuen Design-Patterns eingeführt?
- Dark/Light Mode: Funktioniert die Komponente in beiden Modi?
- Responsive: Funktioniert die Komponente auf verschiedenen Bildschirmgrössen?
Dokumentation erstellt am 23. Januar 2026 Aktualisiert: Resizable Panels und Design-Prinzipien hinzugefügt