mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 09:13:11 +00:00
- 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
123 lines
4.2 KiB
JavaScript
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}>×</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
|
|
);
|
|
}
|