import { useState } from 'react'; import { useForm, useFieldArray } from 'react-hook-form'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { useToast } from '@/hooks/use-toast'; import { useAuth } from '@/hooks/useAuth'; import { useStoreContext } from '@/hooks/useStoreContext'; import { useSimpleCamera } from '@/hooks/useSimpleCamera'; import { apiRequest } from '@/lib/queryClient'; import { validateRequiredFields, validateEAN13, ARTICLE_CATEGORY_MAPPING, IDENTITY_TYPES, PAYMENT_METHODS } from '@/lib/validation'; import { Package, User, Users, Save, Calendar, BadgeCheck, Info, Camera, Upload, Trash2, Plus, Minus } from 'lucide-react'; import SimpleCameraModal from '@/components/SimpleCameraModal'; import type { PhotoType, FormData } from '@/types/sale'; export default function NewSaleFormMulti() { const { toast } = useToast(); const queryClient = useQueryClient(); const { user } = useAuth(); const { selectedStoreId } = useStoreContext(); const [isSubmitting, setIsSubmitting] = useState(false); // Camera functionality const { isOpen: isCameraOpen, currentPhotoType, isCapturing, videoRef, canvasRef, startCamera, capturePhoto, stopCamera } = useSimpleCamera(); const form = useForm({ defaultValues: { vendeur: '', products: [ { typeArticle: '', categorie: '', quantite: '1', gencode: '', } ], nom: '', prenom: '', dateNaissance: '', lieuNaissance: '', modePaiement: 'Espèce', typeIdentite: '', numeroIdentite: '', autoriteDelivrance: '', dateDelivrance: '', photoRecto: '', photoVerso: '', photoTicket: '', } }); const { fields, append, remove } = useFieldArray({ control: form.control, name: "products" }); const createSaleMutation = useMutation({ mutationFn: async (saleData: any) => { const dataToSend = { ...saleData, ...(user?.role === 'administrator' && selectedStoreId && { storeId: selectedStoreId }) }; const response = await apiRequest('/api/sales', { method: 'POST', body: JSON.stringify(dataToSend), }); return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/sales'], exact: false }); toast({ title: "Vente enregistrée", description: "La vente a été enregistrée avec succès", variant: "success", }); form.reset(); }, onError: (error: any) => { const errorMessage = error.message || 'Erreur lors de la création de la vente'; toast({ title: "Erreur", description: errorMessage, variant: "destructive", }); }, }); const onSubmit = async (data: FormData) => { console.log('Form data:', data); setIsSubmitting(true); try { // Validation des champs requis const missingFields = validateRequiredFields(data); if (missingFields.length > 0) { toast({ title: "Champs obligatoires manquants", description: `Veuillez remplir : ${missingFields.join(', ')}`, variant: "destructive", }); setIsSubmitting(false); return; } // Validation EAN-13 pour tous les produits const invalidBarcodes = data.products.filter(product => !validateEAN13(product.gencode)); if (invalidBarcodes.length > 0) { toast({ title: "Code-barres invalide", description: "Certains codes EAN-13 ne sont pas valides", variant: "destructive", }); setIsSubmitting(false); return; } await createSaleMutation.mutateAsync(data); } catch (error) { console.error('Submit error:', error); } finally { setIsSubmitting(false); } }; const handlePhotoCapture = async (photoType: PhotoType) => { console.log('=== PHOTO CAPTURE REQUESTED ==='); console.log('Photo type:', photoType); // Vérifications préliminaires if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { toast({ title: "Navigateur non compatible", description: "Votre navigateur ne supporte pas la capture photo. Utilisez Chrome, Firefox ou Safari récent.", variant: "destructive", }); return; } // Vérifier si nous sommes en HTTPS (requis par certains navigateurs) if (location.protocol !== 'https:' && location.hostname !== 'localhost') { toast({ title: "Connexion sécurisée requise", description: "L'accès à la caméra nécessite une connexion HTTPS.", variant: "destructive", }); return; } try { console.log('Attempting to start camera...'); // Afficher toast de démarrage toast({ title: "Démarrage de la caméra", description: "Préparation de la caméra en cours...", }); // Démarrer la caméra simple await startCamera(photoType); console.log('Simple camera started successfully'); } catch (error) { console.error('Camera start failed:', error); toast({ title: "Erreur d'accès à la caméra", description: error instanceof Error ? error.message : "Impossible d'accéder à la caméra", variant: "destructive", }); } }; const handleCapturePhoto = async () => { if (!currentPhotoType) return; try { console.log('Attempting to capture photo...'); const photoData = await capturePhoto(); console.log('Photo captured successfully, data length:', photoData.length); if (currentPhotoType === 'recto') { form.setValue('photoRecto', photoData); } else if (currentPhotoType === 'verso') { form.setValue('photoVerso', photoData); } else if (currentPhotoType === 'ticket') { form.setValue('photoTicket', photoData); } toast({ title: "Photo capturée", description: `Photo ${currentPhotoType} enregistrée avec succès`, variant: "success", }); stopCamera(); } catch (error) { console.error('Photo capture failed:', error); toast({ title: "Erreur de capture", description: error instanceof Error ? error.message : "Impossible de capturer la photo", variant: "destructive", }); } }; const handleRemovePhoto = (photoType: PhotoType) => { const field = photoType === 'recto' ? 'photoRecto' : photoType === 'verso' ? 'photoVerso' : 'photoTicket'; form.setValue(field, ''); }; const handleFileUpload = (photoType: PhotoType, event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { const result = e.target?.result as string; const field = photoType === 'recto' ? 'photoRecto' : photoType === 'verso' ? 'photoVerso' : 'photoTicket'; form.setValue(field, result); }; reader.readAsDataURL(file); } }; const addProduct = () => { append({ typeArticle: '', categorie: '', quantite: '1', gencode: '', }); }; const removeProduct = (index: number) => { if (fields.length > 1) { remove(index); } }; const getAvailableCategories = (typeArticle: string) => { return typeArticle ? ARTICLE_CATEGORY_MAPPING[typeArticle as keyof typeof ARTICLE_CATEGORY_MAPPING] || [] : []; }; return (
{/* Section Vendeur */}

Informations vendeur

( Nom du vendeur * )} />
{/* Section Produits */}

Produits

{fields.map((field, index) => (

Produit {index + 1}

{fields.length > 1 && ( )}
( Type d'article * )} /> ( Catégorie * )} /> ( Quantité * )} /> ( Code EAN-13 * )} />
))}
{/* Section Client */}

Informations client

( Nom * )} /> ( Prénom * )} /> ( Date de naissance * )} /> ( Lieu de naissance * )} /> ( Mode de paiement * )} />
{/* Section Identité */}

Pièce d'identité

( Type de pièce d'identité * )} /> ( Numéro de pièce d'identité * )} /> ( Autorité de délivrance )} /> ( Date de délivrance * )} />
{/* Photos */}

Photos de la pièce d'identité

{/* Photo Recto */}
{form.watch('photoRecto') && ( )}
{form.watch('photoRecto') && (
✓ Photo recto capturée
)}
{/* Photo Verso */}
{form.watch('photoVerso') && ( )}
{form.watch('photoVerso') && (
✓ Photo verso capturée
)}
{/* Photo Ticket */}
{form.watch('photoTicket') && ( )}
{form.watch('photoTicket') && (
✓ Photo ticket capturée
)}
{/* Boutons d'action */}
); }