Mục tiêu
Thay số mock hard-code trên admin dashboard bằng data thật, đồng thời đánh dấu trung thực phần chưa có backend (lending).
Backend (be/src/modules/dashboard/)
-
GET /admin/dashboard/metrics— đếm live: Total KYC (kyc_info + kyb_info, kèm breakdown theo status), Total cards (user_cards, breakdown theo status), Ramp exceptions (ramp_orders exception set,resolved_by IS NULL); lending →source: NOT_CONNECTED. Work queue "awaiting review" giữ nghĩa on_hold/waiting. -
GET /admin/dashboard/health— đọc snapshot bảngprovider_health; provider chưa có →PENDING. - Cron
@Cron30 phút + chạy 1 lần lúc khởi động: ping Fireblocks/Airwallex (read-only), Reap webhook freshness (>15p → warn), Treasury reconciliation. Mỗi provider try/catch riêng, upsertprovider_health. - Entity
provider_health+ migration. GuardAdminGuard+PermissionsGuard, permissionsystem:read.
Lưu ý review: collectTreasury() chỉ đọc reconciliation_runs mới nhất (method read-only getLatestStatuses()) — KHÔNG gọi runAll() để tránh 12× external call + ops-alert spam so với cron 6h của treasury.
Frontend (admin/)
- orval client cho tag
Admin - Dashboard; service wrap zod; hooks react-query. - Dashboard render theo
source(—/Not connected), chips breakdown status có màu dưới 2 ô total, health dot, nút Refresh. - 4 stat card link tới page tương ứng: KYC→
/users, Cards→/cards, Ramp→/ramp, Lending→/lending. - i18n en + vi.
Test / verify
- BE: build sạch, dashboard specs + treasury spec pass (TDD, fail-first). App boot OK, health populate lúc khởi động.
- Admin: type-check + lint + 244 test pass.
Nợ / phạm vi
-
Lending: giữ
NOT_CONNECTED— chưa có module backend (spec riêng). -
/cardspage hiện là placeholder (link vẫn hợp lệ). - Envelope/Swagger DTO mismatch: zod re-validate runtime; follow-up dùng
ApiEnvelopeDto<T>. - Subtitle đổi "MoanaPay" → "Ahad" cho khớp appName "Ahad Admin" sẵn có — nếu không muốn, revert 1 dòng.