Files
halobestie-clone/requirement/phase3.5.md
ramadhan sjamsani a560b0936c PRDs: phase3.5 (mitra chat request history) + phase3.6 (force-close re-enable)
Phase 3.5: replace _PendingRequestsBanner with a Riwayat Permintaan card
on the mitra home, plus a screen listing the last 20 entries from
chat_request_notifications. Backend endpoint TBD.

Phase 3.6: plan to re-enable mitra force-close (Akhiri) once the moderation
/ accountability story is in place. Backend route and config flag are
already preserved from Phase 3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:09:19 +08:00

8.9 KiB

PRD: Mitra Chat Request History

Overview

Goal: Replace the home-screen "Pending Requests" banner on the mitra app with a single "Riwayat Permintaan" CTA that opens a list of the mitra's last 20 chat requests (regardless of status), and surfaces a count badge on the CTA whenever there are pending requests waiting for a response.

Success looks like: A mitra can open one screen from the home and see, at a glance, every chat request that was blasted to them recently — who requested, when, the topic flag, and what happened (accepted / declined / missed / ignored, or still pending). Pending requests appear at the top, can be acted on directly from this screen, and are visually obvious from the home thanks to a count badge on the CTA.

Affects: mitra_app, backend

Background

  • The mitra home today shows a _PendingRequestsBanner Card (mitra_app/lib/features/home/home_screen.dart:128) that displays the live count of pending requests and re-opens the incoming overlay on tap.
  • The backend already stores a per-mitra log row in chat_request_notifications for every blast, with response ∈ {accepted, declined, missed, ignored, NULL}.
  • That log is currently exposed only to the control center (/internal/mitra-activity/log); the mitra app sees only currently-pending pings via GET /api/mitra/chat/pending.
  • The system-wide "ringing phone" overlay that auto-pops on every incoming CHAT_REQUEST WebSocket message is out of scope for this change and stays as-is.

Functional Requirement

1. Home Screen CTA

Replaces

  • The existing _PendingRequestsBanner Card is removed.
  • A new "Riwayat Permintaan" Card replaces it in the same slot.

Appearance

  • Card style consistent with the existing Sesi Aktif and Riwayat Chat Cards.
  • Title: "Riwayat Permintaan"
  • Subtitle (no pending): "Lihat permintaan chat sebelumnya"
  • Subtitle (with pending): "{N} permintaan baru" — same copy as today's banner.
  • Trailing: chevron (>) plus the badge below.

Badge

  • A red count badge (matches existing pattern, not a dot) is shown on the trailing edge whenever pending count > 0.
  • Pending = response IS NULL for this mitra in chat_request_notifications, with the corresponding session still in pending_acceptance.
  • Badge disappears as soon as that count returns to zero.

Tap

  • Navigates to the Chat Request History screen (Section 2).

2. Chat Request History Screen

Route

  • New GoRouter route: /chat/requests/history.
  • Reachable only from the home CTA.

Data

  • Last 20 rows from chat_request_notifications for the calling mitra, ordered by notified_at DESC.
  • No pagination, no filters, no search in this phase.

List ordering

  • Pending rows (response IS NULL AND session still pending_acceptance) are pinned to the top, newest-first within the group.
  • All other rows follow, sorted by notified_at DESC.

Row content

Each row shows:

  • Customer call name — always whatever the customer chose as their call sign (call_name / display name). If empty or if global anonymity_enabled = false, show "Anonim". Never display phone, email, social id, or internal user id.
  • Sensitive topic flag — when topic_sensitivity = 'sensitive', show the same warning-yellow "Topik sensitif" badge used on the incoming overlay (Phase 3.3).
  • Status badge with localized label and color:
    • Pending → yellow, "Menunggu respon"
    • Accepted → green, "Diterima"
    • Declined → gray, "Ditolak"
    • Missed → gray, "Terlewat" (another mitra accepted)
    • Ignored + session cancelled → gray, "Dibatalkan"
    • Ignored + session expired → gray, "Kedaluwarsa"
  • Relative timestamp of notified_at — "Baru saja", "2 menit lalu", "Kemarin", "3 hari lalu", etc.

Empty state

  • Copy: "Belum ada permintaan chat".

Refresh

  • Pull-to-refresh (Material RefreshIndicator) re-calls the endpoint.
  • The screen does not auto-update when WebSocket events arrive while open. Pull-to-refresh is the only manual refresh affordance.
  • The home-screen badge count, however, stays live (driven by the same WS-backed provider that powers today's banner).

3. Tap Behavior on a Row

Pending row (response IS NULL AND session_status = 'pending_acceptance')

  • Re-opens the existing incoming-request accept/decline overlay for that session.
  • If the request is no longer actionable by the time the overlay opens (another mitra accepted, customer cancelled, 60s expired), the overlay opens in its standard closed state ("Permintaan sudah tidak tersedia") — no special handling needed.

Non-pending row (any non-NULL response)

  • Opens a read-only detail screen showing:
    • Customer call name (or "Anonim")
    • Topic sensitivity
    • Status (localized label per Section 2)
    • notified_at (absolute date/time)
    • responded_at if present
    • For accepted rows only: a "Lihat percakapan" CTA that navigates to the existing chat-history transcript screen for that session_id.
    • For all other non-pending rows: no transcript link.

4. Live Behavior

Incoming auto-overlay — unchanged

  • When a CHAT_REQUEST WebSocket message arrives, the system-wide overlay continues to pop up no matter which screen the mitra is on. This remains the primary "you have a new request" UX.

Home CTA badge — live

  • Driven by the same Riverpod provider that powers today's _PendingRequestsBanner count.
  • Increments on CHAT_REQUEST, decrements on CHAT_REQUEST_CLOSED (accept-by-other / cancel / expiry) and on this mitra's own accept/decline.

History screen — pull-only

  • No WS subscription. Refreshes only when the screen is opened or pulled-to-refresh.
  • Race conditions on accept are handled by the server's existing atomic UPDATE … WHERE status = 'pending_acceptance' — a stale "pending" row that's actually been taken simply fails the accept gracefully via the existing overlay flow.

5. Backend Changes

New route

  • GET /api/mitra/chat/requests/recent (default and max limit = 20)
  • Auth: existing mitra JWT.
  • Strictly per-mitra-scoped — the calling mitra never sees other mitras' rows.

Response shape (per row)

{
  "notification_id": "uuid",
  "session_id": "uuid",
  "notified_at": "ISO-8601",
  "responded_at": "ISO-8601 | null",
  "response": "accepted | declined | missed | ignored | null",
  "session_status": "pending_acceptance | active | closing | completed | cancelled | expired | ...",
  "customer_call_name": "string (already masked to 'Anonim' if anonymity rule applies)",
  "topic_sensitivity": "regular | sensitive"
}

Implementation

  • Reuse the SQL shape of getMitraActivityLog (backend/src/services/mitra-activity.service.js:6-40), scoped to WHERE crn.mitra_id = <self>, ORDER BY crn.notified_at DESC, LIMIT 20.
  • Apply the anonymity mask server-side so the mitra app never has to know the raw value: if app_config.anonymity_enabled = false OR the customer's call name is empty, return "Anonim"; never return phone, email, social id, or user id under any circumstance.

No new tables, columns, or migrations.


6. Mitra App Changes

Item Detail
Removed _PendingRequestsBanner Card in home_screen.dart
Added _RequestHistoryButton Card in home_screen.dart, with count badge
Added Chat Request History screen — features/chat/screens/request_history_screen.dart
Added Read-only detail screen — features/chat/screens/request_history_detail_screen.dart
Reused Incoming accept/decline overlay; existing chat-history transcript screen
Routes /chat/requests/history, /chat/requests/history/:notificationId
Providers New requestHistoryProvider (list state); reuses chatRequestProvider for the live badge count

7. Edge Cases

  • Stale pending row tapped after the request is gone — overlay handles via existing closed-state UX.
  • Anonymity flipped mid-fetch — display rule is evaluated server-side at fetch time. Open lists do not retroactively update; next fetch reflects the new value.
  • More than 20 rows — older rows simply don't appear in this view. The control center retains full history.
  • Mitra has zero notifications — empty state per Section 2.
  • Mitra offline — list shows last fetched data (or empty if never fetched); pull-to-refresh required to recover.

8. Non-Goals (this phase)

  • Pagination, infinite scroll, "load more" beyond 20 rows.
  • Filters by status / date / topic.
  • Search.
  • Push notification when a non-current request changes status.
  • Aggregated stats (acceptance rate, avg response time) — those stay in the control center.
  • Per-mitra notification preferences.

Open Questions

None — ready for plan document.