diff --git a/src/App.js b/src/App.js index ef675548..7bda8672 100644 --- a/src/App.js +++ b/src/App.js @@ -27,6 +27,7 @@ import LocationFormPage from "./pages/locations/form/LocationFormPage"; // WORK ORDERS import WorkOrdersList from "./pages/workorders/list/List"; +import WorkOrderViewPage from "./pages/workorders/view/WorkOrderViewPage"; import WorkOrderFormPage from "./pages/workorders/form/WorkOrderFormPage"; // FOLLOW UPS @@ -98,6 +99,7 @@ export default function App() { {/* WORK ORDERS */} } /> } /> + } /> } /> {/* FOLLOW UPS */} diff --git a/src/pages/workorders/api.js b/src/pages/workorders/api.js index f35ba132..28f12834 100644 --- a/src/pages/workorders/api.js +++ b/src/pages/workorders/api.js @@ -28,6 +28,7 @@ export const getWorkOrderById = async (id) => { estimatedHours: wo.estimatedHours ?? "", po: wo.po ?? "", tt: wo.tt ?? "", + comments: wo.comments ?? [], }; } catch (error) { console.error("Error fetching work order:", error); diff --git a/src/pages/workorders/list/List.js b/src/pages/workorders/list/List.js index 84048ad1..24f95f0a 100644 --- a/src/pages/workorders/list/List.js +++ b/src/pages/workorders/list/List.js @@ -136,7 +136,7 @@ export default function WorkOrdersList() { loading={loading} error={err} rows={rows} - onEdit={(item) => navigate(`/workorders/edit/${item.id}`)} + onEdit={(item) => navigate(`/workorders/${item.id}`)} onDelete={handleDeleteClick} page={page} setPage={setPage} diff --git a/src/pages/workorders/view/WorkOrderViewPage.css b/src/pages/workorders/view/WorkOrderViewPage.css new file mode 100644 index 00000000..95ae090a --- /dev/null +++ b/src/pages/workorders/view/WorkOrderViewPage.css @@ -0,0 +1,133 @@ +.woViewPage { + padding: 0; +} + +.woViewHeader { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 0 20px 20px; + border-bottom: 1px solid #e5e5e5; + margin-bottom: 20px; +} + +.woViewTitle h2 { + margin: 0 0 8px 0; + font-size: 22px; + font-weight: 600; + color: #333; +} + +.woStatusBadge { + display: inline-block; + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + color: #fff; + background: #6c757d; +} + +.woStatusBadge.open { background: #007bff; } +.woStatusBadge.in-progress { background: #ffc107; color: #333; } +.woStatusBadge.on-hold { background: #fd7e14; } +.woStatusBadge.done, .woStatusBadge.completed { background: #28a745; } +.woStatusBadge.cancelled { background: #dc3545; } + +.woViewActions { + display: flex; + gap: 8px; + flex-shrink: 0; +} + +.woViewGrid { + padding: 0 20px; +} + +.woViewSection { + margin-bottom: 24px; +} + +.woViewSection h4 { + font-size: 14px; + font-weight: 600; + text-transform: uppercase; + color: #666; + margin: 0 0 12px 0; + padding-bottom: 8px; + border-bottom: 1px solid #eee; +} + +.woFieldGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +.woField { + display: flex; + flex-direction: column; +} + +.woFieldLabel { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + color: #999; + margin-bottom: 2px; +} + +.woFieldValue { + font-size: 14px; + color: #333; +} + +.woDescription { + font-size: 14px; + color: #333; + line-height: 1.6; + margin: 0; + white-space: pre-wrap; +} + +.woTimeline { + display: flex; + flex-direction: column; + gap: 12px; +} + +.woTimelineItem { + padding: 12px; + background: #f8f9fa; + border-radius: 6px; + border-left: 3px solid #007bff; +} + +.woTimelineHeader { + display: flex; + justify-content: space-between; + margin-bottom: 6px; +} + +.woTimelineAuthor { + font-weight: 600; + font-size: 13px; + color: #333; +} + +.woTimelineDate { + font-size: 12px; + color: #999; +} + +.woTimelineBody { + font-size: 13px; + color: #555; + line-height: 1.5; +} + +.loading { + padding: 40px; + text-align: center; + color: #999; +} diff --git a/src/pages/workorders/view/WorkOrderViewPage.js b/src/pages/workorders/view/WorkOrderViewPage.js new file mode 100644 index 00000000..a07420a5 --- /dev/null +++ b/src/pages/workorders/view/WorkOrderViewPage.js @@ -0,0 +1,127 @@ +import React, { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router"; +import { toast } from "react-toastify"; +import { getWorkOrderById } from "../api"; +import "./WorkOrderViewPage.css"; + +export default function WorkOrderViewPage() { + const { id } = useParams(); + const navigate = useNavigate(); + const [wo, setWo] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const load = async () => { + try { + const data = await getWorkOrderById(id); + setWo(data); + } catch (err) { + toast.error("Failed to load work order"); + } finally { + setLoading(false); + } + }; + load(); + }, [id]); + + if (loading) return
Loading...
; + if (!wo) return
Work order not found.
; + + const statusClass = (wo.status || "").toLowerCase().replace(/\s+/g, "-"); + + return ( +
+
+
+
    +
  1. + { e.preventDefault(); navigate("/workorders"); }}> + Work Orders + +
  2. +
  3. {wo.workOrderNumber}
  4. +
+ +
+
+

{wo.title}

+ {wo.status} +
+
+ + +
+
+ +
+
+

Details

+
+
+ WO # + {wo.workOrderNumber} +
+
+ Priority + {wo.priority || "—"} +
+
+ Location + {wo.location || "—"} +
+
+ Assigned To + {wo.assignedTo || "—"} +
+
+ Created + {wo.createdDate || "—"} +
+
+ Due Date + {wo.dueDate || "—"} +
+
+ PO + {wo.po || "—"} +
+
+ TT + {wo.tt || "—"} +
+
+
+ +
+

Description

+

{wo.description || "No description provided."}

+
+ + {wo.comments && wo.comments.length > 0 && ( +
+

Activity ({wo.comments.length})

+
+ {wo.comments.map((c) => ( +
+
+ {c.userName || c.commenter || "System"} + + {c.createdDate ? new Date(c.createdDate).toLocaleString() : ""} + +
+
{c.commenttext}
+
+ ))} +
+
+ )} +
+
+
+
+ ); +}