Files
halobestie-clone/requirement/phase3.2-plan.md
ramadhan sjamsani 4158fb9432 Phase 3.2 docs + Phase 3.1 testing fixes
- Add phase3.2.md requirement: overlay UX, mitra activity log
- Add phase3.2-plan.md implementation plan
- Fix stale request validation: add GET /:sessionId/status endpoint
- Fix notification tap flow: setIncomingFromNotification + onChatRequestTapped
- IncomingRequestSheet shows stale message instead of auto-dismiss
- Home screen validates on resume, shows immediately on fresh WS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 22:09:25 +08:00

9.8 KiB
Raw Permalink Blame History

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

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<String> _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:

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

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 IGNOREDMISSED 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 12
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 89
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