diff --git a/src/pages/workorders/api.js b/src/pages/workorders/api.js index 19128b68..b906bd73 100644 --- a/src/pages/workorders/api.js +++ b/src/pages/workorders/api.js @@ -121,6 +121,19 @@ export const fetchVendorsDropdown = async (trade, siteZip) => { return (await apiGet(`${API_URL}/Vendor/Dropdown${qs ? `?${qs}` : ""}`)) || []; }; +export const fetchTaskTemplates = async () => { + const { get: apiGet } = await import("../../apiUtil"); + return (await apiGet(`${API_URL}/TaskListTemplate`)) || []; +}; + +export const updateChecklistItem = async (id, isCompleted, completedBy) => { + return post(`${API_URL}/WorkOrder/UpdateChecklistItem`, { Id: id, IsCompleted: isCompleted, CompletedBy: completedBy }); +}; + +export const addChecklistItem = async (dispatchId, itemText, workOrderId) => { + return post(`${API_URL}/WorkOrder/AddChecklistItem`, { DispatchId: dispatchId, ItemText: itemText, WorkOrderId: workOrderId }); +}; + export const getDispatchById = async (id) => { const { get: apiGet } = await import("../../apiUtil"); return apiGet(`${API_URL}/WorkOrder/GetDispatch/${id}`); diff --git a/src/pages/workorders/view/WorkOrderViewPage.css b/src/pages/workorders/view/WorkOrderViewPage.css index 8337864c..23c6c31c 100644 --- a/src/pages/workorders/view/WorkOrderViewPage.css +++ b/src/pages/workorders/view/WorkOrderViewPage.css @@ -734,6 +734,48 @@ padding-top: 4px; } +.dispatchChecklist { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 16px; +} + +.dispatchChecklistItem { + display: flex; + align-items: center; + gap: 10px; + padding: 6px 8px; + border-radius: 4px; + font-size: 13px; +} + +.dispatchChecklistItem:hover { + background: #f8f9fa; +} + +.dispatchChecklistItem.completed .dispatchChecklistText { + text-decoration: line-through; + color: #999; +} + +.dispatchChecklistText { + flex: 1; + color: #333; +} + +.dispatchChecklistDone { + font-size: 11px; + color: #28a745; +} + +.dispatchChecklistItem input[type="checkbox"] { + width: 16px; + height: 16px; + flex-shrink: 0; + cursor: pointer; +} + .dispatchThreadContainer { display: flex; flex-direction: column; diff --git a/src/pages/workorders/view/WorkOrderViewPage.js b/src/pages/workorders/view/WorkOrderViewPage.js index 7c64146d..9b567e5d 100644 --- a/src/pages/workorders/view/WorkOrderViewPage.js +++ b/src/pages/workorders/view/WorkOrderViewPage.js @@ -2,7 +2,8 @@ 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 } from "../api"; +import { getWorkOrderById, addComment, changeWorkOrderStatus, fetchUsers, changeWorkOrderAssignment, fetchVendorsDropdown, dispatchToVendor, getDispatchById, updateDispatch, addDispatchComment, fetchTaskTemplates, updateChecklistItem, addChecklistItem } from "../api"; +import { getCurrentUser } from "../../../services/authService"; import "./WorkOrderViewPage.css"; function AssignDropdown({ value, onAssign }) { @@ -329,6 +330,38 @@ function DispatchDetailModal({ dispatchId, onClose, onUpdated }) {
+ {dispatch.checklistItems && dispatch.checklistItems.length > 0 && ( + <> +

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

+
+ {dispatch.checklistItems.map((item) => ( +
+ { + const currentUser = getCurrentUser(); + const userName = currentUser?.fullname || "User"; + await updateChecklistItem(item.id, !item.isCompleted, userName); + setDispatch((prev) => ({ + ...prev, + checklistItems: prev.checklistItems.map((ci) => + ci.id === item.id ? { ...ci, isCompleted: !ci.isCompleted, completedBy: !ci.isCompleted ? userName : null, completedAt: !ci.isCompleted ? new Date().toISOString() : null } : ci + ), + })); + }} + /> + {item.itemText} + {item.isCompleted && ( + + {item.completedBy}{item.completedAt ? ` ยท ${formatDateTime(item.completedAt)}` : ""} + + )} +
+ ))} +
+ + )}

Vendor Communication

@@ -386,6 +419,8 @@ function DispatchModal({ wo, onClose, onDispatched }) { const [description, setDescription] = useState(wo.description || ""); const [vendorSearch, setVendorSearch] = useState(""); const [sending, setSending] = useState(false); + const [taskTemplates, setTaskTemplates] = useState([]); + const [selectedTemplateId, setSelectedTemplateId] = useState(""); const [loaded, setLoaded] = useState(false); useEffect(() => { @@ -393,6 +428,7 @@ function DispatchModal({ wo, onClose, onDispatched }) { setVendors(data); setLoaded(true); }).catch(() => setLoaded(true)); + fetchTaskTemplates().then(setTaskTemplates).catch(() => {}); }, [wo.trade]); const selectVendor = (id) => { @@ -414,6 +450,7 @@ function DispatchModal({ wo, onClose, onDispatched }) { NTEAmount: nteAmount ? parseFloat(nteAmount) : null, ScheduledDate: scheduledDate || null, Description: description, + TaskListTemplateId: selectedTemplateId ? parseInt(selectedTemplateId) : null, }); toast.success("Dispatched successfully"); onDispatched(); @@ -481,6 +518,15 @@ function DispatchModal({ wo, onClose, onDispatched }) { setScheduledDate(e.target.value)} />
+
+ + +