# PRD: Mitra Chat Request Overlay & Pairing UX # Overview **Goal:** Reliable incoming chat request experience for Mitra — always visible, non-blocking, works in all app states (foreground, background, killed) **Success looks like:** When a customer requests a chat, the mitra is always notified and can accept/reject without leaving their current screen. The notification works regardless of whether the app is in foreground, background, or opened from a push notification. ## Background - Current bottom sheet approach (`showModalBottomSheet`) fails silently when the app is backgrounded - Flutter UI cannot render when the app is not in foreground - Push notifications work in background but the in-app response (bottom sheet) doesn't show reliably when the user taps the notification - The state change from WebSocket gets "consumed" by listeners while backgrounded, so returning to foreground shows stale data - Need a solution that is non-blocking (doesn't interrupt active chat sessions) and works on any page # Functional Requirement ## Incoming Chat Request Overlay ### Trigger - Overlay watches `chatRequestProvider` state — it does NOT depend on any specific page or lifecycle event - Three sources can trigger the overlay: 1. **WebSocket** — real-time delivery when app is in foreground 2. **FCM notification tap** — user taps push notification, app opens, state is set from notification payload 3. **App resume** — app returns to foreground, validates pending request with backend ### Appearance - Slides up from the bottom of the screen, like a bottom sheet - Rounded top corners - Page behind is partially dimmed to get mitra's attention - Shows on top of ANY page (home, chat, history, settings, etc.) ### Content - Session information (duration, etc.) - Accept button - Reject button - Swipe down to close (= ignore, NOT reject) ### Behavior - **Accept** → overlay dismisses, navigate to chat session with customer - **Reject** → overlay dismisses, mitra continues what they were doing - **Swipe down / close** → overlay dismisses, request is ignored (no explicit reject sent to backend) - **Request cancelled by customer** → overlay updates to show "Permintaan dibatalkan oleh customer" - **Request accepted by other mitra** → overlay updates to show "Permintaan diterima oleh Bestie lain" - **Request expired (60s timeout)** → overlay updates to show "Permintaan kedaluwarsa" - Stale messages do NOT auto-dismiss — mitra must acknowledge by tapping OK or swiping down ### Multiple Requests - Show one request at a time - Requests are queued — when current request is resolved (accepted/rejected/ignored/expired), next queued request appears - Future enhancement: dedicated `/chat-requests` page with full list ## Push Notification (Background/Killed) ### When App is Backgrounded - Local notification with sound + vibration (already implemented via WebSocket listener) - FCM push notification as fallback when WebSocket is disconnected ### When Notification is Tapped - App opens → `chatRequestProvider` state is set from notification payload (`session_id`) - Overlay appears with the request detail - Backend validation confirms request is still pending before showing accept/reject ### Stale Notification Handling - If user taps a notification for an already-resolved request, overlay shows appropriate message ("dibatalkan" / "diterima Bestie lain" / "kedaluwarsa") then auto-dismisses ## Mitra Request Activity Log ### Goal Track every mitra's response to incoming chat requests for QC measurement — how many accepted, rejected, ignored (expired without action), and how many active sessions they had at the time. ### Logged Data For each incoming request delivered to a mitra, log: - `mitra_id` — which mitra received the request - `session_id` — which chat request - `response` — `accepted`, `rejected`, `ignored` (expired without action), `missed` (taken by other mitra before response) - `response_time_seconds` — how long it took the mitra to respond (null if ignored) - `active_session_count` — how many active sessions the mitra had at the time of the request - `notified_at` — when the request was delivered to the mitra - `responded_at` — when the mitra responded (null if ignored) ### When to Log All logging is **backend-side and event-driven** — no polling or constant monitoring needed. The frontend only triggers `accepted` and `rejected` through existing API calls. | Response | Triggered by | Frontend action? | |---|---|---| | `accepted` | Backend — when mitra calls `POST /:sessionId/accept` | Yes — mitra taps Accept | | `rejected` | Backend — when mitra calls `POST /:sessionId/decline` | Yes — mitra taps Reject | | `missed` | Backend — when `acceptPairingRequest` marks other mitras' notifications (another mitra accepted first) | No — fully backend | | `ignored` | Backend — when `expirePairingRequest` fires after 60s timeout with no response | No — fully backend | ### Existing Infrastructure The `chat_request_notifications` table already tracks `notified_at`, `response`, `responded_at`. Current changes needed: - Distinguish `missed` from `ignored` (currently both stored as `ignored`) - Add `active_session_count` column — recorded when the notification is created - `response_time_seconds` can be calculated from `responded_at - notified_at` (no new column needed) ### Control Center - New dedicated page: Mitra Request Activity - Dashboard: mitra acceptance rate, average response time, rejection count, ignore count per mitra - Filter by date range and mitra - Auto-flagging mitras with high rejection/ignore rate is **out of scope** for this phase (planned for future) ## Technical Implementation ### Overlay Component - Single `OverlayEntry` managed from `main.dart` - Wrapped around `MaterialApp.router` in a `Stack` - Watches `chatRequestProvider` — shows/hides based on state - Uses `AnimatedPositioned` or `SlideTransition` for bottom-up animation - `GestureDetector` for swipe-to-dismiss ### State Flow ``` WebSocket ──→ FCM tap ──→ chatRequestProvider ──→ Overlay shows/hides App resume ──→ ``` ### No Changes Required - Backend pairing service (already sends WS + FCM) - Push notification payload (already contains session_id + type) - Chat request notifier (already manages state from WS + FCM) ### Cleanup from Phase 3.1 - Remove `showModalBottomSheet` for incoming requests from home screen - Remove `IncomingRequestSheet` widget - Remove `didChangeAppLifecycleState` incoming request check from home screen # Tech Stack - Flutter `Overlay` / `OverlayEntry` or `Stack` with `AnimatedPositioned` - Existing Riverpod `chatRequestProvider` - No backend changes