mirror of
https://github.com/R0m1k3/Regisflow.git
synced 2026-10-14 10:47:19 +02:00
Allow capture of identity document photos directly within the new sale form
Implements camera functionality in NewSaleForm.tsx to capture recto/verso ID photos, storing them as photoRecto/photoVerso fields. Replit-Commit-Author: Agent Replit-Commit-Session-Id: dec54137-d5a2-4406-8ad4-5f2f9fb110bf Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0715008c-7244-40f4-befc-26c014852236/988a9bb7-9903-443d-8c9e-7773c87e8266.jpg
This commit is contained in:
1 parent
33262c73a8
commit
8639adf4be
1 file changed
+152
-3
@@ -9,9 +9,12 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useStoreContext } from '@/hooks/useStoreContext';
|
||||
import { useCamera } from '@/hooks/useCamera';
|
||||
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 } from 'lucide-react';
|
||||
import { Package, User, Users, Save, Calendar, BadgeCheck, Info, Camera } from 'lucide-react';
|
||||
import CameraModal from '@/components/CameraModal';
|
||||
import type { PhotoType } from '@/types/sale';
|
||||
|
||||
interface FormData {
|
||||
vendeur: string;
|
||||
@@ -29,6 +32,8 @@ interface FormData {
|
||||
numeroIdentite: string;
|
||||
autoriteDelivrance: string;
|
||||
dateDelivrance: string;
|
||||
photoRecto?: string;
|
||||
photoVerso?: string;
|
||||
}
|
||||
|
||||
export default function NewSaleForm() {
|
||||
@@ -37,6 +42,18 @@ export default function NewSaleForm() {
|
||||
const { user } = useAuth();
|
||||
const { selectedStoreId } = useStoreContext();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Camera functionality
|
||||
const {
|
||||
isOpen: isCameraOpen,
|
||||
currentPhotoType,
|
||||
isCapturing,
|
||||
videoRef,
|
||||
canvasRef,
|
||||
startCamera,
|
||||
capturePhoto,
|
||||
stopCamera
|
||||
} = useCamera();
|
||||
|
||||
const form = useForm<FormData>({
|
||||
defaultValues: {
|
||||
@@ -55,6 +72,8 @@ export default function NewSaleForm() {
|
||||
numeroIdentite: '',
|
||||
autoriteDelivrance: '',
|
||||
dateDelivrance: '',
|
||||
photoRecto: '',
|
||||
photoVerso: '',
|
||||
}
|
||||
});
|
||||
|
||||
@@ -97,6 +116,8 @@ export default function NewSaleForm() {
|
||||
numeroIdentite: '',
|
||||
autoriteDelivrance: '',
|
||||
dateDelivrance: '',
|
||||
photoRecto: '',
|
||||
photoVerso: '',
|
||||
});
|
||||
},
|
||||
onError: (error: any) => {
|
||||
@@ -145,6 +166,8 @@ export default function NewSaleForm() {
|
||||
numeroIdentite: data.numeroIdentite,
|
||||
autoriteDelivrance: data.autoriteDelivrance,
|
||||
dateDelivrance: data.dateDelivrance,
|
||||
photoRecto: data.photoRecto,
|
||||
photoVerso: data.photoVerso,
|
||||
};
|
||||
|
||||
await createSaleMutation.mutateAsync(saleData);
|
||||
@@ -155,6 +178,51 @@ export default function NewSaleForm() {
|
||||
}
|
||||
};
|
||||
|
||||
// Photo capture functions
|
||||
const handlePhotoCapture = async (photoType: PhotoType) => {
|
||||
try {
|
||||
await startCamera(photoType);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Erreur caméra",
|
||||
description: error instanceof Error ? error.message : "Impossible d'accéder à la caméra",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleTakePhoto = async () => {
|
||||
if (!currentPhotoType) return;
|
||||
|
||||
try {
|
||||
const photoData = await capturePhoto();
|
||||
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) {
|
||||
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`,
|
||||
variant: "success",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="elegant-card animate-slide-in">
|
||||
<div className="p-6">
|
||||
@@ -434,10 +502,80 @@ export default function NewSaleForm() {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Section Photos */}
|
||||
<div className="mt-6 space-y-4">
|
||||
<h4 className="text-sm font-medium text-gray-700">Photos de la pièce d'identité</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{/* Photo Recto */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-600">Photo Recto</label>
|
||||
{form.watch('photoRecto') ? (
|
||||
<div className="relative">
|
||||
<img
|
||||
src={form.watch('photoRecto')}
|
||||
alt="Photo recto"
|
||||
className="w-full h-32 object-cover rounded border"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="absolute top-2 right-2"
|
||||
onClick={() => handleRemovePhoto('recto')}
|
||||
>
|
||||
✕
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full h-32 border-dashed border-2"
|
||||
onClick={() => handlePhotoCapture('recto')}
|
||||
>
|
||||
<Camera className="h-6 w-6 mr-2" />
|
||||
Prendre photo recto
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Photo Verso */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-600">Photo Verso</label>
|
||||
{form.watch('photoVerso') ? (
|
||||
<div className="relative">
|
||||
<img
|
||||
src={form.watch('photoVerso')}
|
||||
alt="Photo verso"
|
||||
className="w-full h-32 object-cover rounded border"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="absolute top-2 right-2"
|
||||
onClick={() => handleRemovePhoto('verso')}
|
||||
>
|
||||
✕
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full h-32 border-dashed border-2"
|
||||
onClick={() => handlePhotoCapture('verso')}
|
||||
>
|
||||
<Camera className="h-6 w-6 mr-2" />
|
||||
Prendre photo verso
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Boutons d'action */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 pt-6">
|
||||
<button
|
||||
@@ -459,6 +597,17 @@ export default function NewSaleForm() {
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
{/* Camera Modal */}
|
||||
<CameraModal
|
||||
isOpen={isCameraOpen}
|
||||
photoType={currentPhotoType}
|
||||
videoRef={videoRef}
|
||||
canvasRef={canvasRef}
|
||||
isCapturing={isCapturing}
|
||||
onClose={stopCamera}
|
||||
onCapture={handleTakePhoto}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user