"fix-guest-auto-flush-on-submit"
This commit is contained in:
@@ -2,18 +2,42 @@
|
|||||||
* Gestion de la liste d'invités pour une facture.
|
* Gestion de la liste d'invités pour une facture.
|
||||||
* Ajout / suppression d'invités (nom + entreprise).
|
* Ajout / suppression d'invités (nom + entreprise).
|
||||||
*/
|
*/
|
||||||
import { useState } from 'react';
|
import { useState, useImperativeHandle, forwardRef } from 'react';
|
||||||
import type { Guest } from '../types';
|
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 {
|
interface Props {
|
||||||
guests: Guest[];
|
guests: Guest[];
|
||||||
onChange: (guests: Guest[]) => void;
|
onChange: (guests: Guest[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GuestManager({ guests, onChange }: Props) {
|
const GuestManager = forwardRef<GuestManagerHandle, Props>(function GuestManager({ guests, onChange }, ref) {
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [company, setCompany] = 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() {
|
function addGuest() {
|
||||||
const trimmed = name.trim();
|
const trimmed = name.trim();
|
||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
@@ -94,4 +118,6 @@ export default function GuestManager({ guests, onChange }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export default GuestManager;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import api from '../api/client';
|
import api from '../api/client';
|
||||||
import Camera from '../components/Camera';
|
import Camera from '../components/Camera';
|
||||||
import GuestManager from '../components/GuestManager';
|
import GuestManager, { type GuestManagerHandle } from '../components/GuestManager';
|
||||||
import { useOCR } from '../hooks/useOCR';
|
import { useOCR } from '../hooks/useOCR';
|
||||||
import { useOfflineQueue } from '../hooks/useOfflineQueue';
|
import { useOfflineQueue } from '../hooks/useOfflineQueue';
|
||||||
import type { Company, Category, Guest, InvoiceImage } from '../types';
|
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
|
// Ref pour retenir l'invoice_id créé si le send échoue hors ligne
|
||||||
const createdInvoiceIdRef = useRef<string | null>(null);
|
const createdInvoiceIdRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
// Ref vers GuestManager pour flusher l'invité en cours de saisie
|
||||||
|
const guestManagerRef = useRef<GuestManagerHandle>(null);
|
||||||
|
|
||||||
// ── Étape du flux ──────────────────────────────────────────
|
// ── Étape du flux ──────────────────────────────────────────
|
||||||
const [step, setStep] = useState<Step>('capture');
|
const [step, setStep] = useState<Step>('capture');
|
||||||
|
|
||||||
@@ -101,6 +104,15 @@ export default function NewInvoice() {
|
|||||||
|
|
||||||
createdInvoiceIdRef.current = null;
|
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) => ({
|
const invoiceImages: InvoiceImage[] = images.map((img) => ({
|
||||||
path: img.filename,
|
path: img.filename,
|
||||||
order: img.order,
|
order: img.order,
|
||||||
@@ -116,7 +128,7 @@ export default function NewInvoice() {
|
|||||||
comment: comment || undefined,
|
comment: comment || undefined,
|
||||||
images: invoiceImages,
|
images: invoiceImages,
|
||||||
add_to_tracking: addTracking,
|
add_to_tracking: addTracking,
|
||||||
guests,
|
guests: finalGuests,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mémoriser l'id en cas d'échec du send hors ligne
|
// Mémoriser l'id en cas d'échec du send hors ligne
|
||||||
@@ -441,7 +453,7 @@ export default function NewInvoice() {
|
|||||||
|
|
||||||
{showGuests && (
|
{showGuests && (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<GuestManager guests={guests} onChange={setGuests} />
|
<GuestManager ref={guestManagerRef} guests={guests} onChange={setGuests} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user