Checkpoint: Implémentation de la signature manuscrite tactile pour l'émargement via QR code :
- Intégration du composant SignaturePad dans EmargementScan.tsx - Ajout des champs de signature dans la table presences (signatureUrl, signatureS3Key, dateSigned) - Modification de la procédure presences.valider pour accepter et uploader la signature en S3 - Stockage automatique de l'URL et de la clé S3 dans la base de données - Correction des types nullables dans exportService.ts (codeEtablissement, fonction) - Les apprenants doivent maintenant signer avec le doigt sur l'écran avant de valider leur présence
This commit is contained in:
@@ -10,10 +10,11 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
import { CheckCircle2, Loader2, XCircle } from "lucide-react";
|
||||
import { CheckCircle2, Loader2, XCircle, PenTool } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "wouter";
|
||||
import { toast } from "sonner";
|
||||
import SignaturePadComponent from "@/components/SignaturePad";
|
||||
|
||||
export default function EmargementScan() {
|
||||
const { token } = useParams<{ token: string }>();
|
||||
@@ -24,6 +25,8 @@ export default function EmargementScan() {
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [heureEnregistrement, setHeureEnregistrement] = useState<Date | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showSignaturePad, setShowSignaturePad] = useState(false);
|
||||
const [signatureDataUrl, setSignatureDataUrl] = useState<string | null>(null);
|
||||
|
||||
// Récupérer l'apprenant par email
|
||||
const { data: apprenant, refetch: refetchApprenant } = trpc.apprenants.getByEmail.useQuery(
|
||||
@@ -56,6 +59,13 @@ export default function EmargementScan() {
|
||||
},
|
||||
});
|
||||
|
||||
const handleSaveSignature = (dataUrl: string) => {
|
||||
setSignatureDataUrl(dataUrl);
|
||||
setShowSignaturePad(false);
|
||||
// Valider automatiquement après la signature
|
||||
handleValiderWithSignature(dataUrl);
|
||||
};
|
||||
|
||||
const handleSearchEmail = async () => {
|
||||
if (!email) {
|
||||
toast.error("Veuillez saisir votre email");
|
||||
@@ -70,12 +80,23 @@ export default function EmargementScan() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher le pad de signature au lieu de valider directement
|
||||
setShowSignaturePad(true);
|
||||
};
|
||||
|
||||
const handleValiderWithSignature = (dataUrl: string) => {
|
||||
if (!selectedInscriptionId || !selectedDateId) {
|
||||
toast.error("Veuillez sélectionner une date de formation");
|
||||
return;
|
||||
}
|
||||
|
||||
validerPresence({
|
||||
token,
|
||||
inscriptionId: selectedInscriptionId,
|
||||
dateFormationId: selectedDateId,
|
||||
periode: selectedPeriode,
|
||||
modeValidation: "qrcode",
|
||||
signatureDataUrl: dataUrl,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -286,15 +307,18 @@ export default function EmargementScan() {
|
||||
<Button
|
||||
onClick={handleValider}
|
||||
disabled={!selectedInscriptionId || !selectedDateId || validating}
|
||||
className="w-full"
|
||||
className="w-full h-14 text-lg bg-gradient-to-r from-blue-600 to-indigo-700 hover:from-blue-700 hover:to-indigo-800"
|
||||
>
|
||||
{validating ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||
Validation en cours...
|
||||
</>
|
||||
) : (
|
||||
"Valider ma présence"
|
||||
<>
|
||||
<PenTool className="mr-2 h-5 w-5" />
|
||||
Signer et valider ma présence
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
@@ -319,6 +343,13 @@ export default function EmargementScan() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{showSignaturePad && (
|
||||
<SignaturePadComponent
|
||||
onSave={handleSaveSignature}
|
||||
onCancel={() => setShowSignaturePad(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user