mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 08:33:14 +00:00
Add interactive status dropdown on work order view page
- Click the status badge to open a dropdown with all status options - Status change calls ChangeStatus API and updates locally - Color-coded status dots in the dropdown menu - Closes on outside click - Also logged in audit trail via backend ChangeStatus endpoint
This commit is contained in:
parent
d3a4bcc984
commit
3b15a43879
3 changed files with 131 additions and 4 deletions
|
|
@ -78,6 +78,10 @@ export const addComment = async (workOrderId, text, commentType = "internal") =>
|
|||
});
|
||||
};
|
||||
|
||||
export const changeWorkOrderStatus = async (id, status) => {
|
||||
return post(`${API_URL}/WorkOrder/ChangeStatus?id=${id}&status=${encodeURIComponent(status)}`);
|
||||
};
|
||||
|
||||
export const deleteWorkOrder = async (id) => {
|
||||
try {
|
||||
await workOrderApi.delete(id);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,75 @@
|
|||
.woStatusBadge.done, .woStatusBadge.completed { background: #28a745; }
|
||||
.woStatusBadge.cancelled { background: #dc3545; }
|
||||
|
||||
.woStatusDropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.woStatusClickable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.woStatusClickable:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.woStatusCaret {
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.woStatusMenu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: 4px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
|
||||
z-index: 100;
|
||||
min-width: 160px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.woStatusOption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 8px 14px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.woStatusOption:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.woStatusOption.active {
|
||||
font-weight: 600;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
.woStatusDot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.woStatusDot.open { background: #007bff; }
|
||||
.woStatusDot.in-progress { background: #ffc107; }
|
||||
.woStatusDot.on-hold { background: #fd7e14; }
|
||||
.woStatusDot.done { background: #28a745; }
|
||||
.woStatusDot.cancelled { background: #dc3545; }
|
||||
|
||||
.woViewActions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useParams, useNavigate } from "react-router";
|
||||
import { toast } from "react-toastify";
|
||||
import { getWorkOrderById, addComment } from "../api";
|
||||
import { getWorkOrderById, addComment, changeWorkOrderStatus } from "../api";
|
||||
import "./WorkOrderViewPage.css";
|
||||
|
||||
function formatDateTime(value) {
|
||||
|
|
@ -117,12 +117,68 @@ function CommentInput({ workOrderId, commentType, onCommentAdded }) {
|
|||
);
|
||||
}
|
||||
|
||||
const STATUS_OPTIONS = ["Open", "In Progress", "On Hold", "Done", "Cancelled"];
|
||||
|
||||
function StatusDropdown({ status, onStatusChange }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
const statusClass = (status || "").toLowerCase().replace(/\s+/g, "-");
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (e) => {
|
||||
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
||||
};
|
||||
document.addEventListener("mousedown", handleClick);
|
||||
return () => document.removeEventListener("mousedown", handleClick);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="woStatusDropdown" ref={ref}>
|
||||
<button
|
||||
className={`woStatusBadge ${statusClass} woStatusClickable`}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{status} <span className="woStatusCaret">▾</span>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="woStatusMenu">
|
||||
{STATUS_OPTIONS.map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
className={`woStatusOption ${s === status ? "active" : ""}`}
|
||||
onClick={() => { onStatusChange(s); setOpen(false); }}
|
||||
>
|
||||
<span className={`woStatusDot ${s.toLowerCase().replace(/\s+/g, "-")}`} />
|
||||
{s}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WorkOrderViewPage() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [wo, setWo] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activeTab, setActiveTab] = useState("customer");
|
||||
const [statusUpdating, setStatusUpdating] = useState(false);
|
||||
|
||||
const handleStatusChange = async (newStatus) => {
|
||||
if (newStatus === wo?.status) return;
|
||||
setStatusUpdating(true);
|
||||
try {
|
||||
await changeWorkOrderStatus(id, newStatus);
|
||||
setWo((prev) => ({ ...prev, status: newStatus }));
|
||||
toast.success(`Status changed to ${newStatus}`);
|
||||
} catch (err) {
|
||||
toast.error("Failed to update status");
|
||||
} finally {
|
||||
setStatusUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
|
|
@ -159,8 +215,6 @@ export default function WorkOrderViewPage() {
|
|||
if (loading) return <div className="woViewPage"><div className="loading">Loading...</div></div>;
|
||||
if (!wo) return <div className="woViewPage"><div className="loading">Work order not found.</div></div>;
|
||||
|
||||
const statusClass = (wo.status || "").toLowerCase().replace(/\s+/g, "-");
|
||||
|
||||
const allComments = wo.comments || [];
|
||||
const customerComments = allComments.filter((c) => c.commentType === "customer");
|
||||
const vendorComments = allComments.filter((c) => c.commentType === "vendor");
|
||||
|
|
@ -192,7 +246,7 @@ export default function WorkOrderViewPage() {
|
|||
<div className="woViewHeader">
|
||||
<div className="woViewTitle">
|
||||
<h2>{wo.title}</h2>
|
||||
<span className={`woStatusBadge ${statusClass}`}>{wo.status}</span>
|
||||
<StatusDropdown status={wo.status} onStatusChange={handleStatusChange} />
|
||||
</div>
|
||||
<div className="woViewActions">
|
||||
<button className="btn btn-primary" onClick={() => navigate(`/workorders/edit/${id}`)}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue