From 82b022ba3bde11e4cd8a3647a46e413cb06f85ae Mon Sep 17 00:00:00 2001 From: olli Date: Sat, 13 Dec 2025 10:02:30 +0100 Subject: [PATCH] Add comprehensive styles for invoice form components and utilities - Introduced CSS custom properties for spacing, typography, colors, and shadows. - Developed styles for form sections, grids, invoice items, and summary components. - Implemented responsive design adjustments for various screen sizes. - Added utility classes for text, spacing, and flex layouts. - Included dark mode and high contrast mode support. - Established loading states and validation/error styles. - Enhanced accessibility features with focus styles and screen reader utilities. --- .claude/agents/architect-review.md | 31 + .claude/agents/symfony-expert.md | 77 + .claude/agents/ux-designer.md | 42 + assets/js/views/InvoiceForm.vue | 524 ++++- assets/js/views/InvoiceManagement.vue | 207 +- docs/ARCHITECTURE_REVIEW_2025-12-12.md | 1376 +++++++++++++ docs/design/INVOICE_FORM_UX_DESIGN.md | 1805 +++++++++++++++++ docs/design/README.md | 448 ++++ docs/design/VISUAL_DESIGN_GUIDE.md | 679 +++++++ docs/design/composables/useFormValidation.js | 400 ++++ .../composables/useKeyboardShortcuts.js | 386 ++++ docs/design/invoice-form-improved.vue | 974 +++++++++ docs/design/invoice-form-styles.css | 748 +++++++ src/Entity/Contact.php | 4 +- 14 files changed, 7507 insertions(+), 194 deletions(-) create mode 100644 .claude/agents/architect-review.md create mode 100644 .claude/agents/symfony-expert.md create mode 100644 .claude/agents/ux-designer.md create mode 100644 docs/ARCHITECTURE_REVIEW_2025-12-12.md create mode 100644 docs/design/INVOICE_FORM_UX_DESIGN.md create mode 100644 docs/design/README.md create mode 100644 docs/design/VISUAL_DESIGN_GUIDE.md create mode 100644 docs/design/composables/useFormValidation.js create mode 100644 docs/design/composables/useKeyboardShortcuts.js create mode 100644 docs/design/invoice-form-improved.vue create mode 100644 docs/design/invoice-form-styles.css 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 + + + + + +``` + +**StatusBadge.vue** - Einheitliche Status-Anzeige +```vue + + + +``` + +--- + +## 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 }} +
+ + + +``` + +### 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 @@ + + +