Skip to content

Commit

Permalink
[feature] Signature pad
Browse files Browse the repository at this point in the history
Signature pad to sign with the mouse (or finger), feature to import an image to use as signature, reset button to clear the signature pad.
  • Loading branch information
Azecko committed Nov 18, 2024
1 parent a11d2e4 commit 044b2d5
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 10 deletions.
18 changes: 17 additions & 1 deletion app/print.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
/* Etat de vaud "signature" component */
.etat-de-vaud-div {
display: flex;
margin-top: 20px;
margin-top: 10px;
margin-bottom: 30px;
}

Expand Down Expand Up @@ -150,4 +150,20 @@
.less-margin-conclusion {
margin-top: 33px;
}
/* Date */
.date-div {
min-width: 100px;
}
/* Signature */
#signature-pad {
border: none;
margin: 0;
width: 200px;
height: 60px;
position: relative;
top: -30px;
}
.signature-buttons {
display: none;
}
}
81 changes: 77 additions & 4 deletions app/responsable/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,40 @@
import React from "react";
import EtatDeVaudSignature from "../components/EtatDeVaudSignature";
import { reportStorageInterface } from "@/interfaces/reportStorageInterface";
import SignaturePad from "signature_pad";

export default function Page() {
const inputFile = React.useRef<HTMLInputElement | null>(null);
const [rapportStorage, setRapportStorage] = React.useState<reportStorageInterface>({})
const [isDataLoaded, setIsDataLoaded] = React.useState(false)
const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
const signaturePadRef = React.useRef<SignaturePad | null>(null);
const signatureImageFile = React.useRef<HTMLInputElement | null>(null);

React.useEffect(() => {
const storedData = localStorage.getItem('rapport-de-stage');
const storedDataObject = JSON.parse(storedData || '{}');
if (storedData) {
setRapportStorage(JSON.parse(storedData));
}
setIsDataLoaded(true);

const timer = setTimeout(() => {
const canvas = canvasRef.current;
if (!canvas) return;

canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
signaturePadRef.current = new SignaturePad(canvas, {penColor: "#1e22aa"});
signaturePadRef.current.addEventListener("endStroke", () => {
updateStorageOnChange('signature', signaturePadRef.current?.toDataURL() || '')
});
if(storedDataObject.signature) {
signaturePadRef.current.fromDataURL(storedDataObject.signature);
}
}, 100);

return () => clearTimeout(timer);
}, []);

const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -49,9 +71,39 @@ export default function Page() {
}
};

const handleSignatureUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const confirm = window.confirm('Voulez-vous vraiment importer cette signature ?')
if(confirm) {
const { files } = e.target;
if (files && files.length) {
const reader = new FileReader();
reader.onload = (event) => {
try {
if(typeof(reader.result) == 'string') {
signaturePadRef.current?.fromDataURL(reader.result)
updateStorageOnChange('signature', reader.result)
}
} catch (error) {
alert("Erreur de lecture de l'image.");
}
};
reader.onerror = () => {
alert("Erreur lors de la lecture du fichier.");
};
reader.readAsDataURL(files[0]);
}
}
};

function updateStorageOnChange(element:string, elementValue:string) {
rapportStorage[element as keyof reportStorageInterface] = elementValue
localStorage.setItem('rapport-de-stage', JSON.stringify(rapportStorage))
setRapportStorage((prevRapportStorage) => {
const updatedRapportStorage = {
...prevRapportStorage,
[element]: elementValue,
};
localStorage.setItem('rapport-de-stage', JSON.stringify(updatedRapportStorage));
return updatedRapportStorage;
})
}

function onButtonClick() {
Expand Down Expand Up @@ -843,7 +895,7 @@ export default function Page() {
<input name="take-time" value="yes" defaultChecked={rapportStorage.bilanStage == 'yes'} onChange={(e) => updateStorageOnChange('bilanStage', e.target.value)} id="take-time-yes" className="radio-input" type="radio" />
<label htmlFor="take-time-yes">Oui</label>
</div>
<div className="dotted-input-text flex flex-row gap-2">
<div className="date-div dotted-input-text flex flex-row gap-2">
<label htmlFor="date">Date</label>
<input name="date" defaultValue={rapportStorage.fillUpDate} onChange={(e) => updateStorageOnChange('fillUpDate', e.target.value)} id="date" className="w-2/3" type="text" placeholder={".".repeat(500)} />
</div>
Expand All @@ -855,7 +907,28 @@ export default function Page() {
</div>
<div className="dotted-input-text flex flex-row gap-2">
<label htmlFor="signature">Signature</label>
<input name="signature" defaultValue={rapportStorage.signature} onChange={(e) => updateStorageOnChange('signature', e.target.value)} id="Signature" className="w-auto" type="text" placeholder={".".repeat(500)} />
<div>
<canvas className="border-2 mb-2" id="signature-pad" width="250" height="70" ref={canvasRef}></canvas>
<button
onClick={() => signaturePadRef.current?.clear()}
className="signature-buttons bg-red-500 p-2 rounded-lg text-white hover:bg-red-600 transition ease-in-out mr-4"
>
Réinitialiser
</button>
<button
onClick={() => signatureImageFile.current?.click()}
className="signature-buttons bg-red-500 p-2 rounded-lg text-white hover:bg-red-600 transition ease-in-out"
>
Importer image
</button>
<input
style={{ display: "none" }}
accept=".png, .jpg, .jpeg"
ref={signatureImageFile}
onChange={handleSignatureUpload}
type="file"
/>
</div>
</div>
</div>
</div>
Expand Down
8 changes: 7 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@
"lint": "next lint"
},
"dependencies": {
"next": "14.2.7",
"react": "^18",
"react-dom": "^18",
"next": "14.2.7"
"signature_pad": "^5.0.4"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.7",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint": "^8",
"eslint-config-next": "14.2.7"
"typescript": "^5"
}
}

0 comments on commit 044b2d5

Please sign in to comment.