diff --git a/src/pages/workorders/api.js b/src/pages/workorders/api.js index 49b034e4..c0c92e23 100644 --- a/src/pages/workorders/api.js +++ b/src/pages/workorders/api.js @@ -134,6 +134,10 @@ export const addChecklistItem = async (dispatchId, itemText, workOrderId) => { return post(`${API_URL}/WorkOrder/AddChecklistItem`, { DispatchId: dispatchId, ItemText: itemText, WorkOrderId: workOrderId }); }; +export const verifyDispatch = async (dispatchId) => { + return post(`${API_URL}/WorkOrder/VerifyDispatch?dispatchId=${dispatchId}`); +}; + export const addDispatchSignoff = async (dispatchId, signoffType, name, signature, signatureMethod) => { return post(`${API_URL}/WorkOrder/AddDispatchSignoff`, { DispatchId: dispatchId, diff --git a/src/pages/workorders/view/WorkOrderViewPage.css b/src/pages/workorders/view/WorkOrderViewPage.css index c39ee98a..0a1f86b7 100644 --- a/src/pages/workorders/view/WorkOrderViewPage.css +++ b/src/pages/workorders/view/WorkOrderViewPage.css @@ -629,6 +629,7 @@ .woDispatchStatus.acknowledged { background: #ffc107; color: #333; } .woDispatchStatus.completed { background: #28a745; } .woDispatchStatus.cancelled { background: #dc3545; } +.woDispatchStatus.verified { background: #155724; } .dispatchClickRow { cursor: pointer; @@ -748,6 +749,34 @@ font-weight: 600; } +.dispatchVerifiedBanner { + padding: 12px 20px; + background: #d4edda; + color: #155724; + font-size: 13px; + font-weight: 600; + text-align: center; + border-top: 1px solid #c3e6cb; + flex-shrink: 0; +} + +.dispatchVerifyFooter { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 12px; + padding: 12px 20px; + border-top: 1px solid #eee; + background: #fafafa; + flex-shrink: 0; +} + +.dispatchVerifyMissing { + font-size: 12px; + color: #999; + flex: 1; +} + .dispatchSignoffs { display: flex; gap: 16px; diff --git a/src/pages/workorders/view/WorkOrderViewPage.js b/src/pages/workorders/view/WorkOrderViewPage.js index 2382208e..456b07ac 100644 --- a/src/pages/workorders/view/WorkOrderViewPage.js +++ b/src/pages/workorders/view/WorkOrderViewPage.js @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from "react"; import ReactDOM from "react-dom"; import { useParams, useNavigate } from "react-router"; import { toast } from "react-toastify"; -import { getWorkOrderById, addComment, changeWorkOrderStatus, fetchUsers, changeWorkOrderAssignment, fetchVendorsDropdown, dispatchToVendor, getDispatchById, updateDispatch, addDispatchComment, fetchTaskTemplates, updateChecklistItem, addChecklistItem, addDispatchSignoff } from "../api"; +import { getWorkOrderById, addComment, changeWorkOrderStatus, fetchUsers, changeWorkOrderAssignment, fetchVendorsDropdown, dispatchToVendor, getDispatchById, updateDispatch, addDispatchComment, fetchTaskTemplates, updateChecklistItem, addChecklistItem, addDispatchSignoff, verifyDispatch } from "../api"; import { getCurrentUser } from "../../../services/authService"; import { timeSince, timeBetween, waitTimeColor } from "../../../utils/timeSince"; import SignatureCapture from "../../../components/SignatureCapture"; @@ -164,7 +164,7 @@ function AuditTimeline({ entries }) { ); } -const DISPATCH_STATUSES = ["Sent", "Acknowledged", "In Progress", "Completed", "Cancelled"]; +const DISPATCH_STATUSES = ["Sent", "Acknowledged", "In Progress", "Completed", "Verified", "Cancelled"]; function DispatchDetailModal({ dispatchId, onClose, onUpdated }) { const [dispatch, setDispatch] = useState(null); @@ -468,6 +468,49 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) { + + {dispatch.verifiedAt ? ( +
+ ✓ Verified by {dispatch.verifiedBy} on {formatDateTime(dispatch.verifiedAt)} +
+ ) : ( +
+ {(() => { + const checklistTotal = (dispatch.checklistItems || []).length; + const checklistDone = (dispatch.checklistItems || []).filter(i => i.isCompleted).length; + const signoffCount = (dispatch.signoffs || []).length; + const missing = []; + if (checklistTotal > 0 && checklistDone < checklistTotal) missing.push(`${checklistTotal - checklistDone} checklist item(s)`); + if (signoffCount < 2) { + const types = (dispatch.signoffs || []).map(s => s.signoffType); + if (!types.includes("Customer")) missing.push("Customer sign-off"); + if (!types.includes("Vendor")) missing.push("Vendor sign-off"); + } + const canVerify = missing.length === 0 && (checklistTotal > 0 || signoffCount >= 2); + return ( + <> + {missing.length > 0 && Missing: {missing.join(", ")}} + + + ); + })()} +
+ )} ,