From 913d75802c540385031ada412e54a5ca7591425c Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:26:22 -0400 Subject: [PATCH] =?UTF-8?q?Polish=20dispatch=20workflow=20=E2=80=94=20cust?= =?UTF-8?q?om=20items,=20name=20input,=20read-only=20after=20verify?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/components/SignatureCapture.css | 12 ++++ src/components/SignatureCapture.js | 17 ++++- .../workorders/view/WorkOrderViewPage.css | 37 ++++++++++ .../workorders/view/WorkOrderViewPage.js | 69 ++++++++++++++++--- 4 files changed, 122 insertions(+), 13 deletions(-) diff --git a/src/components/SignatureCapture.css b/src/components/SignatureCapture.css index 68a3236e..15f281f2 100644 --- a/src/components/SignatureCapture.css +++ b/src/components/SignatureCapture.css @@ -40,6 +40,18 @@ border-bottom: 2px solid #e5e5e5; } +.sigNameField { + margin-bottom: 12px; +} + +.sigNameField label { + display: block; + font-size: 12px; + font-weight: 600; + color: #666; + margin-bottom: 4px; +} + .sigModeTab { padding: 6px 16px; border: none; diff --git a/src/components/SignatureCapture.js b/src/components/SignatureCapture.js index ef51ebf5..dc9e5ac0 100644 --- a/src/components/SignatureCapture.js +++ b/src/components/SignatureCapture.js @@ -4,6 +4,7 @@ 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); @@ -52,11 +53,12 @@ export default function SignatureCapture({ onSave, onClose, title }) { }; const handleSave = () => { + if (!signerName.trim()) return; if (mode === "draw") { const dataUrl = canvasRef.current.toDataURL("image/png"); - onSave(dataUrl, "drawn"); + onSave(dataUrl, "drawn", signerName.trim()); } else { - onSave(typedName, "typed"); + onSave(typedName, "typed", signerName.trim()); } }; @@ -68,6 +70,15 @@ export default function SignatureCapture({ onSave, onClose, title }) {
+
+ + setSignerName(e.target.value)} + placeholder="Enter signer's full name" + /> +
@@ -103,7 +114,7 @@ export default function SignatureCapture({ onSave, onClose, title }) {
- +
, diff --git a/src/pages/workorders/view/WorkOrderViewPage.css b/src/pages/workorders/view/WorkOrderViewPage.css index 7747a645..2762e0e9 100644 --- a/src/pages/workorders/view/WorkOrderViewPage.css +++ b/src/pages/workorders/view/WorkOrderViewPage.css @@ -803,6 +803,43 @@ border-bottom: none; } +.dispatchChecklistAdd { + margin-top: 8px; +} + +.dispatchChecklistAdd .form-control { + font-size: 13px; +} + +.dispatchCustomItems { + display: flex; + flex-direction: column; + gap: 4px; + margin-top: 6px; +} + +.dispatchCustomItem { + display: flex; + align-items: center; + justify-content: space-between; + padding: 4px 8px; + background: #f8f9fa; + border-radius: 4px; + font-size: 12px; +} + +.dispatchCustomItem button { + background: none; + border: none; + color: #999; + cursor: pointer; + font-size: 14px; +} + +.dispatchCustomItem button:hover { + color: #dc3545; +} + .dispatchVerifiedBanner { padding: 12px 20px; background: #d4edda; diff --git a/src/pages/workorders/view/WorkOrderViewPage.js b/src/pages/workorders/view/WorkOrderViewPage.js index ac053183..11918238 100644 --- a/src/pages/workorders/view/WorkOrderViewPage.js +++ b/src/pages/workorders/view/WorkOrderViewPage.js @@ -170,6 +170,7 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) { const [dispatch, setDispatch] = useState(null); const [loading, setLoading] = useState(true); const [editing, setEditing] = useState(false); + const [newChecklistItem, setNewChecklistItem] = useState(""); const [form, setForm] = useState({}); const [saving, setSaving] = useState(false); const [commentText, setCommentText] = useState(""); @@ -336,7 +337,7 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) { )}
- + {!dispatch.verifiedAt && }
)} @@ -344,15 +345,16 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) {
- {dispatch.checklistItems && dispatch.checklistItems.length > 0 && ( + {((dispatch.checklistItems && dispatch.checklistItems.length > 0) || !dispatch.verifiedAt) && ( <> -

Checklist ({dispatch.checklistItems.filter(i => i.isCompleted).length}/{dispatch.checklistItems.length})

+

Checklist ({(dispatch.checklistItems || []).filter(i => i.isCompleted).length}/{(dispatch.checklistItems || []).length})

- {dispatch.checklistItems.map((item) => ( + {(dispatch.checklistItems || []).map((item) => (
{ const currentUser = getCurrentUser(); const userName = currentUser?.fullname || "User"; @@ -374,6 +376,27 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) {
))}
+ {!dispatch.verifiedAt && ( +
+ setNewChecklistItem(e.target.value)} + onKeyDown={async (e) => { + if (e.key === "Enter" && newChecklistItem.trim()) { + e.preventDefault(); + const result = await addChecklistItem(dispatchId, newChecklistItem.trim()); + setDispatch((prev) => ({ + ...prev, + checklistItems: [...(prev.checklistItems || []), { ...result, isCompleted: false }], + })); + setNewChecklistItem(""); + } + }} + placeholder="Add checklist item..." + /> +
+ )} )}

Sign-offs

@@ -394,8 +417,8 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) { {signoff.name} ยท {signoff.signedAt ? formatDateTime(signoff.signedAt) : ""}
) : ( - )} @@ -408,9 +431,7 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) { setSigCapture(null)} - onSave={async (signature, method) => { - const name = prompt(`Enter ${sigCapture.toLowerCase()} name:`); - if (!name) return; + onSave={async (signature, method, name) => { try { const result = await addDispatchSignoff(dispatchId, sigCapture, name, signature, method); setDispatch((prev) => ({ @@ -528,6 +549,8 @@ function DispatchModal({ wo, onClose, onDispatched }) { const [sending, setSending] = useState(false); const [taskTemplates, setTaskTemplates] = useState([]); const [selectedTemplateId, setSelectedTemplateId] = useState(""); + const [customItems, setCustomItems] = useState([]); + const [newCustomItem, setNewCustomItem] = useState(""); const [loaded, setLoaded] = useState(false); const [selectedWOs, setSelectedWOs] = useState([{ id: wo.id, number: wo.internalWONumber, title: wo.title }]); const [siteWOs, setSiteWOs] = useState([]); @@ -568,6 +591,7 @@ function DispatchModal({ wo, onClose, onDispatched }) { ScheduledDate: scheduledDate || null, Description: description, TaskListTemplateId: selectedTemplateId ? parseInt(selectedTemplateId) : null, + CustomChecklistItems: customItems.length > 0 ? customItems : null, }); toast.success("Dispatched successfully"); onDispatched(); @@ -678,11 +702,36 @@ function DispatchModal({ wo, onClose, onDispatched }) {
+ {customItems.length > 0 && ( +
+ {customItems.map((item, idx) => ( +
+ {item} + +
+ ))} +
+ )} +
+ setNewCustomItem(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter" && newCustomItem.trim()) { + e.preventDefault(); + setCustomItems(prev => [...prev, newCustomItem.trim()]); + setNewCustomItem(""); + } + }} + placeholder="Add custom checklist item..." + /> +