mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 03:02:03 +00:00
Add Verify & Close button and verified banner to dispatch detail
- Verify & Close button at bottom of dispatch modal - Disabled with tooltip showing missing items (checklist, signoffs) - Green verified banner after verification with verifier name + timestamp - Verified status badge (dark green) - Added Verified to DISPATCH_STATUSES constant - verifyDispatch API function
This commit is contained in:
parent
5c24b9249d
commit
199715d5b6
3 changed files with 78 additions and 2 deletions
|
|
@ -134,6 +134,10 @@ export const addChecklistItem = async (dispatchId, itemText, workOrderId) => {
|
||||||
return post(`${API_URL}/WorkOrder/AddChecklistItem`, { DispatchId: dispatchId, ItemText: itemText, WorkOrderId: 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) => {
|
export const addDispatchSignoff = async (dispatchId, signoffType, name, signature, signatureMethod) => {
|
||||||
return post(`${API_URL}/WorkOrder/AddDispatchSignoff`, {
|
return post(`${API_URL}/WorkOrder/AddDispatchSignoff`, {
|
||||||
DispatchId: dispatchId,
|
DispatchId: dispatchId,
|
||||||
|
|
|
||||||
|
|
@ -629,6 +629,7 @@
|
||||||
.woDispatchStatus.acknowledged { background: #ffc107; color: #333; }
|
.woDispatchStatus.acknowledged { background: #ffc107; color: #333; }
|
||||||
.woDispatchStatus.completed { background: #28a745; }
|
.woDispatchStatus.completed { background: #28a745; }
|
||||||
.woDispatchStatus.cancelled { background: #dc3545; }
|
.woDispatchStatus.cancelled { background: #dc3545; }
|
||||||
|
.woDispatchStatus.verified { background: #155724; }
|
||||||
|
|
||||||
.dispatchClickRow {
|
.dispatchClickRow {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -748,6 +749,34 @@
|
||||||
font-weight: 600;
|
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 {
|
.dispatchSignoffs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { useParams, useNavigate } from "react-router";
|
import { useParams, useNavigate } from "react-router";
|
||||||
import { toast } from "react-toastify";
|
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 { getCurrentUser } from "../../../services/authService";
|
||||||
import { timeSince, timeBetween, waitTimeColor } from "../../../utils/timeSince";
|
import { timeSince, timeBetween, waitTimeColor } from "../../../utils/timeSince";
|
||||||
import SignatureCapture from "../../../components/SignatureCapture";
|
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 }) {
|
function DispatchDetailModal({ dispatchId, onClose, onUpdated }) {
|
||||||
const [dispatch, setDispatch] = useState(null);
|
const [dispatch, setDispatch] = useState(null);
|
||||||
|
|
@ -468,6 +468,49 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) {
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{dispatch.verifiedAt ? (
|
||||||
|
<div className="dispatchVerifiedBanner">
|
||||||
|
✓ Verified by {dispatch.verifiedBy} on {formatDateTime(dispatch.verifiedAt)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="dispatchVerifyFooter">
|
||||||
|
{(() => {
|
||||||
|
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 && <span className="dispatchVerifyMissing">Missing: {missing.join(", ")}</span>}
|
||||||
|
<button
|
||||||
|
className="btn btn-success"
|
||||||
|
disabled={!canVerify}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const result = await verifyDispatch(dispatchId);
|
||||||
|
toast.success("Dispatch verified");
|
||||||
|
loadDispatch();
|
||||||
|
onUpdated();
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(err.message || "Failed to verify");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Verify & Close
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue