# Phase 3.2 Implementation Plan: Incoming Chat Request Overlay & Mitra Request Activity Log ## Summary of Clarified Requirements | Topic | Decision | |---|---| | Work stream order | Overlay (mitra_app) and Activity Log (backend + CC) can be parallelized | | Overlay approach | `Stack` wrapping `MaterialApp.router` in `main.dart` + `AnimatedPositioned` | | Overlay state source | Watches `chatRequestProvider` exclusively — no per-page listeners | | Multiple requests | Queued in notifier. Show one at a time. Next appears when current is resolved. | | Swipe down behavior | Dismiss = ignore (no reject sent to backend) | | Stale request messages | Must show specific reason: cancelled, accepted by other, expired | | Stale auto-dismiss | No auto-dismiss — mitra must acknowledge by tapping OK or swiping | | Background dimming | Partially dimmed to get mitra's attention | | `missed` vs `ignored` | Backend must distinguish: `missed` = another mitra accepted; `ignored` = 60s timeout | | `active_session_count` | Recorded at notification creation time (snapshot of mitra load) | | `response_time_seconds` | Calculated column, not stored (`responded_at - notified_at`) | | Control center page | New `/mitra-activity` page with acceptance rate, avg response time, filters | | Mitra QC auto-flag | Out of scope for this phase (tracked in memory for future) | --- ## Work Stream 1: Incoming Chat Request Overlay (mitra_app) ### 1.1 Backend: Add `reason` Field to `chat_request_closed` WebSocket Message The current `chat_request_closed` message is sent identically for three different scenarios. The overlay must show a specific message for each case. **File:** `backend/src/services/pairing.service.js` **Change 1 — `acceptPairingRequest`:** When notifying other mitras, add `reason: 'accepted_by_other'` **Change 2 — `cancelPairingRequest`:** Add `reason: 'cancelled_by_customer'` **Change 3 — `expirePairingRequest`:** Add `reason: 'expired'` ### 1.2 Backend: Include Session Metadata in `chat_request` WS Message **File:** `backend/src/services/pairing.service.js` Add `duration_minutes` and `is_free_trial` to the `notifyMitra` call in `createPairingRequest`. ### 1.3 ChatRequestNotifier: Queue Support and Stale Reason **File:** `mitra_app/lib/core/chat/chat_request_notifier.dart` #### New State Classes ```dart class ChatRequestIncomingData extends ChatRequestData { final String sessionId; final int? durationMinutes; final DateTime? createdAt; const ChatRequestIncomingData(this.sessionId, {this.durationMinutes, this.createdAt}); } class ChatRequestStaleData extends ChatRequestData { final String sessionId; final StaleReason reason; const ChatRequestStaleData(this.sessionId, this.reason); } enum StaleReason { cancelledByCustomer, // "Permintaan dibatalkan oleh customer" acceptedByOther, // "Permintaan diterima oleh Bestie lain" expired, // "Permintaan kedaluwarsa" } ``` #### Queue Implementation Add `List _pendingQueue` field: - New request arrives while showing another → add to queue - `chat_request_closed` for queued request → remove silently from queue - `chat_request_closed` for displayed request → transition to `ChatRequestStaleData` #### New Methods - `ignore()` — swipe down on active request, advance queue - `acknowledgeStale()` — OK/swipe on stale message, advance queue ### 1.4 Overlay Widget: `ChatRequestOverlay` **New file:** `mitra_app/lib/core/chat/widgets/chat_request_overlay.dart` A `ConsumerStatefulWidget` wrapping the app that manages the overlay: - **Layout:** `Stack` → app child + positioned overlay at bottom + semi-transparent dim layer - **Animation:** `SlideTransition` from `Offset(0, 1)` to `Offset(0, 0)` for bottom-up slide - **Swipe-to-dismiss:** `GestureDetector` with `onVerticalDragEnd` detecting downward swipe - **State listening:** `ref.listen(chatRequestProvider)` to show/hide: - `ChatRequestIncomingData` → show overlay with accept/reject/swipe - `ChatRequestStaleData` → show overlay with reason message + OK button - `ChatRequestAcceptedData` → hide overlay, navigate to chat - Any other state → hide overlay **Active request content:** ``` [Chat icon] "Ada permintaan chat baru!" Durasi: 30 Menit [Tolak] [Terima] (swipe down to close) ``` **Stale request content:** ``` [Info icon] "Permintaan dibatalkan oleh customer" [OK] ``` **Navigation:** Uses `ref.read(routerProvider)` for `GoRouter` access (overlay sits above the router). ### 1.5 App Root Changes **File:** `mitra_app/lib/main.dart` Wrap `MaterialApp.router` with `ChatRequestOverlay`: ```dart return ChatRequestOverlay( child: MaterialApp.router( title: 'Halo Bestie Mitra', routerConfig: router, ), ); ``` ### 1.6 Cleanup Old Bottom Sheet Code **File:** `mitra_app/lib/features/home/home_screen.dart` - Remove `_showIncomingRequest` method - Remove `didChangeAppLifecycleState` incoming request check - Remove `ref.listen(chatRequestProvider, ...)` block for bottom sheet + navigation - Remove `IncomingRequestSheet` import - Convert from `ConsumerStatefulWidget` to `ConsumerWidget` (no lifecycle observer needed) **Delete:** `mitra_app/lib/features/chat/widgets/incoming_request_sheet.dart` --- ## Work Stream 2: Mitra Request Activity Log (Backend + CC) ### 2.1 Database Migration **File:** `backend/src/db/migrate.js` ```sql ALTER TABLE chat_request_notifications ADD COLUMN IF NOT EXISTS active_session_count INT NOT NULL DEFAULT 0; CREATE INDEX IF NOT EXISTS idx_chat_request_notifications_mitra_notified ON chat_request_notifications (mitra_id, notified_at); ``` ### 2.2 Constants: Add `MISSED` Response Type **File:** `backend/src/constants.js` Add `MISSED: 'missed'` to `NotificationResponse`. ### 2.3 Pairing Service Updates **File:** `backend/src/services/pairing.service.js` - `createPairingRequest`: record `active_session_count` when creating notification rows - `acceptPairingRequest`: change `IGNORED` → `MISSED` when marking other mitras' notifications - `expirePairingRequest`: keep `IGNORED` (correct — 60s timeout) ### 2.4 New Service: Mitra Activity **New file:** `backend/src/services/mitra-activity.service.js` - `getMitraActivityLog({ mitra_id, date_from, date_to, page, limit })` — paginated detail log - `getMitraActivitySummary({ mitra_id, date_from, date_to })` — per-mitra aggregates: total, accepted, rejected, missed, ignored, acceptance rate %, avg response time ### 2.5 New Internal Routes **New file:** `backend/src/routes/internal/mitra-activity.routes.js` | Method | Path | Purpose | |---|---|---| | `GET` | `/internal/mitra-activity/log` | Paginated detail log | | `GET` | `/internal/mitra-activity/summary` | Per-mitra summary stats | Register in `backend/src/app.internal.js`. ### 2.6 Control Center: Mitra Activity Page **New file:** `control_center/src/pages/mitra-activity/MitraActivityPage.jsx` **Filters:** Date range (from/to), mitra dropdown (optional) **Summary table columns:** | Mitra | Total | Accepted | Rejected | Missed | Ignored | Rate (%) | Avg Response (s) | **Detail log table columns:** | Mitra | Session | Response | Response Time | Active Sessions | Notified At | Responded At | Response values color-coded: `accepted` green, `rejected` red, `missed` orange, `ignored` grey. Register route in `App.jsx`, add nav link "Aktivitas Mitra" in `Layout.jsx`. --- ## 3. Implementation Order | Step | What | Apps Affected | Dependencies | |---|---|---|---| | **Work Stream 1: Overlay** | | | | | 1 | Backend: add `reason` to `chat_request_closed` WS messages | Backend | None | | 2 | Backend: include `duration_minutes` in `chat_request` WS message | Backend | None | | 3 | ChatRequestNotifier: add `ChatRequestStaleData`, `StaleReason`, queue, `ignore()`, `acknowledgeStale()` | mitra_app | Steps 1–2 | | 4 | Create `ChatRequestOverlay` widget | mitra_app | Step 3 | | 5 | Integrate overlay into `main.dart` | mitra_app | Step 4 | | 6 | Cleanup: remove bottom sheet code from home screen, delete `IncomingRequestSheet` | mitra_app | Step 5 | | 7 | E2E test overlay flows | mitra_app | Step 6 | | **Work Stream 2: Activity Log** | | | | | 8 | DB migration: `active_session_count` column + index | Backend | None | | 9 | Constants: add `MISSED` to `NotificationResponse` | Backend | None | | 10 | Pairing service: record `active_session_count`, use `MISSED` | Backend | Steps 8–9 | | 11 | New `mitra-activity.service.js` | Backend | Step 10 | | 12 | New `mitra-activity.routes.js` + register | Backend | Step 11 | | 13 | Control center: `MitraActivityPage.jsx` | Control center | Step 12 | | 14 | Control center: register route + nav link | Control center | Step 13 | | 15 | E2E test activity log + summary | All | Step 14 | --- ## 4. New Files | File | Purpose | |---|---| | `mitra_app/lib/core/chat/widgets/chat_request_overlay.dart` | App-wide overlay for incoming chat requests | | `backend/src/services/mitra-activity.service.js` | Mitra activity log query functions | | `backend/src/routes/internal/mitra-activity.routes.js` | Internal API routes for activity data | | `control_center/src/pages/mitra-activity/MitraActivityPage.jsx` | CC mitra activity page | ## 5. Deleted Files | File | Reason | |---|---| | `mitra_app/lib/features/chat/widgets/incoming_request_sheet.dart` | Replaced by `ChatRequestOverlay` | --- ## 6. Risks & Mitigations | Risk | Mitigation | |---|---| | Overlay wrapping `MaterialApp.router` can't use `GoRouter.of(context)` | Use `ref.read(routerProvider)` directly | | Swipe gesture conflicts with overlay content | Overlay content is short (non-scrollable); wrap only drag-handle area | | Multiple rapid WS messages cause queue issues | Queue ops are synchronous in Dart's single-threaded event loop | | `chat_request_closed` arrives during slide-up animation | Transition to stale state immediately; animation handles it smoothly | | Old `ignored` values in DB now ambiguous | Only new requests post-deploy get correct `missed` values; document in CC |