mirror of
https://github.com/R0m1k3/Regisflow.git
synced 2026-10-11 17:29:48 +02:00
Add option to include and display a photo of the sales receipt
Implements `photoTicket` field in `Sale` and `FormData` types, adds UI elements in `NewSaleForm` and `SaleDetailsModal` components, and updates schema.ts. 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/7df9e24f-d78b-4589-9a08-c70e02d6d7d9.jpg
This commit is contained in:
1 parent
e48337925c
commit
4f04428dea
5 files changed
+118
-5
No files matched your search
@@ -35,6 +35,7 @@ interface FormData {
|
||||
dateDelivrance: string;
|
||||
photoRecto?: string;
|
||||
photoVerso?: string;
|
||||
photoTicket?: string;
|
||||
}
|
||||
|
||||
export default function NewSaleForm() {
|
||||
@@ -75,6 +76,7 @@ export default function NewSaleForm() {
|
||||
dateDelivrance: '',
|
||||
photoRecto: '',
|
||||
photoVerso: '',
|
||||
photoTicket: '',
|
||||
}
|
||||
});
|
||||
|
||||
@@ -119,6 +121,7 @@ export default function NewSaleForm() {
|
||||
dateDelivrance: '',
|
||||
photoRecto: '',
|
||||
photoVerso: '',
|
||||
photoTicket: '',
|
||||
});
|
||||
},
|
||||
onError: (error: any) => {
|
||||
@@ -235,7 +238,13 @@ export default function NewSaleForm() {
|
||||
const photoData = await capturePhoto();
|
||||
console.log('Photo captured successfully, data length:', photoData.length);
|
||||
|
||||
form.setValue(currentPhotoType === 'recto' ? 'photoRecto' : 'photoVerso', photoData);
|
||||
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",
|
||||
@@ -255,7 +264,13 @@ export default function NewSaleForm() {
|
||||
};
|
||||
|
||||
const handleRemovePhoto = (photoType: PhotoType) => {
|
||||
form.setValue(photoType === 'recto' ? 'photoRecto' : 'photoVerso', '');
|
||||
if (photoType === 'recto') {
|
||||
form.setValue('photoRecto', '');
|
||||
} else if (photoType === 'verso') {
|
||||
form.setValue('photoVerso', '');
|
||||
} else if (photoType === 'ticket') {
|
||||
form.setValue('photoTicket', '');
|
||||
}
|
||||
toast({
|
||||
title: "Photo supprimée",
|
||||
description: `Photo ${photoType} supprimée`,
|
||||
@@ -288,7 +303,13 @@ export default function NewSaleForm() {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const result = e.target?.result as string;
|
||||
form.setValue(photoType === 'recto' ? 'photoRecto' : 'photoVerso', result);
|
||||
if (photoType === 'recto') {
|
||||
form.setValue('photoRecto', result);
|
||||
} else if (photoType === 'verso') {
|
||||
form.setValue('photoVerso', result);
|
||||
} else if (photoType === 'ticket') {
|
||||
form.setValue('photoTicket', result);
|
||||
}
|
||||
toast({
|
||||
title: "Photo ajoutée",
|
||||
description: `Photo ${photoType} ajoutée avec succès`,
|
||||
@@ -715,6 +736,63 @@ export default function NewSaleForm() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Photo Ticket de caisse */}
|
||||
<div className="mt-6 space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700">Photo du ticket de caisse</h4>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-600">Photo Ticket</label>
|
||||
{form.watch('photoTicket') ? (
|
||||
<div className="relative">
|
||||
<img
|
||||
src={form.watch('photoTicket')}
|
||||
alt="Photo ticket"
|
||||
className="w-full h-32 object-cover rounded border"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="absolute top-2 right-2"
|
||||
onClick={() => handleRemovePhoto('ticket')}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full h-24 border-dashed border-2"
|
||||
onClick={() => handlePhotoCapture('ticket')}
|
||||
>
|
||||
<Camera className="h-5 w-5 mr-2" />
|
||||
Prendre photo
|
||||
</Button>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => handleFileUpload('ticket', e)}
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full h-8 text-xs"
|
||||
>
|
||||
<Upload className="h-3 w-3 mr-1" />
|
||||
Ou choisir un fichier
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Boutons d'action */}
|
||||
|
||||
@@ -87,8 +87,14 @@ export default function SaleDetailsModal({ isOpen, sale, onClose }: SaleDetailsM
|
||||
</div>
|
||||
|
||||
{/* Photos */}
|
||||
{(sale.photoRecto || sale.photoVerso || sale.photoTicket) && (
|
||||
<div className="mobile-form-section border-l-4 border-purple-500 pl-4">
|
||||
<h4 className="responsive-subtitle text-gray-900 mb-3">Photos de la Pièce d'Identité</h4>
|
||||
<h4 className="responsive-subtitle text-gray-900 mb-3">Photos</h4>
|
||||
|
||||
{/* Photos de la pièce d'identité */}
|
||||
{(sale.photoRecto || sale.photoVerso) && (
|
||||
<div className="mb-4">
|
||||
<h5 className="text-sm font-semibold text-gray-700 mb-3">Pièce d'identité</h5>
|
||||
<div className="mobile-form-row">
|
||||
<div>
|
||||
<span className="text-sm font-medium">Photo Recto:</span>
|
||||
@@ -130,6 +136,28 @@ export default function SaleDetailsModal({ isOpen, sale, onClose }: SaleDetailsM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Photo du ticket de caisse */}
|
||||
{sale.photoTicket && (
|
||||
<div>
|
||||
<h5 className="text-sm font-semibold text-gray-700 mb-3">Ticket de caisse</h5>
|
||||
<div className="max-w-xs">
|
||||
<img src={sale.photoTicket} alt="Photo ticket" className="w-full h-auto rounded border mb-2" />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => downloadPhoto(sale.photoTicket!, `vente-${sale.id}-ticket.jpg`)}
|
||||
className="w-full"
|
||||
>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Télécharger Ticket
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* System Information */}
|
||||
<div className="border-l-4 border-gray-400 pl-4">
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface Sale {
|
||||
// Photos
|
||||
photoRecto?: string;
|
||||
photoVerso?: string;
|
||||
photoTicket?: string;
|
||||
}
|
||||
|
||||
export interface FormData {
|
||||
@@ -46,9 +47,10 @@ export interface FormData {
|
||||
dateDelivrance: string;
|
||||
photoRecto?: string;
|
||||
photoVerso?: string;
|
||||
photoTicket?: string;
|
||||
}
|
||||
|
||||
export type PhotoType = 'recto' | 'verso';
|
||||
export type PhotoType = 'recto' | 'verso' | 'ticket';
|
||||
|
||||
export interface BackupData {
|
||||
timestamp: string;
|
||||
|
||||
@@ -264,6 +264,10 @@ Preferred communication style: Simple, everyday language.
|
||||
- ✅ Comprehensive error handling with specific messages for different failure types
|
||||
- ✅ HTTPS requirement detection for secure camera access
|
||||
- ✅ Multiple capture methods: camera → file upload for maximum compatibility
|
||||
- ✅ Added receipt/ticket photo capture as third photo type alongside identity documents
|
||||
- ✅ PayloadTooLargeError resolved by increasing server limit to 10MB for photo uploads
|
||||
- ✅ Complete photo management system with separate sections for identity and receipt photos
|
||||
- ✅ Enhanced SaleDetailsModal with organized photo display and download functionality
|
||||
|
||||
### Production Deployment System (January 11, 2025)
|
||||
- ✅ Complete production deployment configuration with Docker Compose
|
||||
|
||||
@@ -83,6 +83,7 @@ export const sales = pgTable("sales", {
|
||||
// Photos
|
||||
photoRecto: text("photo_recto"),
|
||||
photoVerso: text("photo_verso"),
|
||||
photoTicket: text("photo_ticket"),
|
||||
});
|
||||
|
||||
// Relations
|
||||
|
||||
Reference in new issue
Block a user