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:
118
client/src/components/SignaturePad.tsx
Normal file
118
client/src/components/SignaturePad.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import SignaturePad from "signature_pad";
|
||||
import { Button } from "./ui/button";
|
||||
import { X, RotateCcw, Check } from "lucide-react";
|
||||
|
||||
interface SignaturePadComponentProps {
|
||||
onSave: (dataUrl: string) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export default function SignaturePadComponent({
|
||||
onSave,
|
||||
onCancel,
|
||||
}: SignaturePadComponentProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const signaturePadRef = useRef<SignaturePad | null>(null);
|
||||
const [isEmpty, setIsEmpty] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return;
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
const signaturePad = new SignaturePad(canvas, {
|
||||
backgroundColor: "rgb(255, 255, 255)",
|
||||
penColor: "rgb(0, 0, 0)",
|
||||
});
|
||||
|
||||
signaturePadRef.current = signaturePad;
|
||||
|
||||
// Redimensionner le canvas pour correspondre à sa taille d'affichage
|
||||
const resizeCanvas = () => {
|
||||
const ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext("2d")!.scale(ratio, ratio);
|
||||
signaturePad.clear();
|
||||
};
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
|
||||
// Détecter les changements
|
||||
signaturePad.addEventListener("endStroke", () => {
|
||||
setIsEmpty(signaturePad.isEmpty());
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", resizeCanvas);
|
||||
signaturePad.off();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClear = () => {
|
||||
if (signaturePadRef.current) {
|
||||
signaturePadRef.current.clear();
|
||||
setIsEmpty(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (signaturePadRef.current && !signaturePadRef.current.isEmpty()) {
|
||||
const dataUrl = signaturePadRef.current.toDataURL("image/png");
|
||||
onSave(dataUrl);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-lg shadow-xl w-full max-w-2xl animate-in fade-in zoom-in duration-300">
|
||||
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
Signez votre attestation
|
||||
</h2>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
Dessinez votre signature ci-dessous avec votre doigt ou votre stylet
|
||||
</p>
|
||||
|
||||
<div className="border-2 border-gray-300 rounded-lg overflow-hidden bg-white">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="w-full touch-none"
|
||||
style={{ height: "300px" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClear}
|
||||
disabled={isEmpty}
|
||||
className="flex-1"
|
||||
>
|
||||
<RotateCcw className="w-4 h-4 mr-2" />
|
||||
Effacer
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isEmpty}
|
||||
className="flex-1 bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800"
|
||||
>
|
||||
<Check className="w-4 h-4 mr-2" />
|
||||
Valider la signature
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user