diff --git a/.claude/agents/architect-review.md b/.claude/agents/architect-review.md
new file mode 100644
index 0000000..e9bd2f8
--- /dev/null
+++ b/.claude/agents/architect-review.md
@@ -0,0 +1,31 @@
+---
+name: architect-review
+category: quality-security
+description: Reviews code changes for architectural consistency and patterns. Use PROACTIVELY after any structural changes, new services, or API modifications. Ensures SOLID principles, proper layering, and maintainability.
+---
+
+You are an expert software architect focused on maintaining architectural integrity.
+
+When invoked:
+1. Map changes within overall system architecture
+2. Verify adherence to established patterns and SOLID principles
+3. Analyze dependencies and check for circular references
+4. Evaluate abstraction levels and system modularity
+5. Identify potential scaling or maintenance issues
+
+Process:
+- Review service boundaries and responsibilities
+- Check data flow and coupling between components
+- Verify consistency with domain-driven design
+- Evaluate performance implications of decisions
+- Assess security boundaries and validation points
+
+Provide:
+- Architectural compliance assessment
+- Pattern adherence verification report
+- Dependency analysis with recommendations
+- Modularity and maintainability evaluation
+- Improvement suggestions with rationale
+- Risk assessment for architectural decisions
+
+Focus on long-term maintainability and system coherence.
\ No newline at end of file
diff --git a/.claude/agents/symfony-expert.md b/.claude/agents/symfony-expert.md
new file mode 100644
index 0000000..bd25c1f
--- /dev/null
+++ b/.claude/agents/symfony-expert.md
@@ -0,0 +1,77 @@
+# Symfony Full-Stack Development System Prompt
+
+You are an expert in Symfony, Vue.js, and modern full-stack web development technologies.
+
+## When invoked:
+
+* Analyze full-stack requirements and design Symfony API-first architecture
+* Build Symfony 6.4+/7.0+ backend with PHP 8.2+ features and modern patterns
+* Create Vue3 frontend with Composition API and TypeScript integration
+* Implement state management with Pinia and routing with Vue Router
+* Set up authentication flow with Symfony Security and JWT/API tokens
+* Establish development workflow with Symfony CLI, Webpack Encore, and modern tooling
+
+## Symfony Backend Process:
+
+* Design RESTful APIs with API Platform or custom controllers and serialization
+* Implement Doctrine entities with advanced relationships, repositories, and DQL
+* Apply service layer and repository patterns with dependency injection container
+* Set up authentication/authorization with Symfony Security, JWT tokens, and voters
+* Create database migrations with proper indexing, constraints, and type mapping
+* Implement asynchronous processing with Symfony Messenger and message handlers
+* Apply caching strategies using Symfony Cache (Redis, APCu, Filesystem adapters)
+* Use event-driven architecture with EventDispatcher, subscribers, and listeners
+* Utilize Symfony Validator for comprehensive data validation
+* Implement API versioning strategies and content negotiation
+
+## Vue3 Frontend Process:
+
+* Build components using Composition API with `
+```
+
+**CurrencyDisplay.vue** - Formatierte Währungsanzeige
+```vue
+
+
+ {{ formatted }}
+
+
+
+
+
+
+```
+
+**StatusBadge.vue** - Einheitliche Status-Anzeige
+```vue
+
+
+
+
+ {{ label }}
+
+
+
+
+
+```
+
+---
+
+## 11. Anhang
+
+### 11.1 Checkliste für Entwickler
+
+**Vor der Implementierung:**
+- [ ] Design-System Variablen verstanden
+- [ ] PrimeVue Komponenten-Dokumentation gelesen
+- [ ] Accessibility Guidelines durchgegangen
+- [ ] Responsive Breakpoints definiert
+
+**Während der Implementierung:**
+- [ ] Semantisches HTML verwenden
+- [ ] ARIA-Attribute hinzufügen
+- [ ] Keyboard-Navigation testen
+- [ ] Validierung implementieren
+- [ ] Loading States einbauen
+- [ ] Error Handling abdecken
+
+**Nach der Implementierung:**
+- [ ] Mit Screen Reader testen
+- [ ] Auf verschiedenen Bildschirmgrößen testen
+- [ ] Performance messen
+- [ ] Unit Tests schreiben
+- [ ] Code Review durchführen
+
+### 11.2 Browser-Kompatibilität
+
+**Mindestanforderungen:**
+- Chrome/Edge 90+
+- Firefox 88+
+- Safari 14+
+- Mobile Safari 14+
+- Chrome Android 90+
+
+**Polyfills für:**
+- Intl.NumberFormat (bereits vorhanden)
+- CSS Custom Properties (bereits unterstützt)
+
+### 11.3 Design-Assets
+
+**Icons:** PrimeIcons (pi-*)
+**Fonts:** System Font Stack
+```css
+font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ 'Helvetica Neue', Arial, sans-serif;
+```
+
+**Illustrations:** Optional für Empty States (z.B. unDraw)
+
+---
+
+## 12. Zusammenfassung & Nächste Schritte
+
+### Key Takeaways
+
+1. **Visuelle Hierarchie** durch klare Sektion-Trennung und konsistentes Spacing
+2. **Verbesserte Formular-UX** mit FloatLabels, Echtzeit-Validierung und besseren Fehler-Messages
+3. **Card-basierte Rechnungspositionen** statt komplexer Inline-DataTable
+4. **Echtzeit-Summenberechnung** für sofortiges Feedback
+5. **Accessibility-First** Ansatz mit ARIA-Labels und Keyboard-Support
+6. **Responsive Design** für alle Geräte optimiert
+
+### Quick Wins (sofort umsetzbar)
+
+1. FloatLabel für alle Formular-Felder
+2. Status-Tag in Formular-Header
+3. Verbesserte Kunden-Dropdown mit Icons
+4. Echtzeit-Summenberechnung
+5. Quick Amount Buttons im PaymentForm
+
+### Langfristige Verbesserungen
+
+1. Komplett neues Positions-Management mit Cards
+2. Drag & Drop PDF Upload
+3. Umfassendes Keyboard-Shortcut System
+4. Vollständige WCAG AAA Konformität
+5. Progressive Web App Features
+
+---
+
+**Dokument-Version:** 1.0
+**Letztes Update:** 2025-12-12
+**Autor:** Claude (UX Designer Agent)
+**Projekt:** myCRM Billing Module
diff --git a/docs/design/README.md b/docs/design/README.md
new file mode 100644
index 0000000..462f2f9
--- /dev/null
+++ b/docs/design/README.md
@@ -0,0 +1,448 @@
+# Invoice Form Design Documentation
+**myCRM Billing Module - UX/UI Design System**
+
+---
+
+## Übersicht
+
+Diese Design-Dokumentation enthält eine umfassende UX/UI-Analyse und Verbesserungsvorschläge für das Invoice-Formular im myCRM Billing-Modul. Die Dokumentation folgt professionellen UI/UX-Design-Standards und berücksichtigt Accessibility-Anforderungen nach WCAG 2.1 AA/AAA.
+
+---
+
+## Dokumenten-Struktur
+
+### 1. Hauptdokumentation
+
+**[INVOICE_FORM_UX_DESIGN.md](./INVOICE_FORM_UX_DESIGN.md)**
+- Vollständige UX/UI-Analyse des bestehenden Invoice-Formulars
+- Identifizierte Probleme und Verbesserungspotenziale (P0, P1, P2)
+- Design-Prinzipien und Konzepte
+- Detaillierte Komponentenspezifikationen
+- Accessibility-Guidelines (WCAG 2.1 AA/AAA)
+- Implementierungs-Roadmap (6 Phasen)
+- Testing-Strategie
+
+**Umfang:** ~350 Zeilen | Geschätzte Lesezeit: 30-40 Minuten
+
+### 2. Visuelle Design-Referenz
+
+**[VISUAL_DESIGN_GUIDE.md](./VISUAL_DESIGN_GUIDE.md)**
+- Farbpalette und Kontraste
+- Typography-System
+- Spacing-System (4px Base Unit)
+- Komponent-Anatomie mit ASCII-Diagrammen
+- Interaktive States (Buttons, Inputs, etc.)
+- Animation und Transitions
+- Responsive Breakpoints
+- Iconographie
+- Error Handling Patterns
+- Loading & Empty States
+- Print Styles
+
+**Umfang:** ~500 Zeilen | Geschätzte Lesezeit: 20-25 Minuten
+
+### 3. Referenz-Implementierung
+
+**[invoice-form-improved.vue](./invoice-form-improved.vue)**
+- Vollständige Vue.js 3 Komponente mit allen Verbesserungen
+- PrimeVue Komponenten-Integration
+- Composition API Pattern
+- Verwendung von Composables (useFormValidation, useKeyboardShortcuts)
+- Accessibility-Features (ARIA-Labels, Focus Management)
+- Responsive Design
+- Animations & Transitions
+
+**Umfang:** ~600 Zeilen | Typ: Vue SFC (Single File Component)
+
+### 4. CSS Design System
+
+**[invoice-form-styles.css](./invoice-form-styles.css)**
+- CSS Custom Properties (Design Tokens)
+- Komponent-spezifische Styles
+- Layout-Utilities
+- Animation Keyframes
+- Responsive Media Queries
+- Dark Mode Support
+- Accessibility Utilities (sr-only, focus-visible)
+- Print Styles
+
+**Umfang:** ~800 Zeilen | Typ: CSS
+
+### 5. Composables
+
+#### **[useFormValidation.js](./composables/useFormValidation.js)**
+- Wiederverwendbare Form-Validierungslogik
+- Echtzeit-Validierung
+- Feld-spezifische Regeln
+- Error-Tracking
+- Vordefinierte Validierungsregeln (email, phone, IBAN, etc.)
+
+**Umfang:** ~400 Zeilen | Typ: JavaScript Composable
+
+#### **[useKeyboardShortcuts.js](./composables/useKeyboardShortcuts.js)**
+- Keyboard-Shortcut-Management
+- Modifier-Key-Support (Ctrl, Shift, Alt)
+- Conflict-Detection
+- Platform-spezifische Hints (Mac vs. Windows)
+- Preset-Shortcuts für gängige Aktionen
+
+**Umfang:** ~400 Zeilen | Typ: JavaScript Composable
+
+---
+
+## Quick Start Guide
+
+### Für Designer
+
+1. **Beginnen Sie mit:** [INVOICE_FORM_UX_DESIGN.md](./INVOICE_FORM_UX_DESIGN.md) - Executive Summary
+2. **Dann:** [VISUAL_DESIGN_GUIDE.md](./VISUAL_DESIGN_GUIDE.md) - Farbpalette & Typography
+3. **Zuletzt:** Komponent-Anatomie-Diagramme für Implementierungs-Übergabe
+
+### Für Entwickler
+
+1. **Beginnen Sie mit:** [invoice-form-improved.vue](./invoice-form-improved.vue) - Referenz-Implementierung
+2. **Dann:** [invoice-form-styles.css](./invoice-form-styles.css) - Styling-Referenz
+3. **Integrieren Sie:** Composables aus `./composables/` für Validierung und Shortcuts
+4. **Referenzieren Sie:** [INVOICE_FORM_UX_DESIGN.md](./INVOICE_FORM_UX_DESIGN.md) - Implementierungs-Roadmap
+
+### Für Product Owner
+
+1. **Beginnen Sie mit:** [INVOICE_FORM_UX_DESIGN.md](./INVOICE_FORM_UX_DESIGN.md) - Executive Summary & Problemanalyse
+2. **Dann:** Implementierungs-Roadmap (Phase 1-6)
+3. **Priorisieren Sie:** Nach P0 (kritisch), P1 (mittel), P2 (niedrig) Tags
+
+---
+
+## Key Features des Design-Konzepts
+
+### UX-Verbesserungen
+
+- **Klarere visuelle Hierarchie** durch Sektionierung und konsistentes Spacing
+- **Card-basierte Rechnungspositionen** statt komplexer DataTable
+- **Echtzeit-Summenberechnung** mit sofortigem Feedback
+- **Verbesserte Validierung** mit Inline-Feedback
+- **Empty States** für bessere Benutzerführung
+- **Context Cards** für kontextuelle Informationen (Payment Form)
+- **Drag & Drop** für PDF-Upload
+
+### Accessibility (WCAG 2.1)
+
+- **Keyboard Navigation** mit Focus-Trapping und Shortcuts
+- **ARIA-Labels** für Screen Reader Support
+- **Farbkontrast** WCAG AA/AAA konform
+- **Focus Management** mit sichtbaren Focus-States
+- **Live Regions** für dynamische Updates
+
+### Performance
+
+- **Lazy Loading** von Formular-Komponenten
+- **Debouncing** für Berechnungen
+- **Virtual Scrolling** für lange Listen
+- **Code Splitting** für optimale Bundle-Größen
+
+### Responsive Design
+
+- **Mobile First** Ansatz
+- **Touch-friendly** Elemente (min. 44px)
+- **Adaptive Layouts** für alle Bildschirmgrößen
+- **Sticky Summary** auf Mobile für bessere Übersicht
+
+---
+
+## Implementierungs-Phasen
+
+### Phase 1: Foundation (Woche 1-2)
+**Priorität:** P0 (Kritisch)
+- Styling-System aufsetzen
+- Formular-Layout refactoren
+- FloatLabel implementieren
+- Validation Composable erstellen
+
+**Geschätzter Aufwand:** 16-20 Stunden
+
+### Phase 2: Invoice Items Redesign (Woche 3)
+**Priorität:** P0 (Kritisch)
+- Card-basierte Positions-Ansicht
+- Echtzeit-Summenberechnung
+- Transitions für Add/Remove
+- Empty State Design
+
+**Geschätzter Aufwand:** 12-16 Stunden
+
+### Phase 3: Enhanced Forms (Woche 4)
+**Priorität:** P1 (Mittel)
+- PaymentForm Context Card
+- PDF Upload Drag & Drop
+- Quick Amount Buttons
+- Keyboard Shortcuts
+
+**Geschätzter Aufwand:** 10-14 Stunden
+
+### Phase 4: UX Polish (Woche 5)
+**Priorität:** P1 (Mittel)
+- Loading States & Skeletons
+- Micro-Interactions & Animations
+- Toast-Benachrichtigungen
+- Error Handling
+
+**Geschätzter Aufwand:** 8-12 Stunden
+
+### Phase 5: Accessibility (Woche 6)
+**Priorität:** P0 (Kritisch)
+- ARIA Labels hinzufügen
+- Focus Management
+- Keyboard Navigation
+- Screen Reader Testing
+
+**Geschätzter Aufwand:** 10-14 Stunden
+
+### Phase 6: Testing & Optimization (Woche 7)
+**Priorität:** P1 (Mittel)
+- Unit Tests schreiben
+- E2E Tests implementieren
+- Performance Profiling
+- Code Splitting optimieren
+
+**Geschätzter Aufwand:** 12-16 Stunden
+
+**Gesamt:** 68-92 Stunden (ca. 2-2.5 Monate für einen Entwickler)
+
+---
+
+## Design-Prinzipien
+
+### 1. Progressive Disclosure
+Zeige nur relevante Informationen zum richtigen Zeitpunkt. Komplexität wird schrittweise enthüllt.
+
+**Beispiel:** Notizen-Sektion standardmäßig kollabiert, Summary Box nur wenn Items vorhanden.
+
+### 2. Immediate Feedback
+Sofortige Rückmeldung auf Benutzeraktionen. Keine verzögerten oder fehlenden Bestätigungen.
+
+**Beispiel:** Echtzeit-Berechnung der Zwischensummen beim Eingeben, Toast-Benachrichtigungen nach Aktionen.
+
+### 3. Error Prevention
+Validierung bevor Fehler entstehen. Eingaben werden direkt beim Tippen geprüft.
+
+**Beispiel:** Inline-Validierung, Min-Date beim Fälligkeitsdatum verhindert ungültige Daten.
+
+### 4. Consistency
+Einheitliche Nutzung von PrimeVue-Komponenten, Farben, Spacing und Patterns.
+
+**Beispiel:** Alle Formularfelder nutzen FloatLabel, alle Buttons haben einheitliche Größen.
+
+### 5. Accessibility First
+Design und Implementierung berücksichtigen von Anfang an alle Nutzer, einschließlich Menschen mit Behinderungen.
+
+**Beispiel:** Keyboard Navigation, Screen Reader Support, ausreichende Kontraste.
+
+---
+
+## Technologie-Stack
+
+### Frontend Framework
+- **Vue.js 3** (Composition API)
+- **PrimeVue 4.x** (Aura Theme)
+- **Tailwind CSS v4** (mit PrimeUI Plugin)
+
+### Build & Dev Tools
+- **Webpack Encore**
+- **Symfony 7.1 LTS** (Backend)
+- **API Platform** (REST API)
+
+### Testing
+- **Vitest** (Unit Tests)
+- **Cypress/Playwright** (E2E Tests)
+- **axe DevTools** (Accessibility Testing)
+
+---
+
+## Browser-Kompatibilität
+
+### Unterstützte Browser
+- Chrome/Edge 90+
+- Firefox 88+
+- Safari 14+
+- Mobile Safari 14+
+- Chrome Android 90+
+
+### Polyfills
+- Intl.NumberFormat (bereits vorhanden)
+- CSS Custom Properties (nativ unterstützt)
+
+---
+
+## Design-Assets
+
+### Farbpalette
+Siehe [VISUAL_DESIGN_GUIDE.md - Section 1](./VISUAL_DESIGN_GUIDE.md#1-farbpalette--kontraste)
+
+### Icons
+**PrimeIcons** (pi-*) - Vollständige Icon-Bibliothek integriert
+- ~250 Icons verfügbar
+- SVG-basiert, skalierbar
+- Konsistenter Stil
+
+### Typography
+**System Font Stack** für optimale Performance:
+```css
+font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ 'Helvetica Neue', Arial, sans-serif;
+```
+
+### Illustrations
+Optional für Empty States: **unDraw** (lizenzfrei)
+
+---
+
+## Testing-Strategie
+
+### Unit Tests
+- Validierungs-Logik testen
+- Berechnungs-Funktionen testen
+- Composables isoliert testen
+
+### Integration Tests
+- Formular-Submission testen
+- API-Integration testen
+- State-Management testen
+
+### E2E Tests
+- Komplette User-Flows testen
+- Rechnungserstellung Ende-zu-Ende
+- Zahlungserfassung testen
+
+### Accessibility Tests
+- axe DevTools Audit
+- NVDA/VoiceOver Screen Reader Testing
+- Keyboard-Navigation manuell testen
+- Farbkontrast-Validierung
+
+### Performance Tests
+- Lighthouse Audit (Score > 90)
+- Bundle-Size-Analyse
+- Time to Interactive < 3s
+- First Contentful Paint < 1.5s
+
+---
+
+## Häufige Fragen (FAQ)
+
+### Warum FloatLabel statt normale Labels?
+
+**Antwort:** FloatLabel bietet mehrere Vorteile:
+- Moderneres, aufgeräumteres Design
+- Platzsparend (kein separater Label-Space)
+- Bessere visuelle Hierarchie
+- Klarer Fokus-Zustand
+- Bereits in PrimeVue integriert
+
+### Warum Cards statt DataTable für Invoice Items?
+
+**Antwort:** Cards bieten bessere UX:
+- Einfacheres Inline-Editing
+- Bessere mobile Darstellung
+- Klare visuelle Trennung
+- Flexiblere Layouts
+- Bessere Accessibility
+
+### Wie wird Dark Mode unterstützt?
+
+**Antwort:**
+- PrimeVue Aura Theme hat nativen Dark Mode Support
+- CSS Custom Properties passen sich automatisch an
+- Spezielle Dark Mode Overrides wo nötig (siehe invoice-form-styles.css)
+- App-Toggle über `.app-dark` CSS-Klasse
+
+### Ist das Design responsive?
+
+**Antwort:** Ja, vollständig:
+- Mobile First Ansatz
+- 5 Breakpoints (< 576px bis > 1920px)
+- Touch-friendly Elemente (min. 44px)
+- Adaptive Layouts für alle Größen
+- Stack-Layout auf Mobile
+
+### Wie werden Fehler behandelt?
+
+**Antwort:**
+- Inline-Validierung (on blur)
+- Submit-Validierung (alle Felder)
+- Toast-Benachrichtigungen für System-Fehler
+- Visuelle Error-States (rote Border + Message)
+- ARIA Live Regions für Screen Reader
+
+---
+
+## Nächste Schritte
+
+### Sofort umsetzbar (Quick Wins)
+
+1. FloatLabel für alle Formular-Felder einführen
+2. Status-Tag in Formular-Header einbauen
+3. Verbesserter Kunden-Dropdown mit Icons
+4. Echtzeit-Summenberechnung implementieren
+5. Quick Amount Buttons im PaymentForm
+
+**Aufwand:** ~8 Stunden | **Impact:** Hoch
+
+### Mittelfristig (1-2 Sprints)
+
+1. Card-basiertes Positions-Management
+2. Comprehensive Validation Composable
+3. Keyboard Shortcuts System
+4. Loading States & Skeletons
+
+**Aufwand:** ~30 Stunden | **Impact:** Sehr hoch
+
+### Langfristig (2+ Sprints)
+
+1. Drag & Drop PDF Upload
+2. Vollständige WCAG AAA Konformität
+3. PWA Features (Offline Support)
+4. Advanced Analytics Integration
+
+**Aufwand:** ~50 Stunden | **Impact:** Mittel-Hoch
+
+---
+
+## Mitwirkende
+
+**Design & Konzeption:**
+- Claude (UX Designer Agent) - Initial Design & Dokumentation
+
+**Review & Feedback:**
+- [Ihr Team hier eintragen]
+
+**Implementierung:**
+- [Entwickler hier eintragen]
+
+---
+
+## Änderungshistorie
+
+| Version | Datum | Autor | Änderungen |
+|---------|------------|----------------|------------------------------------|
+| 1.0 | 2025-12-12 | Claude (UX) | Initiale Dokumentation erstellt |
+
+---
+
+## Lizenz & Copyright
+
+© 2025 myCRM Project
+Interne Dokumentation - Vertraulich
+
+---
+
+## Kontakt & Support
+
+Bei Fragen zur Design-Dokumentation:
+- Erstellen Sie ein Issue im Repository
+- Kontaktieren Sie das Design-Team
+- Schlagen Sie Verbesserungen via Pull Request vor
+
+---
+
+**Letzte Aktualisierung:** 2025-12-12
+**Dokument-Version:** 1.0
+**Status:** In Review ✓
diff --git a/docs/design/VISUAL_DESIGN_GUIDE.md b/docs/design/VISUAL_DESIGN_GUIDE.md
new file mode 100644
index 0000000..0f66f0f
--- /dev/null
+++ b/docs/design/VISUAL_DESIGN_GUIDE.md
@@ -0,0 +1,679 @@
+# Visual Design Guide - Invoice Form
+**myCRM Billing Module | Design Reference**
+
+---
+
+## 1. Farbpalette & Kontraste
+
+### Status Colors (Semantic)
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ DRAFT (Entwurf) │ #6B7280 │ ░░░░░░░░░░░░ Secondary │
+│ OPEN (Offen) │ #06B6D4 │ ████████████ Info │
+│ PAID (Bezahlt) │ #22C55E │ ████████████ Success │
+│ PARTIAL (Teilzahlung) │ #F59E0B │ ████████████ Warning │
+│ OVERDUE (Überfällig) │ #EF4444 │ ████████████ Danger │
+│ CANCELLED (Storniert) │ #6B7280 │ ░░░░░░░░░░░░ Secondary │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Surface Colors
+
+```
+Light Mode: Dark Mode:
+┌──────────────────────────────┐ ┌──────────────────────────────┐
+│ Background #FFFFFF │ │ Background #0F172A │
+│ Surface-0 #FFFFFF │ │ Surface-0 #1E293B │
+│ Surface-50 #F9FAFB │ │ Surface-50 #334155 │
+│ Surface-100 #F3F4F6 │ │ Surface-100 #475569 │
+│ Surface-200 #E5E7EB │ │ Surface-200 #64748B │
+└──────────────────────────────┘ └──────────────────────────────┘
+```
+
+### Text Colors
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ Primary Text #111827 ████ High contrast (main text) │
+│ Secondary Text #6B7280 ████ Medium contrast (labels) │
+│ Muted Text #9CA3AF ████ Low contrast (hints) │
+└────────────────────────────────────────────────────────────┘
+```
+
+### Accessibility Compliance
+
+```
+WCAG 2.1 AA Requirements:
+✓ Normal Text (14px): 4.5:1 contrast ratio
+✓ Large Text (18px+): 3.0:1 contrast ratio
+✓ UI Components: 3.0:1 contrast ratio
+
+WCAG 2.1 AAA Requirements:
+✓ Normal Text (14px): 7.0:1 contrast ratio
+✓ Large Text (18px+): 4.5:1 contrast ratio
+```
+
+---
+
+## 2. Typography System
+
+### Font Stack
+```css
+font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ 'Helvetica Neue', Arial, sans-serif;
+```
+
+### Type Scale
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ Dialog Title 24px / 1.5rem Bold (600) │
+│ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
+│ │
+│ Section Title 20px / 1.25rem Semibold (600) │
+│ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
+│ │
+│ Subsection 16px / 1rem Medium (500) │
+│ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
+│ │
+│ Body Text 14px / 0.875rem Regular (400) │
+│ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
+│ │
+│ Small Text 12px / 0.75rem Regular (400) │
+│ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
+└──────────────────────────────────────────────────────────────┘
+```
+
+### Line Heights
+
+```
+Headings: 1.2 (tight)
+Body: 1.5 (normal)
+Dense: 1.3 (compact tables)
+Relaxed: 1.75 (long form text)
+```
+
+---
+
+## 3. Spacing System
+
+### Base Unit: 4px
+
+```
+┌───────────────────────────────────────────────────────────┐
+│ Space Size Usage │
+├───────────────────────────────────────────────────────────┤
+│ 0.25rem 4px │ Minimal gap (inline icons) │
+│ 0.5rem 8px ││ Label margin, tight spacing │
+│ 0.75rem 12px │││ Inline field gaps │
+│ 1rem 16px ││││ Field gaps (standard) │
+│ 1.5rem 24px │││││││ Section padding │
+│ 2rem 32px ││││││││││ Section gaps │
+│ 3rem 48px │││││││││││││││││ Empty state padding │
+└───────────────────────────────────────────────────────────┘
+```
+
+### Component Spacing
+
+```
+FORM SECTION:
+┌─────────────────────────────────────┐
+│ [24px padding] │
+│ │
+│ ┌─────────────────────────────┐ │
+│ │ Header │ │
+│ └─────────────────────────────┘ │
+│ [24px gap] │
+│ ┌─────────────────────────────┐ │
+│ │ Field 1 [16px gap] Field 2 │ │
+│ └─────────────────────────────┘ │
+│ [16px gap] │
+│ ┌─────────────────────────────┐ │
+│ │ Field 3 │ │
+│ └─────────────────────────────┘ │
+│ │
+│ [24px padding] │
+└─────────────────────────────────────┘
+[32px gap between sections]
+```
+
+---
+
+## 4. Component Anatomy
+
+### Invoice Item Card
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Position 1 [Delete] ←16px │
+│ ─────────────────────────────────────────────────────────── │ 16px
+│ │
+│ ┌───────────────────────────────────────────────────────┐ │ │
+│ │ Beschreibung │ │ │
+│ │ (Textarea - 2 rows) │ │ │
+│ └───────────────────────────────────────────────────────┘ │ │
+│ ↕ 12px gap │
+│ ┌─────────────┐ ┌──────────────┐ ┌──────┐ │ │
+│ │ Menge │ │ Einzelpreis │ │ MwSt │ │ │
+│ │ 1.00 Stk. │ │ 100,00 € │ │ 19% │ │ │
+│ └─────────────┘ └──────────────┘ └──────┘ │ │
+│ ↕ 16px gap │
+│ ─────────────────────────────────────────────────────────── │
+│ Zwischensumme: 119,00 € ←semibold │
+└─────────────────────────────────────────────────────────────┘
+ ↑ ↑
+ 16px padding 16px padding
+```
+
+### Invoice Summary Box
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │
+│ ┃ Zusammenfassung [24px padding] ┃ │
+│ ┃ ───────────────────────────────────────────────────── ┃ │
+│ ┃ ┃ │
+│ ┃ Nettobetrag: 1.000,00 € ← 8px │ │
+│ ┃ MwSt (19%): 190,00 € ┃ │
+│ ┃ MwSt (7%): 14,00 € ┃ │
+│ ┃ ↕ 12px gap ┃ │
+│ ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ┃ │
+│ ┃ ↕ 16px gap ┃ │
+│ ┃ GESAMTBETRAG: 1.204,00 € ┃ │
+│ ┃ (20px font, bold) ┃ │
+│ ┃ ┃ │
+│ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
+└─────────────────────────────────────────────────────────────┘
+ Gradient background: primary-50 → primary-100
+ Border: 2px solid primary-color
+```
+
+### Empty State
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ │
+│ 48px ↓ │
+│ ┌──────────────┐ │
+│ │ │ │
+│ │ Inbox │ ← 48px icon, muted │
+│ │ Icon │ │
+│ └──────────────┘ │
+│ 16px ↓ │
+│ Noch keine Positionen hinzugefügt │
+│ (16px, medium weight, secondary color) │
+│ 8px ↓ │
+│ Fügen Sie mindestens eine Position hinzu, │
+│ um die Rechnung zu erstellen │
+│ (12px, muted color) │
+│ 24px ↓ │
+│ ┌──────────────────────────┐ │
+│ │ Erste Position hinzufügen │ │
+│ └──────────────────────────┘ │
+│ │
+│ 48px ↓ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 5. Interactive States
+
+### Button States
+
+```
+DEFAULT:
+┌──────────────┐
+│ Speichern │ Background: primary-color
+└──────────────┘ Text: white
+ Height: 40px (2.5rem)
+ Padding: 12px 20px
+ Border-radius: 6px
+
+HOVER:
+┌──────────────┐
+│ Speichern │ Background: primary-600 (darker)
+└──────────────┘ Transform: scale(1.02)
+ Transition: 0.2s ease
+
+FOCUS:
+┌──────────────┐
+│ Speichern │ Outline: 2px solid primary-color
+└──────────────┘ Outline-offset: 2px
+
+LOADING:
+┌──────────────┐
+│ ⟳ Speichern │ Spinner icon rotating
+└──────────────┘ Cursor: wait
+ Opacity: 0.8
+
+DISABLED:
+┌──────────────┐
+│ Speichern │ Background: surface-200
+└──────────────┘ Text: text-muted
+ Cursor: not-allowed
+ Opacity: 0.6
+```
+
+### Input Field States
+
+```
+DEFAULT (FloatLabel):
+┌─────────────────────────────────────┐
+│ Rechnungsnummer │ ← Label (12px, top)
+│ INV-2025-001 │ ← Value (14px)
+└─────────────────────────────────────┘
+ Border: 1px solid surface-200
+ Height: 44px (touch-friendly)
+
+FOCUS:
+┌─────────────────────────────────────┐
+│ Rechnungsnummer │ ← Label (primary color)
+│ INV-2025-001│ │ ← Cursor visible
+└─────────────────────────────────────┘
+ Border: 2px solid primary-color
+ Box-shadow: 0 0 0 3px primary-50
+
+ERROR:
+┌─────────────────────────────────────┐
+│ Rechnungsnummer │ ← Label (danger color)
+│ INV-2025-001 │
+└─────────────────────────────────────┘
+⚠ Rechnungsnummer ist erforderlich ← Error message (12px, danger)
+ Border: 2px solid danger-color
+
+DISABLED:
+┌─────────────────────────────────────┐
+│ Rechnungsnummer │ ← Label (muted)
+│ INV-2025-001 │ ← Value (muted)
+└─────────────────────────────────────┘
+ Background: surface-100
+ Cursor: not-allowed
+ Opacity: 0.6
+```
+
+---
+
+## 6. Animation & Transitions
+
+### Timing Functions
+
+```javascript
+// Fast interactions (hover, focus)
+transition: all 0.15s ease-in-out;
+
+// Standard interactions (expand, collapse)
+transition: all 0.2s ease;
+
+// Slow interactions (page transitions)
+transition: all 0.3s ease;
+```
+
+### List Animations (Invoice Items)
+
+```
+ENTER (new item):
+┌─────────────┐ ┌─────────────┐
+│ Opacity │ → │ Opacity │
+│ 0.0 │ │ 1.0 │
+└─────────────┘ └─────────────┘
+ ↓ ↓
+TranslateY(-20px) TranslateY(0)
+Duration: 0.3s ease-out
+
+LEAVE (deleted item):
+┌─────────────┐ ┌─────────────┐
+│ Opacity │ → │ Opacity │
+│ 1.0 │ │ 0.0 │
+└─────────────┘ └─────────────┘
+ ↓ ↓
+TranslateX(0) TranslateX(20px)
+Duration: 0.3s ease
+```
+
+### Micro-interactions
+
+```
+Button Click:
+ • Transform: scale(0.98)
+ • Duration: 0.1s
+ • Timing: ease-out
+
+Card Hover:
+ • Box-shadow: 0 2px 8px rgba(0,0,0,0.08)
+ • Border-color: primary-color
+ • Duration: 0.2s
+ • Timing: ease
+
+Toast Notification:
+ • Slide in from right
+ • Transform: translateX(100%) → translateX(0)
+ • Duration: 0.3s
+ • Timing: cubic-bezier(0.4, 0, 0.2, 1)
+```
+
+---
+
+## 7. Responsive Breakpoints
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ BREAKPOINT WIDTH COLUMNS LAYOUT │
+├────────────────────────────────────────────────────────────┤
+│ Small Mobile < 576px 1 Stack all fields │
+│ Mobile 576-768px 1 Stack + full width │
+│ Tablet 768-992px 1-2 Hybrid layout │
+│ Desktop 992-1200px 2 2-column grid │
+│ Large Desktop > 1200px 2-3 Optimized spacing │
+└────────────────────────────────────────────────────────────┘
+```
+
+### Layout Examples
+
+**Desktop (992px+)**
+```
+┌─────────────────────────────────────────────────────┐
+│ Rechnungsinformationen [Tag] │
+│ ─────────────────────────────────────────────────── │
+│ ┌────────────────┐ ┌────────────────┐ │
+│ │ Rechnungsnr. │ │ Status │ │
+│ └────────────────┘ └────────────────┘ │
+│ ┌──────────────────────────────────────────────┐ │
+│ │ Kunde (full width) │ │
+│ └──────────────────────────────────────────────┘ │
+│ ┌────────────────┐ ┌────────────────┐ │
+│ │ Datum │ │ Fälligk. │ │
+│ └────────────────┘ └────────────────┘ │
+└─────────────────────────────────────────────────────┘
+```
+
+**Tablet (768-992px)**
+```
+┌─────────────────────────────────────┐
+│ Rechnungsinformationen [Tag] │
+│ ───────────────────────────────────── │
+│ ┌───────────────────────────────┐ │
+│ │ Rechnungsnr. │ │
+│ └───────────────────────────────┘ │
+│ ┌───────────────────────────────┐ │
+│ │ Status │ │
+│ └───────────────────────────────┘ │
+│ ┌───────────────────────────────┐ │
+│ │ Kunde │ │
+│ └───────────────────────────────┘ │
+│ ┌───────────────────────────────┐ │
+│ │ Datum │ │
+│ └───────────────────────────────┘ │
+│ ┌───────────────────────────────┐ │
+│ │ Fälligkeit │ │
+│ └───────────────────────────────┘ │
+└─────────────────────────────────────┘
+```
+
+**Mobile (< 768px)**
+```
+┌─────────────────────────┐
+│ Rechnung │
+│ [Tag] │
+│ ─────────────────────── │
+│ ┌─────────────────────┐ │
+│ │ Rechnungsnr. │ │
+│ └─────────────────────┘ │
+│ ┌─────────────────────┐ │
+│ │ Status │ │
+│ └─────────────────────┘ │
+│ ┌─────────────────────┐ │
+│ │ Kunde │ │
+│ └─────────────────────┘ │
+│ ... │
+└─────────────────────────┘
+
+Dialog = Full Screen
+Summary = Sticky Bottom
+```
+
+---
+
+## 8. Iconography
+
+### PrimeIcons Usage
+
+```
+┌────────────────────────────────────────────────────────┐
+│ CONTEXT ICON SIZE USAGE │
+├────────────────────────────────────────────────────────┤
+│ Add Item pi-plus 1rem Buttons │
+│ Remove Item pi-trash 1rem Buttons │
+│ Save pi-check 1rem Buttons │
+│ Cancel pi-times 1rem Buttons │
+│ Upload pi-upload 2rem Empty state │
+│ PDF pi-file-pdf 2.5rem Preview │
+│ Calendar pi-calendar 0.875 Input icon │
+│ Building/Company pi-building 1rem Dropdown │
+│ Info pi-info-circle 1rem Messages │
+│ Warning pi-exclamation 1rem Alerts │
+│ Success pi-check-circle 1rem Toast │
+│ Error pi-times-circle 1rem Toast │
+└────────────────────────────────────────────────────────┘
+```
+
+### Icon Colors
+
+```
+Default: var(--text-secondary) #6B7280
+Primary: var(--primary-color) #3B82F6
+Success: var(--success-color) #22C55E
+Warning: var(--warning-color) #F59E0B
+Danger: var(--danger-color) #EF4444
+Muted: var(--text-muted) #9CA3AF (opacity: 0.5)
+```
+
+---
+
+## 9. Error Handling & Validation
+
+### Validation Patterns
+
+```
+INLINE VALIDATION (on blur):
+┌─────────────────────────────────────┐
+│ E-Mail │
+│ invalid.email │ ← Invalid input
+└─────────────────────────────────────┘
+⚠ Ungültige E-Mail-Adresse ← Error appears immediately
+ Duration: 0.2s fade-in
+
+SUBMIT VALIDATION:
+┌─────────────────────────────────────┐
+│ Rechnungsnummer │
+│ │ ← Empty required field
+└─────────────────────────────────────┘
+⚠ Rechnungsnummer ist erforderlich
+
+ All errors show simultaneously on submit
+ Focus moves to first invalid field
+```
+
+### Error States Visual Hierarchy
+
+```
+PRIORITY 1 - Critical Errors (Red border + icon):
+┌─────────────────────────────────────┐
+│ ⚠ Rechnungsnummer │ ← Red label
+│ │
+└─────────────────────────────────────┘
+ Border: 2px solid danger-color
+
+PRIORITY 2 - Warnings (Orange border):
+┌─────────────────────────────────────┐
+│ ⚠ Fälligkeitsdatum │ ← Orange label
+│ 01.01.2024 │
+└─────────────────────────────────────┘
+⚠ Datum liegt in der Vergangenheit
+ Border: 2px solid warning-color
+
+PRIORITY 3 - Info (Blue border):
+┌─────────────────────────────────────┐
+│ ℹ Steuer-ID │
+│ DE123456789 │
+└─────────────────────────────────────┘
+ℹ Optional, aber empfohlen
+ Border: 1px solid info-color
+```
+
+---
+
+## 10. Loading & Empty States
+
+### Skeleton Screens
+
+```
+LOADING FORM:
+┌─────────────────────────────────────┐
+│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ ← Animated gradient
+│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
+│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │
+│ ▓▓▓▓▓▓▓▓▓▓ │
+└─────────────────────────────────────┘
+ Animation: shimmer effect (1.5s loop)
+ Background: linear-gradient 200% width
+
+LOADING BUTTON:
+┌──────────────┐
+│ ⟳ Lädt... │ ← Spinner rotates
+└──────────────┘
+ Disabled: true
+ Cursor: wait
+```
+
+### Progress Indication
+
+```
+UPLOAD PROGRESS:
+┌─────────────────────────────────────┐
+│ rechnung_2025_001.pdf │ [×] │
+│ 2.4 MB │
+│ ▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░ 45% │ ← Progress bar
+└─────────────────────────────────────┘
+ Height: 6px
+ Background: success-color
+ Animation: smooth increment
+```
+
+---
+
+## 11. Accessibility Features
+
+### Focus Management
+
+```
+TAB ORDER:
+1. Rechnungsnummer [← First field]
+2. Status
+3. Kunde
+4. Rechnungsdatum
+5. Fälligkeitsdatum
+6. Position 1 - Beschreibung
+7. Position 1 - Menge
+8. Position 1 - Preis
+9. Position 1 - MwSt
+10. Position hinzufügen Button
+...
+N-1. Abbrechen Button
+N. Speichern Button [← Last focusable]
+
+ESC: Return to previous focusable
+```
+
+### ARIA Live Regions
+
+```html
+
+
+ Rechnung wird gespeichert...
+ Rechnung wurde gespeichert
+ Fehler: {{ errorMessage }}
+
+
+
+
+ Position {{ index + 1 }} wurde entfernt
+
+```
+
+### Keyboard Navigation Hints
+
+```
+┌────────────────────────────────────────────────────────┐
+│ [Ctrl+S] Speichern • [Ctrl+⇧+P] Position hinzufügen │
+│ [Esc] Abbrechen • [Tab] Nächstes Feld │
+└────────────────────────────────────────────────────────┘
+ Display: Desktop & Tablet only
+ Hidden on mobile (<768px)
+ Can be toggled with [?] key
+```
+
+---
+
+## 12. Print Styles
+
+```css
+@media print {
+ /* Hide interactive elements */
+ .form-actions,
+ .keyboard-hints,
+ .invoice-item-actions,
+ button { display: none !important; }
+
+ /* Optimize for paper */
+ .form-section {
+ box-shadow: none;
+ page-break-inside: avoid;
+ }
+
+ /* Black & white optimization */
+ .invoice-summary {
+ background: white;
+ border: 2px solid black;
+ }
+
+ /* Add page breaks */
+ .page-break { page-break-after: always; }
+}
+```
+
+### Print Layout Preview
+
+```
+PAGE 1:
+┌─────────────────────────────────────┐
+│ myCRM Logo │
+│ ─────────────────────────────────── │
+│ │
+│ RECHNUNG INV-2025-001 │
+│ │
+│ Rechnungsinformationen │
+│ Rechnungsnr: INV-2025-001 │
+│ Datum: 12.12.2025 │
+│ Kunde: ACME Corp │
+│ │
+│ Rechnungspositionen │
+│ 1. Consulting Services │
+│ 10 Stk × 100,00 € = 1.000,00 € │
+│ │
+│ ─────────────────────────────────── │
+│ Nettobetrag: 1.000,00 € │
+│ MwSt (19%): 190,00 € │
+│ GESAMTBETRAG: 1.190,00 € │
+│ │
+│ Seite 1 von 1 │
+└─────────────────────────────────────┘
+```
+
+---
+
+**Document Version:** 1.0
+**Last Updated:** 2025-12-12
+**Author:** UX Designer Agent
+**Project:** myCRM Billing Module
diff --git a/docs/design/composables/useFormValidation.js b/docs/design/composables/useFormValidation.js
new file mode 100644
index 0000000..405d9f2
--- /dev/null
+++ b/docs/design/composables/useFormValidation.js
@@ -0,0 +1,400 @@
+/**
+ * COMPOSABLE: useFormValidation
+ *
+ * Provides reusable form validation logic with real-time validation,
+ * error tracking, and field-level validation rules.
+ *
+ * Usage:
+ * ```js
+ * import { useFormValidation } from '@/composables/useFormValidation'
+ *
+ * const validation = useFormValidation()
+ *
+ * // Validate a field
+ * validation.validateField('email', form.email, {
+ * required: true,
+ * pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+ * })
+ *
+ * // Check if field has error
+ * validation.hasError('email') // true/false
+ *
+ * // Get error message
+ * validation.getError('email') // "E-Mail ist erforderlich"
+ * ```
+ */
+
+import { ref, computed } from 'vue'
+
+export function useFormValidation() {
+ // State
+ const errors = ref({})
+ const touched = ref({})
+ const validating = ref({})
+
+ /**
+ * Validates a single field with given rules
+ * @param {string} fieldName - Name of the field
+ * @param {any} value - Current field value
+ * @param {object} rules - Validation rules
+ * @returns {boolean} - True if valid
+ */
+ const validateField = (fieldName, value, rules = {}) => {
+ validating.value[fieldName] = true
+ const fieldErrors = []
+
+ // Required validation
+ if (rules.required) {
+ if (value === null || value === undefined || value === '' ||
+ (Array.isArray(value) && value.length === 0)) {
+ fieldErrors.push('Dieses Feld ist erforderlich')
+ }
+ }
+
+ // If field is empty and not required, skip other validations
+ if (!value && !rules.required) {
+ errors.value[fieldName] = []
+ validating.value[fieldName] = false
+ return true
+ }
+
+ // Min length validation
+ if (rules.minLength && value && value.length < rules.minLength) {
+ fieldErrors.push(`Mindestens ${rules.minLength} Zeichen erforderlich`)
+ }
+
+ // Max length validation
+ if (rules.maxLength && value && value.length > rules.maxLength) {
+ fieldErrors.push(`Maximal ${rules.maxLength} Zeichen erlaubt`)
+ }
+
+ // Pattern validation
+ if (rules.pattern && value && !rules.pattern.test(value)) {
+ fieldErrors.push(rules.patternMessage || 'Ungültiges Format')
+ }
+
+ // Min value validation (for numbers)
+ if (rules.min !== undefined && value < rules.min) {
+ fieldErrors.push(`Wert muss mindestens ${rules.min} sein`)
+ }
+
+ // Max value validation (for numbers)
+ if (rules.max !== undefined && value > rules.max) {
+ fieldErrors.push(`Wert darf maximal ${rules.max} sein`)
+ }
+
+ // Email validation
+ if (rules.email && value) {
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+ if (!emailPattern.test(value)) {
+ fieldErrors.push('Ungültige E-Mail-Adresse')
+ }
+ }
+
+ // URL validation
+ if (rules.url && value) {
+ try {
+ new URL(value)
+ } catch {
+ fieldErrors.push('Ungültige URL')
+ }
+ }
+
+ // Date validation
+ if (rules.date && value) {
+ const date = new Date(value)
+ if (isNaN(date.getTime())) {
+ fieldErrors.push('Ungültiges Datum')
+ }
+ }
+
+ // Custom validation function
+ if (rules.custom && typeof rules.custom === 'function') {
+ const customError = rules.custom(value)
+ if (customError) {
+ fieldErrors.push(customError)
+ }
+ }
+
+ // Async validation
+ if (rules.asyncValidator && typeof rules.asyncValidator === 'function') {
+ rules.asyncValidator(value).then(error => {
+ if (error) {
+ errors.value[fieldName] = [error]
+ }
+ validating.value[fieldName] = false
+ })
+ } else {
+ validating.value[fieldName] = false
+ }
+
+ // Update errors
+ errors.value[fieldName] = fieldErrors
+
+ return fieldErrors.length === 0
+ }
+
+ /**
+ * Validates multiple fields at once
+ * @param {object} formData - Object with field names and values
+ * @param {object} rulesMap - Object with field names and their rules
+ * @returns {boolean} - True if all fields are valid
+ */
+ const validateForm = (formData, rulesMap) => {
+ let isValid = true
+
+ Object.keys(rulesMap).forEach(fieldName => {
+ const value = getNestedValue(formData, fieldName)
+ const rules = rulesMap[fieldName]
+ const fieldValid = validateField(fieldName, value, rules)
+
+ if (!fieldValid) {
+ isValid = false
+ touchField(fieldName)
+ }
+ })
+
+ return isValid
+ }
+
+ /**
+ * Marks a field as touched
+ * @param {string} fieldName - Name of the field
+ */
+ const touchField = (fieldName) => {
+ touched.value[fieldName] = true
+ }
+
+ /**
+ * Marks multiple fields as touched
+ * @param {string[]} fieldNames - Array of field names
+ */
+ const touchFields = (fieldNames) => {
+ fieldNames.forEach(fieldName => {
+ touched.value[fieldName] = true
+ })
+ }
+
+ /**
+ * Marks all fields as touched
+ */
+ const touchAll = () => {
+ Object.keys(errors.value).forEach(fieldName => {
+ touched.value[fieldName] = true
+ })
+ }
+
+ /**
+ * Checks if a field has an error and has been touched
+ * @param {string} fieldName - Name of the field
+ * @returns {boolean}
+ */
+ const hasError = (fieldName) => {
+ return touched.value[fieldName] &&
+ errors.value[fieldName] &&
+ errors.value[fieldName].length > 0
+ }
+
+ /**
+ * Gets the first error message for a field
+ * @param {string} fieldName - Name of the field
+ * @returns {string|null}
+ */
+ const getError = (fieldName) => {
+ if (!hasError(fieldName)) return null
+ return errors.value[fieldName][0]
+ }
+
+ /**
+ * Gets all error messages for a field
+ * @param {string} fieldName - Name of the field
+ * @returns {string[]}
+ */
+ const getErrors = (fieldName) => {
+ if (!hasError(fieldName)) return []
+ return errors.value[fieldName]
+ }
+
+ /**
+ * Clears errors for a specific field
+ * @param {string} fieldName - Name of the field
+ */
+ const clearFieldError = (fieldName) => {
+ errors.value[fieldName] = []
+ touched.value[fieldName] = false
+ }
+
+ /**
+ * Clears all errors
+ */
+ const clearErrors = () => {
+ errors.value = {}
+ touched.value = {}
+ }
+
+ /**
+ * Resets validation state
+ */
+ const reset = () => {
+ errors.value = {}
+ touched.value = {}
+ validating.value = {}
+ }
+
+ /**
+ * Checks if the entire form is valid
+ * @returns {boolean}
+ */
+ const isValid = computed(() => {
+ return Object.keys(errors.value).every(
+ fieldName => !errors.value[fieldName] || errors.value[fieldName].length === 0
+ )
+ })
+
+ /**
+ * Checks if any field is currently being validated
+ * @returns {boolean}
+ */
+ const isValidating = computed(() => {
+ return Object.values(validating.value).some(v => v === true)
+ })
+
+ /**
+ * Gets nested value from object using dot notation
+ * @param {object} obj - Object to get value from
+ * @param {string} path - Path using dot notation (e.g., 'items.0.description')
+ * @returns {any}
+ */
+ const getNestedValue = (obj, path) => {
+ return path.split('.').reduce((acc, part) => {
+ return acc && acc[part] !== undefined ? acc[part] : undefined
+ }, obj)
+ }
+
+ /**
+ * Sets a custom error for a field
+ * @param {string} fieldName - Name of the field
+ * @param {string|string[]} error - Error message(s)
+ */
+ const setError = (fieldName, error) => {
+ errors.value[fieldName] = Array.isArray(error) ? error : [error]
+ touched.value[fieldName] = true
+ }
+
+ /**
+ * Sets multiple errors at once
+ * @param {object} errorsMap - Object with field names and error messages
+ */
+ const setErrors = (errorsMap) => {
+ Object.entries(errorsMap).forEach(([fieldName, error]) => {
+ setError(fieldName, error)
+ })
+ }
+
+ return {
+ // State
+ errors,
+ touched,
+ validating,
+
+ // Methods
+ validateField,
+ validateForm,
+ touchField,
+ touchFields,
+ touchAll,
+ hasError,
+ getError,
+ getErrors,
+ clearFieldError,
+ clearErrors,
+ reset,
+ setError,
+ setErrors,
+
+ // Computed
+ isValid,
+ isValidating
+ }
+}
+
+/**
+ * Common validation rules presets
+ */
+export const validationRules = {
+ required: {
+ required: true
+ },
+
+ email: {
+ required: true,
+ email: true
+ },
+
+ optionalEmail: {
+ email: true
+ },
+
+ phone: {
+ pattern: /^[\d\s()+\-/.]+$/,
+ patternMessage: 'Ungültige Telefonnummer'
+ },
+
+ url: {
+ url: true
+ },
+
+ invoiceNumber: {
+ required: true,
+ minLength: 3,
+ pattern: /^[A-Z0-9-]+$/i,
+ patternMessage: 'Nur Buchstaben, Zahlen und Bindestriche erlaubt'
+ },
+
+ postalCode: {
+ pattern: /^\d{5}$/,
+ patternMessage: 'Postleitzahl muss 5 Zahlen enthalten'
+ },
+
+ iban: {
+ pattern: /^[A-Z]{2}\d{2}[A-Z0-9]+$/,
+ patternMessage: 'Ungültiges IBAN-Format'
+ },
+
+ taxId: {
+ pattern: /^DE\d{9}$/,
+ patternMessage: 'Ungültige Steuernummer (Format: DE123456789)'
+ },
+
+ currency: {
+ min: 0,
+ custom: (value) => {
+ if (value && isNaN(parseFloat(value))) {
+ return 'Bitte geben Sie einen gültigen Betrag ein'
+ }
+ return null
+ }
+ },
+
+ percentage: {
+ min: 0,
+ max: 100,
+ custom: (value) => {
+ if (value && (isNaN(parseFloat(value)) || value < 0 || value > 100)) {
+ return 'Prozentsatz muss zwischen 0 und 100 liegen'
+ }
+ return null
+ }
+ },
+
+ quantity: {
+ required: true,
+ min: 0.01,
+ custom: (value) => {
+ if (value && (isNaN(parseFloat(value)) || value <= 0)) {
+ return 'Menge muss größer als 0 sein'
+ }
+ return null
+ }
+ }
+}
diff --git a/docs/design/composables/useKeyboardShortcuts.js b/docs/design/composables/useKeyboardShortcuts.js
new file mode 100644
index 0000000..6ba9d68
--- /dev/null
+++ b/docs/design/composables/useKeyboardShortcuts.js
@@ -0,0 +1,386 @@
+/**
+ * COMPOSABLE: useKeyboardShortcuts
+ *
+ * Provides keyboard shortcut functionality with support for
+ * modifier keys (Ctrl/Cmd, Shift, Alt) and custom key combinations.
+ *
+ * Usage:
+ * ```js
+ * import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts'
+ *
+ * useKeyboardShortcuts({
+ * 'ctrl+s': saveForm,
+ * 'ctrl+shift+p': addNewItem,
+ * 'escape': closeDialog,
+ * 'alt+n': () => console.log('Alt+N pressed')
+ * })
+ * ```
+ */
+
+import { onMounted, onUnmounted, ref } from 'vue'
+
+export function useKeyboardShortcuts(shortcuts, options = {}) {
+ const {
+ enabled = true,
+ preventDefault = true,
+ target = null // null means window, or pass a ref to an element
+ } = options
+
+ const isEnabled = ref(enabled)
+ const registeredShortcuts = ref(shortcuts)
+
+ /**
+ * Normalizes a key string to lowercase and handles platform differences
+ * @param {string} key - Key string
+ * @returns {string}
+ */
+ const normalizeKey = (key) => {
+ return key.toLowerCase()
+ .replace('meta', 'ctrl') // Treat Meta (Cmd) as Ctrl for consistency
+ .replace('command', 'ctrl')
+ }
+
+ /**
+ * Builds a shortcut string from a KeyboardEvent
+ * @param {KeyboardEvent} event - Keyboard event
+ * @returns {string}
+ */
+ const buildShortcutString = (event) => {
+ const parts = []
+
+ // Add modifiers in consistent order
+ if (event.ctrlKey || event.metaKey) parts.push('ctrl')
+ if (event.shiftKey) parts.push('shift')
+ if (event.altKey) parts.push('alt')
+
+ // Add the actual key (normalized)
+ const key = event.key.toLowerCase()
+
+ // Handle special keys
+ const specialKeys = {
+ ' ': 'space',
+ 'arrowup': 'up',
+ 'arrowdown': 'down',
+ 'arrowleft': 'left',
+ 'arrowright': 'right',
+ 'enter': 'enter',
+ 'escape': 'escape',
+ 'tab': 'tab',
+ 'backspace': 'backspace',
+ 'delete': 'delete',
+ 'insert': 'insert',
+ 'home': 'home',
+ 'end': 'end',
+ 'pageup': 'pageup',
+ 'pagedown': 'pagedown'
+ }
+
+ const normalizedKey = specialKeys[key] || key
+ parts.push(normalizedKey)
+
+ return parts.join('+')
+ }
+
+ /**
+ * Checks if the event should be ignored (e.g., inside an input field)
+ * @param {KeyboardEvent} event - Keyboard event
+ * @returns {boolean}
+ */
+ const shouldIgnoreEvent = (event) => {
+ // Don't trigger shortcuts when typing in input fields (unless specified)
+ const target = event.target
+ const tagName = target.tagName.toLowerCase()
+
+ // Check if target is an editable element
+ if (
+ tagName === 'input' ||
+ tagName === 'textarea' ||
+ tagName === 'select' ||
+ target.isContentEditable
+ ) {
+ // Allow Escape key even in input fields
+ if (event.key === 'Escape') return false
+
+ // Allow Ctrl+S (save) even in input fields
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
+ return false
+ }
+
+ return true
+ }
+
+ return false
+ }
+
+ /**
+ * Handles keyboard events
+ * @param {KeyboardEvent} event - Keyboard event
+ */
+ const handleKeydown = (event) => {
+ // Check if shortcuts are enabled
+ if (!isEnabled.value) return
+
+ // Check if we should ignore this event
+ if (shouldIgnoreEvent(event)) return
+
+ // Build the shortcut string
+ const shortcutString = buildShortcutString(event)
+
+ // Find matching shortcut
+ const matchingShortcut = Object.keys(registeredShortcuts.value).find(
+ shortcut => normalizeKey(shortcut) === shortcutString
+ )
+
+ if (matchingShortcut) {
+ const action = registeredShortcuts.value[matchingShortcut]
+
+ // Prevent default browser behavior if configured
+ if (preventDefault) {
+ event.preventDefault()
+ }
+
+ // Execute the shortcut action
+ if (typeof action === 'function') {
+ action(event)
+ }
+ }
+ }
+
+ /**
+ * Registers a new shortcut
+ * @param {string} shortcut - Shortcut string (e.g., 'ctrl+s')
+ * @param {Function} action - Action to execute
+ */
+ const registerShortcut = (shortcut, action) => {
+ registeredShortcuts.value[shortcut] = action
+ }
+
+ /**
+ * Unregisters a shortcut
+ * @param {string} shortcut - Shortcut string
+ */
+ const unregisterShortcut = (shortcut) => {
+ delete registeredShortcuts.value[shortcut]
+ }
+
+ /**
+ * Enables keyboard shortcuts
+ */
+ const enable = () => {
+ isEnabled.value = true
+ }
+
+ /**
+ * Disables keyboard shortcuts
+ */
+ const disable = () => {
+ isEnabled.value = false
+ }
+
+ /**
+ * Gets all registered shortcuts
+ * @returns {object}
+ */
+ const getShortcuts = () => {
+ return { ...registeredShortcuts.value }
+ }
+
+ // Setup and cleanup
+ onMounted(() => {
+ const element = target?.value || window
+ element.addEventListener('keydown', handleKeydown)
+ })
+
+ onUnmounted(() => {
+ const element = target?.value || window
+ element.removeEventListener('keydown', handleKeydown)
+ })
+
+ return {
+ registerShortcut,
+ unregisterShortcut,
+ enable,
+ disable,
+ getShortcuts,
+ isEnabled
+ }
+}
+
+/**
+ * Composable for displaying keyboard shortcut hints
+ */
+export function useKeyboardShortcutHints(shortcuts) {
+ /**
+ * Formats a shortcut string for display
+ * @param {string} shortcut - Shortcut string (e.g., 'ctrl+s')
+ * @returns {string[]} Array of key labels
+ */
+ const formatShortcut = (shortcut) => {
+ const parts = shortcut.toLowerCase().split('+')
+ const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
+
+ return parts.map(part => {
+ // Platform-specific labels
+ if (part === 'ctrl') {
+ return isMac ? '⌘' : 'Ctrl'
+ }
+ if (part === 'shift') {
+ return isMac ? '⇧' : 'Shift'
+ }
+ if (part === 'alt') {
+ return isMac ? '⌥' : 'Alt'
+ }
+
+ // Special key labels
+ const specialKeys = {
+ space: 'Space',
+ enter: 'Enter',
+ escape: 'Esc',
+ tab: 'Tab',
+ backspace: 'Backspace',
+ delete: 'Del',
+ up: '↑',
+ down: '↓',
+ left: '←',
+ right: '→',
+ pageup: 'PgUp',
+ pagedown: 'PgDn'
+ }
+
+ return specialKeys[part] || part.toUpperCase()
+ })
+ }
+
+ /**
+ * Gets all shortcuts with formatted labels
+ * @returns {Array} Array of {shortcut, keys, description}
+ */
+ const getFormattedShortcuts = (descriptions = {}) => {
+ return Object.keys(shortcuts).map(shortcut => ({
+ shortcut,
+ keys: formatShortcut(shortcut),
+ description: descriptions[shortcut] || ''
+ }))
+ }
+
+ return {
+ formatShortcut,
+ getFormattedShortcuts
+ }
+}
+
+/**
+ * Common keyboard shortcuts presets
+ */
+export const commonShortcuts = {
+ // Form actions
+ save: 'ctrl+s',
+ cancel: 'escape',
+ submit: 'ctrl+enter',
+
+ // Navigation
+ nextField: 'tab',
+ prevField: 'shift+tab',
+ firstField: 'ctrl+home',
+ lastField: 'ctrl+end',
+
+ // Editing
+ undo: 'ctrl+z',
+ redo: 'ctrl+shift+z',
+ copy: 'ctrl+c',
+ cut: 'ctrl+x',
+ paste: 'ctrl+v',
+ selectAll: 'ctrl+a',
+
+ // Item management
+ addItem: 'ctrl+shift+p',
+ deleteItem: 'ctrl+d',
+ duplicateItem: 'ctrl+shift+d',
+
+ // Search
+ search: 'ctrl+f',
+ searchNext: 'ctrl+g',
+ searchPrev: 'ctrl+shift+g',
+
+ // View
+ toggleFullscreen: 'f11',
+ zoomIn: 'ctrl+plus',
+ zoomOut: 'ctrl+minus',
+ resetZoom: 'ctrl+0'
+}
+
+/**
+ * Keyboard shortcut descriptions for invoice form
+ */
+export const invoiceFormShortcuts = {
+ 'ctrl+s': 'Rechnung speichern',
+ 'ctrl+shift+p': 'Position hinzufügen',
+ 'escape': 'Formular schließen',
+ 'ctrl+shift+d': 'Als Entwurf speichern',
+ 'alt+c': 'Kunde auswählen',
+ 'alt+d': 'Datum ändern'
+}
+
+/**
+ * Hook for managing shortcut conflicts
+ */
+export function useShortcutConflicts() {
+ const conflicts = ref([])
+
+ /**
+ * Checks for conflicting shortcuts
+ * @param {object} shortcuts - Shortcuts object
+ * @returns {Array} Array of conflicts
+ */
+ const checkConflicts = (shortcuts) => {
+ const normalized = {}
+ const conflictsList = []
+
+ Object.keys(shortcuts).forEach(key => {
+ const normalizedKey = key.toLowerCase()
+ if (normalized[normalizedKey]) {
+ conflictsList.push({
+ shortcut: key,
+ conflictsWith: normalized[normalizedKey]
+ })
+ } else {
+ normalized[normalizedKey] = key
+ }
+ })
+
+ conflicts.value = conflictsList
+ return conflictsList
+ }
+
+ /**
+ * Checks if a shortcut conflicts with browser defaults
+ * @param {string} shortcut - Shortcut string
+ * @returns {boolean}
+ */
+ const conflictsWithBrowser = (shortcut) => {
+ const browserShortcuts = [
+ 'ctrl+t', // New tab
+ 'ctrl+w', // Close tab
+ 'ctrl+n', // New window
+ 'ctrl+r', // Reload
+ 'ctrl+p', // Print
+ 'ctrl+o', // Open file
+ 'ctrl+l', // Address bar
+ 'ctrl+k', // Search bar
+ 'ctrl+d', // Bookmark
+ 'ctrl+h', // History
+ 'ctrl+j', // Downloads
+ 'ctrl+shift+t', // Reopen closed tab
+ 'ctrl+shift+n', // New incognito window
+ 'ctrl+shift+delete' // Clear browsing data
+ ]
+
+ return browserShortcuts.includes(shortcut.toLowerCase())
+ }
+
+ return {
+ conflicts,
+ checkConflicts,
+ conflictsWithBrowser
+ }
+}
diff --git a/docs/design/invoice-form-improved.vue b/docs/design/invoice-form-improved.vue
new file mode 100644
index 0000000..2619da3
--- /dev/null
+++ b/docs/design/invoice-form-improved.vue
@@ -0,0 +1,974 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/design/invoice-form-styles.css b/docs/design/invoice-form-styles.css
new file mode 100644
index 0000000..13e5e35
--- /dev/null
+++ b/docs/design/invoice-form-styles.css
@@ -0,0 +1,748 @@
+/**
+ * INVOICE FORM DESIGN SYSTEM STYLES
+ * Zusätzliche CSS-Utilities und Variablen für das verbesserte Invoice-Formular
+ *
+ * Hinweis: Diese Styles sollten in das bestehende Tailwind CSS System integriert werden
+ */
+
+/* ========================================
+ CSS Custom Properties (Design Tokens)
+ ======================================== */
+
+:root {
+ /* Spacing System */
+ --space-section-gap: 2rem; /* 32px - Abstand zwischen Sektionen */
+ --space-section-padding: 1.5rem; /* 24px - Innen-Padding der Sektionen */
+ --space-field-gap: 1rem; /* 16px - Abstand zwischen Feldern */
+ --space-inline-gap: 0.75rem; /* 12px - Abstand zwischen Inline-Feldern */
+
+ /* Component Heights */
+ --height-form-element: 2.75rem; /* 44px - Touch-friendly height */
+ --height-button: 2.5rem; /* 40px - Standard button height */
+ --height-button-small: 2rem; /* 32px - Small button height */
+
+ /* Typography */
+ --font-size-dialog-title: 1.5rem; /* 24px */
+ --font-size-section-title: 1.25rem; /* 20px */
+ --font-size-subsection: 1rem; /* 16px */
+ --font-size-body: 0.875rem; /* 14px */
+ --font-size-small: 0.75rem; /* 12px */
+ --font-size-large: 1rem; /* 16px */
+
+ /* Font Weights */
+ --font-weight-normal: 400;
+ --font-weight-medium: 500;
+ --font-weight-semibold: 600;
+ --font-weight-bold: 700;
+
+ /* Border Radius */
+ --radius-section: 0.75rem; /* 12px */
+ --radius-card: 0.5rem; /* 8px */
+ --radius-small: 0.375rem; /* 6px */
+ --radius-button: 0.375rem; /* 6px */
+
+ /* Shadows */
+ --shadow-section: 0 1px 3px rgba(0, 0, 0, 0.1);
+ --shadow-hover: 0 2px 8px rgba(0, 0, 0, 0.08);
+ --shadow-elevated: 0 4px 12px rgba(0, 0, 0, 0.12);
+
+ /* Transitions */
+ --transition-fast: 0.15s ease-in-out;
+ --transition-normal: 0.2s ease;
+ --transition-slow: 0.3s ease;
+
+ /* Z-Index Scale */
+ --z-base: 1;
+ --z-dropdown: 1000;
+ --z-sticky: 1020;
+ --z-fixed: 1030;
+ --z-modal-backdrop: 1040;
+ --z-modal: 1050;
+ --z-popover: 1060;
+ --z-tooltip: 1070;
+}
+
+/* Dark Mode Adjustments */
+.app-dark {
+ --shadow-section: 0 1px 3px rgba(0, 0, 0, 0.3);
+ --shadow-hover: 0 2px 8px rgba(0, 0, 0, 0.25);
+ --shadow-elevated: 0 4px 12px rgba(0, 0, 0, 0.35);
+}
+
+/* ========================================
+ Form Section Components
+ ======================================== */
+
+.form-section {
+ background: var(--surface-0);
+ border: 1px solid var(--surface-200);
+ border-radius: var(--radius-section);
+ padding: var(--space-section-padding);
+ box-shadow: var(--shadow-section);
+ transition: box-shadow var(--transition-normal);
+}
+
+.form-section:hover {
+ box-shadow: var(--shadow-hover);
+}
+
+.form-section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+ padding-bottom: 0.75rem;
+ border-bottom: 1px solid var(--surface-200);
+}
+
+.form-section-title {
+ font-size: var(--font-size-section-title);
+ font-weight: var(--font-weight-semibold);
+ color: var(--text-primary);
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.form-section-body {
+ /* Container for form fields */
+}
+
+/* ========================================
+ Form Layout Utilities
+ ======================================== */
+
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--space-field-gap);
+ align-items: start;
+}
+
+.form-grid--3-col {
+ grid-template-columns: repeat(3, 1fr);
+}
+
+.form-grid--4-col {
+ grid-template-columns: repeat(4, 1fr);
+}
+
+.form-field-full {
+ grid-column: 1 / -1;
+}
+
+.form-field-span-2 {
+ grid-column: span 2;
+}
+
+/* Responsive Grid */
+@media (max-width: 992px) {
+ .form-grid,
+ .form-grid--3-col,
+ .form-grid--4-col {
+ grid-template-columns: 1fr;
+ }
+
+ .form-field-span-2 {
+ grid-column: span 1;
+ }
+}
+
+/* ========================================
+ Invoice Item Components
+ ======================================== */
+
+.invoice-items-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.invoice-item-card {
+ background: var(--surface-50);
+ border: 1px solid var(--surface-200);
+ border-radius: var(--radius-card);
+ padding: 1rem;
+ transition: all var(--transition-normal);
+}
+
+.invoice-item-card:hover {
+ box-shadow: var(--shadow-hover);
+ border-color: var(--primary-color);
+}
+
+.invoice-item-card--new {
+ animation: slideIn var(--transition-slow) ease-out;
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.invoice-item-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.invoice-item-number {
+ font-weight: var(--font-weight-semibold);
+ color: var(--text-secondary);
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.invoice-item-fields {
+ display: grid;
+ grid-template-columns: 1fr 1fr auto;
+ gap: 1rem;
+ align-items: start;
+}
+
+.invoice-item-field--small {
+ max-width: 120px;
+}
+
+@media (max-width: 768px) {
+ .invoice-item-fields {
+ grid-template-columns: 1fr;
+ }
+
+ .invoice-item-field--small {
+ max-width: 100%;
+ }
+}
+
+.invoice-item-subtotal {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 1rem;
+ padding-top: 1rem;
+ border-top: 1px solid var(--surface-200);
+}
+
+/* ========================================
+ Invoice Summary Component
+ ======================================== */
+
+.invoice-summary {
+ background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%);
+ border: 2px solid var(--primary-color);
+ border-radius: var(--radius-section);
+ padding: 1.5rem;
+ margin-top: 1.5rem;
+}
+
+.app-dark .invoice-summary {
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(59, 130, 246, 0.25) 100%);
+}
+
+.invoice-summary-header {
+ margin-bottom: 1rem;
+}
+
+.invoice-summary-header h4 {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: var(--font-weight-semibold);
+ color: var(--text-primary);
+}
+
+.invoice-summary-body {
+ /* Container for summary rows */
+}
+
+.invoice-summary-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem 0;
+ font-size: var(--font-size-body);
+ color: var(--text-secondary);
+}
+
+.invoice-summary-total {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem 0 0;
+ margin-top: 0.75rem;
+ border-top: 2px solid var(--surface-200);
+ font-size: 1.25rem;
+ font-weight: var(--font-weight-bold);
+ color: var(--text-primary);
+}
+
+/* ========================================
+ Empty State Component
+ ======================================== */
+
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 3rem 1rem;
+ text-align: center;
+}
+
+.empty-state-icon {
+ font-size: 3rem;
+ color: var(--text-muted);
+ margin-bottom: 1rem;
+ opacity: 0.5;
+}
+
+.empty-state-text {
+ font-size: 1rem;
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+ font-weight: var(--font-weight-medium);
+}
+
+.empty-state-description {
+ font-size: var(--font-size-small);
+ color: var(--text-muted);
+ margin-bottom: 1.5rem;
+}
+
+/* ========================================
+ Form Actions
+ ======================================== */
+
+.form-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+ padding: 1.5rem 0;
+ border-top: 1px solid var(--surface-200);
+ margin-top: 1.5rem;
+}
+
+.form-actions-secondary {
+ display: flex;
+ gap: 0.5rem;
+ align-items: center;
+}
+
+@media (max-width: 768px) {
+ .form-actions {
+ flex-direction: column-reverse;
+ align-items: stretch;
+ }
+
+ .form-actions-secondary {
+ flex-direction: column;
+ width: 100%;
+ }
+}
+
+/* ========================================
+ Context Card (Payment Form)
+ ======================================== */
+
+.context-card {
+ background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%);
+ border: 1px solid var(--primary-200);
+ border-radius: var(--radius-section);
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.app-dark .context-card {
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.2) 100%);
+ border-color: rgba(59, 130, 246, 0.3);
+}
+
+.context-card-header {
+ margin-bottom: 1rem;
+ padding-bottom: 0.75rem;
+ border-bottom: 1px solid var(--primary-200);
+}
+
+.context-card-header h4 {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: var(--font-weight-semibold);
+}
+
+.context-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem 0;
+}
+
+.context-row--highlight {
+ padding: 1rem 0 0;
+ margin-top: 0.5rem;
+ border-top: 1px solid var(--primary-300);
+}
+
+.context-label {
+ color: var(--text-secondary);
+ font-size: var(--font-size-body);
+}
+
+.context-value {
+ text-align: right;
+ font-weight: var(--font-weight-medium);
+}
+
+/* ========================================
+ Upload Area (PDF Upload)
+ ======================================== */
+
+.upload-area {
+ border: 2px dashed var(--surface-200);
+ border-radius: var(--radius-section);
+ padding: 3rem 2rem;
+ text-align: center;
+ transition: all var(--transition-normal);
+ cursor: pointer;
+ background: var(--surface-0);
+}
+
+.upload-area:hover {
+ border-color: var(--primary-color);
+ background: var(--primary-50);
+}
+
+.upload-area--dragover {
+ border-color: var(--primary-color);
+ background: var(--primary-100);
+ border-width: 3px;
+ transform: scale(1.02);
+}
+
+.upload-placeholder {
+ /* Container for upload placeholder content */
+}
+
+.upload-icon {
+ font-size: 4rem;
+ color: var(--primary-color);
+ margin-bottom: 1rem;
+ opacity: 0.8;
+}
+
+.upload-hint {
+ font-size: var(--font-size-small);
+ color: var(--text-muted);
+ margin-top: 1rem;
+}
+
+.upload-preview {
+ /* Container for file preview */
+}
+
+.file-info {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem;
+ background: var(--surface-50);
+ border: 1px solid var(--surface-200);
+ border-radius: var(--radius-card);
+}
+
+.file-icon {
+ font-size: 2.5rem;
+ color: var(--danger-color);
+ flex-shrink: 0;
+}
+
+.file-details {
+ flex: 1;
+ text-align: left;
+ min-width: 0; /* Allow text truncation */
+}
+
+.file-name {
+ font-weight: var(--font-weight-semibold);
+ margin-bottom: 0.25rem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.file-size {
+ font-size: var(--font-size-body);
+ color: var(--text-secondary);
+}
+
+/* ========================================
+ Keyboard Hints
+ ======================================== */
+
+.keyboard-hints {
+ text-align: center;
+ padding: 1rem;
+ border-top: 1px solid var(--surface-200);
+ margin-top: 1rem;
+}
+
+kbd {
+ background: var(--surface-100);
+ border: 1px solid var(--surface-300);
+ border-radius: 0.25rem;
+ padding: 0.125rem 0.375rem;
+ font-size: var(--font-size-small);
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+@media (max-width: 768px) {
+ .keyboard-hints {
+ display: none;
+ }
+}
+
+/* ========================================
+ Validation & Error States
+ ======================================== */
+
+.p-error {
+ color: var(--danger-color);
+ font-size: var(--font-size-small);
+ margin-top: 0.25rem;
+ display: block;
+ animation: fadeIn 0.2s ease;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-4px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Required field indicator */
+abbr[title] {
+ text-decoration: none;
+ color: var(--danger-color);
+ font-weight: var(--font-weight-bold);
+ margin-left: 0.125rem;
+}
+
+/* ========================================
+ List Transitions
+ ======================================== */
+
+.list-enter-active,
+.list-leave-active {
+ transition: all 0.3s ease;
+}
+
+.list-enter-from {
+ opacity: 0;
+ transform: translateY(-20px);
+}
+
+.list-leave-to {
+ opacity: 0;
+ transform: translateX(20px);
+}
+
+.list-move {
+ transition: transform 0.3s ease;
+}
+
+/* ========================================
+ Accessibility Utilities
+ ======================================== */
+
+/* Screen reader only content */
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+/* Focus visible styles */
+*:focus-visible {
+ outline: 2px solid var(--primary-color);
+ outline-offset: 2px;
+ border-radius: var(--radius-small);
+}
+
+/* Skip to main content link */
+.skip-link {
+ position: absolute;
+ top: -40px;
+ left: 0;
+ background: var(--primary-color);
+ color: white;
+ padding: 8px 16px;
+ text-decoration: none;
+ z-index: var(--z-tooltip);
+ border-radius: 0 0 var(--radius-small) 0;
+ font-weight: var(--font-weight-medium);
+}
+
+.skip-link:focus {
+ top: 0;
+}
+
+/* ========================================
+ Loading States
+ ======================================== */
+
+.skeleton-loader {
+ background: linear-gradient(
+ 90deg,
+ var(--surface-200) 25%,
+ var(--surface-100) 50%,
+ var(--surface-200) 75%
+ );
+ background-size: 200% 100%;
+ animation: loading 1.5s ease-in-out infinite;
+ border-radius: var(--radius-small);
+}
+
+@keyframes loading {
+ 0% {
+ background-position: 200% 0;
+ }
+ 100% {
+ background-position: -200% 0;
+ }
+}
+
+/* ========================================
+ Utility Classes
+ ======================================== */
+
+/* Text utilities */
+.text-muted {
+ color: var(--text-muted);
+}
+
+.text-secondary {
+ color: var(--text-secondary);
+}
+
+.text-primary-color {
+ color: var(--primary-color);
+}
+
+/* Spacing utilities */
+.gap-section {
+ gap: var(--space-section-gap);
+}
+
+.gap-field {
+ gap: var(--space-field-gap);
+}
+
+.gap-inline {
+ gap: var(--space-inline-gap);
+}
+
+/* Flex utilities */
+.flex-between {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.flex-center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+/* ========================================
+ Print Styles
+ ======================================== */
+
+@media print {
+ .form-actions,
+ .keyboard-hints,
+ .invoice-item-actions,
+ .form-section-header button {
+ display: none !important;
+ }
+
+ .form-section {
+ box-shadow: none;
+ border: 1px solid #ddd;
+ page-break-inside: avoid;
+ }
+
+ .invoice-summary {
+ border: 2px solid #333;
+ page-break-inside: avoid;
+ }
+}
+
+/* ========================================
+ Dark Mode Specific Adjustments
+ ======================================== */
+
+.app-dark .invoice-item-card {
+ background: rgba(255, 255, 255, 0.05);
+}
+
+.app-dark .form-section {
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
+.app-dark kbd {
+ background: rgba(255, 255, 255, 0.1);
+ border-color: rgba(255, 255, 255, 0.2);
+}
+
+/* ========================================
+ High Contrast Mode Support
+ ======================================== */
+
+@media (prefers-contrast: high) {
+ .form-section {
+ border-width: 2px;
+ }
+
+ .invoice-item-card {
+ border-width: 2px;
+ }
+
+ *:focus-visible {
+ outline-width: 3px;
+ }
+}
+
+/* ========================================
+ Reduced Motion Support
+ ======================================== */
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+}
diff --git a/src/Entity/Contact.php b/src/Entity/Contact.php
index f47500d..d340fe8 100644
--- a/src/Entity/Contact.php
+++ b/src/Entity/Contact.php
@@ -59,11 +59,11 @@ class Contact implements ModuleAwareInterface
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
- #[Groups(['contact:read', 'project:read'])]
+ #[Groups(['contact:read', 'project:read', 'invoice:read'])]
private ?int $id = null;
#[ORM\Column(length: 255)]
- #[Groups(['contact:read', 'contact:write', 'project:read'])]
+ #[Groups(['contact:read', 'contact:write', 'project:read', 'invoice:read'])]
#[Assert\NotBlank(message: 'Der Firmenname darf nicht leer sein')]
#[Assert\Length(max: 255)]
private ?string $companyName = null;