feat(portal): render ticket comment thread + send-reply form (PMS-480) #327

Merged
YousifShkara merged 1 commit from feat/PMS-480-portal-comment-thread into main 2026-06-24 09:35:26 +02:00
Owner

PMS-449 phase 1 shipped the portal ticket-comments server surface (GET / POST /api/v1/portal/tickets/{id}/notes) and PMS-468 then exposed created_by_contact_id on the wire shape so a portal SPA can distinguish Customer vs Agent comments without the lossy name-equality heuristic. The portal ticket-detail page never consumed either of those. PMS-480 lands the consumer: a "Conversation" card below the existing detail card that lists the public-note thread oldest-first and accepts a new reply.

What landed:

  • New PortalTicketComments component nested under PortalTicketDetailPage. Fetches GET /portal/tickets/{id}/notes?page=1&per_page=200 via use_resource with a version signal that bumps on every successful POST so the thread refreshes without a page reload.

  • Each note row renders the author name, a Customer / Agent badge driven by created_by_contact_id.is_some() (PMS-468), the formatted timestamp, and the content (whitespace-preserved so multi-line replies don't collapse). The author label falls back to "You" / "Agent" when the server omitted a display name (deleted user, missing first+last).

  • Send-reply form below the thread: textarea + Primary button that POSTs { "content": "..." } to /portal/tickets/{id}/notes, clears the draft on success, bumps version, and notes_resource.restart()s. Submit button disabled while the request is in flight so a customer who clicks twice can't double-post; empty-content is rejected client-side with an inline error. Server-side fields the same validation, this just keeps the round-trip honest.

  • Loading / failed / empty states all render distinct messages so an empty fresh ticket reads differently from a 500 on the GET.

Agent SPA already surfaces customer comments via the existing tickets/notes panel - the server reads the same ticket_notes table from both sides, so no agent-side change is needed.

#PMS-480

PMS-449 phase 1 shipped the portal ticket-comments server surface (`GET / POST /api/v1/portal/tickets/{id}/notes`) and PMS-468 then exposed `created_by_contact_id` on the wire shape so a portal SPA can distinguish Customer vs Agent comments without the lossy name-equality heuristic. The portal ticket-detail page never consumed either of those. PMS-480 lands the consumer: a "Conversation" card below the existing detail card that lists the public-note thread oldest-first and accepts a new reply. What landed: * New `PortalTicketComments` component nested under `PortalTicketDetailPage`. Fetches `GET /portal/tickets/{id}/notes?page=1&per_page=200` via `use_resource` with a `version` signal that bumps on every successful POST so the thread refreshes without a page reload. * Each note row renders the author name, a Customer / Agent badge driven by `created_by_contact_id.is_some()` (PMS-468), the formatted timestamp, and the content (whitespace-preserved so multi-line replies don't collapse). The author label falls back to "You" / "Agent" when the server omitted a display name (deleted user, missing first+last). * Send-reply form below the thread: textarea + Primary button that POSTs `{ "content": "..." }` to `/portal/tickets/{id}/notes`, clears the draft on success, bumps `version`, and `notes_resource.restart()`s. Submit button disabled while the request is in flight so a customer who clicks twice can't double-post; empty-content is rejected client-side with an inline error. Server-side fields the same validation, this just keeps the round-trip honest. * Loading / failed / empty states all render distinct messages so an empty fresh ticket reads differently from a 500 on the GET. Agent SPA already surfaces customer comments via the existing tickets/notes panel - the server reads the same `ticket_notes` table from both sides, so no agent-side change is needed. #PMS-480
feat(portal): render ticket comment thread + send-reply form (PMS-480)
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 2m53s
Create release / Create release from merged PR (pull_request) Has been skipped
dd39bf44ec
PMS-449 phase 1 shipped the portal ticket-comments server surface (`GET / POST /api/v1/portal/tickets/{id}/notes`) and PMS-468 then exposed `created_by_contact_id` on the wire shape so a portal SPA can distinguish Customer vs Agent comments without the lossy name-equality heuristic. The portal ticket-detail page never consumed either of those. PMS-480 lands the consumer: a "Conversation" card below the existing detail card that lists the public-note thread oldest-first and accepts a new reply.

What landed:

* New `PortalTicketComments` component nested under `PortalTicketDetailPage`. Fetches `GET /portal/tickets/{id}/notes?page=1&per_page=200` via `use_resource` with a `version` signal that bumps on every successful POST so the thread refreshes without a page reload.

* Each note row renders the author name, a Customer / Agent badge driven by `created_by_contact_id.is_some()` (PMS-468), the formatted timestamp, and the content (whitespace-preserved so multi-line replies don't collapse). The author label falls back to "You" / "Agent" when the server omitted a display name (deleted user, missing first+last).

* Send-reply form below the thread: textarea + Primary button that POSTs `{ "content": "..." }` to `/portal/tickets/{id}/notes`, clears the draft on success, bumps `version`, and `notes_resource.restart()`s. Submit button disabled while the request is in flight so a customer who clicks twice can't double-post; empty-content is rejected client-side with an inline error. Server-side fields the same validation, this just keeps the round-trip honest.

* Loading / failed / empty states all render distinct messages so an empty fresh ticket reads differently from a 500 on the GET.

Agent SPA already surfaces customer comments via the existing tickets/notes panel - the server reads the same `ticket_notes` table from both sides, so no agent-side change is needed.

#PMS-480
YousifShkara deleted branch feat/PMS-480-portal-comment-thread 2026-06-24 09:35:26 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!327
No description provided.