import { useState } from 'react'; import { useForm } 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 } from 'lucide-react'; import SimpleCameraModal from '@/components/SimpleCameraModal'; import type { PhotoType } from '@/types/sale'; interface FormData { vendeur: string; dateVente: string; typeArticle: string; categorie: string; quantite: string; gencode: string; nom: string; prenom: string; dateNaissance: string; lieuNaissance: string; modePaiement: string; typeIdentite: string; numeroIdentite: string; autoriteDelivrance: string; dateDelivrance: string; photoRecto?: string; photoVerso?: string; } export default function NewSaleForm() { 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: '', dateVente: new Date().toISOString().split('T')[0], typeArticle: '', categorie: '', quantite: '', gencode: '', nom: '', prenom: '', dateNaissance: '', lieuNaissance: '', modePaiement: 'Espèce', typeIdentite: '', numeroIdentite: '', autoriteDelivrance: '', dateDelivrance: '', photoRecto: '', photoVerso: '', } }); const typeArticle = form.watch('typeArticle'); const availableCategories = typeArticle ? ARTICLE_CATEGORY_MAPPING[typeArticle as keyof typeof ARTICLE_CATEGORY_MAPPING] || [] : []; const createSaleMutation = useMutation({ mutationFn: async (saleData: any) => { const dataToSend = { ...saleData, ...(user?.role === 'admin' && 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({ vendeur: '', dateVente: new Date().toISOString().split('T')[0], typeArticle: '', categorie: '', quantite: '', gencode: '', nom: '', prenom: '', dateNaissance: '', lieuNaissance: '', modePaiement: 'Espèce', typeIdentite: '', numeroIdentite: '', autoriteDelivrance: '', dateDelivrance: '', photoRecto: '', photoVerso: '', }); }, onError: (error: any) => { toast({ title: "Erreur", description: error.message || "Erreur lors de l'enregistrement de la vente", variant: "destructive", }); }, }); const onSubmit = async (data: FormData) => { try { setIsSubmitting(true); const missingFields = validateRequiredFields(data); if (missingFields.length > 0) { toast({ title: "Champs manquants", description: `Veuillez remplir: ${missingFields.join(', ')}`, variant: "destructive", }); return; } if (!validateEAN13(data.gencode)) { toast({ title: "Code EAN13 invalide", description: "Le gencode doit être un code EAN13 valide à 13 chiffres", variant: "destructive", }); return; } const saleData = { vendeur: data.vendeur, typeArticle: data.typeArticle, categorie: data.categorie as 'F2' | 'F3', quantite: parseInt(data.quantite), gencode: data.gencode, nom: data.nom, prenom: data.prenom, dateNaissance: data.dateNaissance, lieuNaissance: data.lieuNaissance, typeIdentite: data.typeIdentite, numeroIdentite: data.numeroIdentite, autoriteDelivrance: data.autoriteDelivrance, dateDelivrance: data.dateDelivrance, photoRecto: data.photoRecto, photoVerso: data.photoVerso, }; await createSaleMutation.mutateAsync(saleData); } catch (error) { console.error('Sale creation error:', error); } finally { setIsSubmitting(false); } }; // Photo capture functions 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 handleTakePhoto = async () => { if (!currentPhotoType) return; try { console.log('Attempting to capture photo...'); const photoData = await capturePhoto(); console.log('Photo captured successfully, data length:', photoData.length); form.setValue(currentPhotoType === 'recto' ? 'photoRecto' : 'photoVerso', photoData); toast({ title: "Photo capturée", description: `Photo ${currentPhotoType} enregistrée avec succès`, variant: "success", }); stopCamera(); } catch (error) { console.error('Photo capture error:', error); toast({ title: "Erreur de capture", description: error instanceof Error ? error.message : "Erreur lors de la capture", variant: "destructive", }); } }; const handleRemovePhoto = (photoType: PhotoType) => { form.setValue(photoType === 'recto' ? 'photoRecto' : 'photoVerso', ''); toast({ title: "Photo supprimée", description: `Photo ${photoType} supprimée`, }); }; // File upload fallback const handleFileUpload = (photoType: PhotoType, event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; if (!file.type.startsWith('image/')) { toast({ title: "Type de fichier invalide", description: "Veuillez sélectionner une image", variant: "destructive", }); return; } if (file.size > 5 * 1024 * 1024) { // 5MB limit toast({ title: "Fichier trop volumineux", description: "La taille maximale est de 5MB", variant: "destructive", }); return; } const reader = new FileReader(); reader.onload = (e) => { const result = e.target?.result as string; form.setValue(photoType === 'recto' ? 'photoRecto' : 'photoVerso', result); toast({ title: "Photo ajoutée", description: `Photo ${photoType} ajoutée avec succès`, }); }; reader.readAsDataURL(file); }; // Camera test function const handleCameraTest = async () => { toast({ title: "Test en cours...", description: "Vérification de l'accès aux caméras", }); const results = await runCameraTest(); let message = `Test de caméra terminé:\n`; message += `✓ Navigateur compatible: ${results.hasMediaDevices ? 'Oui' : 'Non'}\n`; message += `✓ getUserMedia disponible: ${results.hasGetUserMedia ? 'Oui' : 'Non'}\n`; message += `✓ Caméras détectées: ${results.videoDevicesCount}\n`; message += `✓ Permissions: ${results.permissions}\n`; if (results.error) { message += `❌ Erreur: ${results.error}`; } else { message += `✅ Caméra fonctionnelle`; } toast({ title: results.error ? "Test échoué" : "Test réussi", description: message, variant: results.error ? "destructive" : "default", }); }; return (
{/* Section Vendeur */}
Informations Vendeur
( Vendeur * )} />
{/* Section Produit */}
Informations Produit
( Type d'article * )} /> ( Catégorie réglementaire * )} /> ( Quantité * )} /> ( Gencode (EAN13) * )} />
Information réglementaire : F1 = vente interdite aux mineurs de moins de 12 ans • F2/F3 = artifices puissants réservés aux majeurs, vente encadrée
{/* Section Client */}
Informations Client
( Nom de famille * )} /> ( 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 * )} />
{/* Section Photos */}

Photos de la pièce d'identité

{/* Photo Recto */}
{form.watch('photoRecto') ? (
Photo recto
) : (
handleFileUpload('recto', e)} />
)}
{/* Photo Verso */}
{form.watch('photoVerso') ? (
Photo verso
) : (
handleFileUpload('verso', e)} />
)}
{/* Boutons d'action */}
{/* Camera Modal */}
); }