2 Commits

Author SHA1 Message Date
ordinarthur
06dcf38fee feat(ui): DatePicker brandé Rubis (remplace input type=date)
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 20s
Le natif <input type="date"> affiche le calendrier moche du navigateur
(différent par client OS, pas d'alignement palette). On le remplace par
un composant Radix Popover + grille mensuelle Tailwind aux couleurs
Rubis.

Composant : apps/web/src/components/ui/DatePicker.tsx
- Trigger : bouton style Input (border line, focus rubis-glow,
  data-[state=open]:border-rubis pour hint visuel)
- Popover Radix : focus trap, Escape, click outside, animation
- Grille 7×6 (semaine commence lundi, locale FR via date-fns)
- Sélection : bg-rubis text-white + ombre rubis
- Today : ring-inset rubis-glow
- Hover : bg-cream
- Raccourci "Aujourd'hui" en footer

API alignée avec l'usage existant :
- value: string ISO | Date | null
- onChange(date: Date) — l'appelant fait .toISOString() comme avant

Usages migrés :
- ManualInvoiceDialog.tsx : Date d'émission
- factures_.import_.$batchId.tsx : Date d'échéance (avec préservation
  du className aria-invalid pour les low-confidence OCR)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:01:31 +02:00
ordinarthur
cfd3680bb4 feat(web): saisie manuelle de facture (modale Radix Dialog)
Modale 'Nouvelle facture' (cf. wireframe 2.3) accessible depuis 4 points :
- Topbar '+ Saisir' (était disabled)
- /factures/import bouton 'Saisir manuellement' (header)
- Dropzone empty state sur /factures (variant full)
- (Reachable de partout dans _app/* via le topbar)

Composants ajoutés :
- Dialog : wrapper Radix Dialog stylé (overlay ink/35 + blur, content
  bg-cream + border-line + shadow-card, close button discret, animations
  fade+zoom). Header / Title / Description / Footer / Close.
- ClientCombobox : autocomplete maison (pas Radix Combobox qui n'existe
  pas, pas cmdk overkill). Input + dropdown filtré, click-outside ferme,
  Escape ferme, option 'Créer le client « X »' quand pas de match exact.
  Border rubis quand un client existant est sélectionné.
- ManualInvoiceDialog : form complet (TanStack Form + validateurs Zod
  par champ). Client (combobox), N° + date émission (côte-à-côte), montant
  + échéance relative 15/30/45/60/90j (Select Radix), plan de relance.

Architecture clean :
- ManualInvoiceProvider au sommet d'AppLayout rend la modale une seule
  fois (un seul réseau de portals Radix)
- Hook useManualInvoice() expose open()/close()/isOpen, accessible
  depuis n'importe quelle route enfant sans plumber des callbacks
- État local de la modale (pas dans l'URL — propre pour V1)

Logique métier MSW :
- GET /api/v1/clients (autocomplete)
- POST /api/v1/invoices : résolution client (clientId fourni → utilise,
  sinon match par nom case-insensitive, sinon création à la volée).
  +1 rubis bonus saisie.
- Conversion relativeDueDays (15/30/45/60/90) → dueDate absolue à la
  soumission

Bug fix montant TTC :
- L'input était contrôlé avec value={(cents/100).toFixed(2)} → reformat
  à chaque keystroke écrasait '10000' en '1.00' (impossible de taper
  des gros montants)
- Passé en defaultValue (uncontrolled) avec step='any' + inputMode='decimal'
- Accepte virgule FR (1240,50) et point (1240.50)
- DialogContent unmount à la fermeture → defaultValue ré-évalué à
  chaque réouverture (reset OK)

Bouton '+ Saisir' du topbar plus disabled, bouton 'Saisir manuellement'
de /factures/import plus disabled. Le bouton dans la dropzone (variant
full) reçoit un onManualEntry prop optionnel.

Bundle prod : 117.62 KB gzip core (+0.06 KB), useManualInvoiceDialog
chunk 6.68 KB gzip, Select chunk 25.14 KB gzip (partagé OCR + plan
editor + manual entry).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 11:50:46 +02:00