diff --git a/frontend/src/components/GuestManager.tsx b/frontend/src/components/GuestManager.tsx index de16b88..08eac9d 100644 --- a/frontend/src/components/GuestManager.tsx +++ b/frontend/src/components/GuestManager.tsx @@ -2,18 +2,42 @@ * Gestion de la liste d'invités pour une facture. * Ajout / suppression d'invités (nom + entreprise). */ -import { useState } from 'react'; +import { useState, useImperativeHandle, forwardRef } from 'react'; import type { Guest } from '../types'; +export interface GuestManagerHandle { + /** + * Si un invité est en cours de saisie (champ non validé), l'ajoute à la liste + * et retourne la liste complète (incluant ce nouvel invité). + * Retourne null si rien à flusher. + */ + flushPending: () => Guest[] | null; +} + interface Props { guests: Guest[]; onChange: (guests: Guest[]) => void; } -export default function GuestManager({ guests, onChange }: Props) { +const GuestManager = forwardRef(function GuestManager({ guests, onChange }, ref) { const [name, setName] = useState(''); const [company, setCompany] = useState(''); + useImperativeHandle(ref, () => ({ + flushPending() { + const trimmed = name.trim(); + if (!trimmed) return null; + const updated: Guest[] = [ + ...guests, + { name: trimmed, company: company.trim() || null, sort_order: guests.length }, + ]; + onChange(updated); + setName(''); + setCompany(''); + return updated; + }, + })); + function addGuest() { const trimmed = name.trim(); if (!trimmed) return; @@ -94,4 +118,6 @@ export default function GuestManager({ guests, onChange }: Props) { ); -} +}); + +export default GuestManager; diff --git a/frontend/src/pages/NewInvoice.tsx b/frontend/src/pages/NewInvoice.tsx index ab4ae82..2c2bb71 100644 --- a/frontend/src/pages/NewInvoice.tsx +++ b/frontend/src/pages/NewInvoice.tsx @@ -7,7 +7,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import toast from 'react-hot-toast'; import api from '../api/client'; import Camera from '../components/Camera'; -import GuestManager from '../components/GuestManager'; +import GuestManager, { type GuestManagerHandle } from '../components/GuestManager'; import { useOCR } from '../hooks/useOCR'; import { useOfflineQueue } from '../hooks/useOfflineQueue'; import type { Company, Category, Guest, InvoiceImage } from '../types'; @@ -34,6 +34,9 @@ export default function NewInvoice() { // Ref pour retenir l'invoice_id créé si le send échoue hors ligne const createdInvoiceIdRef = useRef(null); + // Ref vers GuestManager pour flusher l'invité en cours de saisie + const guestManagerRef = useRef(null); + // ── Étape du flux ────────────────────────────────────────── const [step, setStep] = useState('capture'); @@ -101,6 +104,15 @@ export default function NewInvoice() { createdInvoiceIdRef.current = null; + // Auto-ajouter l'invité en cours de saisie s'il n'a pas encore été + // validé via "Ajouter l'invité" (cas fréquent : l'utilisateur tape + // le nom et clique directement sur "Envoyer"). + // flushPending() retourne la liste complète si un guest a été flushé, + // null sinon — on utilise cette valeur pour éviter un souci de timing + // avec la mise à jour asynchrone du state React. + const guestsFlushed = guestManagerRef.current?.flushPending() ?? null; + const finalGuests = guestsFlushed ?? guests; + const invoiceImages: InvoiceImage[] = images.map((img) => ({ path: img.filename, order: img.order, @@ -116,7 +128,7 @@ export default function NewInvoice() { comment: comment || undefined, images: invoiceImages, add_to_tracking: addTracking, - guests, + guests: finalGuests, }); // Mémoriser l'id en cas d'échec du send hors ligne @@ -441,7 +453,7 @@ export default function NewInvoice() { {showGuests && (
- +
)}