diff --git a/src/App.js b/src/App.js index 7bda8672..a143e4f8 100644 --- a/src/App.js +++ b/src/App.js @@ -46,6 +46,9 @@ import AssetFormPage from "./pages/assets/form/AssetFormPage"; import CalendarPage from "./pages/calendar/list/CalendarPage"; import EventFormPage from "./pages/calendar/form/EventFormPage"; +// SETTINGS +import DropdownOptionsPage from "./pages/settings/DropdownOptionsPage"; + // Protected Route Component function ProtectedRoute({ children }) { return isAuthenticated() ? children : ; @@ -121,6 +124,9 @@ export default function App() { } /> } /> } /> + + {/* SETTINGS */} + } /> diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js index a5f0e553..c8970b5c 100644 --- a/src/components/Sidebar.js +++ b/src/components/Sidebar.js @@ -13,6 +13,7 @@ import { FaClipboardList, FaCubes, FaMapMarkerAlt, + FaCog, } from "react-icons/fa"; const navItems = [ @@ -28,6 +29,7 @@ const navItems = [ { label: "PM Schedules", icon: , path: "/pmschedules" }, { label: "Assets", icon: , path: "/assets" }, { label: "Locations", icon: , path: "/locations" }, + { label: "Settings", icon: , path: "/settings/dropdowns" }, ]; export default function Sidebar() { diff --git a/src/components/Topbar.js b/src/components/Topbar.js index 5c40afa0..4a5a8f20 100644 --- a/src/components/Topbar.js +++ b/src/components/Topbar.js @@ -107,8 +107,8 @@ function UserDropdown({ currentUser, onLogout, onNavigate }) { - + ))} + + +
+
+ {categoryConfig?.parent && ( + + )} + setNewValue(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={`Add new ${categoryConfig?.label?.slice(0, -1) || "option"}...`} + /> + +
+ + {loading ? ( +
Loading...
+ ) : options.length === 0 ? ( +
No options configured yet
+ ) : ( +
+ {options.map((item) => ( +
+ {editingId === item.id ? ( + setEditValue(e.target.value)} + onKeyDown={(e) => { if (e.key === "Enter") handleEditSave(item); if (e.key === "Escape") setEditingId(null); }} + autoFocus + /> + ) : ( +
+ {item.value} + {item.parentValue && {item.parentValue}} +
+ )} +
+ {editingId === item.id ? ( + <> + + + + ) : ( + <> + + + + )} +
+
+ ))} +
+ )} +
+ + + ); +} diff --git a/src/pages/workorders/api.js b/src/pages/workorders/api.js index c069fb05..459d4869 100644 --- a/src/pages/workorders/api.js +++ b/src/pages/workorders/api.js @@ -32,6 +32,13 @@ export const getWorkOrderById = async (id) => { status: wo.status ?? "Open", po: wo.po ?? "", tt: wo.tt ?? "", + problem: wo.problem ?? "", + trade: wo.trade ?? "", + subTrade: wo.subTrade ?? "", + vendorNTE: wo.vendorNTE != null ? String(wo.vendorNTE) : "", + scheduledDate: wo.scheduledDate ? wo.scheduledDate.split("T")[0] : "", + completedDate: wo.completedDate ? wo.completedDate.split("T")[0] : "", + source: wo.source ?? "", comments: wo.comments ?? [], auditLog: wo.auditLog ?? [], }; @@ -64,6 +71,13 @@ export const editWorkOrder = async (workOrder) => { Priority: workOrder.priority || null, Status: workOrder.status, LocationId: workOrder.locationId ? parseInt(workOrder.locationId) : null, + Problem: workOrder.problem || null, + Trade: workOrder.trade || null, + SubTrade: workOrder.subTrade || null, + VendorNTE: workOrder.vendorNTE ? parseFloat(workOrder.vendorNTE) : null, + ScheduledDate: workOrder.scheduledDate || null, + CompletedDate: workOrder.completedDate || null, + Source: workOrder.source || null, }); } catch (error) { console.error("Error updating work order:", error); @@ -89,6 +103,13 @@ export const fetchUsers = async () => { return (data || []).map((u) => ({ id: u.id ?? u.Id, name: u.name ?? u.Name ?? u.userName ?? "" })); }; +export const fetchDropdownOptions = async (category, parent) => { + const { get: apiGet } = await import("../../apiUtil"); + let url = `${API_URL}/DropdownOptions/ByCategory/${encodeURIComponent(category)}`; + if (parent) url += `?parent=${encodeURIComponent(parent)}`; + return (await apiGet(url)) || []; +}; + export const changeWorkOrderStatus = async (id, status) => { return post(`${API_URL}/WorkOrder/ChangeStatus?id=${id}&status=${encodeURIComponent(status)}`); }; diff --git a/src/pages/workorders/form/WorkOrderFormPage.css b/src/pages/workorders/form/WorkOrderFormPage.css index 474e9a6a..46a9c999 100644 --- a/src/pages/workorders/form/WorkOrderFormPage.css +++ b/src/pages/workorders/form/WorkOrderFormPage.css @@ -164,6 +164,10 @@ padding-left: 15px; } +.col-sm-3 { + width: 25%; +} + .col-sm-4 { width: 33.33333333%; } diff --git a/src/pages/workorders/form/WorkOrderFormPage.js b/src/pages/workorders/form/WorkOrderFormPage.js index 228d7f7d..2991703d 100644 --- a/src/pages/workorders/form/WorkOrderFormPage.js +++ b/src/pages/workorders/form/WorkOrderFormPage.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { useParams, useNavigate } from "react-router"; import { toast } from "react-toastify"; -import { getWorkOrderById, addWorkOrder, editWorkOrder } from "../api"; +import { getWorkOrderById, addWorkOrder, editWorkOrder, fetchDropdownOptions } from "../api"; import { get } from "../../../apiUtil"; import { API_URL } from "../../../constants"; import "./WorkOrderFormPage.css"; @@ -20,21 +20,41 @@ export default function WorkOrderFormPage() { status: "Open", createdDate: new Date().toISOString().split("T")[0], dueDate: "", + scheduledDate: "", + completedDate: "", assignedTo: "", description: "", po: "", tt: "", + problem: "", + trade: "", + subTrade: "", + vendorNTE: "", + source: "", }); const [loading, setLoading] = useState(false); const [locations, setLocations] = useState([]); + const [problems, setProblems] = useState([]); + const [trades, setTrades] = useState([]); + const [subTrades, setSubTrades] = useState([]); useEffect(() => { get(`${API_URL}/Contact/GetLocations`) .then((data) => setLocations(data || [])) .catch(() => {}); + fetchDropdownOptions("Problem").then(setProblems).catch(() => {}); + fetchDropdownOptions("Trade").then(setTrades).catch(() => {}); }, []); + useEffect(() => { + if (formData.trade) { + fetchDropdownOptions("SubTrade", formData.trade).then(setSubTrades).catch(() => {}); + } else { + setSubTrades([]); + } + }, [formData.trade]); + useEffect(() => { if (isEdit && id) { const loadWorkOrder = async () => { @@ -60,10 +80,11 @@ export default function WorkOrderFormPage() { const handleChange = (e) => { const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value, - })); + setFormData(prev => { + const updated = { ...prev, [name]: value }; + if (name === "trade") updated.subTrade = ""; + return updated; + }); }; const handleSave = async (e) => { @@ -106,7 +127,6 @@ export default function WorkOrderFormPage() {
- {/* Page Header */}

@@ -119,57 +139,29 @@ export default function WorkOrderFormPage() {

- {/* Work Order Details Section */}
-
- Work Order Details -
+
Work Order Details
- +
- +
-
- {locations.map((loc) => ( @@ -179,73 +171,63 @@ export default function WorkOrderFormPage() {
- - +
+
+
+
+ +
+
+
Classification
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
- {/* Schedule & Priority Section */}
-
- Schedule & Priority -
+
Schedule & Priority
-
-
- - -
-
-
-
- - -
-
-
- -
-
+
- @@ -255,17 +237,10 @@ export default function WorkOrderFormPage() {
-
+
- @@ -274,81 +249,86 @@ export default function WorkOrderFormPage() {
-
- -
-
+
- - + +
-
+
+
+
- - + + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
- {/* Description Section */}
-
- Description -
+
Financial
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
+
Description
-