-
- {/* Description Section */}
-
- Description
-
+ Financial
+
+
+
+
+
- {/* Form Actions */}
-
diff --git a/src/pages/workorders/view/WorkOrderViewPage.css b/src/pages/workorders/view/WorkOrderViewPage.css
index 37720735..f23a2dfc 100644
--- a/src/pages/workorders/view/WorkOrderViewPage.css
+++ b/src/pages/workorders/view/WorkOrderViewPage.css
@@ -120,6 +120,76 @@
border-bottom: 1px solid #eee;
}
+.woAssignBtn {
+ background: none;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ padding: 2px 8px;
+ font-size: 14px;
+ color: #333;
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.woAssignBtn:hover {
+ border-color: #ccc;
+ background: #f5f5f5;
+}
+
+.woAssignMenu {
+ position: fixed;
+ background: #fff;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ box-shadow: 0 6px 20px rgba(0,0,0,0.2);
+ z-index: 99999;
+ min-width: 180px;
+ max-height: 250px;
+ overflow-y: auto;
+ padding: 4px 0;
+}
+
+.woAssignOption {
+ display: block;
+ width: 100%;
+ padding: 8px 14px;
+ border: none;
+ background: #fff;
+ font-size: 13px;
+ color: #333;
+ cursor: pointer;
+ text-align: left;
+}
+
+.woAssignOption:hover {
+ background: #f0f0f0;
+}
+
+.woAssignOption.active {
+ font-weight: 600;
+ background: #e6f0ff;
+}
+
+.woSectionToggle {
+ cursor: pointer;
+ user-select: none;
+}
+
+.woSectionToggle:hover {
+ color: #007bff;
+}
+
+.woSectionArrow {
+ display: inline-block;
+ margin-right: 6px;
+ transition: transform 0.15s;
+ font-size: 12px;
+}
+
+.woSectionArrow.open {
+ transform: rotate(90deg);
+}
+
.woFieldGrid {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -152,6 +222,11 @@
white-space: pre-wrap;
}
+.woFieldPlaceholder {
+ color: #ccc;
+ font-style: italic;
+}
+
.woTimeline {
display: flex;
flex-direction: column;
diff --git a/src/pages/workorders/view/WorkOrderViewPage.js b/src/pages/workorders/view/WorkOrderViewPage.js
index 87b60abc..e71c36cc 100644
--- a/src/pages/workorders/view/WorkOrderViewPage.js
+++ b/src/pages/workorders/view/WorkOrderViewPage.js
@@ -2,9 +2,82 @@ 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 } from "../api";
+import { getWorkOrderById, addComment, changeWorkOrderStatus, fetchUsers, changeWorkOrderAssignment } from "../api";
import "./WorkOrderViewPage.css";
+function AssignDropdown({ value, onAssign }) {
+ const [open, setOpen] = useState(false);
+ const [users, setUsers] = useState([]);
+ const [loaded, setLoaded] = useState(false);
+ const btnRef = useRef(null);
+ const menuRef = useRef(null);
+ const [menuPos, setMenuPos] = useState({ top: 0, left: 0 });
+
+ useEffect(() => {
+ const handleClick = (e) => {
+ if (btnRef.current?.contains(e.target)) return;
+ if (menuRef.current?.contains(e.target)) return;
+ setOpen(false);
+ };
+ document.addEventListener("mousedown", handleClick);
+ return () => document.removeEventListener("mousedown", handleClick);
+ }, []);
+
+ const handleOpen = async () => {
+ if (!loaded) {
+ try {
+ const data = await fetchUsers();
+ setUsers(data);
+ setLoaded(true);
+ } catch { return; }
+ }
+ if (btnRef.current) {
+ const rect = btnRef.current.getBoundingClientRect();
+ setMenuPos({ top: rect.bottom + 4, left: rect.left });
+ }
+ setOpen(!open);
+ };
+
+ const handleSelect = async (userId, userName) => {
+ setOpen(false);
+ onAssign(userId, userName);
+ };
+
+ return (
+ <>
+
+ {value || "Unassigned"} ▾
+
+ {open && ReactDOM.createPortal(
+
+ handleSelect("", "")}>
+ Unassigned
+
+ {users.map((u) => (
+ handleSelect(u.id, u.name)}>
+ {u.name}
+
+ ))}
+
,
+ document.body
+ )}
+ >
+ );
+}
+
+function CollapsibleSection({ title, defaultOpen = false, children }) {
+ const [open, setOpen] = useState(defaultOpen);
+ return (
+
+
setOpen(!open)}>
+ ▸
+ {title}
+
+ {open && children}
+
+ );
+}
+
function formatDateTime(value) {
const d = new Date(value.endsWith("Z") ? value : value + "Z");
if (isNaN(d)) return value;
@@ -328,8 +401,49 @@ export default function WorkOrderViewPage() {