shoc-frontend-new/src/components/SignatureCapture.js
Adam Moussa 913d75802c Polish dispatch workflow — custom items, name input, read-only after verify
- Custom checklist items in dispatch modal (type + Enter, removable chips)
- Add checklist item input in dispatch detail modal
- SignatureCapture now has proper Full Name field instead of prompt()
- Verified dispatches are read-only: edit hidden, checkboxes disabled, signoff buttons disabled
- Custom items sent as CustomChecklistItems in dispatch payload
2026-04-17 16:26:22 -04:00

123 lines
4.2 KiB
JavaScript

import React, { useState, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
export default function SignatureCapture({ onSave, onClose, title }) {
const [mode, setMode] = useState("draw");
const [typedName, setTypedName] = useState("");
const [signerName, setSignerName] = useState("");
const canvasRef = useRef(null);
const isDrawing = useRef(false);
useEffect(() => {
if (mode === "draw" && canvasRef.current) {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "#333";
ctx.lineWidth = 2;
ctx.lineCap = "round";
}
}, [mode]);
const getPos = (e) => {
const rect = canvasRef.current.getBoundingClientRect();
const touch = e.touches ? e.touches[0] : e;
return { x: touch.clientX - rect.left, y: touch.clientY - rect.top };
};
const startDraw = (e) => {
e.preventDefault();
isDrawing.current = true;
const ctx = canvasRef.current.getContext("2d");
const pos = getPos(e);
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
};
const draw = (e) => {
e.preventDefault();
if (!isDrawing.current) return;
const ctx = canvasRef.current.getContext("2d");
const pos = getPos(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
};
const stopDraw = () => { isDrawing.current = false; };
const clearCanvas = () => {
const ctx = canvasRef.current.getContext("2d");
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
};
const handleSave = () => {
if (!signerName.trim()) return;
if (mode === "draw") {
const dataUrl = canvasRef.current.toDataURL("image/png");
onSave(dataUrl, "drawn", signerName.trim());
} else {
onSave(typedName, "typed", signerName.trim());
}
};
return ReactDOM.createPortal(
<div className="sigOverlay" onClick={onClose}>
<div className="sigModal" onClick={(e) => e.stopPropagation()}>
<div className="sigHeader">
<h3>{title || "Capture Signature"}</h3>
<button className="woDispatchClose" onClick={onClose}>&times;</button>
</div>
<div className="sigBody">
<div className="sigNameField">
<label>Full Name</label>
<input
className="form-control"
value={signerName}
onChange={(e) => setSignerName(e.target.value)}
placeholder="Enter signer's full name"
/>
</div>
<div className="sigModeTabs">
<button className={`sigModeTab ${mode === "draw" ? "active" : ""}`} onClick={() => setMode("draw")}>Draw</button>
<button className={`sigModeTab ${mode === "type" ? "active" : ""}`} onClick={() => setMode("type")}>Type</button>
</div>
{mode === "draw" ? (
<div className="sigCanvasWrap">
<canvas
ref={canvasRef}
width={460}
height={180}
className="sigCanvas"
onMouseDown={startDraw}
onMouseMove={draw}
onMouseUp={stopDraw}
onMouseLeave={stopDraw}
onTouchStart={startDraw}
onTouchMove={draw}
onTouchEnd={stopDraw}
/>
<button className="sigClearBtn" onClick={clearCanvas}>Clear</button>
</div>
) : (
<div className="sigTypeWrap">
<input
className="form-control sigTypeInput"
value={typedName}
onChange={(e) => setTypedName(e.target.value)}
placeholder="Type your name"
/>
{typedName && <div className="sigTypePreview">{typedName}</div>}
</div>
)}
</div>
<div className="sigFooter">
<button className="btn btn-default" onClick={onClose}>Cancel</button>
<button className="btn btn-primary" onClick={handleSave} disabled={!signerName.trim() || (mode === "type" && !typedName.trim())}>Save Signature</button>
</div>
</div>
</div>,
document.body
);
}