Skip to content

Đặc tả UI (UI Spec) — Insight Ghi Âm cho BOD

Phiên bản: 1.0 Ngày: 15/05/2026 Tác giả: PO/BA Mục đích: Hợp đồng màn hình/thay đổi/tương tác. KHÔNG mô tả lại business rule dài (xem prd.md).

Source of mockups: Tất cả ASCII mockups chi tiết đã có trong design doc gốc ../../superpowers/specs/2026-05-15-record-bod-insight-design.md (§4.2.1 đến §4.2.20). File này tóm tắt + thêm As-Is inventory + matrices required cho L profile.


B-PRE) Discovery context

B-PRE.1) Layout UI hiện tại (/e/record baseline)

Component path: diva-admin/src/modules/ecommerce/pages/Records.tsx + components/record/RecordTable.tsx

Bố cục hiện tại:

┌──────────────────────────────────────────────────────────────┐
│ Quản Lý Ghi Âm                          [🔔] [N] NGUYỄN SƠN THỌ│
├──────────────────────────────────────────────────────────────┤
│ [🔍 search...] [Bộ lọc] [Long Khánh ▼] [Tất cả ▼] [Tải] [Xóa]│
├──────────────────────────────────────────────────────────────┤
│ ┌──┬─────┬──────────┬──────────┬──────┬─────┬─────┬─────┐  │
│ │  │STT  │FILE GHI  │TIME START│TIME  │THỜI │KÍCH │ ... │  │
│ │  │     │ÂM        │          │END   │LƯỢNG│THƯỚC│     │  │
│ ├──┼─────┼──────────┼──────────┼──────┼─────┼─────┼─────┤  │
│ │☐ │ 1   │DV230689_ │15/05 08:26│08:31 │4:57 │0.85M│ ... │  │
│ │☐ │ 2   │DV230689_ │14/05 13:08│13:40 │24:13│4.16M│ ... │  │
│ │... rows                                                  │  │
│ └──┴─────┴──────────┴──────────┴──────┴─────┴─────┴─────┘  │
│ [Pagination: Số hàng 20 ▼  1 2 3 ... 6 ►]                    │
└──────────────────────────────────────────────────────────────┘

B-PRE.2) Kiểm kê UI hiện tại (As-Is UI Inventory)

Khu vựcTrường/Cột/CTAThứ tựHành vi
Tiêu đềTiêu đề "Quản Lý Ghi Âm" + breadcrumb1Tĩnh
Tiêu đềBell notification + user menu2Tái sử dụng pattern chung
Thanh lọcSearch input (tên file, KH, NV)3Lọc thời gian thực
Thanh lọc"Bộ lọc" icon button4Mở popup lọc nâng cao
Thanh lọcBranch select5Chọn nhiều
Thanh lọcStatus select (Tất cả / Đã tải lên / Chưa tải)6Lọc theo file.url null
Thanh thao tácCheckbox chọn hàng loạt7Bật/tắt checkbox dòng
Thanh thao tác"Tải xuống" button8Tải ZIP các file đã chọn
Thanh thao tác"Xóa file" button9Xác nhận xóa + soft delete
Cột bảngSTT, FILE GHI ÂM, TIME START, TIME END, THỜI LƯỢNG, KÍCH THƯỚC, KHÁCH HÀNG, LỊCH HẸN, NGƯỜI TẠO, TRẠNG THÁI10-19Sắp xếp theo created_at DESC mặc định
Thao tác dòngPlay icon (▶) — open RecordForm modal20Modal audio HTML5
Phân trangSố hàng dropdown (20 default) + page numbers + arrows21Phân trang server-side

B-PRE.3) Bản đồ tái sử dụng (Reuse Map)

Asset hiện cóSẽ reuse / extend cho insight
RecordForm.tsx HTML5 audio player✅ Reuse via drill-down /e/record
RecordTable.tsx filter state + GraphQL query🔧 Extend (parse URL query + 2 filter mới)
DashboardCardItem.tsx✅ Reuse trong KPI Row
Chart.js wrappers (BarChart, LineChart, PieChart)✅ Reuse cho Trend/TopStaff/Branch charts
CustomerDasboardFilterType pattern✅ Adapt cho FilterBar
Sidebar entry pattern (module.ts:2160)✅ Reuse cho sidebar "Insight Ghi Âm"

B0) Ma trận chuẩn hóa (Required matrices)

B0.4) Field × Surface (mapping field hiển thị qua screens)

Field nguồnTrang chính (Dashboard)Modal Lịch hẹn thiếuModal KH chờ/e/record drill-down
record.id(chỉ là khóa)khóa dòngkhóa dòng
record.created_attrục X biểu đồ trend, heatmapTóm tắt KPI, cột "Ngày giờ"cột "TIME START"
record.created_by (NV)Biểu đồ Top NVcột "NV TƯ VẤN"cột "NV phụ trách"cột "NGƯỜI TẠO"
record.appointment_idTuân thủ % KPI"STT" link → lịch hẹncột "LỊCH HẸN" link
record.customer_idcột "KHÁCH HÀNG"cột "KH"cột "KHÁCH HÀNG"
appointment.branch_idBiểu đồ chi nhánh, Heatmap tổng hợpcột "CHI NHÁNH"lọc chi nhánh
appointment."from"cột "NGÀY GIỜ"cột "Lịch hẹn"cột "TIME START"
reference_file.durationKPI #2 Thời lượng TBcolumn "THỜI LƯỢNG"
reference_file.urlthao tác dòng phát
account.display_nameTop Staff label, tooltiphiển thị "NV TƯ VẤN"hiển thị "NV phụ trách"cột "NGƯỜI TẠO" display

B0.5) State × Screen (lifecycle states)

Trạng tháiDashboardModal Lịch hẹn thiếuModal KH chờ/e/record
Rỗng (không có dữ liệu trong phạm vi)Trạng thái rỗng cho mỗi widget: "Chưa đủ dữ liệu" / "Không có cuộc tư vấn""✅ Tất cả lịch hẹn đều có ghi âm""✅ Không có KH chờ TV"(hành vi cũ)
Đang tải (lần đầu / filter thay đổi)Skeleton pulse cho 5 KPI + 4 chartSpinner trong nội dung modalSpinner(cũ)
Đã tải (cache)Hiện cache + chỉ báo cũ(tức thì nếu có cache)(tức thì nếu có cache)(cũ)
Lỗi (query thất bại)Banner đỏ nhạt cho mỗi widget + [Thử lại]Modal hiện lỗi + thử lạiModal hiện lỗi(cũ)
Không có quyền (403)Chuyển hướng /forbidden(N/A — modal chỉ mở khi có quyền)(N/A)(cũ)
Mạng chậm (> 8s timeout)Banner "Đang tải lâu, retry?"Spinner "Đang tải..." > 3sSpinner(cũ)

B0.6) Chất lượng Wireframe

Wireframes detail trong design doc §4.2.1 đến §4.2.20. Mọi mockup tuân thủ:

  • ASCII chuẩn, canh lề monospace
  • Tiếng Việt label tự nhiên
  • Số liệu sample thực tế (vd 124 cuộc, 70 chi nhánh, 156 KH chờ TV)
  • Chú thích chỉ rõ hành vi (click → navigate, hover → tooltip)
  • Token màu rõ ràng (xanh lá, đỏ, vàng cam, gray neutral)

B0.7) Cặp song ngữ Việt-Anh

Khái niệmUI label (VI)Code/tech term (EN)
Ghi âm tư vấnCuộc tư vấn, Ghi âmrecord, voice consultation
Thời lượng TBThời lượng TBavg_duration_sec
NV hoạt độngNV hoạt độngactive_staff
Tuân thủ ghi âmTuân thủ ghi âmcompliance_rate
KH chờ TVKH chờ TVawaiting_consultation_count
Chi nhánhChi nhánhbranch
So tuần trướcso tuần trướcdelta_wow_pct
Insight DashboardInsight Ghi Âmrecord_insight_dashboard
Tải xuốngTải xuốngdownload
Lịch hẹnLịch hẹnappointment
Mở appointmentMở appointmentopen_appointment
Tải xuống audioTải xuốngdownload_audio

B0.8) Đối chiếu Schema chéo

Tất cả fields hiển thị UI đều có corresponding DB column trong public.record, public.appointment, public.reference_file, hoặc account. KHÔNG có field UI nào không có nguồn DB. Cross-ref: dev-spec.md §C4 Data Model.

B0.9) Kiểm kê tương tác (10 loại canonical)

LoạiVị tríMô tả
Nhấn → Điều hướngBiểu đồ Top NV, Trend point, Heatmap cell, Branch bar, KPI title (P2)Drill-down /e/record với query params
Nhấn → Mở ModalAnomaly card CTAMở MissingRecordsModal / AnomalyMissingTVModal
Nhấn ▼ → Mở/đóng dropdownFilter chips (Branch ▼, Date ▼, Staff ▼, Sort ▼), Page size selector ▼, So sánh ▼Hiển/ẩn dropdown menu. Nhấn ra ngoài hoặc ESC → đóng
Di chuột → TooltipKPI label ⓘ, chart segments, footer "Cập nhật lần cuối"Hiển thị thông tin theo ngữ cảnh
Di chuột → Đổi trạng tháiBar chart bar, table rowViền nổi bật, cursor pointer
Ô tìm kiếmFilter dropdown (Branch, Staff), Modal search boxLọc thời gian thực với debounce 300ms
Phím tắtModal: ESC → close; Player: space → play/pauseHành vi chuẩn của trình duyệt/component
Kéo/thay đổi kích thước(KHÔNG có)
Chọn nhiềuBranch dropdown, Staff dropdown chipsThêm/xóa item
Phân trangModal pagination, /e/record pagination← / → / page number / jump

B-VISUAL) Mockup chuẩn (copy từ design doc — nguồn duy nhất cho FE)

Bộ mockup tự túc cho FE Dev implement P0 mà KHÔNG cần đọc design doc song song. Nguồn gốc: docs/superpowers/specs/2026-05-15-record-bod-insight-design.md §4.2.1 / 4.2.6 / 4.2.7 / 4.2.8 / 4.2.10 / 4.2.15.c.

B-VISUAL.1) Bố cục desktop đầy đủ — 1280px (chuẩn canonical)

╔══════════════════════════════════════════════════════════════════════════════════════════╗
║  [≡] Diva Admin   ›  Ecommerce  ›  Quản Lý Ghi Âm  ›  Insight              [🔔3] [N] ▾  ║
╠══════════════════════════════════════════════════════════════════════════════════════════╣
║                                                                                          ║
║  Insight Ghi Âm                                                                          ║
║  Theo dõi mức độ tuân thủ ghi âm và hiệu suất tư vấn của toàn hệ thống                  ║
║                                                                                          ║
║  ┌────────────────────────────────────────────────────────────────────────────────────┐ ║
║  │  Chi nhánh             Khoảng thời gian        Nhân viên                           │ ║
║  │  [🏢 70 chi nhánh ▼]   [📅 7 ngày qua    ▼]   [👤 Tất cả NV ▼]                    │ ║
║  │                                                                                    │ ║
║  │                                                                      [🔄 Làm mới]  │ ║
║  └────────────────────────────────────────────────────────────────────────────────────┘ ║
║                                                                                          ║
║  ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌────────────┐║
║  │ CUỘC TƯ VẤN  ⓘ│ │ THỜI LƯỢNG TB │ │ NV HOẠT ĐỘNG │ │ TUÂN THỦ GHI ÂM│ │ KH CHỜ TV  ⓘ│║
║  │     124       │ │   15p 36s      │ │      8        │ │     94%        │ │     12      │║
║  │ ▲ 12% so tuần │ │ ▲ 8% so tuần   │ │ ─ Không đổi   │ │ ▼ 2% so tuần   │ │ ⚠ Cần xem  │║
║  │ trước         │ │ trước          │ │               │ │ trước          │ │             │║
║  └───────────────┘ └───────────────┘ └───────────────┘ └───────────────┘ └────────────┘║
║   (xanh lá)         (xanh lá)         (xám trung tính)   (đỏ cam — chú ý)  (đỏ alert)   ║
║                                                                                          ║
║  ┌──────────────────────────────────────────────┐ ┌─────────────────────────────────────┐║
║  │ Số cuộc tư vấn theo ngày                 ⓘ⤡ │ │ Top 10 nhân viên tư vấn        ⓘ⤡ │║
║  │  40 ┤ ●                                      │ │ Nguyễn T. Lam   ████████████ 32     │║
║  │     │ ╲                ●                     │ │ Võ T. Hiếu      ████████     24     │║
║  │  30 ┤  ╲              ╱ ╲                    │ │ Trần V. Hùng    ██████       18     │║
║  │     │   ●            ╱   ╲              ●    │ │ ...                                  │║
║  │  20 ┤    ╲          ╱     ●            ╱     │ │  Click vào tên → drill /e/record    │║
║  │     │     ●────────●       ╲          ╱      │ │                                      │║
║  │  10 ┤                       ●────────●       │ │                                      │║
║  │   0 └──┬──┬──┬──┬──┬──┬──┬─                  │ │                                      │║
║  │      9/5 10 11 12 13 14 15                   │ │                                      │║
║  │  Click 1 điểm → drill /e/record ngày đó      │ │                                      │║
║  └──────────────────────────────────────────────┘ └─────────────────────────────────────┘║
║                                                                                          ║
║  ┌──────────────────────────────────────────────┐ ┌─────────────────────────────────────┐║
║  │ Heatmap: giờ × thứ                       ⓘ⤡ │ │ Top 10 chi nhánh (trên 70 CN)  ⓘ⤡ │║
║  │         T2   T3   T4   T5   T6   T7   CN     │ │ Long Khánh   ████████████ 156 (28%) │║
║  │   8h   ░░  ░░  ▒▒  ▒▒  ▓▓  ░░  ░░           │ │ Quận 1       ██████████   124 (22%) │║
║  │   9h   ▒▒  ▓▓  ▓▓  ▓▓  ▓▓  ▒▒  ░░           │ │ Quận 3       ████████      98 (17%) │║
║  │  10h   ▓▓  ██  ██  ██  ██  ▓▓  ▒▒           │ │ Tân Phú      █████         62 (11%) │║
║  │  11h   ██  ██  ██  ██  ██  ▓▓  ▒▒           │ │ ...                                  │║
║  │  ...                                         │ │ ─────────────────────────────────── │║
║  │   Ít     ░░ ▒▒ ▓▓ ██     Nhiều               │ │ Khác (60 CN) ██            76 (14%) │║
║  │   0      1-3 4-7 8+                          │ │                                      │║
║  │ Tổng hợp qua 70 CN đã chọn                  │ │ [Xem tất cả 70 chi nhánh →]         │║
║  └──────────────────────────────────────────────┘ └─────────────────────────────────────┘║
║                                                                                          ║
║  ┌────────────────────────────────────────────────────────────────────────────────────┐ ║
║  │ 🚨 Cảnh báo & bất thường                                                            │ ║
║  │ 🔴  X lịch hẹn tư vấn KHÔNG có ghi âm                       [Xem X lịch hẹn →]    │ ║
║  │     Cao nhất: NV ... (N cases) — Chi nhánh ...                                     │ ║
║  │                                                                                    │ ║
║  │ 🟡  Y khách hàng có lịch hẹn nhưng chưa được TV trong khoảng                       │ ║
║  │     [Xem danh sách Y KH →]                                                         │ ║
║  └────────────────────────────────────────────────────────────────────────────────────┘ ║
║                                                                                          ║
║  Cập nhật lần cuối: DD/MM/YYYY HH:MM:SS  |  Tự động làm mới mỗi 60s                    ║
╚══════════════════════════════════════════════════════════════════════════════════════════╝

Reading flow (cảm giác BOD khi mở trang):
| Vùng | Câu hỏi BOD | Component trả lời |
|---|---|---|
| Trên cùng (3 giây) | "What's happening now?" | 5 KPI cards — số liệu + delta WoW |
| Giữa trên | "Where's the trend?" | Trend chart + Top staff bar |
| Giữa dưới | "When/where?" | Heatmap + Branch top bar |
| Cuối trang | "What needs my attention?" | Anomaly alerts (2 cards) với CTA |

B-VISUAL.2) Thanh lọc — 4 trạng thái chuẩn

Trạng thái 1 — Đóng (mặc định):

┌──────────────────────────────────────────────────────────────────────────┐
│ [🏢 70 chi nhánh ▼]  [📅 7 ngày qua ▼]  [👤 Tất cả NV ▼]  [🔄 Làm mới]    │
└──────────────────────────────────────────────────────────────────────────┘

Trạng thái mặc định cho BOD (có view_all): "70 chi nhánh" (= all)
Trạng thái mặc định cho BranchManager: "Long Khánh" (= branches user được phép)
Quy ước chip compact:
  - 0 chọn  → "Chọn chi nhánh"
  - 1 chọn  → "Long Khánh"
  - 2-3 chọn → "Long Khánh, Quận 1, +1"
  - >3 chọn → "5 chi nhánh đã chọn"
  - tất cả → "70 chi nhánh"

Trạng thái 2 — Dropdown Chi nhánh mở (search + multi-select + bulk + region group):

┌────────────────────────────────────────────┐
│ 🔍 Tìm chi nhánh...                    [✕] │  ← Lọc thời gian thực, debounce 150ms
│ ──────────────────────────────────────────│
│ Đã chọn 70/70 chi nhánh                    │
│ [Chọn tất cả]  [Bỏ chọn tất cả]            │  ← Thao tác hàng loạt
│ ──────────────────────────────────────────│
│ ▾ 📍 TP. Hồ Chí Minh (28 CN)               │  ← Nhóm theo khu vực (mở/đóng được)
│   ☑ Long Khánh                             │
│   ☑ Quận 1                                 │
│   ☑ Quận 3                                 │
│   ... (25 chi nhánh nữa, scroll)          │
│                                            │
│ ▾ 📍 Hà Nội (24 CN)                        │
│   ☑ Hà Đông                                │
│   ... (23 nữa)                            │
│                                            │
│ ▸ 📍 Đà Nẵng (12 CN)                       │  ← Nhóm đang đóng
│ ▸ 📍 Hải Phòng (3 CN)                      │
│ ▸ 📍 Cần Thơ (3 CN)                        │
│                                            │
│              [Hủy]  [Áp dụng 70/70]        │
└────────────────────────────────────────────┘

Fallback nếu BE chưa có column region (PD-001) → flat list 70 CN với search:
┌────────────────────────────────────────────┐
│ 🔍 Tìm chi nhánh...                    [✕] │
│ Đã chọn 70/70                              │
│ [Chọn tất cả]  [Bỏ chọn tất cả]            │
│ ──────────────────────────────────────────│
│ ☑ Long Khánh                               │
│ ☑ Quận 1                                   │
│ ... (66 chi nhánh nữa, scroll)             │
│              [Hủy]  [Áp dụng 70/70]        │
└────────────────────────────────────────────┘

Hành vi:
- Dropdown max height ~500px, scroll bên trong
- Tìm kiếm lọc thời gian thực (debounce 150ms) — show "Tìm thấy X chi nhánh"
- Tiêu đề nhóm có checkbox 3 trạng thái (☑ all / ☒ partial / ☐ none)
- "Chọn tất cả" / "Bỏ chọn tất cả" áp dụng TOÀN BỘ
- Nút "Áp dụng" hiển thị số đếm "(X/70)"
- Nhấn ngoài → đóng dropdown KHÔNG áp dụng (= Hủy)

Trạng thái 3 — Dropdown khoảng thời gian (preset + custom):

┌──────────────────────────────────┐
│ ○ Hôm nay                        │
│ ○ Hôm qua                        │
│ ● 7 ngày qua                ✓    │
│ ○ 30 ngày qua                    │
│ ○ Tháng này                      │
│ ○ Tháng trước                    │
│ ○ Quý này                        │
│ ○ Tùy chỉnh...                   │
│ ──────────────────────────────── │
│ Từ: [09/05/2026]  Đến: [15/05]   │
│        [Hủy]      [Áp dụng]      │
└──────────────────────────────────┘

Hard limit: range > 365 ngày → disable [Áp dụng] + tooltip "Tối đa 365 ngày"

Trạng thái 4 — Dropdown Nhân viên (chế độ tìm-only, 700 NV):

Mặc định rỗng — KHÔNG render 700 NV upfront. BOD type để search.

┌────────────────────────────────────────────┐
│ 🔍 Tìm NV theo tên hoặc số điện thoại...   │
│                                            │
│ ┌────────────────────────────────────────┐ │
│ │ Đã chọn: 0 NV                          │ │
│ │ (Để trống = Tất cả NV)                 │ │
│ └────────────────────────────────────────┘ │
│                                            │
│ ────────── Trạng thái rỗng ────────────── │
│ Nhập tên NV để tìm                         │
│ (Không hiển thị danh sách 700 NV mặc định) │
│                                            │
│  Hoặc lọc nhanh:                           │
│  [Top 10 NV active 7 ngày qua]             │
│  [NV của các chi nhánh đã chọn]            │
└────────────────────────────────────────────┘

Khi type "lam" (debounce 300ms):
┌────────────────────────────────────────────┐
│ 🔍 lam                              [✕]    │
│ Đã chọn: 0 NV                              │
│ Tìm thấy 12 NV (hiển thị 10 đầu):          │
│ ☐ Nguyễn T. Lam     — Long Khánh           │
│ ☐ Lâm V. Cường      — Quận 1               │
│ ☐ Trần Thanh Lam    — Quận 3               │
│ ... (8 nữa)                                │
│ [Tải thêm 2 NV]                            │  ← Tải lazy
│              [Hủy]     [Áp dụng]           │
└────────────────────────────────────────────┘

Khi đã chọn 3 NV (chips):
┌────────────────────────────────────────────┐
│ 🔍 Tìm thêm NV...                          │
│ ┌────────────────────────────────────────┐ │
│ │ [Nguyễn T. Lam ✕] [Lâm V. Cường ✕]    │ │
│ │ [Trần Thanh Lam ✕]                     │ │
│ └────────────────────────────────────────┘ │
│ Filter chip "Tất cả NV" → "3 NV đã chọn"  │
│              [Hủy]     [Áp dụng (3)]       │
└────────────────────────────────────────────┘

B-VISUAL.3) Trạng thái Đang tải / Rỗng / Lỗi

Đang tải (filter thay đổi / lần đầu):

┌──────────────────────────────────────────────────────────────────────────┐
│ Insight Ghi Âm                                                            │
│ ─────────────────────────────────────────────────────────────────────── │
│ [filter bar — vẫn click được, không bị disable]                          │
│                                                                          │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐       │
│ │ ░░░░░░░  │ │ ░░░░░░░  │ │ ░░░░░░░  │ │ ░░░░░░░  │ │ ░░░░░░░  │       │  ← Skeleton
│ │  ░░░░░   │ │  ░░░░░   │ │  ░░░░░   │ │  ░░░░░   │ │  ░░░░░   │       │     pulse 1.2s
│ │  ░░░░    │ │  ░░░░    │ │  ░░░░    │ │  ░░░░    │ │  ░░░░    │       │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘       │
│                                                                          │
│ ┌────────────────────────┐ ┌────────────────────────┐                   │
│ │     ░░░ Đang tải ░░░   │ │     ░░░ Đang tải ░░░   │                   │
│ └────────────────────────┘ └────────────────────────┘                   │
└──────────────────────────────────────────────────────────────────────────┘

Trạng thái rỗng (không có data trong phạm vi):

┌──────────────────────────────────────────────────────────────────────────┐
│ Insight Ghi Âm                                                            │
│ [Chi nhánh: Tân Phú ▼] [📅 Hôm qua ▼] [👤 Tất cả ▼]                    │
│                                                                          │
│ ┌──────────────────────────────────────────────────────────────────────┐│
│ │                       🎙                                              ││
│ │                                                                      ││
│ │             Không có dữ liệu ghi âm trong khoảng đã chọn             ││
│ │       Có thể chi nhánh chưa phát sinh tư vấn hoặc lọc quá hẹp.      ││
│ │                                                                      ││
│ │             [Mở rộng khoảng thời gian]  [Xem tất cả chi nhánh]      ││
│ └──────────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────┘

Trạng thái lỗi (1 chart bị lỗi, phần còn lại OK):

┌──────────────────────────────────────────────────────────────────────────┐
│ ┌──────────────────────────────────────────────────────────────────────┐│
│ │ ⚠ Không tải được dữ liệu trend                          [Thử lại] [✕]││ ← Banner đỏ nhạt
│ │   Connection timeout (8s). Các phần khác vẫn hiển thị bình thường.   ││   chỉ chart fail
│ └──────────────────────────────────────────────────────────────────────┘│   không phá toàn page
│ [KPI row + các chart khác vẫn render bình thường]                       │
└──────────────────────────────────────────────────────────────────────────┘

B-VISUAL.4) Modal Lịch hẹn thiếu ghi âm — Phân trang + tìm + sắp xếp + lọc CN

┌──────────────────────────────────────────────────────────────────────────┐
│  87 lịch hẹn tư vấn không có ghi âm — 7 ngày qua                  [✕]  │
│  ─────────────────────────────────────────────────────────────────────  │
│  [🔍 Tìm KH hoặc NV...]            [Sort: Ngày giờ ▼]  [Lọc CN ▼]      │  ← Search + filter
│                                                                          │
│  ┌─────┬────────────────┬──────────────┬──────────┬─────────────┬──────┐│
│  │ STT │ KHÁCH HÀNG     │ NV TƯ VẤN    │ NGÀY GIỜ │ CHI NHÁNH   │ ACT  ││
│  ├─────┼────────────────┼──────────────┼──────────┼─────────────┼──────┤│
│  │  1  │ NGUYỄN T. A    │ Trần V. Hùng │ 14/05    │ Long Khánh  │ [Mở] ││
│  │  2  │ TRẦN T. B      │ Trần V. Hùng │ 13/05    │ Long Khánh  │ [Mở] ││
│  │  3  │ LÊ V. C        │ Trần V. Hùng │ 12/05    │ Long Khánh  │ [Mở] ││
│  │  4  │ PHẠM T. D      │ Lam          │ 11/05    │ Quận 1      │ [Mở] ││
│  │  5  │ HỒ V. E        │ Hiếu         │ 10/05    │ Quận 3      │ [Mở] ││
│  │ ... │ (5 nữa trong trang này)                                       ││
│  │ 10  │ ĐỖ V. J        │ Mai          │ 09/05    │ Long Khánh  │ [Mở] ││
│  └─────┴────────────────┴──────────────┴──────────┴─────────────┴──────┘│
│                                                                          │
│  Top NV vi phạm: Trần V. Hùng (12 cases), Lam (8), Hiếu (7)             │
│  Top CN vi phạm: Long Khánh (23), Quận 1 (15), Quận 3 (12)              │
│                                                                          │
│  Hiển thị 1-10 / 87  |  [Page size: 10 ▼]  |  ← 1 2 3 ... 9 →           │
│                                                                          │
│                                                              [Đóng]     │  ← P0 KHÔNG có Export
└──────────────────────────────────────────────────────────────────────────┘

Hành vi:
- Phân trang server-side với offset/limit
- Page size: 10 (default), 20, 50
- Total count luôn hiển thị
- Numeric pagination smart truncation > 7 pages
- ← / → disabled state edge
- Search debounce 300ms → reset page 1
- Sort options: Ngày giờ mới/cũ; KH A-Z/Z-A; NV; CN
- [Mở] → tab mới /appointment/<id>
- Trạng thái rỗng: "✅ Tất cả lịch hẹn đều có ghi âm"
- P0 KHÔNG có button Export Excel (defer P2 theo DEC-014)

B-VISUAL.5) Modal KH chờ tư vấn — 156 KH

┌──────────────────────────────────────────────────────────────────────────┐
│  ℹ 156 khách hàng có lịch hẹn nhưng chưa được tư vấn — 7 ngày qua [✕]│
│  ──────────────────────────────────────────────────────────────────────  │
│                                                                          │
│  💡 Những KH này đã đặt lịch nhưng KHÔNG có record được tạo. Có thể:    │
│     • KH không đến (no-show)                                             │
│     • NV chưa upload ghi âm (compliance gap)                            │
│     • Cuộc gọi không thành công                                          │
│                                                                          │
│  Phân loại tự động (chỉ tham khảo)                                       │
│  ┌────────────────────┬───────┐                                          │
│  │ Lý do (đoán)       │ Số KH │                                          │
│  ├────────────────────┼───────┤                                          │
│  │ No-show            │   72  │                                          │
│  │ Compliance gap     │   53  │                                          │
│  │ Pending follow-up  │   31  │                                          │
│  └────────────────────┴───────┘                                          │
│                                                                          │
│  [🔍 Tìm KH/NV...]    [Lọc CN ▼]   [Lọc lý do ▼]    [Sort ▼]            │
│                                                                          │
│  ┌────┬──────────────────┬──────────────┬─────────────┬────────────────┐ │
│  │ #  │ KH               │ NV phụ trách │ Lịch hẹn    │ HÀNH ĐỘNG      │ │
│  ├────┼──────────────────┼──────────────┼─────────────┼────────────────┤ │
│  │ 1  │ NGUYỄN V. AN     │ Lam          │ 14/05 09:00 │ [Mở appt][KH]  │ │
│  │ 2  │ TRẦN T. BÌNH     │ Hiếu         │ 13/05 14:30 │ [Mở appt][KH]  │ │
│  │ 3  │ LÊ V. CƯỜNG      │ Hùng         │ 12/05 10:15 │ [Mở appt][KH]  │ │
│  │ ...│ (7 nữa trong trang này)                                         │ │
│  │ 10 │ ĐẶNG V. P        │ Mai          │ 09/05 16:00 │ [Mở appt][KH]  │ │
│  └────┴──────────────────┴──────────────┴─────────────┴────────────────┘ │
│                                                                          │
│   [Mở appt]  → tab mới /appointment/<id> (view-only)                     │
│   [KH]       → tab mới /crm/customer/<id> (view-only)                    │
│                                                                          │
│  Hiển thị 1-10 / 156  |  [Page size: 10 ▼]  |  ← 1 2 3 ... 16 →         │
│                                                                          │
│                                                              [Đóng]     │  ← P0 KHÔNG có Export
└──────────────────────────────────────────────────────────────────────────┘

Hành vi: tương tự MissingRecordsModal + Filter lý do (3 options).
P0 KHÔNG có button Export Excel.

B-VISUAL.6) Bảng pattern URL drill-down (chuẩn canonical)

Tất cả click chart segment → navigate /e/record với URL query params. RecordTable parse trong onMounted (xem dev-spec.md §C5.2).

TriggerSource widgetNavigate URL patternFilter applied trong RecordTable
Click bar NV "Nguyễn T. Lam"Top Staff chart/e/record?staffId=<acc_lam_uuid>&from=<YYYY-MM-DD>&to=<YYYY-MM-DD>&branchId=<scope>created_by + date range + branch
Click 1 điểm dot ngày 12/05/2026Trend Chart/e/record?dateExact=2026-05-12&branchId=<scope>from = to = 12/05/2026
Click ô T5 lúc 10hHeatmap/e/record?dateExact=2026-05-15&branchId=<scope> (chính xác đến NGÀY — DEC-006 P0 tradeoff)date single ngày T5 gần nhất
Click bar "Long Khánh"Branch Top chart/e/record?branchId=<lk_uuid>&from=<>&to=<>branch + date range
Click row trong MissingRecordsModal [Mở]Modal/appointment/<id> (new tab)(appointment detail page)
Click row trong AnomalyMissingTVModal [Mở appt]Modal/appointment/<id> (new tab)(appointment detail page)
Click row [KH]Modal/crm/customer/<id> (new tab)(customer detail page)
Click [Xem tất cả 70 chi nhánh →]Branch Top CTA/e/record/insights/staff (P2 — defer) HOẶC /e/record?from=<>&to=<> (P0 fallback)(full ranking page P2 hoặc list /e/record P0)

Hành vi nút Quay lại trình duyệt: Quay lại dashboard với filter state preserved (qua URQL cache + URL state).


B-PRD) Liên kết FR → Màn hình

FRSection/ComponentFile
FR-001 KPI cardsRecordInsightKPIRowDesign doc §4.2.1 + §4.2.2
FR-002 Trend ChartRecordTrendChart§4.2.1 + §4.2.3
FR-003 Top StaffRecordTopStaffChart§4.2.1
FR-004 HeatmapRecordHourHeatmap§4.2.1 + §4.2.4
FR-005 Branch TopRecordBranchTopChart§4.2.1
FR-006 Missing modalMissingRecordsModal§4.2.8
FR-007 KH chờ TV modalAnomalyMissingTVModal§4.2.15.c
FR-008 Filter barRecordInsightFilterBar§4.2.6
FR-009 Audio drill-down(drill-down navigation pattern)§4.2.12 + §4.2.13/14/18
FR-010 Permission v2(sidebar + button visibility)§4.2.9 + §4.5
FR-011 /e/record extendRecordTable.tsx (modified)§4.4.5

B1) Bản đồ màn hình (Screen Map)

[Sidebar "Insight Ghi Âm" entry — chỉ hiển thị với view_insight permission]


SCR-01: /e/record/insights  (RecordInsightPage)

       ├──► Click anomaly 🔴 Missing → SCR-02 MissingRecordsModal (overlay)
       ├──► Click anomaly 🟡 KH chờ TV → SCR-03 AnomalyMissingTVModal (overlay)
       ├──► Click Biểu đồ Top NV → /e/record?staffId=&from=&to=
       ├──► Click Trend point → /e/record?dateExact=
       ├──► Click Heatmap cell → /e/record?dateExact=
       ├──► Click Branch bar → /e/record?branchId=
       └──► Click "📥 Xuất báo cáo ▾" (P2) → ExportDropdown
              └──► Click "🔗 Sao chép link" (P0) → clipboard + toast

SCR-02: MissingRecordsModal (overlay)

       ├──► Click row [Mở] → /appointment/:id (new tab)
       └──► Click [Xuất Excel] (P2) → download .xlsx

SCR-03: AnomalyMissingTVModal (overlay)

       ├──► Click row [Mở appt] → /appointment/:id (new tab)
       ├──► Click row [KH] → /crm/customer/:id (new tab)
       └──► Click [Xuất Excel] (P2) → download .xlsx

B2) Kiểm kê (mức Component)

Detailed mockups: design doc §4.2.1 đến §4.2.20.

B2.7E) Tương tác phụ (8 quy tắc)

Tương tácTriggerHành vi
Trạng thái đang tảiMount lần đầu + filter thay đổiSkeleton nhấp nháy 1.2 giây lặp
Trạng thái rỗngQuery trả 0 dòngThông báo riêng cho mỗi widget
Trạng thái lỗiMạng lỗi / 500 serverBanner đỏ nhạt + retry button
Chỉ báo dữ liệu cũDữ liệu cache > 60 giâyBadge "Đang cập nhật..."
Di chuột hiện tooltipⓘ icon, chart segmentHiện sau 300ms khi di chuột
Phím ESCModal openClose modal
Nhấn ra ngoàiDropdown openClose dropdown (KHÔNG apply if "Áp dụng" required)
Nút quay lại trình duyệtDrill-down /e/recordReturn to dashboard with filter state preserved

B2.8) Thay đổi cấp Field (cho RecordTable extend)

Field/FilterTrước (existing)Sau (extended)Phase
Phân tích URL queryKhông sync URLParse branchId, from, to, staffId, customerId, durationGt, durationLt, dateExact khi mountP0
Lọc theo thời lượngKhông cóThêm 2 input "Thời lượng từ" / "đến" trong advanced filterP0
Banner bất thườngKhông cóBanner "Đang xem drill-down từ Insight" với button "Quay lại Insight"P1
Lọc theo giờ trong ngàyKhông cóOptional dropdown "Giờ trong ngày"P1

B4) Thông báo (KHÔNG ÁP DỤNG P0)

P0 không có notification. Schedule email báo cáo defer P2 (xem prd.md A5 FR-008 P2).


B5) Ma trận quyền (Permission Matrix)

B2.8) Ma trận quyền (canonical, enum phản hồi khi từ chối)

Denied feedback enum: hidden (ẩn hoàn toàn) / disabled (hiển thị nhưng disable) / redirect (chuyển hướng tới forbidden page).

RoleSidebar entryButton trên /e/recordRoute guardBranch scopeDenied feedback
BOD✅ Hiện✅ Hiện✅ ĐạtToàn 70 CN(N/A — có quyền)
ITLeader✅ Hiện✅ Hiện✅ ĐạtToàn 70 CN(N/A — có quyền)
Admin (ROLE_MODERATOR)✅ Hiện✅ Hiện✅ Đạt (bypass)All(N/A — có quyền)
BranchManager❌ Ẩn❌ Ẩn❌ Không đạt → redirect /forbidden(P2: own branches)hidden (sidebar + button KHÔNG xuất hiện); nếu type URL → redirect /forbidden
Staff (NV)❌ Ẩn❌ Ẩn❌ Không đạt → redirect /forbiddenN/Ahidden (sidebar + button KHÔNG xuất hiện); nếu type URL → redirect /forbidden
POS / CRM portal❌ Ẩn(N/A — không có /e/record)❌ Không đạtN/Ahidden (entire module ẩn)

Permission check pattern (canonical):

typescript
const canViewInsight = globalStore.hasPermission('voice_recording_management', 'view_insight');
// Render conditionally
v-if="canViewInsight"

Route guard:

typescript
beforeEnter: (to, from, next) => {
  if (!globalStore.hasPermission('voice_recording_management', 'view_insight')) {
    return next({ name: 'forbidden' });
  }
  next();
}

Hasura permission cho 3 materialized views (P0 — moved từ P1 sau code review B4):

  • Role user SELECT cho 3 views (record_daily_summary, _staff_summary, _hourly_summary)
  • Filter session-claim: branch_id: { _in: X-Hasura-Allowed-Branches }
  • BE middleware pre-compute claim theo permission user (PD-011 — NEW P0)

B6) Ma trận trạng thái (theo widget)

WidgetMặc địnhĐang tảiRỗngLỗiKhông có quyền
RecordInsightFilterBarrender filter chips(N/A — filter bar always available)(N/A)retry button(N/A — route guard handles)
RecordInsightKPIRow5 numbers + delta5 skeleton cards5 cards với "0"Banner đỏ + retry(N/A)
RecordTrendChartline chart with dataskeleton chart area"Chưa đủ dữ liệu để vẽ xu hướng (cần ≥ 2 ngày)"Banner riêng cho widget này + retry(N/A)
RecordTopStaffCharttop 10 barsskeleton bars"Không có nhân viên tư vấn trong khoảng đã chọn"Banner + retry(N/A)
RecordHourHeatmapgrid với colorsskeleton grid"Chưa đủ dữ liệu để vẽ heatmap"Banner + retry(N/A)
RecordBranchTopCharttop 10 + "Khác"skeleton"Không có dữ liệu chi nhánh"Banner + retry(N/A)
RecordAnomalyAlerts2 cards với countskeleton cards"✅ Không phát hiện bất thường"Banner + retry(N/A)
MissingRecordsModaltable với paginationspinner trong modal body"✅ Tất cả lịch hẹn đều có ghi âm"Modal hiện lỗi + thử lại(N/A)
AnomalyMissingTVModaltable tương tựspinner"✅ Không có KH chờ TV"Modal error + retry(N/A)

B7) Nội dung hiển thị (UI Copy Contract)

B7.1) Tiêu đề trang

  • Title: "Insight Ghi Âm"
  • Subtitle: "Theo dõi mức độ tuân thủ ghi âm và hiệu suất tư vấn của toàn hệ thống"

B7.2) Nhãn KPI (chữ hoa, 12px, gray-600)

  1. CUỘC TƯ VẤN
  2. THỜI LƯỢNG TB
  3. NV HOẠT ĐỘNG
  4. TUÂN THỦ GHI ÂM
  5. KH CHỜ TV

B7.3) Chỉ báo delta

  • Positive: ▲ X% so tuần trước (xanh lá #4CAF50)
  • Negative: ▼ X% so tuần trước (đỏ #E53935)
  • No change: ─ Không đổi (gray #9E9E9E)
  • New (previous = 0): ▲ Mới (xanh)

B7.4) Thông báo Rỗng / Lỗi / Đang tải

Trạng tháiCopy
Rỗng tất cả widget"Không có dữ liệu ghi âm trong khoảng đã chọn"
Rỗng biểu đồ trend"Chưa đủ dữ liệu để vẽ xu hướng (cần ≥ 2 ngày)"
Rỗng top NV"Không có nhân viên tư vấn trong khoảng đã chọn"
Rỗng heatmap"Chưa đủ dữ liệu để vẽ heatmap"
Rỗng chi nhánh"Không có dữ liệu chi nhánh"
Rỗng bất thường"✅ Không phát hiện bất thường — Tất cả lịch hẹn đều được ghi âm đầy đủ"
Đang tải"Đang tải..."
Lỗi mạng"⚠ Không tải được dữ liệu — Connection timeout (8s). Các phần khác vẫn hiển thị bình thường."
Tải chậm > 3s"Đang tải lâu — Mạng có thể chậm. Tiếp tục chờ hay [Thử lại]?"
Không có quyền(Redirect — không có message inline)
Modal rỗng"✅ Tất cả lịch hẹn đều có ghi âm"

B7.5) Chân trang

  • "Cập nhật lần cuối: DD/MM/YYYY HH:MM:SS | Tự động làm mới mỗi 60s"
  • Stale badge khi cache > 60s: "Đang cập nhật..."

B8) Phân tích sự kiện (Analytics event tracking)

Event nameTriggerProperties
record_insight_viewedPage mount sau khi pass permissionuser_id, role, branch_filter_count, time_range_preset
record_insight_filter_changedFilter dropdown applyfilter_type (branch/date/staff), value_count
record_insight_chart_clickedClick chart segmentchart_type (trend/top_staff/heatmap/branch), target_filter
record_insight_anomaly_drilldownClick anomaly CTAanomaly_type (missing/awaiting), count
record_insight_export_clickedClick export (P2)format (excel/pdf), scope
record_insight_drilldown_navigatedDrill-down sang /e/recordfrom_widget, query_params
record_insight_audio_playedPlay audio sau drill-down (event trên /e/record)record_id, source (insight_drilldown)

B9) Từ điển Tooltip

Màn hìnhTrường/IconNội dung tooltipĐiều kiện hiện
Insight DashboardKPI #1 ⓘ"Tổng số ghi âm tư vấn đã tạo trong khoảng + chi nhánh đã chọn. Nguồn: COUNT(record) WHERE disabled=false"Di chuột icon ⓘ
Insight DashboardKPI #2 ⓘ"Thời lượng trung bình mỗi cuộc tư vấn (phút:giây). Công thức: AVG(EXTRACT(EPOCH FROM rf.duration))/60. Insight: TB quá ngắn (<5p) có thể NV chưa tư vấn đủ; quá dài (>30p) có thể KH có vấn đề phức tạp"Di chuột icon ⓘ
Insight DashboardKPI #3 ⓘ"Số nhân viên unique có ít nhất 1 cuộc tư vấn được ghi âm trong period. Nguồn: COUNT(DISTINCT record.created_by)"Di chuột icon ⓘ
Insight DashboardKPI #4 ⓘ"Tỷ lệ % cuộc tư vấn đã có ghi âm so với tổng số lịch hẹn loại 'Tư vấn dịch vụ' trong khoảng. Công thức: (appointment có record) / (tổng appt TV) × 100"Di chuột icon ⓘ
Insight DashboardKPI #5 ⓘ"Số khách hàng có lịch hẹn loại tư vấn nhưng chưa được tạo ghi âm. Đoán nguyên nhân: no-show / NV chưa upload / pending follow-up"Di chuột icon ⓘ
Insight DashboardTrend chart ⓘ"Số cuộc tư vấn theo ngày. Click 1 điểm để xem chi tiết trong /e/record"Di chuột icon ⓘ
Insight DashboardHeatmap ⓘ"Mật độ cuộc tư vấn theo giờ × thứ trong tuần. Tổng hợp qua các chi nhánh đã chọn. Click ô để drill-down ngày tương ứng"Di chuột icon ⓘ
Insight DashboardTop Staff ⓘ"Top 10 nhân viên có nhiều cuộc tư vấn nhất trong period. Click bar để xem chi tiết records của NV đó"Di chuột icon ⓘ
Insight DashboardBranch Top ⓘ"Top 10 chi nhánh có nhiều cuộc tư vấn nhất, các CN còn lại gộp 'Khác'. Tổng: 70 chi nhánh"Di chuột icon ⓘ
Insight DashboardFooter "Cập nhật lần cuối""Thời điểm dashboard refresh data gần nhất. Tự động làm mới mỗi 60s khi tab active"Di chuột label
Anomaly card 🔴Card title"Compliance gap — appointment đã đến nhưng NV chưa upload audio. Cần BOD theo dõi tỷ lệ và delegate Manager xử lý"Di chuột card
Anomaly card 🟡Card title"Coverage gap — KH đặt lịch nhưng không có record. Có thể: no-show / compliance gap / pending follow-up"Di chuột card
Thanh lọcBranch chip "70 chi nhánh""BOD đang xem dữ liệu của toàn bộ 70 chi nhánh trong hệ thống. Click để thay đổi"Di chuột chip
Thanh lọcDate chip "7 ngày qua""Hôm nay + 6 ngày trước (rolling window)"Di chuột chip
Modal MissingHeader dynamic count"Số lịch hẹn loại tư vấn trong period mà không có record được tạo (compliance gap)"Di chuột tiêu đề
Modal KH chờ TVHeader dynamic count"Số KH có lịch hẹn loại tư vấn nhưng chưa được tạo ghi âm (coverage gap)"Di chuột tiêu đề
Modal pagination"Hiển thị 1-10 / N""Số rows đang hiển thị trên tổng số rows toàn dataset"Di chuột label

B-Desktop) Bố cục desktop (mặc định 1280px)

Mockup chi tiết: Design doc §4.2.1 (Full Desktop View).

Grid system:

  • 12-column grid, 24px gutter
  • KPI row: 5 cards × 2.4 columns (= 12)
  • Charts row 1: 2 widgets × 6 columns
  • Charts row 2: 2 widgets × 6 columns
  • Anomaly section: full-width
  • Container max-width: 1440px (responsive cap)

Breakpoint: Web-only, không có mobile P0. Tablet 768px → 2 columns layout (P2 optional).


B-EdgeCases) Trường hợp biên (12 nhóm)

GEdge caseBehavior
G1 — DataPeriod rỗng (filter quá hẹp)All widgets hiển thị empty state riêng
G11 ngày có data, 6 ngày emptyTrend chart hiển thị 1 dot + axis 7 days
G1Tất cả reference_file.duration IS NULLKPI #2 hiển thị + warning text "X cuộc không có metadata duration"
G2 — NetworkSlow query > 8sBanner per widget "Đang tải lâu" + retry; không phá toàn page
G2Network completely failToast "Mất kết nối — Đang thử lại..."
G3 — PermissionUser mất quyền giữa sessionNext API call 403 → redirect /forbidden + toast "Quyền truy cập đã thay đổi"
G4 — FilterBranch filter chọn 0 CNShow empty state "Vui lòng chọn ít nhất 1 chi nhánh"
G4Date range > 365 ngàyDisable Apply button + tooltip "Tối đa 365 ngày"
G4Date "from" > "to"Validation error trên picker
G5 — Concurrency2 tabs cùng filter khác → URL conflictMỗi tab độc lập, không sync state
G6 — Volume156 records trong modal MissingPagination 10/page → 16 pages
G60 recordsTrạng thái rỗng hiển thị
G7 — AudioFile audio bị xóa (404 khi play)Player toast "File audio không khả dụng. Có thể đã hết hạn lưu trữ"
G8 — PaginationClick page out-of-rangeAuto clamp to last valid page
G9 — Filter persistenceReload page với URL query paramsRecordTable parse và apply filters
G10 — Compare mode(Defer P1)
G11 — Export (P2)Click export khi loadingDisable button + tooltip "Đang tải data..."
G12 — Backwards compatUser cũ vào URL /e/record không thay đổi behaviorRecordTable hoạt động như cũ nếu không có URL query params

B-i18n) Quốc tế hóa (i18n)

  • Tiếng Việt là ngôn ngữ chính (như Diva hiện hữu)
  • Date format: DD/MM/YYYY trong UI; YYYY-MM-DD trong URL query
  • Number format: 1,234.56 (period decimal, comma thousand)
  • Currency: KHÔNG áp dụng (feature không liên quan tiền)
  • Timezone: Asia/Ho_Chi_Minh cho tất cả tính toán date_trunc

B-Microcopy) Quy ước microcopy

  • Câu ngắn: chủ ngữ + động từ + đối tượng. VD: "Click 1 điểm → drill-down /e/record"
  • Avoid passive voice: "Filter được áp dụng" → "Áp dụng filter"
  • Avoid technical jargon trên label: "duration_sec" → "Thời lượng" (UI); để tooltip chứa technical context
  • Số có đơn vị: "124 cuộc", "32h 15m", "94%", "70 chi nhánh"
  • Plural / Singular: tiếng Việt không phân biệt → dùng số trước noun: "1 cuộc / 2 cuộc / 156 KH"

B-Voice) Giọng và tone

  • Tone: Chuyên nghiệp executive, không thân mật. Mục tiêu: BOD thấy thông tin nghiêm túc, data-driven.
  • Avoid: emoji thái quá (chỉ dùng 🔴🟡 cho anomaly level + ▲▼─ cho delta), exclamation mark "!"
  • Encourage: rõ ràng, số liệu cụ thể, actionable insight trong tooltip

B-Versioning) Quy ước version UI

  • Footer hiển thị version build (P2): "Insight Dashboard v1.0.0"
  • Khi BE schema thay đổi (vd thêm field) → bump minor version + show "Mới có" badge trên field

B-Help) Trợ giúp & Tài liệu

  • ⓘ tooltips cho mọi KPI + chart (B9)
  • "?" icon ở header → link tới docs (P2)
  • Nội dung trạng thái rỗng gợi ý user thao tác ("Mở rộng khoảng thời gian" / "Xem tất cả chi nhánh")

B-POST) Kiểm chứng sau implement

#Verify itemMethod
1All 11 FR có UI component tương ứngCross-check prd.md A11 traceability
2Tất cả permission checks dùng hasPermission(module, action) v2grep hasPermissions([UserRole → 0 results
3Drill-down URL pattern consistentTest 5 drill-down paths với staging
4Pagination behavior (search/filter reset page 1; P0 KHÔNG có Export button trong modal)Manual test

B-QUALITY) Sổ rủi ro UI (30+ risks)

Chi tiết trong design doc §4.6 Xử lý lỗi + §4.11 Hiệu năng. Top 10 rủi ro:

#Rủi roGiảm thiểu
1TTFMP > 3s với 30 ngày × 70 CNTier 1 indexes mandatory + URQL stale-while-revalidate (DEC-012)
2BOD không nhận diện được anomaly criticalColor đỏ #E53935 + icon 🔴 + đặt cuối page (high attention)
3Filter dropdown 70 CN quá dàiSearch realtime + group region + bulk select (DEC-009)
4Staff dropdown 700 NV crash pageSearch-only mode + lazy load 10/lần (DEC-009)
5Branch pie 70 slices không readableĐổi sang Bar top 10 + "Khác" (DEC-008)
6Modal list dài (>50 rows) không paginationPagination 10/20/50 (DEC-013)
7User mất context khi drill-down /e/recordAnomaly banner P1 + browser back giữ state
8Audio file 404 (đã hết hạn)Toast "File hết hạn lưu trữ" graceful
9Permission denied giữa sessionToast + redirect, không crash UI
10Date range > 365 ngàyDisable Apply + tooltip warning
... 20+ moreXem design doc §4.6 + §4.11

Tham chiếu chéo:

  • PRD: ./prd.md
  • Source of Truth: ./SOURCE_OF_TRUTH.md
  • Dev Spec: ./dev-spec.md (next)
  • Design doc gốc: ../../superpowers/specs/2026-05-15-record-bod-insight-design.md