Add four-tab activity section: Customer, Vendor, Internal, Audit Log

- Filter comments by commentType field (customer, vendor, internal)
- Add AuditTimeline component showing field changes with old → new values
- Pass auditLog from API response to view page
This commit is contained in:
Adam Moussa 2026-04-16 18:09:57 -04:00
parent 1958973968
commit d979d5571a
3 changed files with 80 additions and 22 deletions

View file

@ -30,6 +30,7 @@ export const getWorkOrderById = async (id) => {
po: wo.po ?? "",
tt: wo.tt ?? "",
comments: wo.comments ?? [],
auditLog: wo.auditLog ?? [],
};
} catch (error) {
console.error("Error fetching work order:", error);

View file

@ -161,6 +161,32 @@
text-align: center;
}
.woAuditItem {
border-left-color: #6c757d;
}
.woAuditField {
font-weight: 600;
color: #333;
margin-right: 8px;
}
.woAuditOld {
color: #dc3545;
text-decoration: line-through;
margin-right: 4px;
}
.woAuditArrow {
color: #999;
margin: 0 4px;
}
.woAuditNew {
color: #28a745;
font-weight: 500;
}
.loading {
padding: 40px;
text-align: center;

View file

@ -35,12 +35,39 @@ function CommentTimeline({ comments }) {
);
}
function AuditTimeline({ entries }) {
if (!entries || entries.length === 0) {
return <p className="woNoComments">No changes recorded.</p>;
}
return (
<div className="woTimeline">
{entries.map((e) => (
<div key={e.id} className="woTimelineItem woAuditItem">
<div className="woTimelineHeader">
<span className="woTimelineAuthor">{e.userName || "System"}</span>
<span className="woTimelineDate">
{e.createdAt ? formatDateTime(e.createdAt) : ""}
</span>
</div>
<div className="woTimelineBody">
<span className="woAuditField">{e.fieldName}</span>
{e.oldValue && <span className="woAuditOld">{e.oldValue}</span>}
<span className="woAuditArrow">→</span>
<span className="woAuditNew">{e.newValue}</span>
</div>
</div>
))}
</div>
);
}
export default function WorkOrderViewPage() {
const { id } = useParams();
const navigate = useNavigate();
const [wo, setWo] = useState(null);
const [loading, setLoading] = useState(true);
const [commentTab, setCommentTab] = useState("customer");
const [activeTab, setActiveTab] = useState("customer");
useEffect(() => {
const load = async () => {
@ -61,12 +88,18 @@ export default function WorkOrderViewPage() {
const statusClass = (wo.status || "").toLowerCase().replace(/\s+/g, "-");
const customerComments = (wo.comments || []).filter(
(c) => c.recordType || c.commenter
);
const vendorComments = (wo.comments || []).filter(
(c) => !c.recordType && !c.commenter && c.userName
);
const allComments = wo.comments || [];
const customerComments = allComments.filter((c) => c.commentType === "customer");
const vendorComments = allComments.filter((c) => c.commentType === "vendor");
const internalComments = allComments.filter((c) => c.commentType === "internal");
const auditLog = wo.auditLog || [];
const tabs = [
{ key: "customer", label: "Customer", count: customerComments.length },
{ key: "vendor", label: "Vendor", count: vendorComments.length },
{ key: "internal", label: "Internal", count: internalComments.length },
{ key: "audit", label: "Audit Log", count: auditLog.length },
];
return (
<div className="woViewPage">
@ -145,22 +178,20 @@ export default function WorkOrderViewPage() {
<div className="woViewSection">
<h4>Activity</h4>
<div className="woCommentTabs">
<button
className={`woCommentTab ${commentTab === "customer" ? "active" : ""}`}
onClick={() => setCommentTab("customer")}
>
Customer ({customerComments.length})
</button>
<button
className={`woCommentTab ${commentTab === "vendor" ? "active" : ""}`}
onClick={() => setCommentTab("vendor")}
>
Vendor ({vendorComments.length})
</button>
{tabs.map((tab) => (
<button
key={tab.key}
className={`woCommentTab ${activeTab === tab.key ? "active" : ""}`}
onClick={() => setActiveTab(tab.key)}
>
{tab.label} ({tab.count})
</button>
))}
</div>
<CommentTimeline
comments={commentTab === "customer" ? customerComments : vendorComments}
/>
{activeTab === "customer" && <CommentTimeline comments={customerComments} />}
{activeTab === "vendor" && <CommentTimeline comments={vendorComments} />}
{activeTab === "internal" && <CommentTimeline comments={internalComments} />}
{activeTab === "audit" && <AuditTimeline entries={auditLog} />}
</div>
</div>
</div>