Add time tracking to dispatch workflow

- timeSince utility: relative time display (2h ago, 3d ago)
- timeBetween utility: duration between two timestamps
- waitTimeColor utility: green (<4h), yellow (4-24h), red (>24h)
- Dispatch detail modal shows accepted time with duration
- Awaiting acceptance shows elapsed time with color coding
- Dispatches table shows Wait column with color-coded time for Sent dispatches
This commit is contained in:
Adam Moussa 2026-04-17 15:41:51 -04:00
parent 5dcfc9d303
commit 31479029e3
3 changed files with 52 additions and 4 deletions

View file

@ -735,11 +735,19 @@
}
.dispatchWaiting {
color: #ffc107;
font-weight: 500;
font-size: 13px;
}
.dispatchWait-green { color: #28a745; }
.dispatchWait-yellow { color: #ffc107; }
.dispatchWait-red { color: #dc3545; }
.dispatchWaitDot {
font-size: 12px;
font-weight: 600;
}
.dispatchChecklist {
display: flex;
flex-direction: column;

View file

@ -4,6 +4,7 @@ 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 } from "../api";
import { getCurrentUser } from "../../../services/authService";
import { timeSince, timeBetween, waitTimeColor } from "../../../utils/timeSince";
import "./WorkOrderViewPage.css";
function AssignDropdown({ value, onAssign }) {
@ -314,9 +315,9 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) {
<div className="dispatchDetailField">
<label>Accepted</label>
{dispatch.acknowledgedAt ? (
<span style={{ color: "#28a745" }}>{formatDateTime(dispatch.acknowledgedAt)}</span>
<span style={{ color: "#28a745" }}>{formatDateTime(dispatch.acknowledgedAt)} (after {timeBetween(dispatch.dispatchedAt, dispatch.acknowledgedAt)})</span>
) : dispatch.status === "Sent" ? (
<span className="dispatchWaiting">Awaiting acceptance</span>
<span className={`dispatchWaiting dispatchWait-${waitTimeColor(dispatch.dispatchedAt)}`}>Awaiting acceptance · {timeSince(dispatch.dispatchedAt)}</span>
) : (
<span>—</span>
)}
@ -883,7 +884,7 @@ export default function WorkOrderViewPage() {
{wo.dispatches?.length ? (
<table className="woDispatchTable">
<thead>
<tr><th>Dispatch #</th><th>Vendor</th><th>NTE</th><th>Status</th><th>Date</th></tr>
<tr><th>Dispatch #</th><th>Vendor</th><th>NTE</th><th>Status</th><th>Date</th><th>Wait</th></tr>
</thead>
<tbody>
{wo.dispatches.map(d => (
@ -893,6 +894,7 @@ export default function WorkOrderViewPage() {
<td>{d.nteAmount != null ? `$${parseFloat(d.nteAmount).toFixed(2)}` : "—"}</td>
<td><span className={`woDispatchStatus ${(d.status || "").toLowerCase()}`}>{d.status}</span></td>
<td>{d.dispatchedAt ? formatDateTime(d.dispatchedAt) : "—"}</td>
<td>{d.status === "Sent" ? <span className={`dispatchWaitDot dispatchWait-${waitTimeColor(d.dispatchedAt)}`}>{timeSince(d.dispatchedAt)}</span> : "—"}</td>
</tr>
))}
</tbody>

38
src/utils/timeSince.js Normal file
View file

@ -0,0 +1,38 @@
export function timeSince(dateString) {
if (!dateString) return "";
const d = new Date(dateString.endsWith("Z") ? dateString : dateString + "Z");
if (isNaN(d)) return "";
const now = new Date();
const diffMs = now - d;
const mins = Math.floor(diffMs / 60000);
if (mins < 1) return "just now";
if (mins < 60) return `${mins}m ago`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
if (days < 7) return `${days}d ago`;
return `${Math.floor(days / 7)}w ago`;
}
export function timeBetween(startStr, endStr) {
if (!startStr || !endStr) return "";
const start = new Date(startStr.endsWith("Z") ? startStr : startStr + "Z");
const end = new Date(endStr.endsWith("Z") ? endStr : endStr + "Z");
if (isNaN(start) || isNaN(end)) return "";
const diffMs = end - start;
const mins = Math.floor(diffMs / 60000);
if (mins < 60) return `${mins}m`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h`;
const days = Math.floor(hours / 24);
return `${days}d`;
}
export function waitTimeColor(dateString) {
if (!dateString) return "";
const d = new Date(dateString.endsWith("Z") ? dateString : dateString + "Z");
const hours = (new Date() - d) / 3600000;
if (hours < 4) return "green";
if (hours < 24) return "yellow";
return "red";
}