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>
This commit is contained in:
182
requirement/phase3.5.md
Normal file
182
requirement/phase3.5.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# 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](mitra_app/lib/features/home/home_screen.dart#L128)) 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)
|
||||
```jsonc
|
||||
{
|
||||
"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](backend/src/services/mitra-activity.service.js#L6-L40)), 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._
|
||||
Reference in New Issue
Block a user