mirror of
https://github.com/R0m1k3/Regisflow.git
synced 2026-10-24 07:38:18 +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
+151
-38
No files matched your search
@@ -35,6 +35,7 @@ interface FormData {
|
|||||||
dateDelivrance: string;
|
dateDelivrance: string;
|
||||||
photoRecto?: string;
|
photoRecto?: string;
|
||||||
photoVerso?: string;
|
photoVerso?: string;
|
||||||
|
photoTicket?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NewSaleForm() {
|
export default function NewSaleForm() {
|
||||||
@@ -75,6 +76,7 @@ export default function NewSaleForm() {
|
|||||||
dateDelivrance: '',
|
dateDelivrance: '',
|
||||||
photoRecto: '',
|
photoRecto: '',
|
||||||
photoVerso: '',
|
photoVerso: '',
|
||||||
|
photoTicket: '',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,6 +121,7 @@ export default function NewSaleForm() {
|
|||||||
dateDelivrance: '',
|
dateDelivrance: '',
|
||||||
photoRecto: '',
|
photoRecto: '',
|
||||||
photoVerso: '',
|
photoVerso: '',
|
||||||
|
photoTicket: '',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
@@ -235,7 +238,13 @@ export default function NewSaleForm() {
|
|||||||
const photoData = await capturePhoto();
|
const photoData = await capturePhoto();
|
||||||
console.log('Photo captured successfully, data length:', photoData.length);
|
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({
|
toast({
|
||||||
title: "Photo capturée",
|
title: "Photo capturée",
|
||||||
@@ -255,7 +264,13 @@ export default function NewSaleForm() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRemovePhoto = (photoType: PhotoType) => {
|
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({
|
toast({
|
||||||
title: "Photo supprimée",
|
title: "Photo supprimée",
|
||||||
description: `Photo ${photoType} supprimée`,
|
description: `Photo ${photoType} supprimée`,
|
||||||
@@ -288,7 +303,13 @@ export default function NewSaleForm() {
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const result = e.target?.result as string;
|
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({
|
toast({
|
||||||
title: "Photo ajoutée",
|
title: "Photo ajoutée",
|
||||||
description: `Photo ${photoType} ajoutée avec succès`,
|
description: `Photo ${photoType} ajoutée avec succès`,
|
||||||
@@ -715,6 +736,63 @@ export default function NewSaleForm() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Boutons d'action */}
|
{/* Boutons d'action */}
|
||||||
|
|||||||
@@ -87,49 +87,77 @@ export default function SaleDetailsModal({ isOpen, sale, onClose }: SaleDetailsM
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Photos */}
|
{/* Photos */}
|
||||||
<div className="mobile-form-section border-l-4 border-purple-500 pl-4">
|
{(sale.photoRecto || sale.photoVerso || sale.photoTicket) && (
|
||||||
<h4 className="responsive-subtitle text-gray-900 mb-3">Photos de la Pièce d'Identité</h4>
|
<div className="mobile-form-section border-l-4 border-purple-500 pl-4">
|
||||||
<div className="mobile-form-row">
|
<h4 className="responsive-subtitle text-gray-900 mb-3">Photos</h4>
|
||||||
<div>
|
|
||||||
<span className="text-sm font-medium">Photo Recto:</span>
|
{/* Photos de la pièce d'identité */}
|
||||||
{sale.photoRecto ? (
|
{(sale.photoRecto || sale.photoVerso) && (
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mb-4">
|
||||||
<img src={sale.photoRecto} alt="Photo recto" className="w-32 h-auto rounded border" />
|
<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>
|
||||||
|
{sale.photoRecto ? (
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<img src={sale.photoRecto} alt="Photo recto" className="w-32 h-auto rounded border" />
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => downloadPhoto(sale.photoRecto!, `vente-${sale.id}-recto.jpg`)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<Download className="h-4 w-4 mr-2" />
|
||||||
|
Télécharger Recto
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500 mt-2">Aucune photo</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium">Photo Verso:</span>
|
||||||
|
{sale.photoVerso ? (
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<img src={sale.photoVerso} alt="Photo verso" className="w-32 h-auto rounded border" />
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => downloadPhoto(sale.photoVerso!, `vente-${sale.id}-verso.jpg`)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<Download className="h-4 w-4 mr-2" />
|
||||||
|
Télécharger Verso
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500 mt-2">Aucune photo</p>
|
||||||
|
)}
|
||||||
|
</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
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => downloadPhoto(sale.photoRecto!, `vente-${sale.id}-recto.jpg`)}
|
onClick={() => downloadPhoto(sale.photoTicket!, `vente-${sale.id}-ticket.jpg`)}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4 mr-2" />
|
<Download className="h-4 w-4 mr-2" />
|
||||||
Télécharger Recto
|
Télécharger Ticket
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<p className="text-sm text-gray-500 mt-2">Aucune photo</p>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-sm font-medium">Photo Verso:</span>
|
|
||||||
{sale.photoVerso ? (
|
|
||||||
<div className="mt-2 space-y-2">
|
|
||||||
<img src={sale.photoVerso} alt="Photo verso" className="w-32 h-auto rounded border" />
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => downloadPhoto(sale.photoVerso!, `vente-${sale.id}-verso.jpg`)}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
<Download className="h-4 w-4 mr-2" />
|
|
||||||
Télécharger Verso
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-gray-500 mt-2">Aucune photo</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* System Information */}
|
{/* System Information */}
|
||||||
<div className="border-l-4 border-gray-400 pl-4">
|
<div className="border-l-4 border-gray-400 pl-4">
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface Sale {
|
|||||||
// Photos
|
// Photos
|
||||||
photoRecto?: string;
|
photoRecto?: string;
|
||||||
photoVerso?: string;
|
photoVerso?: string;
|
||||||
|
photoTicket?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
@@ -46,9 +47,10 @@ export interface FormData {
|
|||||||
dateDelivrance: string;
|
dateDelivrance: string;
|
||||||
photoRecto?: string;
|
photoRecto?: string;
|
||||||
photoVerso?: string;
|
photoVerso?: string;
|
||||||
|
photoTicket?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PhotoType = 'recto' | 'verso';
|
export type PhotoType = 'recto' | 'verso' | 'ticket';
|
||||||
|
|
||||||
export interface BackupData {
|
export interface BackupData {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
|
|||||||
@@ -264,6 +264,10 @@ Preferred communication style: Simple, everyday language.
|
|||||||
- ✅ Comprehensive error handling with specific messages for different failure types
|
- ✅ Comprehensive error handling with specific messages for different failure types
|
||||||
- ✅ HTTPS requirement detection for secure camera access
|
- ✅ HTTPS requirement detection for secure camera access
|
||||||
- ✅ Multiple capture methods: camera → file upload for maximum compatibility
|
- ✅ 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)
|
### Production Deployment System (January 11, 2025)
|
||||||
- ✅ Complete production deployment configuration with Docker Compose
|
- ✅ Complete production deployment configuration with Docker Compose
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export const sales = pgTable("sales", {
|
|||||||
// Photos
|
// Photos
|
||||||
photoRecto: text("photo_recto"),
|
photoRecto: text("photo_recto"),
|
||||||
photoVerso: text("photo_verso"),
|
photoVerso: text("photo_verso"),
|
||||||
|
photoTicket: text("photo_ticket"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
|
|||||||
Reference in new issue
Block a user