Split activity into customer and vendor comment tabs

- Extract CommentTimeline into reusable component
- Filter comments: customer (from ingest pipeline, has recordType/commenter)
  vs vendor (internal, has userName from ApplicationUser)
- Add tab UI to switch between streams with counts
- Format comment timestamps with formatDateTime
- Show "No comments yet" when a tab is empty
This commit is contained in:
Adam Moussa 2026-04-16 18:01:25 -04:00
parent b0811aae65
commit 1958973968
2 changed files with 84 additions and 17 deletions

View file

@ -126,6 +126,41 @@
line-height: 1.5;
}
.woCommentTabs {
display: flex;
gap: 0;
margin-bottom: 16px;
border-bottom: 2px solid #e5e5e5;
}
.woCommentTab {
padding: 8px 20px;
border: none;
background: none;
font-size: 13px;
font-weight: 600;
color: #999;
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
}
.woCommentTab:hover {
color: #333;
}
.woCommentTab.active {
color: #007bff;
border-bottom-color: #007bff;
}
.woNoComments {
font-size: 13px;
color: #999;
padding: 20px 0;
text-align: center;
}
.loading {
padding: 40px;
text-align: center;

View file

@ -13,11 +13,34 @@ function formatDateTime(value) {
return `${date} ${time} ${tz}`;
}
function CommentTimeline({ comments }) {
if (!comments || comments.length === 0) {
return <p className="woNoComments">No comments yet.</p>;
}
return (
<div className="woTimeline">
{comments.map((c) => (
<div key={c.id} className="woTimelineItem">
<div className="woTimelineHeader">
<span className="woTimelineAuthor">{c.userName || c.commenter || "System"}</span>
<span className="woTimelineDate">
{c.createdDate ? formatDateTime(c.createdDate) : ""}
</span>
</div>
<div className="woTimelineBody">{c.commenttext}</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");
useEffect(() => {
const load = async () => {
@ -38,6 +61,13 @@ 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
);
return (
<div className="woViewPage">
<div className="panel panel-default panel-connected">
@ -112,24 +142,26 @@ export default function WorkOrderViewPage() {
<p className="woDescription">{wo.description || "No description provided."}</p>
</div>
{wo.comments && wo.comments.length > 0 && (
<div className="woViewSection">
<h4>Activity ({wo.comments.length})</h4>
<div className="woTimeline">
{wo.comments.map((c) => (
<div key={c.id} className="woTimelineItem">
<div className="woTimelineHeader">
<span className="woTimelineAuthor">{c.userName || c.commenter || "System"}</span>
<span className="woTimelineDate">
{c.createdDate ? new Date(c.createdDate).toLocaleString() : ""}
</span>
</div>
<div className="woTimelineBody">{c.commenttext}</div>
</div>
))}
</div>
<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>
</div>
)}
<CommentTimeline
comments={commentTab === "customer" ? customerComments : vendorComments}
/>
</div>
</div>
</div>
</div>