Skip to content

Đặc tả giao diện (UI Spec) — Tích hợp Pancake CRM (Pancake CRM Integration)

Phiên bản: 1.0 Ngày: 15/05/2026 Tác giả: PO/BA + AI Tech Lead Tham chiếu: PRD v1.0 + SOURCE_OF_TRUTH v1.0 + EVIDENCE_PACK v1.0 Profile: L (Lớn)


Mục đích: mô tả 5 màn Settings Pancake CRM mới (SCR-01..05) + 1 nhóm delta dropdown ticket source (SCR-06) trên 3 màn hiện hữu của module CRM. Đủ căn cứ để FE triển khai, designer hoàn thiện design system, QA viết test case, PO/BA review. Đọc trước: decision-brief.md (nếu có) → B-PRE Discovery checklist → B0 Hiện trạng UI → B1 Bản đồ màn hình → B2.SCR-01..06B-POST Verification → B-QUALITY Rà soát rủi ro. Văn phong: theo _LANGUAGE_RULES.md + _STYLE_GUIDE.md. Heading + label Mermaid/wireframe Việt-first. CTA dùng Lưu / Hủy / Áp dụng / Xác nhận / Phát lại / Kiểm tra kết nối / Kích hoạt lại. Mọi copy hiển thị tiếng Việt tự nhiên ngữ cảnh SaaS spa/thẩm mỹ viện. Enum/code giữ backtick (received, dead_letter, outage_started). Phụ lục mở rộng đã load: EXT-3 (lifecycle ≥4 trạng thái), EXT-4 (RBAC field-level cho api_key), EXT-6 (L flow nhiều màn), EXT-9 (audit log Tab 3), EXT-12 (autosave conflict source routing), EXT-13 (bulk replay DLQ).

Tài liệu đầu vào chuẩn

FileVai tròNếu xung đột
SOURCE_OF_TRUTH.md v1.0Nguồn sự thật chuẩn (25 DEC, Solution Lock)Ưu tiên cao nhất
EVIDENCE_PACK.md v1.0Layout As-Is, inventory hiện trạng, ứng viên reuse (file:line)Ưu tiên bằng chứng code/screen
prd.md v1.0FR-001..018, LIFECYCLE-001/002, A9 glossary, A10 công thứcTheo truth đã khóa

Quy tắc: UI Spec là tài liệu dẫn xuất, không tự phát minh rule. Mọi REMOVE/HIDE phải có DEC/FR/PO approval. Trace ngược: SCR-01..04 = FR-012 (Settings UI 4 tabs). SCR-05 = container XDetailLayout. SCR-06 = FR-018 (FE delta dropdown). DEC-016 = 4 tabs Admin only. DEC-021 = 3-mức feature flag. DEC-008 = VIP tag NAME case-insensitive. DEC-009 = test mode is_test. DEC-014 = opt-out marketing. DEC-024 = 3 notification template mới.

Hướng dẫn đọc

Đối tượngSection cần đọc
PO/BAB-PREB0.4 Field × SurfaceB1 Screen Map → B2.7 Copy text (B7)
Designer / FE DevB0.1 InventoryB2 SCR-01..06 wireframe + variantsB-MicrocopyB-i18nB-EdgeCases
QAB0.5 State × ScreenB5 Permission MatrixB6 State Matrix lifecycleB-EdgeCases G1-G12
Tech LeadB0.8 Schema cross-checkB0.9 Interactive InventoryB-QUALITY
Admin Diva (ops)B2 SCR-01..04B7 Copy → B-Help

B-PRE) Discovery checklist (đã hoàn thành Phase 3)

Lưu ý: discovery cho feature này đã hoàn thành đầy đủ ở Phase 3 ANALYZE và được canonical hóa trong EVIDENCE_PACK.md. Section này ref evidence thay vì re-scan.

B-PRE.1) Tìm component/page hiện có liên quan

Đã scan ở Phase 3 (EVIDENCE_PACK §2-3):

Hạng mụcPath đã verifyEvidence
Tickets list pagediva-admin/src/modules/crm/pages/Tickets.tsxEVIDENCE_PACK §2.1 + line 128-149 (sourceDescriptions), 851-865 (XMultipleSelect filter), 915 (permission gate)
Ticket bulk create formdiva-admin/src/modules/crm/components/ticket/TicketMultipleAdd.tsx§2.2 + line 758 (dropdown source)
Customer ticket drawerdiva-admin/src/modules/user/components/customer/CustomerTicketManager.tsx§2.3
Settings tab pattern referencediva-admin/src/modules/settings/pages/AppSettingsSmsTemplate.tsx§2.4 line 14-28 (XDetailLayout)
Ticket source arraydiva-admin/src/modules/crm/types.ts:146-164§3.1 (8 slot hiện có)
i18n vi.tsdiva-admin/src/modules/crm/i18n/vi.ts:130-137§3.1
Permission frameworkdiva-admin/src/stores/useGlobalStore.ts:169-182§2.5 (hasPermission(moduleId, actionId))
GraphQL queries existingdiva-admin/src/modules/crm/graphql/ticket.graphql§5 R15 (source_id nullable, không đổi)

B-PRE.1.A) Scan 100% interactive elements trên existing screens (SCR-06 delta)

Áp dụng cho SCR-06 (FE delta dropdown ticket source). SCR-01..05 là build mới — không cần scan As-Is interactive.

Loại tương tácFile hiện cóLinePattern reuse
Filter dropdown Nguồn ticketTickets.tsx851-865XMultipleSelect :options="TicketSources" :label="$t('crm.ticket_source_X')" — auto render slot 9 khi types.ts có entry mới
Form Tạo Ticket dropdown NguồnTicketMultipleAdd.tsx758dropdown loop — auto render
Drawer ticket KHCustomerTicketManager.tsxTBD-verifyreuse pattern XMultipleSelect — auto render
Tooltip mô tả source (hover icon ⓘ)Tickets.tsx128-149sourceDescriptions[ticket_source_X] HTML span — phải ADD entry slot 9
Row click drilldown ticketTickets.tsxTBD-verifyreuse — slot 9 hành xử y như slot 1-8

Kết luận discovery: FE delta cho SCR-06 = 3 file thay đổi cứng (types.ts + i18n/vi.ts + Tickets.tsx sourceDescriptions). Không phát hiện hardcode 'ticket_source_1'..'ticket_source_8' ở module CRM (đã grep §3.2). Cảnh báo nhỏ: module report (customer-service, telesales) chưa verify — F18 phải grep audit trước deploy MVP-1 (RSK-005).

B-PRE.2) Bảng inventory phải điền TRƯỚC khi viết B0

Hạng mụcĐã check?File / màn hiện cóGhi chú
Page / route hiện có[x]Settings/AppSettings* các loạiXDetailLayout pattern reuse
Component reusable[x]XDetailLayout, XMultipleSelect, XTable, XDrawer, UserSelectCó sẵn
Form/dialog liên quan[x]TicketMultipleAdd (form drawer)Pattern XDrawer
Field hiện có trên Tickets list[x]Mã / Trạng thái / Liên hệ / Chi nhánh / Người phụ trách / Nhãn KH / Ngày tạo§2.1
CTA hiện có[x]+ Thêm Sửa Xóa toolbar, Áp dụng filter§2.1
Filter / search hiện có[x]Nguồn ticket (8 slot), Trạng thái (4), Người phụ trách, Người nhận bàn giao§2.1
State hiện có[x]Loading skeleton 5 row, Has-data 20/page pagination, Empty§2
Permission / role gating[x]globalStore.hasPermission('ticket_management', 'access')§2.1 line 915
Notification trigger hiện có[x]noti_ticket_reminder_today/tomorrow cron onlyEVIDENCE_PACK G5
Export columns hiện có[x]ExportTickets.tsx reuse — slot 9 source rendering auto§3.2
Tooltip / hint hiện có[x]sourceDescriptions HTML span hover iconTickets.tsx 128-149
Mobile / responsive[x]Diva admin desktop-first, tablet OKPattern XDetailLayout
Analytics events hiện có[x]Chưa có analytics tracking cho ticket source changeBuild mới (B8)

B-PRE.3) Phân loại reuse (BẮT BUỘC, ref SOURCE_OF_TRUTH SL-10/SL-13)

Phần featurePhân loạiEvidenceDelta cần
Container XDetailLayout 4 tabs Pancake✅ ReuseAppSettingsSmsTemplate.tsx:14-28Tạo PancakeCrmSetting.tsx copy pattern
Tab 1 form Kết nối🆕 Build mớiForm mới: workspace_id, api_key, webhook_url copy, kill switch, VIP tag multi-line, "Kiểm tra kết nối" button
Tab 2 Source routing table inline-edit🔧 Extend patternXTable + BranchSelect existingBảng mới với inline-edit autosave, "Đồng bộ từ Pancake" button
Tab 3 Audit + DLQ list🆕 Build mớiList event với filter combo, drawer detail raw_payload, replay action
Tab 4 Health metrics🆕 Build mớiMetric cards + outage history + chart trend
Dropdown ticket source slot 9🔧 Extend (FE delta)Tickets.tsx:851, TicketMultipleAdd.tsx:758, types.ts:146-1643 file delta + 1 migration master data
Permission gating Admin only✅ ReuseuseGlobalStore.hasPermissionRegister module pancake_crm_integration
Notification template🆕 Build mới (3 template)notification-v2-api sendNotifications actionSeed 3 row vào notification_template

B0) Hiện trạng UI và Delta Contract

B0.1) Bảng kiểm kê đầy đủ (As-Is + Delta Status)

Phạm vi inventory: (a) các vùng UI hiện hữu của 3 màn CRM mà SCR-06 delta đụng vào. (b) các vùng existing trên Settings sidebar nơi gắn menu "Tích hợp Pancake CRM".

B0.1.A) Tickets list (Tickets.tsx) — SCR-06 delta

UI IDMàn / routeSectionBlock / field / actionThứ tựHành vi hiện tạiPermissionMobile?Delta StatusHành vi đíchEvidence
SCR-06-BLK-01/crm/ticketsPanel trái filterHeader "Nguồn ticket"1Tiêu đề labelticket_management.accessTablet OKKEEPGiữ nguyênTickets.tsx:848
SCR-06-FLD-02/crm/ticketsPanel trái filterXMultipleSelect dropdown 8 source2Render từ TicketSources array map qua i18n; multi-select; default "Tất cả"ticket_management.accessUPDATERender 9 source (thêm slot ticket_source_pancake = "Pancake CRM")Tickets.tsx:851-865, types.ts:146-164
SCR-06-BLK-03/crm/ticketsPanel trái filterTooltip hover icon ⓘ mô tả source 7-83HTML span popup từ sourceDescriptions[ticket_source_X]ticket_management.accessCó (long-press)UPDATEAdd entry ticket_source_pancake: "Lead từ Pancake CRM (40+ kênh: FB, Zalo, TikTok, Google Ads...)"Tickets.tsx:128-149
SCR-06-BLK-04/crm/ticketsPanel trái filterSection "Trạng thái" (4 status)4Multi-selectticket_management.accessKEEPGiữ nguyênTickets.tsx
SCR-06-BLK-05/crm/ticketsPanel trái filterSection "Người phụ trách" UserSelect5dropdown userticket_management.accessKEEPGiữ nguyênTickets.tsx
SCR-06-BLK-06/crm/ticketsPanel trái filterSection "Người nhận bàn giao" + checkbox6User select + checkboxticket_management.accessKEEPGiữ nguyênTickets.tsx
SCR-06-BLK-07/crm/ticketsPanel phải toolbarButtons + Thêm Sửa Xóa + filter chip1Action toolbarticket_management.create/update/deleteKEEPGiữ nguyên (form Tạo Ticket sẽ render slot 9 trong dropdown source — xem SCR-06-BLK-09)Tickets.tsx
SCR-06-BLK-08/crm/ticketsPanel phải tableCột # / Mã / Trạng thái / Liên hệ / Chi nhánh / Người phụ trách / Nhãn KH / Ngày tạo + pagination1Table 20 row/pageticket_management.accessCard list mobileKEEPGiữ nguyên — ticket Pancake render trong cùng tableTickets.tsx

B0.1.B) Form Tạo Ticket (TicketMultipleAdd.tsx) — SCR-06 delta

UI IDMàn / routeSectionBlock / field / actionThứ tựHành viPermissionMobile?Delta StatusHành vi đíchEvidence
SCR-06-BLK-09/crm/tickets drawerForm modalField Nguồn XMultipleSelect(theo form layout)Multi-select source từ TicketSourcesticket_management.createUPDATERender 9 source — slot 9 hiển thị "Pancake CRM"TicketMultipleAdd.tsx:758
SCR-06-BLK-10/crm/tickets drawerForm modalField Khách hàng *, Liên hệ *, Nhóm sản phẩm, ..., Người phụ trách, Ghi chú(form)Form drawer existingticket_management.createKEEPGiữ nguyênTicketMultipleAdd.tsx

B0.1.C) Drawer ticket KH (CustomerTicketManager.tsx) — SCR-06 delta

UI IDMàn / routeSectionBlock / field / actionHành viPermissionDelta StatusHành vi đíchEvidence
SCR-06-BLK-11/customers/:id drawerDrawer phảiList ticket của KH + nút "Thêm ticket"List + drawer mở TicketMultipleAddticket_management.accessKEEPSlot 9 tự render trong drawer source nếu user mở form Tạo TicketCustomerTicketManager.tsx

B0.1.D) Settings sidebar — vị trí gắn menu mới

UI IDMàn / routeSectionBlock / field / actionHành viPermissionDelta StatusHành vi đíchEvidence
SCR-05-BLK-01Sidebar /settingsMục "Tích hợp" hoặc Settings listCác mục SMS template, ZNS template, Notification, Print template...Navigation existingsettings.accessKEEPGiữ nguyênMainSidebar.vue (suy luận)
SCR-05-BLK-02Sidebar /settingsMục "Tích hợp"Mục menu "Tích hợp Pancake CRM"pancake_crm_integration.accessNEWMục menu mới (v-if="hasPermission('pancake_crm_integration','access')") deeplink /settings/pancake-crmFR-012, DEC-016

B0.1.E) SCR-01..04 — 4 tabs build mới (NEW)

UI IDMànSectionBlock / field / actionPermissionDelta StatusHành vi đíchEvidence
SCR-01-*/settings/pancake-crm/connectionTab 1 Kết nốiForm workspace + api_key + webhook URL + kill switch + VIP tagpancake_crm_integration.access/updateNEWToàn bộ build mớiFR-012, DEC-016, FR-015
SCR-02-*/settings/pancake-crm/source-routingTab 2 Map nguồnTable inline-edit + "Đồng bộ Pancake" buttonpancake_crm_integration.access/updateNEWToàn bộ build mớiFR-012, FR-013
SCR-03-*/settings/pancake-crm/auditTab 3 Lịch sử + DLQFilter + table + drawer detail + replay actionpancake_crm_integration.access/view_all/replay_dlqNEWToàn bộ build mớiFR-012, FR-014, FR-016, FR-017
SCR-04-*/settings/pancake-crm/healthTab 4 Sức khỏeMetric cards + outage history + alert configpancake_crm_integration.accessNEWToàn bộ build mớiFR-014, FR-011

B0.2) Từ điển Delta Status

StatusÝ nghĩaEvidence bắt buộc
KEEPGiữ nguyên hành vi + layoutGhi rõ vẫn hiển thị ở target wireframe (B2)
UPDATEĐổi copy / data / option list (không đổi layout)PRD FR + ref file:line
MOVEĐổi vị trí, giữ behaviorGhi trước/sau khu vực nào
NEWThêm mới hoàn toànPRD FR + ref DEC
REMOVEBỏDEC + FR/AC + PO approval (≥2 evidence)
HIDEẨn theo điều kiện (permission/state)Permission + condition rõ

Trong feature này:

  • Không có dòng nào REMOVE (không xóa UI hiện hữu).
  • Tất cả HIDE là gating theo permission pancake_crm_integration.access cho menu Settings/Pancake (Diva pattern: ẩn hoàn toàn, không disable).

B0.3) Tiêu chí hoàn thành B0

  • [x] 100% UI hiện tại của Tickets/TicketMultipleAdd/CustomerTicketManager (3 màn SCR-06 delta đụng) đã inventory ở B0.1 với Evidence file:line
  • [x] Tất cả block existing có Delta Status (KEEP cho 9/11 row, UPDATE cho 3/11 row, NEW cho 6 row mới)
  • [x] Không có REMOVE/HIDE cần evidence PO approval (feature additive only)
  • [x] Target wireframe B2 SCR-06 hiển thị cả vùng KEEP (filter section, table) bao quanh vùng UPDATE (dropdown 9 source)
  • [x] Đường gắn menu mới (B0.1.D) được verify với route Settings existing

B0.4) Ma trận Field × Surface + Validation (11 cột — BẮT BUỘC M+)

Bảng cover toàn bộ field mới xuất hiện trong UI Spec (cross-ref B0.4 dòng X từ B2.4 mỗi SCR). Field thuộc backend-only (vd payload_hash) không nằm trong bảng này.

B0.4.A) Tab 1 Kết nối (SCR-01)

FieldLoạiListDetail/FormExportSearch/FilterRequiredFormatBusiness ruleError copyTooltip ref
workspace_idTextSCR-01 input readonly sau setupyesUUID Pancake (≤64 ký tự)Lock sau setup ban đầu, đổi qua "Reset connection" (DEC-021)"Mã workspace Pancake không hợp lệ. Liên hệ Ops để xác nhận."B9 row 1
api_keyText (sensitive)SCR-01 input password mask ●●●●●●yesAPI key Pancake (16-64 ký tự)Encrypted at rest (KMS); chỉ Admin update; reveal toggle hover icon mắt"API key sai. Kiểm tra trên trang Pancake admin."B9 row 2
webhook_urlURLSCR-01 readonly + copy iconyes (auto-gen)https://diva.com.vn/api/pancake/record/{token}Auto-generate khi save lần đầu, không cho edit(readonly, no error)B9 row 3
webhook_tokenText (sensitive)SCR-01 readonly mask + revealyes (auto-gen)UUID v4Lock sau gen lần đầu; "Reset token" tạo mới + invalidate Pancake config(readonly, hành động Reset yêu cầu confirm)B9 row 4
kill_switchBool (toggle)SCR-01 toggleyesenumDEC-021 mức 1 — app_setting.pancake_integration.enabled(toggle, no error)B9 row 5
vip_tag_namesText multi-lineSCR-01 textareano (optional)mỗi dòng = 1 tag NAME, max 100 dòng, ≤60 ký tự/dòngDEC-008: match case-insensitive với account.pancake_metadata.pancake_tag_names[]; trim whitespace + lowercase khi save"Danh sách tag VIP vượt quá 100 dòng. Vui lòng giảm bớt."B9 row 6
connection_statusEnum 4SCR-01 badge + SCR-04 metricyes (auto)enum {active, paused, suspended_by_pancake, error}DEC-021 mức 2; auto-update từ Cron/health-check(badge, no error)B9 row 7

B0.4.B) Tab 2 Source routing (SCR-02)

FieldLoạiListDetail/FormExportSearch/FilterRequiredFormatBusiness ruleError copyTooltip ref
pancake_source_idText (FK Pancake)cột readonly(audit log)yes (auto từ /sources)TEXT Pancake source idLock — không cho admin sửa, tự đồng bộ qua Cron 3(readonly)B9 row 8
pancake_source_nameTextcột readonly(audit log)text searchyes≤200 ký tựCập nhật qua Cron 3 hourly; nếu Pancake đổi tên → update tự động + log(readonly)B9 row 9
diva_branch_idFK uuidcột inline-editdropdown BranchSelect(audit log)dropdown filterconditional (Loose mode cho phép NULL)UUID branchDEC-013 Loose mode: NULL → ticket tạo với branch=NULL, leader assign sau"Chi nhánh không tồn tại. Vui lòng chọn chi nhánh hợp lệ."B9 row 10
is_activeBool (toggle)cột inline-toggletoggle(audit log)enum filter {Tất cả, Đang bật, Đang tắt}yesenum {true, false}DEC-021 mức 3 per-source; tắt → status skipped_source_disabled(toggle, no error)B9 row 11
last_sync_atDateTimecột readonlynoDD/MM/YYYY HH:mmAuto-update mỗi lần Cron 3 hoặc manual sync(readonly)B9 row 12

B0.4.C) Tab 3 Audit + DLQ (SCR-03)

FieldLoạiListDetail/FormExportSearch/FilterRequiredFormatBusiness ruleError copyTooltip ref
event_idUUIDcột (8 ký tự đầu)drawer detail full(audit log)search exactyes (PK)UUID v4Click row → mở drawer(readonly)B9 row 13
record_idText (FK Pancake)cột truncate 20 ký tựdrawer detail full(audit log)search exactyesTEXT từ PancakeClick → highlight các event cùng record_id"Không tìm thấy record id này."B9 row 14
statusEnum 12cột badgedrawer detail(audit log)multi-select dropdownyesenum 12 trạng thái (xem B9 row 15-26)LIFECYCLE-001 — terminal vs non-terminal khác badge color(no error, badge render)B9 row 15-26
event_typeEnumcộtdrawer(audit log)dropdownyesenum {record, reconciled, polling_injected}DEC-025 reconciliation inject với reconciled(readonly)B9 row 27
is_testBoolcột icon (test tube nếu true)drawer toggle readonly(audit log)checkbox filter "Bao gồm test events"no (default false)enum {true, false}DEC-009 test events KHÔNG count KPI, auto-cleanup 24h(readonly)B9 row 28
retry_countIntcột sốdrawer(audit log)range filterno (default 0)INT 0-3retry_count >=3 → status permanently_failed(readonly)B9 row 29
created_atDateTimecộtdrawer(audit log)date range pickeryes (auto)DD/MM/YYYY HH:mm:ssMặc định filter "7 ngày gần nhất"(readonly)B9 row 30
processed_atDateTime nullablecột (— nếu NULL)drawer(audit log)date range filternoDD/MM/YYYY HH:mm:ss hoặc NULL nếu chưa process (ingested/received/processing)(readonly)B9 row 31
error_messageText nullablecột truncatedrawer full text(audit log)text searchnoTEXT (max ~2KB hiển thị)Chỉ có khi dead_letter/permanently_failed(readonly)B9 row 32
raw_payloadJSON(không cột list)drawer JSON viewer formatted(audit log)yes (auto)JSON object indent 2"Sao chép JSON" button + collapse nested(readonly)B9 row 33
raw_headersJSON(không cột list)drawer JSON viewer(audit log)yes (auto)JSON object(readonly)(readonly)B9 row 34
source_ipINET(không list)drawer(audit log)yes (auto)IPv4/IPv6Resolve qua X-Forwarded-For(readonly)B9 row 35
latency_p95 (derived)Calculated (s)SCR-04 metric cardN/A12,5s (1 dec)FORMULA-001 PRD A10(calculated)B9 row 36

B0.4.D) Tab 3 Filter bar + Replay action

FieldLoạiListDetail/FormExportSearch/FilterRequiredFormatBusiness ruleError copyTooltip ref
filter_date_rangeDate rangeSCR-03 picker (từ — đến)(chính nó)yesDD/MM/YYYY half-openMặc định 7 ngày gần nhất; max range 90 ngày (G11.1)"Khoảng thời gian tối đa 90 ngày. Vui lòng thu hẹp."B9 row 37
filter_statusEnum 12SCR-03 multi-select(chính nó)noenum 12Default "Tất cả"(no error)B9 row 15-26
filter_sourceFKSCR-03 multi-select sources(chính nó)noUUID listDefault "Tất cả"(no error)B9 row 9
filter_is_testBoolSCR-03 checkbox "Bao gồm test events"(chính nó)noenum {bao gồm, ẩn}Default "ẩn" (DEC-009 không pollute)(no error)B9 row 28
replay_actionActionSCR-03 button row hoặc bulkconditional (chỉ status dead_letter hoặc auth_failed)DEC-015 layer 4 + DEC-021; bulk rate limit 5/s, batch ≤100"Không thể replay event với trạng thái {status}. Chỉ áp dụng cho 'Lỗi DLQ' hoặc 'Lỗi xác thực'."B9 row 38

B0.4.E) Tab 4 Health (SCR-04)

FieldLoạiListDetail/FormExportSearch/FilterRequiredFormatBusiness ruleError copyTooltip ref
event_success_24hCalculated (%)metric card 24hN/A99,950% (3 dec)FORMULA-004 PRD A10(calculated)B9 row 39
event_success_7dCalculated (%)metric card 7dN/A99,9XX% (3 dec)FORMULA-004 7-day window(calculated)B9 row 39
latency_p95Calculated (s)metric cardN/A12,5s (1 dec)FORMULA-001(calculated)B9 row 36
outage_stateEnum 3banner + cardyes (auto)enum {healthy, outage_started, outage_recovered}LIFECYCLE-002(badge)B9 row 40-42
last_event_received_atDateTimemetric cardyes (auto)DD/MM/YYYY HH:mm:ss (ago N phút)NULL nếu chưa nhận event nào"Chưa có event nào nhận được"B9 row 43
outage_historyListtable rows (10 gần nhất)drill-down detail(audit)date rangenorow {started_at, ended_at, duration_min}LIFECYCLE-002 transitions(readonly)B9 row 44
current_polling_intervalInt (s)textyes (auto)60-900sCron 6 adaptive; start 60s, max 900s(readonly)B9 row 45
auto_assign_correct_rateCalculated (%)metric cardN/A92,00% (2 dec)FORMULA-003 PRD A10(calculated)B9 row 46

Quy tắc B0.4: mọi cell có giá trị hoặc /N/A; tất cả error copy tiếng Việt; tất cả tooltip ref trỏ đến B9 row tương ứng.

B0.5) Ma trận State × Screen (≥6 state — BẮT BUỘC)

SCR + VariantLoadingHas-dataEmpty-no-dataEmpty-filterErrorNo-permission
SCR-01 Connection (Admin)Skeleton form 5 row + spinner button "Kiểm tra kết nối"Form filled với workspace_id + api_key mask + URL copy + toggle + tag listForm rỗng + helper text "Nhập workspace + api_key để bắt đầu thiết lập" + CTA disabled "Kiểm tra kết nối" cho tới khi điềnN/A vì không có filterBanner đỏ "Không thể lưu cấu hình. Mã sự cố: {trace_id}. Vui lòng báo Ops." + nút Thử lạiN/A — toàn route ẩn (xem hàng cuối)
SCR-01 (Manager / Telesale / POS)N/A vì màn không hiển thịN/AN/AN/AN/AHidden completely — menu Sidebar Settings/Pancake không render; gõ URL → redirect /403 (toast "Bạn không có quyền truy cập Tích hợp Pancake CRM")
SCR-02 Source routing (Admin)Skeleton table 5 row + button "Đồng bộ" loading stateTable với 40 source (FB Diva HCM Q1, Zalo OA, ...) + inline branch dropdown + toggle"Chưa có nguồn nào. Click 'Đồng bộ từ Pancake' để bắt đầu." + CTA primary "Đồng bộ từ Pancake" + helper "Yêu cầu Tab 1 đã thiết lập kết nối thành công""Không có nguồn nào phù hợp với bộ lọc. Xóa bộ lọc?" + CTA "Xóa bộ lọc"Banner "Không thể tải danh sách nguồn. Hiển thị bản cache từ {timestamp}." + Thử lạiHidden completely
SCR-02 (Manager / Telesale)N/AN/AN/AN/AN/AHidden — tab không hiển thị
SCR-03 Audit + DLQ (Admin)Skeleton table 10 row + skeleton drawer rỗngTable với 100 event/page + pagination + drawer detail khi click row"Chưa nhận event nào trong khoảng {date_range}." + CTA "Mở rộng khoảng thời gian""Không có event nào khớp bộ lọc. {N} event tổng trong khoảng." + CTA "Xóa bộ lọc""Không thể tải audit log. {error}. Mã sự cố: {trace_id}." + Thử lạiHidden completely
SCR-03 (Bulk replay)Progress bar "Đang phát lại {X}/{N} event..." + spinner per rowSau xong: banner xanh "Đã phát lại {N} event thành công. {Y} event lỗi: [Xem chi tiết]"N/AN/A"Tỉ lệ phát lại lỗi cao bất thường ({Y}/{N}). Liên hệ Tech Lead." + button hủy bulkButton replay hidden nếu thiếu replay_dlq
SCR-04 Health (Admin)Skeleton cards (4 ô) + spinner chartMetric cards filled + chart trend + outage history table"Chưa có dữ liệu trong khoảng {timeframe}."N/A vì không có filter (chỉ timeframe toggle 24h/7d)"Không thể tải metric. Mã sự cố: {trace_id}." + Thử lạiHidden completely
SCR-04 (Trạng thái outage)(vẫn render cards với data cuối cùng + banner)Banner đỏ "Pancake outage {duration_min} phút. Đã chuyển sang adaptive polling."N/AN/A(banner đỏ luôn)Hidden completely
SCR-05 Container (Admin)Skeleton tab strip + spinner contentXDetailLayout 4 tabs + RouterView active tab(delegate to each tab)(delegate)(delegate)Hidden completely (route guard redirect /403)
SCR-06 Dropdown delta (mọi role có ticket_management.access)Loading dropdown (skeleton option)Dropdown 9 source: "Nguồn 1, ..., Nguồn 8, Pancake CRM" + hover tooltipN/A (luôn có 9 option)(filter dropdown empty result) "Không có nguồn nào khớp""Không tải được danh sách nguồn. Thử lại."Dropdown hidden hoàn toàn cho user không có ticket_management.access

6 state canonical đã cover toàn bộ. Mọi cell có giá trị (không có cell trống). Non-applicable đã ghi N/A vì <lý do>.

B0.6) Wireframe Quality Contract

Quy tắcTuân thủ
Số cột bảng ASCIIHeader và data row cùng số , không lệch quá 1 ký tự
Stepper labels(N/A — feature không có wizard nhiều bước)
Dữ liệu mẫuDùng tên KH VN ("Nguyễn Thị Mai", "Trần Văn Hùng"), SĐT bắt đầu 09xxx/03xxx, branch name realistic ("Diva HCM Q1", "Diva Hà Nội Cầu Giấy", "Diva Đà Nẵng"), source name Pancake realistic ("FB Diva HCM Q1", "Zalo OA Diva Premium", "FB Bệnh viện Da liễu Diva", "Google Ads Diva Brand")
Không TBD / LoremToàn bộ wireframe có copy thực tế
Không hard-code styleKHÔNG dùng cam đỏ, font 12px, emoji trong copy; dùng intent token warning/success/muted/negative-value/primary emphasis
Buttons labelMọi button trong wireframe có dòng tương ứng ở B7 từ điển copy
TruncationCell dài → ghi cuối hoặc xuống dòng có
Mobile previewDiva admin desktop-first; SCR-01..04 desktop only — không cần mobile wireframe riêng (B8)

B0.7) Cặp Code ↔ Display VI (Bilingual Pairing)

Áp dụng cho feature này vì có ≥3 enum (lifecycle 12 states + outage 3 states + connection_status 4 + event_type 3).

CodeDisplay VI canonicalPhân biệt vớiDùng ở section
ingested"Đã nhận (chưa xác thực)""Đã xác thực" (received)B6 lifecycle, B2.3 wireframe SCR-03 badge
received"Đã xác thực""Đã xử lý" (processed), "Đã nhận" (ingested)B6 lifecycle
processing"Đang xử lý""Đã xử lý" (processed)B6 lifecycle
processed"Đã xử lý""Đang xử lý" (processing)B6 lifecycle
auth_failed"Lỗi xác thực""Lỗi IP" (ip_blocked), "Lỗi cấu hình" (parse_error)B6 lifecycle
ip_blocked"Bị chặn IP""Lỗi xác thực" (auth_failed)B6 lifecycle
parse_error"Lỗi cấu trúc dữ liệu""Lỗi xác thực" (auth_failed)B6 lifecycle
skipped_duplicate"Bỏ qua (trùng lặp)""Bỏ qua (đã tắt nguồn)" (skipped_source_disabled)B6 lifecycle
skipped_source_disabled"Bỏ qua (nguồn đã tắt)""Bỏ qua (đã tắt hệ thống)" (skipped_kill_switch)B6 lifecycle
skipped_kill_switch"Bỏ qua (đã tắt hệ thống)""Bỏ qua (nguồn đã tắt)"B6 lifecycle
skipped_opt_out"Bỏ qua (khách từ chối marketing)"(no near duplicate)B6 lifecycle
dead_letter"Lỗi DLQ (chờ phát lại)""Lỗi vĩnh viễn" (permanently_failed)B6 lifecycle
permanently_failed"Lỗi vĩnh viễn (cần xem xét)""Lỗi DLQ (chờ phát lại)"B6 lifecycle
healthy"Khỏe mạnh""Đang phục hồi" (outage_recovered)B6 outage
outage_started"Mất kết nối""Khỏe mạnh" (healthy)B6 outage
outage_recovered"Đang phục hồi""Khỏe mạnh" (healthy)B6 outage
active"Đang hoạt động" (connection)"Tạm dừng" (paused)B6 connection_status
paused"Tạm dừng""Đang hoạt động" (active), "Bị Pancake khóa" (suspended_by_pancake)B6 connection_status
suspended_by_pancake"Bị Pancake khóa""Tạm dừng" (paused, do admin)B6 connection_status
error"Lỗi" (connection)(chỉ context kết nối)B6 connection_status
record"Sự kiện record""Đối soát" (reconciled), "Polling" (polling_injected)B6 event_type
reconciled"Đối soát (Cron 7)""Polling (Cron 6)" (polling_injected)B6 event_type
polling_injected"Polling (Cron 6)""Đối soát (Cron 7)"B6 event_type

Quy tắc: trong văn xuôi UI Spec dùng Đã xác thực (received) lần đầu, sau đó chỉ Đã xác thực. Trong Mermaid stateDiagram giữ code gốc (ingested → received). Trong dropdown filter SCR-03 hiển thị display VI; tooltip hover hiển thị code backtick để dev đối chiếu.

B0.8) Đối soát schema (Schema Cross-Check)

Field UI cho phép NULL / blankCột schemaNOT NULL?Cách UI xử lý
diva_branch_id ở SCR-02 (Loose mode)pancake_source_routing.diva_branch_idNULL allowed (DEC-013 Loose mode)UI cho phép dropdown rỗng + tooltip "Để trống = Loose mode, ticket sẽ tạo với chi nhánh NULL, leader assign sau"
assignee_id của ticket Pancaketicket.assignee_idNULL allowed(không thuộc UI Spec này — ticket auto-create từ webhook, render trong CRM existing)
pancake_connection.vip_tag_names rỗngpancake_connection.vip_tag_names text[] DEFAULT '{}'NULL allowed (default empty)UI textarea cho phép trống; nếu trống → không match VIP tag (DEC-008)
error_message ở Tab 3 drawerpancake_webhook_event.error_messageNULL allowed (chỉ có khi status dead_letter/permanently_failed)Drawer hiển thị nếu NULL
processed_at ở Tab 3pancake_webhook_event.processed_atNULL allowed (chỉ có khi status processed)Cột hiển thị nếu NULL; KHÔNG hiển thị 00:00:00

Lưu ý: không có field UI cho phép trống mà schema yêu cầu NOT NULL trong feature này. Tất cả trường required đã validate ở B0.4 cột Required.

B0.9) Bảng kiểm kê tương tác (Interactive Element Inventory)

BẮT BUỘC M+ — liệt kê 100% interactive elements per SCR. Cross-ref từ B2.7E.

SCR-01 Connection

SCRElementLoại tương tácBehavior summaryRef ruleState coverage
SCR-01Field api_key input passwordInline editClick → reveal/edit; mask ●●●●●● mặc định; hover icon mắt → reveal 5s rồi tự maskB2.7E rule 6default+focused+revealed+error
SCR-01Icon mắt reveal/hide passwordSecondary CTAToggle reveal API key; aria-label "Hiện/Ẩn API key"; reveal kèm banner "Đang hiển thị API key — không chia sẻ màn hình"B2.7default+revealed+hover
SCR-01Button "Kiểm tra kết nối"Primary CTACall Pancake /sources REST → hiển thị list sources detect hoặc errorB2.7default+loading+success+error+disabled (nếu workspace/api_key chưa điền)
SCR-01Icon copy URL webhookSecondary CTAClick → copy URL vào clipboard + toast "Đã sao chép URL webhook"; aria-label "Sao chép URL webhook"B2.7default+hover+success-flash
SCR-01Icon copy token webhookSecondary CTAClick → copy token + toast; mask ●●●●●●●● mặc định, reveal toggle riêngB2.7default+revealed+hover
SCR-01Button "Tạo lại token"Destructive CTAConfirm modal 2 bước "Token mới sẽ vô hiệu hóa cấu hình Pancake hiện tại. Gõ 'TẠO LẠI' để xác nhận."B2.7 + B2.10default+confirm-modal+loading+success
SCR-01Toggle kill switchSecondary CTAToggle on/off → confirm modal "Tắt sẽ ngừng nhận lead từ Pancake (mọi event sẽ chuyển trạng thái 'Bỏ qua (đã tắt hệ thống)'). Xác nhận?"B2.7 + B2.10default+confirm+loading+success
SCR-01Textarea VIP tag namesInline editMulti-line input; debounce 500ms; char counter {X}/100 dòng; trim+lowercase khi saveB2.7E rule 6default+focused+over-limit-warning+error
SCR-01Button "Lưu cấu hình"Primary CTAExplicit save toàn form; disabled nếu chưa thay đổi (form pristine)B2.7default+loading+success+disabled-pristine+error
SCR-01Button "Kích hoạt lại webhook" (chỉ hiện khi status=suspended_by_pancake)Primary CTAReset status → active; gọi Pancake re-enable; alert toast khi xongB2.7hidden-by-default+visible-on-suspended+loading+success+error

SCR-02 Source routing

SCRElementLoại tương tácBehavior summaryRef ruleState coverage
SCR-02Button "Đồng bộ từ Pancake"Primary CTAManual call Cron 3 logic; loading spinner; toast "Đã đồng bộ {N} nguồn ({M} nguồn mới)"B2.7default+loading+success+error+disabled (nếu connection chưa active)
SCR-02Click row trong tableDrill-downClick → drawer phải mở chi tiết source (lịch sử event 7d, KPI, raw metadata Pancake)B2.7E rule 1default+hover+selected+disabled-deleted
SCR-02Inline edit cột Diva branch (dropdown)Inline editClick cell → BranchSelect dropdown; autosave debounce 500ms; toast "Đã cập nhật mapping cho '{source_name}'"B2.7E rule 6default+editing+saving+saved+error-conflict
SCR-02Inline toggle cột is_activeInline editToggle ngay → autosave; toast confirm; nếu off thì warn "Tắt nguồn sẽ ngừng nhận event từ '{source_name}'" cho khả năng undo 10sB2.7E rule 6 + B2.14default+toggling+saved+undo-pending
SCR-02Text search "Tìm nguồn theo tên"SearchDebounce 300ms client-side filter; min 2 ký tự; clear icon ×B2.13default+typing+filtered+cleared
SCR-02Filter dropdown Trạng tháiFilterenum {Tất cả, Đang bật, Đang tắt}; reset trigger filter cho tableB2.5default+open+selected
SCR-02Bulk select checkbox headerBulk action (defer MVP-2)Hidden MVP-1 (DEC-022); placeholder cho MVP-2 bulk import CSV(N/A MVP-1)hidden-MVP-1

SCR-03 Audit + DLQ

SCRElementLoại tương tácBehavior summaryRef ruleState coverage
SCR-03Date range picker filterFilterDefault "7 ngày gần nhất"; max 90 ngày; auto-apply on changeB2.5default+open+applied+error-range-too-wide
SCR-03Multi-select status dropdownFilter12 status; default "Tất cả"; auto-applyB2.5default+open+selected+cleared
SCR-03Multi-select source dropdownFilterSources mapped; default "Tất cả"; auto-applyB2.5default+open+selected
SCR-03Checkbox "Bao gồm test events"FilterDefault unchecked (DEC-009); auto-applyB2.5default+checked+unchecked
SCR-03Button "Xóa bộ lọc"Secondary CTAReset toàn bộ filter về defaultB2.7default+hover+disabled (khi filter pristine)
SCR-03Click row eventDrill-downMở drawer phải chi tiết event (raw_payload, raw_headers, error_message, lifecycle trace)B2.7E rule 1default+hover+selected+drawer-open
SCR-03Hover row → action icons (replay, copy json)Hover stateOpacity 0 → 1 transition 200ms; action chỉ enable cho status dead_letter/auth_failed/parse_error (replay) hoặc luôn enable (copy)B2.7E rule 3default+hover+actions-visible+keyboard-focus
SCR-03Button "Phát lại sự kiện" (single row)Primary CTAConfirm modal "Phát lại event {event_id}? Status sẽ chuyển '{current_status}' → 'Đã xác thực'."B2.7 + B2.10default+confirm+loading+success+error
SCR-03Checkbox bulk select per rowBulk actionChỉ enable cho status replayable (dead_letter, auth_failed); selected count sticky barB2.14 + EXT-13default+selected+disabled-non-replayable
SCR-03Bulk action bar "Phát lại {N} event"Bulk CTAConfirm "Phát lại {N} event? Sẽ chạy với rate 5 event/giây (~{time} giây)."B2.14 + EXT-13default+confirm+progress+success+partial-fail
SCR-03 drawerButton "Sao chép JSON payload"Secondary CTAClick → copy raw_payload JSON formatted vào clipboard + toastB2.7default+hover+success-flash
SCR-03 drawerTab strip "Payload / Headers / Lịch sử trạng thái"Tab switchLazy load mỗi tab; URL hash ?tab=payload/headers/historyB2.7E rule 7default+active+loading-per-tab
SCR-03 drawerButton "Xem các event cùng record_id"Drill-downSet filter record_id=current + close drawerB2.7E rule 1default+hover
SCR-03PaginationLoad moreDefault 100 event/page; cho phép 50/100/200; total countB2.7E rule 5default+loading+exhausted

SCR-04 Health

SCRElementLoại tương tácBehavior summaryRef ruleState coverage
SCR-04Toggle timeframe 24h / 7dTab switchRe-query metrics; aria-selected; URL hash ?timeframe=24h|7dB2.7E rule 7default+active+loading
SCR-04Hover icon ⓘ cho mỗi metricHover stateTooltip B9 3-part: định nghĩa + công thức + ví dụB2.7E rule 8default+hover-show
SCR-04Click metric cardDrill-downClick "Tỉ lệ thành công" → mở SCR-03 với filter status non-successB2.7E rule 1default+hover+pointer
SCR-04Banner outage (khi outage_started)NotificationBanner đỏ sticky top + button "Xem chi tiết" + button "Tạm tắt cảnh báo (1h)"B2.7 + B-Voicealways-visible-during-outage+hover-dismiss
SCR-04Outage history table row clickDrill-downDrawer detail outage event với timeline (started/recovered) + count missing events injectedB2.7E rule 1default+hover+selected
SCR-04Button "Tải báo cáo PDF" (defer MVP-2)Secondary CTA (hidden MVP-1)placeholder(N/A MVP-1)hidden-MVP-1

SCR-05 Container

SCRElementLoại tương tácBehavior summaryRef ruleState coverage
SCR-05Tab strip 4 tabsTab switchClick → router-view child render; URL /settings/pancake-crm/{connection|source-routing|audit|health}B2.7E rule 7default+active+keyboard-arrow-nav

SCR-06 Dropdown delta (3 màn existing)

SCRElementLoại tương tácBehavior summaryRef ruleState coverage
SCR-06Dropdown Nguồn ticket (Tickets list filter)FilterRender 9 option; option mới "Pancake CRM" với tooltip mô tả; multi-selectB2.5default+open+selected+cleared
SCR-06Dropdown Nguồn (Form Tạo Ticket)Inline editRender 9 option trong form drawerB2.7E rule 6default+open+selected
SCR-06Dropdown drawer KH (CustomerTicketManager)Inline editRender 9 option khi mở form Tạo Ticket từ drawer KHB2.7E rule 6default+open+selected
SCR-06Hover icon ⓘ tooltip "Pancake CRM"Hover stateHover → popup "Lead từ Pancake CRM (40+ kênh: FB, Zalo, TikTok, Google Ads, Whatsapp, Shopee...)"B2.7E rule 8default+hover-show

Tổng kết B0.9: 50+ interactive elements được liệt kê đầy đủ. 10 loại tương tác canonical đều được dùng. Mọi element có ref B2.7/B2.7E rule và state coverage.


B1) Bản đồ màn hình và hành trình

B1.1) Danh sách màn

SCRTênRouteLoạiMô tả
SCR-01Kết nối Pancake/settings/pancake-crm/connection🆕 Build mớiForm workspace + api_key + webhook URL/token + kill switch + VIP tag
SCR-02Map nguồn → chi nhánh/settings/pancake-crm/source-routing🆕 Build mớiTable source ↔ Diva branch + is_active toggle
SCR-03Lịch sử event + DLQ/settings/pancake-crm/audit🆕 Build mớiAudit log + filter + drawer detail + replay action
SCR-04Sức khỏe & cảnh báo/settings/pancake-crm/health🆕 Build mớiMetrics 24h/7d + outage history + chart trend
SCR-05Container Tích hợp Pancake CRM/settings/pancake-crm (default → /connection)✅ Reuse XDetailLayoutTabs container + RouterView
SCR-06Dropdown ticket source (3 màn existing)/crm/tickets, /crm/tickets drawer, /customers/:id drawer🔧 Extend (FE delta)9 source thay vì 8 — auto render từ TicketSources array sau 3 file delta

B1.2) Hành trình end-to-end

B1.3) Hành trình theo vai trò

Vai tròEntry pointĐường mànKết quả đầu ra
Admin (setup lần đầu)Sidebar Settings → "Tích hợp Pancake CRM"SCR-05 → SCR-01 (nhập config + Kiểm tra kết nối) → SCR-02 (review + map branch) → SCR-04 (verify health)Connection active, ≥1 source mapped, webhook URL configured ở Pancake admin
Admin (vận hành hàng ngày)Sidebar Settings → "Tích hợp Pancake CRM"SCR-05 → SCR-04 (xem health metric) → SCR-03 (nếu có dead_letter event → replay)Event success ≥99,5%, zero DLQ tồn đọng
Admin (xử lý sự cố outage)Notification push "Pancake outage X phút" → click → SCR-04SCR-05 → SCR-04 (banner đỏ + detail) → (đợi auto-recovery hoặc gọi Pancake support)Outage recovered, MTTR ≤5 phút
Admin (thay đổi mapping)Sidebar Settings → "Tích hợp Pancake CRM"SCR-05 → SCR-02 (inline edit branch dropdown 1 row)Source mới được route sang branch khác
TelesaleNotification push "Bạn vừa nhận lead Pancake CRM: {KH}"/crm/tickets (existing) → filter source = "Pancake CRM" (SCR-06) → click ticket → drawer detailPhản hồi khách trong 30 phút
Manager/crm/tickets → filter chi nhánh + source = "Pancake CRM" (SCR-06)(existing flow) → leader assign manual cho ticket branch=NULLBranch coverage ≥90%
Non-admin (Manager/Telesale/POS)Gõ URL /settings/pancake-crm trực tiếpRedirect /403 + toastKhông truy cập Settings/Pancake

B1.4) Phạm vi thay đổi tối thiểu (SCR-06 delta on existing)

Màn hiện cóBằng chứngThay đổiVị trí updateVì sao đủ
Tickets.tsx filter dropdownTickets.tsx:851-865 TicketSources.map(...)Render slot 9 tự độngKhông cần code change ngoài types.ts + i18n/vi.ts + sourceDescriptions (line 128-149)Dropdown auto-render từ array; tooltip cần add entry
TicketMultipleAdd.tsx:758Dropdown loop từ TicketSourcesRender slot 9 tự độngKhông cần code changeAuto từ types.ts
CustomerTicketManager.tsxreuse patternRender slot 9 tự độngKhông cần code changeAuto
Module report (customer-service, telesales)TBD-verifyCó thể cần delta nếu hardcode slot 1..8Phase 4 verify (RSK-005)Out-of-band grep audit

B2) Inventory màn — Canonical Interaction Rules

B2.7E) Quy ước tương tác phụ — Canonical Rules (Secondary Interactions Contract)

Canonical 8 rules — mọi B2.SCR-XX.7E per-SCR đều ref đến rule trong section này. B0.9 Interactive Inventory cũng ref B2.7E rule N.

#Loại tương tácĐịnh nghĩa canonicalBehavior chuẩnÁp dụng SCR
Rule 1Drill-down (row click)Click row trong table mở drawer detail bên phải overlay (40% viewport)(1) Single-click row → mở drawer. (2) ESC + click backdrop → đóng drawer. (3) Drawer read-only → không có draft state. (4) URL update với query param ?detail={id} cho deeplink. (5) Focus trap trong drawer (a11y).SCR-02 (source detail), SCR-03 (event detail)
Rule 2Expand-CollapseMở rộng/thu gọn row trong table hoặc card(1) Click chevron / toggle. (2) Animation 200ms ease-out. (3) State lưu localStorage để remember across reload. (4) Keyboard: Enter/Space toggle khi focus chevron.SCR-04 (outage history detail), SCR-03 (group by record_id)
Rule 3Hover-actionAction icon ẩn → hiện khi hover row(1) Opacity 0 → 1 transition 200ms. (2) Keyboard focus = hover (a11y). (3) Action chỉ enable theo condition (vd "Phát lại" chỉ enable cho status dead_letter/auth_failed/parse_error). (4) Trên touch device (tablet) → luôn visible.SCR-02 (row actions), SCR-03 (replay + copy_json hover)
Rule 4Context menuRight-click row hoặc click icon mở menu(1) Position auto-flip nếu sát viewport edge. (2) ESC + click outside → đóng. (3) Keyboard: Arrow + Enter navigate. (4) Items có separator nếu nhóm. (5) MVP-1 không dùng (preferred: visible buttons) — defer M2.(defer M2)
Rule 5Load more / PaginationMở rộng kết quả khi danh sách dài(1) Server-side pagination 20 row/page (mặc định). (2) Page size selector [10, 20, 50, 100]. (3) Total count hiển thị "Hiển thị {X}-{Y} / {Total}". (4) Keyboard: PgUp/PgDn navigate page. (5) URL update ?page=N&size=M. (6) Loading skeleton trên page change.SCR-02 (sources pagination), SCR-03 (events pagination)
Rule 6Inline editEdit field tại chỗ không cần modal(1) Click cell → field input render in-place. (2) Autosave debounce 500ms (cho dropdown), 1s (cho text input). (3) Save indicator inline: spinner small → checkmark 2s. (4) ESC → revert. (5) Tab/Enter → save + move next. (6) Concurrency: optimistic UI, conflict warning nếu server reject (LWW hoặc version-based). (7) Permission check ở client trước, server enforce.SCR-01 (api_key, vip_tag_names textarea), SCR-02 (branch dropdown + is_active toggle)
Rule 7Tab switchChuyển tab trong tab-strip(1) Lazy load content per tab (chỉ render tab active). (2) URL hash #tab=name để deeplink. (3) Tab indicator active highlight + aria-current. (4) Keyboard: Arrow Left/Right navigate, Tab key vào content. (5) Unsaved changes warning nếu user chuyển tab (link Pinia draft state).SCR-05 (4 tab Settings), SCR-03 drawer (Payload/Headers/Lịch sử trạng thái tabs)
Rule 8Overlay (modal/drawer/popover)Layer trên content chính(1) Modal (center, blocking): confirm, form mới — ESC + click backdrop close (trừ destructive action). (2) Drawer (side panel 40% viewport, non-blocking edit): detail view, edit form — ESC + click backdrop close. (3) Popover (anchor, dismissible): tooltip, dropdown menu — click outside close. (4) Focus trap trong layer. (5) Stack max 2 (modal trong drawer OK, drawer trong modal KHÔNG). (6) z-index canonical: modal=2000, drawer=1500, popover=1000.SCR-01 (confirm modal kill switch), SCR-02 (drawer source detail), SCR-03 (drawer event detail + replay confirm modal), SCR-04 (outage detail drawer)

Quy tắc áp dụng:

  • Mỗi B0.9 interactive element có cột Ref rule → trỏ đến B2.7E rule N trong section này (không phải per-SCR section)
  • Per-SCR section B2.SCR-XX.7E chỉ liệt kê các tương tác SPECIFIC cho SCR đó, không repeat full definition
  • Conflict giữa rule canonical (B2.7E) vs per-SCR → ưu tiên per-SCR (vì có context cụ thể)

B2.SCR-01) Kết nối Pancake

B2.SCR-01.1) Ngữ cảnh nghiệp vụ

Câu hỏiQuyết định
Ai dùng?Admin role (system-wide); module pancake_crm_integration.access/update
Vào màn để quyết định gì?(1) Thiết lập lần đầu workspace_id + api_key Pancake. (2) Lấy URL + token webhook để paste vào Pancake admin. (3) Bật/tắt kill switch. (4) Quản lý danh sách VIP tag NAME. (5) Re-enable webhook khi bị Pancake suspend.
Dữ liệu chínhpancake_connection row duy nhất (single workspace MVP-1) + app_setting.pancake_integration.enabled
CTA chính / phụPrimary: Lưu cấu hình (explicit save toàn form). Secondary: Kiểm tra kết nối, Sao chép URL, Sao chép token, Tạo lại token, Kích hoạt lại webhook (conditional)
Điều không được hiểu nhầm"Kiểm tra kết nối" = test REST API call → KHÔNG có nghĩa "webhook đã active". "Tạo lại token" = invalidate cấu hình Pancake hiện tại, ops phải update Pancake admin sau. "Kill switch" = mức 1 (tổng) — tắt KHÔNG xóa cấu hình. "VIP tag" = NAME case-insensitive, KHÔNG phải tag ID.

B2.SCR-01.1A) Nguyên tắc UX bắt buộc

Nguyên tắcQuyết định
Mục tiêu user trong 5 giâyAdmin biết: connection có đang active không, URL webhook là gì, có kill switch đang bật/tắt
Thứ tự ưu tiên thông tin(1) Connection status badge top-right, (2) Workspace + api_key (config tổng), (3) Webhook URL + token (cần copy sang Pancake), (4) Kill switch toggle (vận hành), (5) VIP tag list (nghiệp vụ)
Dữ liệu nhạy cảmapi_key + webhook_token mask ●●●●●●●● mặc định; reveal toggle hover icon mắt + banner cảnh báo "Đang hiển thị — không chia sẻ màn hình"; field KHÔNG có quyền update (Manager nếu future) → readonly với value masked qua API (không leak qua DOM)
Pending/partial dataLần đầu vào: tất cả field rỗng, Kiểm tra kết nối disabled; status error (DB chưa có row). Sau "Lưu cấu hình" lần đầu → status active
Thuật ngữ không được đổi"Workspace ID" (giữ EN vì Pancake terminology), "API key" (giữ), "Webhook URL", "Webhook token", "Kill switch" → đặt label tiếng Việt "Tắt khẩn cấp toàn hệ thống", "VIP tag (theo tên)" (giải thích "tên" để phân biệt tag ID)
Thiết kế thị giácDùng intent primary emphasis cho "Lưu cấu hình"; negative-value cho "Tạo lại token" (destructive); success badge cho active; warning cho paused; negative cho suspended_by_pancake/error
Không được tự suy diễnKHÔNG thêm action "Xóa connection" (xóa qua DB migration only, không qua UI MVP-1). KHÔNG ẩn field api_key cho Admin (RBAC field-level reject).

B2.SCR-01.2) Ma trận variant

Variant IDĐiều kiện kích hoạtAi thấyWireframeField/block không renderQA ref
Variant ADefault — connection.status='active'AdminCó (B2.SCR-01.3.A)Banner "Kích hoạt lại webhook" ẩnTC-UI-SCR01-001
Variant BFirst-time setup — chưa có row pancake_connectionAdminCó (B2.SCR-01.3.B)Field webhook_url + webhook_token mask (chưa gen); badge status ẩnTC-UI-SCR01-002
Variant CNo permissionManager/Telesale/POSHidden completely — menu Sidebar không render; URL gõ trực tiếp → /403Toàn bộ mànTC-PERM-SCR01-001
Variant DLoadingAdminCó (B2.SCR-01.3.D) — skeleton form 5 rowN/ATC-UI-SCR01-003
Variant EError saveAdminCó (B2.SCR-01.3.E) — banner đỏ "Không thể lưu. Mã: {trace_id}"N/ATC-UI-SCR01-004
Variant FSuspended — connection.status='suspended_by_pancake'AdminCó (B2.SCR-01.3.F) — banner đỏ + button "Kích hoạt lại webhook" hiển thịToggle kill switch disabled (vì đã suspended bên Pancake)TC-UI-SCR01-005

B2.SCR-01.3) Wireframes

Variant A — Default (active)

text
/settings/pancake-crm/connection

┌──────────────────────────────────────────────────────────────────────────┐
│  Tích hợp Pancake CRM                            [Trạng thái: Đang hoạt động ✓] │
│  ────────────────────────────────────────────────────────────────────── │
│  [Tab 1 Kết nối*] [Tab 2 Map nguồn] [Tab 3 Lịch sử + DLQ] [Tab 4 Sức khỏe]    │
│  ────────────────────────────────────────────────────────────────────── │
│                                                                          │
│  Thông tin workspace Pancake                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ Workspace ID *           [ws_diva_2026_a8c3d4f5________________]    │ │
│  │ API key *      [●●●●●●●●●●●●●●●●●●●●] [👁 Hiện 5s]                │ │
│  │ Giấy phép Pancake (PD-001)            Trust IP + URL token (W1-W3)   │ │
│  │                                                                     │ │
│  │            [Kiểm tra kết nối]  ⓘ Test API GET /sources              │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  Cấu hình webhook (paste vào Pancake admin)                              │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ URL webhook   https://diva.com.vn/api/pancake/record/abc123token   │ │
│  │              [📋 Sao chép URL]                                       │ │
│  │ Token webhook [●●●●●●●●●●●●●●●●] [👁 Hiện] [📋 Sao chép] [↻ Tạo lại] │ │
│  │ ⓘ Token xác thực request từ Pancake. Tạo lại sẽ vô hiệu hóa         │ │
│  │   cấu hình Pancake hiện tại.                                         │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  Tắt khẩn cấp toàn hệ thống (kill switch)                                │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ Đang nhận event Pancake     [●━━ Bật]                                │ │
│  │ ⓘ Tắt sẽ chuyển mọi event sang trạng thái "Bỏ qua (đã tắt hệ thống)".│ │
│  │   Pancake không bị suspend (vẫn trả 200).                            │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  Danh sách tag VIP (theo tên, không phân biệt hoa thường)               │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ ⓘ Mỗi dòng = 1 tag. Khi Pancake gửi event chứa tag trùng tên,       │ │
│  │   hệ thống coi khách là VIP và luôn tạo ticket dù chỉ update info.   │ │
│  │ ┌───────────────────────────────────────────────────────────────┐ │ │
│  │ │ VIP                                                            │ │ │
│  │ │ Hot Lead                                                       │ │ │
│  │ │ Khách lớn                                                      │ │ │
│  │ │ Khách quay lại                                                 │ │ │
│  │ │ Premium Member                                                 │ │ │
│  │ │                                                                │ │ │
│  │ └───────────────────────────────────────────────────────────────┘ │ │
│  │                                                       5 / 100 dòng  │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  Đã chỉnh sửa • Lưu lần cuối: 14/05/2026 09:42 bởi NGUYEN VAN A         │
│                                                                          │
│                          [Hủy thay đổi]    [Lưu cấu hình ✓]              │
└──────────────────────────────────────────────────────────────────────────┘

Không render: button "Kích hoạt lại webhook" (chỉ hiện khi suspended). Recovery/next step: bấm "Lưu cấu hình" → toast xanh "Đã lưu cấu hình Pancake" → ở lại tab; bấm "Kiểm tra kết nối" → loading → toast "Kết nối thành công. Phát hiện {N} nguồn" hoặc banner đỏ "Không thể kết nối. Kiểm tra api_key + thử lại."

Variant B — First-time setup (chưa có connection)

text
/settings/pancake-crm/connection

┌──────────────────────────────────────────────────────────────────────────┐
│  Tích hợp Pancake CRM                       [Trạng thái: Chưa thiết lập] │
│  ────────────────────────────────────────────────────────────────────── │
│  [Tab 1 Kết nối*] [Tab 2 Map nguồn] [Tab 3 Lịch sử + DLQ] [Tab 4 Sức khỏe]    │
│  ────────────────────────────────────────────────────────────────────── │
│                                                                          │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ ℹ️  Chào mừng đến với Tích hợp Pancake CRM                            │ │
│  │ Nhập thông tin workspace để bắt đầu nhận lead tự động từ Pancake.   │ │
│  │ Cần Workspace ID + API key (lấy từ Pancake admin > Tích hợp).        │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  Thông tin workspace Pancake                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ Workspace ID *           [_______________________________________]   │ │
│  │ API key *      [_______________________________________] [👁]       │ │
│  │                                                                     │ │
│  │            [Kiểm tra kết nối]  (Vô hiệu — điền 2 field trên trước)  │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  ⓘ URL + Token webhook sẽ được tự động tạo sau khi lưu thành công.     │
│                                                                          │
│                          [Hủy]              [Lưu cấu hình]  (Vô hiệu)    │
└──────────────────────────────────────────────────────────────────────────┘

Không render: kill switch (chưa có row), VIP tag list (sẽ hiện sau lưu), webhook URL/token. Recovery/next step: Admin điền workspace_id + api_key → "Kiểm tra kết nối" enable → bấm test → nếu pass → "Lưu cấu hình" enable → save → chuyển sang Variant A đầy đủ.

Variant D — Loading

text
/settings/pancake-crm/connection

┌──────────────────────────────────────────────────────────────────────────┐
│  Tích hợp Pancake CRM                                    [▓▓▓▓▓ Đang tải]│
│  ────────────────────────────────────────────────────────────────────── │
│  [Tab 1 Kết nối*] [Tab 2 Map nguồn] [Tab 3 Lịch sử + DLQ] [Tab 4 Sức khỏe]    │
│  ────────────────────────────────────────────────────────────────────── │
│                                                                          │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓     │ │
│  │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓     │ │
│  │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓     │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘

Variant E — Error save

text
┌──────────────────────────────────────────────────────────────────────────┐
│  ⚠️  Không thể lưu cấu hình.                                              │
│      Mã sự cố: TRC-a8c3d4f5-2026-05-15. Vui lòng báo bộ phận Ops với mã.│
│                                              [Thử lại]   [Đóng]          │
└──────────────────────────────────────────────────────────────────────────┘
(banner đỏ sticky top form; form vẫn giữ data user đã điền, không reset)

Variant F — Suspended by Pancake

text
┌──────────────────────────────────────────────────────────────────────────┐
│  Tích hợp Pancake CRM                  [Trạng thái: Bị Pancake khóa]    │
│  ────────────────────────────────────────────────────────────────────── │
│                                                                          │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ 🚫  Pancake đã tự động khóa webhook                                    │ │
│  │     Lý do: 80% error / 30 phút (suspension rule).                    │ │
│  │     Phát hiện lúc: 14/05/2026 14:23 (Cron 4 detect).                 │ │
│  │     Mọi event mới sẽ KHÔNG được Pancake gửi tới Diva.                │ │
│  │                                                                       │ │
│  │     Hành động cần làm:                                                │ │
│  │     1. Vào Pancake admin > Tích hợp > Webhook → Bật lại thủ công    │ │
│  │     2. Sau đó bấm nút bên dưới để cập nhật trạng thái Diva           │ │
│  │                                                                       │ │
│  │                          [Kích hoạt lại webhook ↻]                    │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  (Phần form workspace + tag VIP vẫn render, nhưng toggle kill switch     │
│   bị disabled với tooltip "Webhook đang bị Pancake khóa")                │
└──────────────────────────────────────────────────────────────────────────┘

B2.SCR-01.4) Phân loại reuse + điểm update

Phân loạiFile hiện cóVị trí updateLý do vị trí
🆕 Build mớisrc/modules/settings/pages/pancake/PancakeConnection.tsxTab 1 trong XDetailLayout của SCR-05Đầu tiên — config nền tảng trước các tab khác
✅ ReuseXInput, XPassword, XToggle, XTextarea, XBadge, XSpinner componentsForm fieldsDiva component library

B2.SCR-01.5) Quy ước field / cột / CTA (ref B0.4.A)

Mọi field đã liệt kê chi tiết ở B0.4.A. Section này chỉ ghi quy ước UI cụ thể không nằm trong B0.4.

Field/CTALoạiHiển thị ởMặc địnhValidationĐiều kiện hiển thịTooltip
Kiểm tra kết nối buttonPrimary CTASCR-01 dưới api_keyDisabled khi workspace_id hoặc api_key trốngcall REST /sources; timeout 30sLuôn hiện cho Adminref B9 row 47
Sao chép URL iconSecondary CTACạnh webhook_url readonlyAlways enabled khi URL đã gennavigator.clipboard.writeTextLuôn hiện sau setupref B9 row 48
Tạo lại token buttonDestructive CTACạnh webhook_tokenEnabled cho AdminConfirm modal 2 bước "TẠO LẠI" gõ tayLuôn hiện sau setupref B9 row 49
Kích hoạt lại webhook buttonPrimary CTABanner Variant FEnabled chỉ khi status='suspended_by_pancake'Confirm modal "Đã bật lại trên Pancake chưa?"Conditional Variant Fref B9 row 50

B2.SCR-01.6) Thanh lọc — N/A (không có filter trên SCR-01)

B2.SCR-01.7) Quy ước tương tác

Tình huốngHành vi
Chế độ lưuExplicit Lưu cấu hình button. Dirty form → dấu cuối "Đã chỉnh sửa" + chặn chuyển trang (browser warn)
Có thay đổi chưa lưuBanner sticky top "Có thay đổi chưa lưu" + buttons "Hủy thay đổi" / "Lưu"
Search/filter applyN/A
Reset dây chuyền"Tạo lại token" → invalidate webhook URL hiện tại (URL không đổi, nhưng token mới — Pancake side phải update)
Cập nhật asyncPessimistic — đợi server confirm trước khi update UI status badge
Tác vụ chạy lâu"Kiểm tra kết nối" có thể chậm 5-10s (Pancake REST) — spinner + cho phép hủy sau 15s

B2.SCR-01.7B) Form Interaction Deep (ref B2.7B template)

Khía cạnhQuy ước cho SCR-01
Trigger validationOn blur cho api_key (format check ≥16 ký tự); on submit cho toàn form
AutosaveKHÔNG có autosave (cấu hình tổng — explicit save tránh accidental change)
Char counterTextarea VIP tag: hiển thị {X}/100 dòng; đỏ khi >90 dòng
Max length cứngworkspace_id max 64 ký tự, api_key max 64 ký tự, tag NAME ≤60 ký tự/dòng
IME composingCho VIP tag textarea — KHÔNG validate trim/lowercase khi đang compose tiếng Việt
Paste rulesCho phép paste vào api_key (admin thường copy từ Pancake admin)
Auto-formatTrim whitespace + lowercase tag NAME khi blur textarea; api_key KHÔNG format (giữ raw)
Required marker* đỏ sau label workspace_id, api_key
Inline errorDưới field; aria-live="assertive"
Field disabled vs readonlywebhook_url + webhook_token: readonly (tab-focusable, copyable); kill switch khi suspended: disabled (không tab-focusable)
Field locked theo lifecycleworkspace_id sau setup lần đầu: readonly với tooltip "Đổi workspace cần Reset connection (yêu cầu Ops)"

B2.SCR-01.7C) Concurrency (ref B2.7C)

Tình huốngHành vi
2 admin cùng mở SCR-01Banner cuối form "Đang chỉnh sửa cùng: NGUYEN VAN A (HCM)" — presence indicator
2 admin cùng sửa, A lưu trướcB nhận modal "Cấu hình đã thay đổi từ thiết bị khác. [Tải lại] / [Ghi đè] (cần xác nhận)"
Server-pushed update khi đang xemBanner "Có cập nhật mới từ user khác. [Tải lại]" — KHÔNG tự reload nếu user đang gõ

B2.SCR-01.7D) Network Resilience (ref B2.7D)

Tình huốngHành vi
Mất mạng giữa "Kiểm tra kết nối"Banner "Mất kết nối Diva server. Đang thử lại..." (autoretry 30s)
Mất mạng khi "Lưu cấu hình"Disable button "Lưu", show "Đang chờ kết nối..."; giữ form data
Response > 5sSpinner + "Pancake phản hồi chậm" + nút "Hủy" sau 10s
Response > 30sAuto-hủy + thông báo "Quá lâu. Đã hủy. Vui lòng thử lại sau."

B2.SCR-01.7E) Secondary Interactions Contract (ref B2.7E)

  • Rule 1 Drill-down: N/A (không có row table trong SCR-01)
  • Rule 2 Expand-Collapse: N/A
  • Rule 3 Hover state: Hover icon mắt reveal api_key/token; hover icon ⓘ → tooltip B9
  • Rule 4 Context menu: N/A
  • Rule 5 Load more: N/A
  • Rule 6 Inline edit: api_key + textarea VIP tag dùng pattern inline edit (explicit save)
  • Rule 7 Tab switch: Cấp container SCR-05 tabs, không thuộc SCR-01
  • Rule 8 Overlay: Confirm modal khi "Tạo lại token", "Kích hoạt lại webhook"; tooltip popover hover icon ⓘ

B2.SCR-01.8) Phân quyền (ref B5 chính)

ActionAdminManagerTelesalePOS-onlyDenied feedback
Xem SCR-01 (access)fullhiddenhiddenhiddenhidden (menu không render + redirect /403)
Edit api_key (update)fullhiddenhiddenhiddenhidden
Reveal api_keyfull(N/A)(N/A)(N/A)(N/A)
Tạo lại token (update)fullhiddenhiddenhiddenhidden
Toggle kill switch (update)fullhiddenhiddenhiddenhidden

EXT-4 RBAC field-level: Nếu future Manager được cấp view (read-only) — api_key sẽ trả null từ Hasura permission (KHÔNG mask ●●●●●● để tránh leak qua DOM cache). MVP-1 không applicable.

B2.SCR-01.9) Ma trận trạng thái (ref B0.5)

(xem B0.5 hàng SCR-01)

B2.SCR-01.10) Phản hồi sau thao tác

Hành độngPhản hồi UICopy mẫuHành động tiếp
Lưu cấu hình thành côngToast xanh top-right 3s"Đã lưu cấu hình Pancake CRM."Ở lại tab
Lưu cấu hình lần đầuToast xanh + auto-jump"Đã lưu. Hệ thống đã tạo URL webhook. Tiếp theo: map nguồn → chi nhánh." + auto-redirect SCR-02 sau 3sModal hỏi "Sang Tab 2 ngay?"
Validation lỗiInline + banner tổng"Workspace ID không hợp lệ. Vui lòng kiểm tra (≤64 ký tự, UUID Pancake)."Focus field lỗi
Kiểm tra kết nối OKToast xanh + drawer hiển thị"Kết nối thành công. Đã phát hiện 12 nguồn từ Pancake workspace." + link "Sang Tab 2 để map"(button stay)
Kiểm tra kết nối FAILBanner đỏ form"Không thể kết nối Pancake. Kiểm tra api_key + thử lại. Mã: {trace_id}"Focus field api_key
Tạo lại tokenConfirm modal 2 bước"Token mới sẽ vô hiệu hóa cấu hình Pancake hiện tại. Mọi event tới URL cũ sẽ bị từ chối. Gõ 'TẠO LẠI' để xác nhận."Modal → "Tạo lại" enabled khi gõ đúng
Toggle kill switch on (tắt)Confirm modal"Tắt sẽ ngừng nhận lead Pancake. Mọi event sẽ chuyển trạng thái 'Bỏ qua (đã tắt hệ thống)'. Xác nhận?"Toggle + log audit
Toggle kill switch off (bật)Toast + auto-save"Đã bật lại nhận lead Pancake."(no confirm — bật là an toàn)
Kích hoạt lại webhookConfirm + loading"Đã bật lại trên Pancake chưa? Sau khi bạn xác nhận, Diva sẽ chuyển trạng thái sang 'Đang hoạt động'."Confirm → loading 3s → toast "Đã kích hoạt. Pancake bắt đầu gửi event."

B2.SCR-01.10A) Phân loại lỗi (ref B2.10A)

Loại lỗiTriggerUI patternRecovery
Validation clientapi_key < 16 ký tự, workspace_id sai formatInline dưới fieldUser sửa
Validation serverapi_key đúng format nhưng Pancake rejectBanner đỏ form + correlation_idLiên hệ Pancake support 0972273341
Quyền 403Manager gõ URL directRedirect /403 + toastLiên hệ Admin
Conflict 4092 admin cùng saveModal "Cấu hình đã đổi. [Tải lại] / [Ghi đè]"User chọn
Network/timeoutDiva server chậm hoặc mất mạngBanner + retryRetry tự động/thủ công
Server 5xxDB lỗiToast + correlation_idBáo Ops
Rate limit Pancake 429"Kiểm tra kết nối" gọi quá nhiềuBanner đếm ngượcChờ 1 phút

B2.SCR-02) Map nguồn → chi nhánh

B2.SCR-02.1) Ngữ cảnh nghiệp vụ

Câu hỏiQuyết định
Ai dùng?Admin role (system-wide) + Marketing manager (defer Dynamic Permission MVP-2). Module pancake_crm_integration.access/update/delete
Vào màn để quyết định gì?(1) Map từng Pancake source (FB Diva HCM, Zalo OA, ...) → Diva branch. (2) Bật/tắt per-source flag pilot rollout (W1=1 source → W4=40+). (3) Đồng bộ source mới từ Pancake manual khi cần. (4) Xem last_sync_at để biết source nào lâu chưa update.
Dữ liệu chínhpancake_source_routing table — auto INSERT khi Cron 3 phát hiện source mới (is_active=false, branch_id=NULL); admin map sau
CTA chính / phụPrimary: Đồng bộ từ Pancake. Inline-edit autosave (branch dropdown, is_active toggle). Drill-down click row → drawer detail
Điều không được hiểu nhầm"Đồng bộ" = pull source list từ Pancake REST → KHÔNG xóa source đã có. Source bị xóa khỏi Pancake → vẫn giữ row + warning "Đã ngừng tồn tại trên Pancake". "Loose mode" = branch=NULL → ticket vẫn tạo, leader assign sau. is_active=false → status skipped_source_disabled

B2.SCR-02.1A) Nguyên tắc UX bắt buộc

Nguyên tắcQuyết định
Mục tiêu user trong 5 giâyAdmin nhìn thấy: tổng số source, số đã map (% phủ branch), source đang bật/tắt, source mới chưa map
Thứ tự ưu tiên thông tin(1) Summary header "{N} nguồn, {M} đã map, {K} đang bật", (2) Table source với cột Tên Pancake source, Chi nhánh Diva, Trạng thái bật/tắt, Cập nhật cuối, (3) Filter + search, (4) Button đồng bộ
Dữ liệu nhạy cảmKhông có field nhạy cảm; tên source là public
Pending/partial dataSource mới detect (vừa qua Cron 3, branch=NULL, is_active=false) → highlight row với badge "Mới — cần map"; tooltip "Nguồn vừa được đồng bộ từ Pancake, chưa được map sang chi nhánh Diva"
Thuật ngữ canonical"Nguồn Pancake" (≠ "Nguồn ticket" của Diva A9). "Chi nhánh Diva" (≠ "Source" của Pancake). "Loose mode" → label "Để trống (Loose mode)" trong dropdown
Intent tokenwarning cho row Loose mode (branch=NULL); success cho row mapped+active; muted cho row inactive
Không tự suy diễnKHÔNG hard delete source khỏi DB (DEC-007 — soft delete với disabled cho audit); KHÔNG cho admin đổi pancake_source_id (lock from Pancake)

B2.SCR-02.2) Ma trận variant

Variant IDĐiều kiệnAi thấyWireframeQA ref
Variant ADefault — đã đồng bộ ≥1 sourceAdminCó (B2.SCR-02.3.A)TC-UI-SCR02-001
Variant BEmpty — chưa đồng bộ source nàoAdminCó (B2.SCR-02.3.B)TC-UI-SCR02-002
Variant CNo permissionManager/Telesale/POSHidden completelyTC-PERM-SCR02-001
Variant DLoadingAdminSkeleton table 5 rowTC-UI-SCR02-003
Variant EError đồng bộ (Pancake REST timeout)AdminBanner "Hiển thị cache từ {timestamp}" + Thử lạiTC-UI-SCR02-004
Variant FConnection chưa active (Tab 1 chưa setup)AdminBanner deeplink "Cần thiết lập kết nối ở Tab 1 trước" + button "Sang Tab 1"TC-UI-SCR02-005

B2.SCR-02.3) Wireframes

Variant A — Default (có data)

text
/settings/pancake-crm/source-routing

┌──────────────────────────────────────────────────────────────────────────────────┐
│  Tích hợp Pancake CRM                              [Trạng thái: Đang hoạt động ✓] │
│  [Tab 1 Kết nối] [Tab 2 Map nguồn*] [Tab 3 Lịch sử + DLQ] [Tab 4 Sức khỏe]       │
│  ──────────────────────────────────────────────────────────────────────────────│
│                                                                                  │
│  Tổng: 12 nguồn  •  Đã map chi nhánh: 9  •  Đang bật: 7  •  Mới chưa map: 2   │
│                                                                                  │
│  [🔍 Tìm theo tên nguồn________________]  [Trạng thái: Tất cả ▾]  [Chi nhánh: Tất cả ▾] │
│                                                                                  │
│                                                       [↻ Đồng bộ từ Pancake]    │
│                                                                                  │
│  ┌────────────────────────────────────────────────────────────────────────────┐│
│  │ # │ Tên nguồn Pancake          │ Chi nhánh Diva       │ Bật/Tắt│ Cập nhật cuối ││
│  │───┼────────────────────────────┼──────────────────────┼────────┼───────────────││
│  │ 1 │ FB Diva HCM Q1             │ [Diva HCM Q1     ▾]  │ [●━━] │ 15/05 09:30  ││
│  │ 2 │ FB Diva HCM Q3             │ [Diva HCM Q3     ▾]  │ [●━━] │ 15/05 09:30  ││
│  │ 3 │ Zalo OA Diva Premium       │ [Diva HCM Q1     ▾]  │ [●━━] │ 15/05 09:30  ││
│  │ 4 │ Google Ads Diva Brand      │ [Diva HCM Q1     ▾]  │ [●━━] │ 15/05 09:30  ││
│  │ 5 │ FB Diva Hà Nội Cầu Giấy    │ [Diva HN Cầu Giấy ▾] │ [●━━] │ 15/05 09:30  ││
│  │ 6 │ FB Diva Hà Nội Mỹ Đình     │ [Diva HN Mỹ Đình ▾]  │ [●━━] │ 15/05 09:30  ││
│  │ 7 │ FB Diva Đà Nẵng            │ [Diva Đà Nẵng    ▾]  │ [●━━] │ 15/05 09:30  ││
│  │ 8 │ TikTok Diva Beauty         │ [Để trống (Loose)▾]  │ [●━━] │ 15/05 09:30  ││
│  │ 9 │ Shopee Diva Official       │ [Để trống (Loose)▾]  │ [━━●] │ 15/05 09:30  ││
│  │   │  ⓘ Đang tắt — KHÔNG nhận event                    │        │              ││
│  │10 │ FB Bệnh viện Da liễu Diva  │ [Diva HCM Q1     ▾]  │ [━━●] │ 15/05 09:30  ││
│  │11 │ FB Diva Cần Thơ  [Mới]    │ [— Chọn chi nhánh ▾] │ [━━●] │ 15/05 09:30  ││
│  │12 │ Zalo OA Diva HN  [Mới]    │ [— Chọn chi nhánh ▾] │ [━━●] │ 15/05 09:30  ││
│  └────────────────────────────────────────────────────────────────────────────┘│
│                                                                                  │
│  ⓘ Click vào dòng để xem chi tiết nguồn + lịch sử event 7 ngày                  │
│                                                                                  │
│  Hiển thị 12 / 12                                            [<< 1 >>]           │
└──────────────────────────────────────────────────────────────────────────────────┘

Hover row → action icons xuất hiện cuối row: [✏️ Sửa] [🗑️ Vô hiệu hóa] (chỉ Admin có delete permission). Click row → drawer phải mở:

text
                            ╔════════════════════════════════════════╗
                            ║ ⓘ Chi tiết nguồn Pancake          [✕] ║
                            ║ ────────────────────────────────────── ║
                            ║                                        ║
                            ║ Tên nguồn:        FB Diva HCM Q1       ║
                            ║ Pancake source ID: src_fb_hcm_q1_2024  ║
                            ║ Chi nhánh Diva:   Diva HCM Quận 1      ║
                            ║ Trạng thái:       Đang bật             ║
                            ║ Cập nhật cuối:    15/05/2026 09:30     ║
                            ║                                        ║
                            ║ ─── Hoạt động 7 ngày gần nhất ───      ║
                            ║                                        ║
                            ║ Tổng event nhận:        342            ║
                            ║ Đã xử lý thành công:    340 (99,4%)    ║
                            ║ Bỏ qua trùng lặp:       1              ║
                            ║ Lỗi DLQ:                1              ║
                            ║                                        ║
                            ║ Khách mới tạo:          287            ║
                            ║ Khách update info:      55             ║
                            ║ Ticket tạo mới:         312            ║
                            ║ Đã assign telesale:     297 (95,2%)    ║
                            ║                                        ║
                            ║ Tỉ lệ phản hồi telesale ≤30 phút:      ║
                            ║                          278 (89,1%)   ║
                            ║                                        ║
                            ║         [Xem nhật ký event 7 ngày →]   ║
                            ║         (chuyển SCR-03 với filter)     ║
                            ╚════════════════════════════════════════╝

Variant B — Empty (chưa đồng bộ)

text
┌──────────────────────────────────────────────────────────────────────────────────┐
│  Tổng: 0 nguồn                                                                   │
│                                                                                  │
│  ┌──────────────────────────────────────────────────────────────────────────────┐│
│  │                                                                              ││
│  │              ▣  Chưa có nguồn nào được đồng bộ từ Pancake                    ││
│  │                                                                              ││
│  │      Khi bạn click "Đồng bộ từ Pancake", hệ thống sẽ gọi Pancake API        ││
│  │      và tự động thêm danh sách nguồn (FB page, Zalo OA, TikTok...).         ││
│  │      Sau đó bạn map từng nguồn sang chi nhánh Diva tương ứng.               ││
│  │                                                                              ││
│  │                       [↻ Đồng bộ từ Pancake]                                ││
│  │                                                                              ││
│  │      ⓘ Cần Tab 1 đã thiết lập kết nối thành công.                            ││
│  │                                                                              ││
│  └──────────────────────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────────────┘

Variant E — Error đồng bộ

text
┌──────────────────────────────────────────────────────────────────────────────────┐
│  ⚠️  Không thể đồng bộ từ Pancake                                                │
│      Pancake API không phản hồi (timeout 30s). Hiển thị danh sách cache từ      │
│      15/05/2026 08:30 (1 giờ trước).                                            │
│      Mã sự cố: TRC-2026-05-15-a8c3d4. Sẽ tự động thử lại sau 30 phút.           │
│                                                              [Thử lại ngay]      │
└──────────────────────────────────────────────────────────────────────────────────┘
(Table vẫn render với data cache; user có thể edit bình thường;
 row last_sync_at hiển thị giá trị cũ)

Variant F — Connection chưa active

text
┌──────────────────────────────────────────────────────────────────────────────────┐
│  ⓘ  Chưa thiết lập kết nối Pancake                                              │
│     Vui lòng hoàn tất Tab 1 (Kết nối) trước khi map nguồn → chi nhánh.          │
│                                                              [Sang Tab 1 →]      │
└──────────────────────────────────────────────────────────────────────────────────┘

B2.SCR-02.4) Phân loại reuse + điểm update

Phân loạiFile hiện cóVị trí updateLý do
🆕 Build mớisrc/modules/settings/pages/pancake/PancakeSourceRouting.tsxTab 2 SCR-05
✅ ReuseXTable, BranchSelect (existing src/modules/user/components/BranchSelect.vue), XToggle, XSearchInput, XDrawerDiva component library

B2.SCR-02.5) Quy ước field (ref B0.4.B + B0.4.D)

Field/CTALoạiHiển thị ởMặc địnhValidationTooltip
Cột # (STT)intTable leftsequential
Cột Tên nguồn Pancaketext readonlyTable(auto từ Pancake)text search matchB9 row 9
Cột Chi nhánh Divainline-edit FKTableNULL (Loose mode)UUID branch validB9 row 10
Cột Bật/Tắtinline-toggleTablefalse (mặc định khi source mới detect)boolB9 row 11
Cột Cập nhật cuốidatetime readonlyTableautoDD/MM HH:mmB9 row 12
Đồng bộ từ Pancake buttonPrimary CTATop rightenabledcall REST /sourcesB9 row 51

B2.SCR-02.6) Bảng — cấu hình cột

CộtĐộ rộngCăn lềĐịnh dạngStickySortable
#40pxGiữaintKhôngKhông
Tên nguồn Pancakeflex (≥200px)Tráitext + badge "Mới"KhôngCó (A-Z)
Chi nhánh Diva240pxTráidropdownKhông
Bật/Tắt100pxGiữatoggleKhông
Cập nhật cuối140pxPhảidatetimeKhôngCó (DESC default)

B2.SCR-02.7) Quy ước tương tác

Tình huốngHành vi
Chế độ lưuAutosave inline-edit (branch dropdown + is_active toggle) debounce 500ms
Đồng bộManual button → loading 5-30s → toast "Đã đồng bộ {N} nguồn ({M} mới)"
Reset dây chuyềnTắt is_active không ảnh hưởng branch dropdown
Cập nhật asyncOptimistic — UI update ngay, rollback nếu server reject
Concurrency2 admin edit cùng row → modal "Bản ghi đã thay đổi từ user khác. [Tải lại]" (ref EXT-12)

B2.SCR-02.7E) Secondary Interactions (ref B2.7E)

  • Rule 1 Drill-down: Click row → drawer phải chi tiết source + lịch sử 7d
  • Rule 2 Expand-Collapse: N/A
  • Rule 3 Hover state: Hover row → action icons "✏️ Sửa" (edit name local nickname — defer MVP-2) + "🗑️ Vô hiệu hóa" (set is_active=false)
  • Rule 4 Context menu: Click ⋮ button cuối row → dropdown "Xem chi tiết / Vô hiệu hóa / Đồng bộ riêng nguồn này"
  • Rule 5 Load more: Pagination — default 50/page, cho phép 25/50/100 (source list nhỏ ~40 row)
  • Rule 6 Inline edit: Branch dropdown + is_active toggle autosave (xem rule 6 chính)
  • Rule 7 Tab switch: Cấp SCR-05
  • Rule 8 Overlay: Drawer drill-down + confirm modal khi tắt is_active có ≥10 event trong 24h gần nhất

B2.SCR-02.7C) Concurrency (ref EXT-12)

Tình huốngHành vi
2 admin cùng edit row NA save trước → B nhận banner "Bản ghi 'FB Diva HCM Q1' đã thay đổi. [Tải lại]"
Optimistic update + server rejectRollback UI + toast "Không thể lưu — bản ghi đã đổi. Đã tải lại."
Inline edit dirty + user close drawerConfirm "Có thay đổi chưa lưu. [Lưu] / [Hủy]"

B2.SCR-02.8) Phân quyền

ActionAdminManagerTelesalePOS-onlyDenied feedback
Xem SCR-02 (access)fullhiddenhiddenhiddenhidden
Đồng bộ từ Pancake (update)fullhiddenhiddenhiddenhidden
Edit branch dropdown (update)fullhiddenhiddenhiddenhidden
Toggle is_active (update)fullhiddenhiddenhiddenhidden
Vô hiệu hóa source (delete)fullhiddenhiddenhiddenhidden

B2.SCR-02.9) Ma trận trạng thái (ref B0.5)

B2.SCR-02.10) Phản hồi sau thao tác

Hành độngPhản hồi UICopy mẫu
Inline-edit branch dropdown save thành côngToast 2s"Đã cập nhật chi nhánh cho 'FB Diva HCM Q1' → 'Diva HCM Q1'."
Inline-edit toggle is_active sang offToast + undo 10s"Đã tắt nguồn 'TikTok Diva Beauty'. Sẽ ngừng nhận event. [Hoàn tác]"
Đồng bộ Pancake thành côngToast xanh"Đã đồng bộ. Phát hiện 12 nguồn ({M} nguồn mới). Vui lòng map nguồn mới sang chi nhánh."
Đồng bộ Pancake failBanner top + cache fallback"Pancake không phản hồi. Hiển thị bản cache từ {timestamp}. Sẽ tự thử lại sau 30 phút."
Branch dropdown chọn "Để trống (Loose mode)"Confirm modal"Loose mode: ticket sẽ tạo với chi nhánh NULL, leader manual assign sau. Xác nhận?"
Vô hiệu hóa source (set is_active=false)Warning modal nếu ≥10 event 24h"Nguồn 'FB Diva HCM Q1' có 87 event trong 24h gần nhất. Tắt sẽ ngừng nhận event. Xác nhận?"

B2.SCR-03) Lịch sử event + DLQ replay

B2.SCR-03.1) Ngữ cảnh nghiệp vụ

Câu hỏiQuyết định
Ai dùng?Admin (system-wide audit). Module pancake_crm_integration.access/view_all/replay_dlq
Vào màn để quyết định gì?(1) Audit: xem mọi event Pancake đã nhận + status + lifecycle history. (2) Debug: xem raw payload + headers + error_message. (3) Replay: phát lại event status dead_letter hoặc auth_failed/parse_error (sau khi đã fix root cause). (4) Bulk replay nhiều event cùng lúc. (5) Filter test events.
Dữ liệu chínhpancake_webhook_event — capacity 100k events/tháng (DEC-023 capacity model); index (created_at, status) + record_id + is_test
CTA chính / phụPrimary: Phát lại sự kiện (single row). Bulk: Phát lại {N} sự kiện đã chọn (rate limit 5/s, batch ≤100). Secondary: Sao chép JSON payload, Xem các event cùng record_id, Xóa bộ lọc
Điều không được hiểu nhầm"Phát lại" KHÔNG gọi lại Pancake — chỉ reset status về received để Hasura trigger fire lại crm-api handler. "Trùng lặp (skipped_duplicate)" KHÔNG phát lại được (đúng nghiệp vụ — đã có row khác xử lý). Status processed không cho phát lại (idempotency). Test events ẩn mặc định (DEC-009).

B2.SCR-03.1A) Nguyên tắc UX bắt buộc

Nguyên tắcQuyết định
Mục tiêu user trong 5 giâyAdmin nhìn thấy: tổng event, % thành công, có DLQ tồn đọng cần xử lý không, event mới nhất là khi nào
Thứ tự ưu tiên thông tin(1) Filter bar (date range mặc định 7 ngày), (2) Summary chip "Đã xử lý: 9985 (99,9%) • DLQ chờ replay: 10 • Test: 5", (3) Table list event với status badge nổi bật, (4) Drawer detail khi cần debug
Dữ liệu nhạy cảmraw_payload có thể chứa SĐT, tên KH (PII) — chỉ Admin xem; KHÔNG export raw CSV mà không masking PII (defer MVP-2)
Pending/partial dataEvent status ingested/received/processing (non-terminal) → badge muted + tooltip "Đang xử lý"; cell processed_at hiển thị
Thuật ngữ canonical"Sự kiện" (event) — KHÔNG "Webhook event" (technical). "DLQ" → "Lỗi DLQ (chờ phát lại)" trong UI label; tooltip giải thích "Dead Letter Queue". "Replay" → "Phát lại"
Intent tokensuccess cho processed; negative-value cho dead_letter/permanently_failed; warning cho auth_failed/ip_blocked/parse_error; muted cho skipped_*
Không tự suy diễnKHÔNG cho phép edit raw_payload (audit-only); KHÔNG cho phép delete event row (audit immutable)

B2.SCR-03.2) Ma trận variant

Variant IDĐiều kiệnAi thấyQA ref
Variant ADefault — có eventAdminTC-UI-SCR03-001
Variant BEmpty — chưa có event nàoAdminTC-UI-SCR03-002
Variant B2Empty-filter — có event nhưng filter narrowAdminTC-UI-SCR03-003
Variant CNo permissionManager/Telesale/POSTC-PERM-SCR03-001
Variant DLoadingAdminTC-UI-SCR03-004
Variant EError tảiAdminTC-UI-SCR03-005
Variant FBulk replay đang chạyAdminTC-UI-SCR03-006
Variant GDLQ tồn đọng cao bất thường (≥50 dead_letter trong 1h)AdminTC-UI-SCR03-007

B2.SCR-03.3) Wireframes

Variant A — Default

text
/settings/pancake-crm/audit

┌────────────────────────────────────────────────────────────────────────────────────┐
│  Tích hợp Pancake CRM                              [Trạng thái: Đang hoạt động ✓]  │
│  [Tab 1 Kết nối] [Tab 2 Map nguồn] [Tab 3 Lịch sử + DLQ*] [Tab 4 Sức khỏe]         │
│  ────────────────────────────────────────────────────────────────────────────────│
│                                                                                    │
│  Khoảng thời gian: [09/05/2026 - 15/05/2026 (7 ngày) ▾]                            │
│  Trạng thái: [Tất cả ▾]   Nguồn: [Tất cả ▾]   ☐ Bao gồm test events               │
│                                                                  [Xóa bộ lọc]      │
│                                                                                    │
│  Tổng: 10.000 sự kiện  •  Đã xử lý: 9.985 (99,85%)  •  Lỗi DLQ: 10  •  Bỏ qua: 5  │
│                                                                                    │
│  ┌──────────────────────────────────────────────────────────────────────────────┐ │
│  │ ☐ │ Mã sự kiện │ Record ID  │ Trạng thái      │ Nguồn         │ Thời gian    │ │
│  │───┼────────────┼────────────┼─────────────────┼───────────────┼──────────────│ │
│  │ ☐ │ a8c3d4f5   │ rec_xyz_01 │ ✓ Đã xử lý      │ FB Diva HCM Q1│15/05 09:42:31│ │
│  │ ☐ │ b9d4e5f6   │ rec_xyz_02 │ ✓ Đã xử lý      │ Zalo OA Diva  │15/05 09:42:18│ │
│  │ ☐ │ c0e5f6a7   │ rec_xyz_03 │ ⚠ Lỗi DLQ       │ FB Diva HN    │15/05 09:41:55│ │
│  │ ☐ │ d1f6a7b8   │ rec_xyz_04 │ ✓ Đã xử lý      │ Google Ads    │15/05 09:41:30│ │
│  │ ☐ │ e2a7b8c9   │ rec_xyz_05 │ ◌ Bỏ qua (trùng)│ FB Diva HCM Q1│15/05 09:41:12│ │
│  │ ☐ │ f3b8c9d0   │ rec_xyz_06 │ ⚠ Lỗi DLQ       │ FB Diva HCM Q3│15/05 09:40:48│ │
│  │ ☐ │ a4c9d0e1   │ rec_xyz_07 │ ⚠ Lỗi xác thực  │ (không xác định)│15/05 09:40:25│ │
│  │ ☐ │ b5d0e1f2   │ rec_xyz_08 │ ◌ Bỏ qua (KH từ │ FB Diva HCM Q1│15/05 09:40:01│ │
│  │   │            │            │   chối marketing)│              │              │ │
│  │ ☐ │ c6e1f2a3   │ rec_xyz_09 │ ✓ Đã xử lý      │ TikTok Diva   │15/05 09:39:45│ │
│  │ ☐ │ d7f2a3b4   │ rec_xyz_10 │ ⏳ Đang xử lý    │ FB Diva HCM Q1│15/05 09:39:20│ │
│  └──────────────────────────────────────────────────────────────────────────────┘ │
│                                                                                    │
│  Hover một dòng để hiện hành động: [Phát lại] [Sao chép JSON] [Xem cùng record]  │
│                                                                                    │
│  Hiển thị 1-100 / 10.000     [< Trước]  Trang [1] / 100  [Sau >]   100/page ▾    │
└────────────────────────────────────────────────────────────────────────────────────┘

(Click dòng "c0e5f6a7" Lỗi DLQ → drawer phải mở)

                            ╔═══════════════════════════════════════════════════╗
                            ║ Chi tiết sự kiện                              [✕] ║
                            ║ ────────────────────────────────────────────────── ║
                            ║                                                   ║
                            ║ [Payload] [Headers] [Lịch sử trạng thái]          ║
                            ║                                                   ║
                            ║ Mã sự kiện:        c0e5f6a7-1f8d-...               ║
                            ║ Record ID:         rec_xyz_03                     ║
                            ║                    [Xem các event cùng record →] ║
                            ║ Trạng thái:        ⚠ Lỗi DLQ (chờ phát lại)       ║
                            ║ Loại:              Sự kiện record                 ║
                            ║ Test event:        Không                          ║
                            ║ Số lần retry:      2 / 3                          ║
                            ║ Tạo lúc:           15/05/2026 09:41:55            ║
                            ║ Xử lý lúc:         —                              ║
                            ║ IP nguồn:          103.20.149.117                 ║
                            ║                                                   ║
                            ║ ── Thông báo lỗi ──                                ║
                            ║ ┌─────────────────────────────────────────────┐ ║
                            ║ │ STEP 9 round-robin failed: no active        │ ║
                            ║ │ telesale found in branch 'Diva HN Cầu Giấy'.│ ║
                            ║ │ Possible cause: ngoài giờ làm việc          │ ║
                            ║ │ (current time 09:41 < 09:00 không khớp).    │ ║
                            ║ └─────────────────────────────────────────────┘ ║
                            ║                                                   ║
                            ║ ── Raw payload (JSON) ──    [📋 Sao chép JSON]   ║
                            ║ ┌─────────────────────────────────────────────┐ ║
                            ║ │ {                                            │ ║
                            ║ │   "record_id": "rec_xyz_03",                 │ ║
                            ║ │   "phone_number": "+84912345678",            │ ║
                            ║ │   "name": "Nguyễn Thị Mai",                  │ ║
                            ║ │   "source": [{                               │ ║
                            ║ │     "id": "src_fb_hn_caugiay",               │ ║
                            ║ │     "name": "FB Diva HN Cầu Giấy"            │ ║
                            ║ │   }],                                        │ ║
                            ║ │   "tags": ["VIP", "Hot Lead"],               │ ║
                            ║ │   "modified_on": "2026-05-15T09:41:50+07:00",│ ║
                            ║ │   ...                                        │ ║
                            ║ │ }                                            │ ║
                            ║ └─────────────────────────────────────────────┘ ║
                            ║                                                   ║
                            ║         [Hủy]      [↻ Phát lại sự kiện]          ║
                            ╚═══════════════════════════════════════════════════╝

Variant B — Empty (no event)

text
┌────────────────────────────────────────────────────────────────────────────────────┐
│  Tổng: 0 sự kiện trong khoảng 7 ngày                                              │
│                                                                                    │
│  ┌──────────────────────────────────────────────────────────────────────────────┐ │
│  │            ▣  Chưa nhận sự kiện nào từ Pancake trong khoảng đã chọn          ││
│  │                                                                              ││
│  │      Kiểm tra:                                                              ││
│  │      • Tab 1 Kết nối: trạng thái có 'Đang hoạt động' không?                 ││
│  │      • Tab 2 Map nguồn: ≥1 nguồn được bật?                                  ││
│  │      • Pancake admin: URL webhook đã paste đúng?                            ││
│  │                                                                              ││
│  │      [Mở rộng khoảng thời gian (30 ngày)]   [Sang Tab 4 xem health →]       ││
│  │                                                                              ││
│  └──────────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────────┘

Variant B2 — Empty filter

text
┌────────────────────────────────────────────────────────────────────────────────────┐
│  Không có sự kiện nào khớp bộ lọc                                                 │
│  (10.000 sự kiện tổng trong khoảng 7 ngày — có lẽ bộ lọc quá hẹp)                │
│                                                                                    │
│                              [Xóa bộ lọc]                                          │
└────────────────────────────────────────────────────────────────────────────────────┘

Variant F — Bulk replay đang chạy

text
┌────────────────────────────────────────────────────────────────────────────────────┐
│  ┌──────────────────────────────────────────────────────────────────────────────┐ │
│  │  ⏳  Đang phát lại 10 sự kiện DLQ...                                          ││
│  │      ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░  4 / 10 (40%)                                  ││
│  │      Rate: 5 sự kiện / giây (ước tính 2 giây còn lại)                       ││
│  │                                                       [Hủy bulk]              ││
│  └──────────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────────┘
(Sau khi xong, banner đổi thành:)
┌────────────────────────────────────────────────────────────────────────────────────┐
│  ✓ Đã phát lại 8/10 sự kiện thành công. 2 sự kiện vẫn lỗi (chuyển sang DLQ).      │
│  Lý do thường gặp: STEP 9 round-robin fail (không có telesale active).           │
│                                                       [Xem chi tiết 2 lỗi]        │
└────────────────────────────────────────────────────────────────────────────────────┘

Variant G — DLQ tồn đọng cao bất thường

text
┌────────────────────────────────────────────────────────────────────────────────────┐
│  🚨  Cảnh báo: 87 sự kiện DLQ trong 1 giờ gần nhất (bình thường <5/giờ)           │
│      Có thể có vấn đề hệ thống (round-robin, telesale roster). Kiểm tra Tab 4    │
│      Sức khỏe + báo Tech Lead nếu cần.                                            │
│                              [Lọc chỉ DLQ]  [Sang Tab 4 →]                         │
└────────────────────────────────────────────────────────────────────────────────────┘
(banner đỏ sticky top, không tự đóng; user dismiss qua "Tạm tắt 1h")

B2.SCR-03.4) Phân loại reuse

Phân loạiFileLý do
🆕 Build mớisrc/modules/settings/pages/pancake/PancakeAuditDlq.tsxTab 3 SCR-05
✅ ReuseXTable, XDrawer, XDateRangePicker, XMultiSelect, XCheckbox, XJsonViewer (component có sẵn?)TBD verify; nếu không có XJsonViewer → build mới (~0.3d)

B2.SCR-03.5) Quy ước field (ref B0.4.C + B0.4.D)

B2.SCR-03.6) Bảng — cấu hình cột

CộtĐộ rộngCăn lềĐịnh dạngStickySortable
Checkbox bulk40pxGiữaboolCó (header)Không
Mã sự kiện100pxTráiuuid 8 ký tự đầuKhôngKhông
Record ID140pxTráitext truncate ...KhôngKhông
Trạng thái200pxTráibadge + iconKhôngCó (group by status)
Nguồn180pxTráitextKhông
Thời gian160pxPhảidatetimeKhôngCó (DESC default)

B2.SCR-03.7) Quy ước tương tác

Tình huốngHành vi
Filter applyAuto-apply on change; debounce 500ms cho date range
Drill-downClick row → drawer (không full screen) — preserve scroll state khi đóng
Inline action hoverAction icons opacity 0 → 1; mobile long-press 500ms
Bulk replayChỉ chọn được row status replayable (dead_letter, auth_failed, parse_error); modal confirm + progress bar
Search payloadDefer MVP-2 (text search trong raw_payload JSON cần index riêng)

B2.SCR-03.7C) Concurrency

Tình huốngHành vi
2 admin cùng replay 1 eventA click trước → status chuyển received; B click thấy "Sự kiện đã được phát lại bởi {A} lúc {time}"
Bulk replay đang chạy + admin khác mở SCR-03Banner "Bulk replay đang chạy bởi {user}. Tránh chạy song song."

B2.SCR-03.7E) Secondary Interactions (ref B2.7E)

  • Rule 1 Drill-down: Click row → drawer detail (đã cover)
  • Rule 2 Expand-Collapse: Trong drawer, JSON viewer cho phép collapse nested object (chevron ▶/▼)
  • Rule 3 Hover state: Action icons "Phát lại / Sao chép / Xem cùng record" reveal on row hover
  • Rule 4 Context menu: Click ⋮ → "Phát lại / Sao chép JSON / Xem cùng record / Đánh dấu permanently_failed"
  • Rule 5 Load more: Pagination 100/page; cho phép 50/100/200; total count "10.000 sự kiện"
  • Rule 6 Inline edit: N/A (audit immutable)
  • Rule 7 Tab switch: Drawer có 3 tab "Payload / Headers / Lịch sử trạng thái" — lazy load mỗi tab
  • Rule 8 Overlay: Drawer + confirm modal replay

B2.SCR-03.8) Phân quyền

ActionAdminManagerTelesalePOS-onlyDenied feedback
Xem SCR-03 (access + view_all)fullhiddenhiddenhiddenhidden
Xem raw_payload (PII KH)fullhiddenhiddenhiddenhidden (API trả null)
Phát lại single (replay_dlq)fullhiddenhiddenhiddenhidden (button không render)
Bulk replay (replay_dlq)fullhiddenhiddenhiddenhidden
Export CSV (defer MVP-2)(defer)hiddenhiddenhidden

B2.SCR-03.9) Ma trận trạng thái (ref B0.5)

B2.SCR-03.10) Phản hồi sau thao tác

Hành độngPhản hồi UICopy mẫu
Phát lại 1 event thành côngToast xanh 3s"Đã phát lại sự kiện. Trạng thái chuyển 'Lỗi DLQ' → 'Đã xác thực'. Sẽ xử lý trong vài giây."
Phát lại fail (status không cho phép)Toast cam"Không thể phát lại sự kiện trạng thái '{status}'. Chỉ áp dụng cho 'Lỗi DLQ', 'Lỗi xác thực', 'Lỗi cấu trúc dữ liệu'."
Bulk replay đang chạyProgress modal + banner sticky"Đang phát lại {X}/{N} sự kiện. Rate 5/giây."
Bulk replay xong (full success)Toast + banner xanh"Đã phát lại {N} sự kiện thành công."
Bulk replay xong (partial)Banner cam"Đã phát lại {X}/{N} thành công. {Y} sự kiện vẫn lỗi DLQ. [Xem chi tiết]"
Sao chép JSONToast 1s"Đã sao chép JSON payload vào clipboard."
Date range > 90 ngàyInline error"Khoảng thời gian tối đa 90 ngày. Vui lòng thu hẹp."

B2.SCR-03.10A) Error Taxonomy

Loại lỗiTriggerUIRecovery
Validation clientDate range > 90 ngàyInline error filterUser thu hẹp
Validation serverEvent không cho phép replayToast cam(đúng nghiệp vụ)
Conflict 4092 admin replay cùng eventModalTải lại
Network/timeoutBulk replay 100 event timeoutBanner + retryRetry batch nhỏ hơn
Server 5xxHasura action failToast + correlation_idBáo Ops
Rate limit 429Bulk replay > 5/sBanner đếm ngượcChờ

B2.SCR-04) Sức khỏe & cảnh báo

B2.SCR-04.1) Ngữ cảnh nghiệp vụ

Câu hỏiQuyết định
Ai dùng?Admin (ops monitoring). Module pancake_crm_integration.access
Vào màn để quyết định gì?(1) Quick health check: tỉ lệ event thành công 24h/7d, latency p95, outage status hiện tại. (2) Outage history: xem các sự cố gần đây, MTTR. (3) Alert config (defer MVP-2). (4) Quick link sang SCR-03 khi metric bất thường.
Dữ liệu chínhAggregate query từ pancake_webhook_event + pancake_outage_state
CTA chính / phụPrimary: (không có CTA mutate — read-only dashboard). Secondary: toggle timeframe 24h/7d, click metric drill-down sang SCR-03, "Tải báo cáo PDF" (defer MVP-2)
Điều không được hiểu nhầm"Tỉ lệ thành công" filter skipped_* ra khỏi denominator (FORMULA-004). "Latency p95" loại outlier > 5 phút. "Outage" detect dựa trên 0 event trong 5 phút giờ làm việc — KHÔNG phải Pancake đã suspend.

B2.SCR-04.1A) Nguyên tắc UX bắt buộc

Nguyên tắcQuyết định
Mục tiêu user trong 5 giâyAdmin nhìn thấy: hệ thống có khỏe không (success ≥99,5%), có outage đang xảy ra không, latency có trong target ≤30s không
Thứ tự ưu tiên thông tin(1) Banner outage (nếu có) sticky top, (2) 4 metric cards lớn (Success rate, Latency p95, Auto-assign rate, Outage current state), (3) Chart trend 7 ngày, (4) Outage history table
Pending/partial dataKhi mới setup (chưa có event nào) → các card hiển thị thay vì 0% để tránh hiểu nhầm "đang fail"
Intent tokensuccess cho metric đạt target; warning cho metric trong cảnh báo (vd success 95-99.5%); negative-value cho dưới ngưỡng
Không tự suy diễnKHÔNG render alert email/SMS từ UI MVP-1 (DEC-010 chỉ in-app push)

B2.SCR-04.2) Ma trận variant

Variant IDĐiều kiệnWireframeQA ref
Variant AHealthy — outage_state=healthy, success ≥99,5%Có (B2.SCR-04.3.A)TC-UI-SCR04-001
Variant BOutage active — outage_state=outage_startedCó (B2.SCR-04.3.B)TC-UI-SCR04-002
Variant B2Outage recovered (5 phút sau)banner xanhTC-UI-SCR04-003
Variant CNo permissionHiddenTC-PERM-SCR04-001
Variant DLoadingSkeletonTC-UI-SCR04-004
Variant EErrorBanner + cachedTC-UI-SCR04-005
Variant FSuccess < 99,5% (warning)Cards với badge camTC-UI-SCR04-006

B2.SCR-04.3) Wireframes

Variant A — Healthy

text
/settings/pancake-crm/health

┌────────────────────────────────────────────────────────────────────────────────────┐
│  Tích hợp Pancake CRM                              [Trạng thái: Đang hoạt động ✓]  │
│  [Tab 1 Kết nối] [Tab 2 Map nguồn] [Tab 3 Lịch sử + DLQ] [Tab 4 Sức khỏe*]         │
│  ────────────────────────────────────────────────────────────────────────────────│
│                                                                                    │
│  Khoảng thời gian: [24 giờ] / [7 ngày]            Cập nhật cuối: 15/05 09:42      │
│                                                                                    │
│  ┌────────────────────┬────────────────────┬────────────────────┬────────────────┐│
│  │ Tỉ lệ thành công ⓘ │ Độ trễ webhook ⓘ   │ Auto-assign ⓘ      │ Trạng thái Pancake│
│  │                    │  → ticket (p95)    │ telesale (chính xác)│              │
│  │                    │                    │                    │                ││
│  │     99,950%        │      12,5 s        │      92,00%        │   ✓ Khỏe mạnh  ││
│  │  ────────────      │  ────────────      │  ────────────      │                ││
│  │  Mục tiêu ≥ 99,5%  │  Mục tiêu ≤ 30s    │  Mục tiêu ≥ 90%    │  Nhận event    ││
│  │  ✓ Đạt             │  ✓ Đạt             │  ✓ Đạt             │  cuối: 38s trước│ │
│  │                    │                    │                    │                ││
│  │ [Xem chi tiết →]   │ [Xem chi tiết →]   │ [Xem chi tiết →]   │ [Xem lịch sử →]│ │
│  └────────────────────┴────────────────────┴────────────────────┴────────────────┘│
│                                                                                    │
│  Xu hướng 7 ngày (Tỉ lệ thành công + Số event)                                    │
│  ┌──────────────────────────────────────────────────────────────────────────────┐ │
│  │ %                                                                            │ │
│  │ 100 ──●──●──●──●──●──●──●  (Tỉ lệ thành công, axis trái)                    │ │
│  │  99 │  │  │  │  │  │  │  │                                                  │ │
│  │  98 │  │  │  │  │  │  │  │                                                  │ │
│  │  97 │  │  │  │  │  │  │  │                                                  │ │
│  │     09/05 10/05 11/05 12/05 13/05 14/05 15/05                                │ │
│  │ Event nhận / ngày (axis phải): 1.5k 1.4k 1.6k 1.5k 1.4k 1.6k 1.5k            │ │
│  └──────────────────────────────────────────────────────────────────────────────┘ │
│                                                                                    │
│  Lịch sử outage (10 gần nhất)                                                     │
│  ┌──────────────────────────────────────────────────────────────────────────────┐ │
│  │ Bắt đầu              │ Kết thúc            │ Thời lượng │ Event miss đã rescue  │
│  │──────────────────────┼─────────────────────┼────────────┼───────────────────────│
│  │ 14/05/2026 14:23     │ 14/05/2026 14:31    │   8 phút   │ 0 (Cron 6 rescue)    │ │
│  │ 12/05/2026 03:15     │ 12/05/2026 03:18    │   3 phút   │ 0 (Cron 6 rescue)    │ │
│  │ 09/05/2026 21:50     │ 09/05/2026 22:12    │  22 phút   │ 2 (Cron 7 rescue)    │ │
│  │ (...)                                                                          │ │
│  └──────────────────────────────────────────────────────────────────────────────┘ │
│                                                                                    │
│  MTTR (Mean Time To Recovery): 4,40 phút (target ≤ 5 phút) ✓                      │
└────────────────────────────────────────────────────────────────────────────────────┘

Variant B — Outage active

text
┌────────────────────────────────────────────────────────────────────────────────────┐
│  🚨  Pancake outage — không nhận event trong 5 phút gần nhất                      │
│      Bắt đầu: 15/05/2026 14:23 (8 phút trước). Trạng thái: outage_started.       │
│      Cron 6 đã kích hoạt adaptive polling fallback (interval 60s).               │
│      Đã rescue: 4 event miss tới giờ.                                            │
│                                                                                    │
│              [Tạm tắt cảnh báo (1h)]  [Xem chi tiết outage →]                     │
└────────────────────────────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────────────────────────┐
│  ┌────────────────────┬────────────────────┬────────────────────┬────────────────┐│
│  │ Tỉ lệ thành công   │ Độ trễ webhook     │ Auto-assign        │ Trạng thái       ││
│  │                    │  → ticket          │ telesale           │                  ││
│  │                    │                    │                    │                  ││
│  │     99,945%        │      14,2 s        │      91,85%        │   🚨 Mất kết nối  ││
│  │  ────────────      │  ────────────      │  ────────────      │  Đã 8 phút      ││
│  │  Mục tiêu ≥ 99,5%  │  Mục tiêu ≤ 30s    │  Mục tiêu ≥ 90%    │  Polling: 60s   ││
│  │  ✓ Đạt             │  ✓ Đạt             │  ✓ Đạt             │  fallback active││
│  │ [Xem chi tiết →]   │ [Xem chi tiết →]   │ [Xem chi tiết →]   │ [Lịch sử →]     ││
│  └────────────────────┴────────────────────┴────────────────────┴────────────────┘│
└────────────────────────────────────────────────────────────────────────────────────┘

Variant F — Warning (success < 99,5%)

text
┌────────────────────────────────────────────────────────────────────────────────────┐
│  ⚠️  Tỉ lệ thành công thấp bất thường (96,2% — bình thường ≥99,5%)                │
│      Khả năng: tăng DLQ trong vài giờ qua. Kiểm tra Tab 3 lọc status 'Lỗi DLQ'.   │
│                                                  [Sang Tab 3 lọc DLQ →]            │
└────────────────────────────────────────────────────────────────────────────────────┘
(card "Tỉ lệ thành công" hiển thị 96,2% với badge cam "Cần xem xét" thay vì xanh)

B2.SCR-04.4) Phân loại reuse

Phân loạiFileLý do
🆕 Build mớisrc/modules/settings/pages/pancake/PancakeHealth.tsxTab 4 SCR-05
✅ ReuseXMetricCard (?), XChart (echarts wrapper), XTableTBD verify

B2.SCR-04.5..10) Quy ước chi tiết

MụcQuy ước
RefreshPolling 60s (ref EXT-14.1 dashboard tổng); KHÔNG polling khi tab unfocus
Realtime banner outageBanner sticky top trong khi outage_state=outage_started
Click metric cardDrill-down sang SCR-03 với pre-filter (vd "Tỉ lệ thành công" → filter status NOT IN ('processed', 'skipped_*'))
Toggle 24h/7dURL hash ?timeframe=24h|7d; lưu sessionStorage
Chart hoverTooltip hiển thị giá trị chính xác cho ngày đó
Outage history click rowDrawer detail outage với timeline + count missing events rescued

B2.SCR-04.7E) Secondary Interactions

  • Rule 1 Drill-down: Click metric card / row outage history
  • Rule 3 Hover state: Hover icon ⓘ → tooltip B9 công thức
  • Rule 7 Tab switch: Toggle 24h/7d
  • Rule 8 Overlay: Banner sticky + drawer outage detail

B2.SCR-04.8) Phân quyền

Tất cả Admin only — Hidden cho non-admin.


B2.SCR-05) Container Tích hợp Pancake CRM

B2.SCR-05.1) Ngữ cảnh

Container XDetailLayout 4 tabs (DEC-016). Reuse pattern AppSettingsSmsTemplate.tsx:14-28.

Route: /settings/pancake-crm (redirect default → /connection).

B2.SCR-05.2) Wireframe

text
/settings/pancake-crm

┌────────────────────────────────────────────────────────────────────────────────────┐
│  ← Settings                                                                        │
│                                                                                    │
│  Tích hợp Pancake CRM                                                              │
│  Trạng thái: [Đang hoạt động ✓]  •  Workspace: ws_diva_2026_a8c3d4f5              │
│  ────────────────────────────────────────────────────────────────────────────────│
│  [📡 Kết nối]  [🔗 Map nguồn]  [📋 Lịch sử + DLQ]  [💚 Sức khỏe]                   │
│  ────────────────────────────────────────────────────────────────────────────────│
│                                                                                    │
│  <RouterView />                                                                    │
│  (Active tab content: SCR-01 / SCR-02 / SCR-03 / SCR-04)                          │
│                                                                                    │
└────────────────────────────────────────────────────────────────────────────────────┘

B2.SCR-05.3) Quy ước

MụcQuy ước
Default tabconnection (Tab 1) khi user vào lần đầu
Preserve statesessionStorage pancake-crm-last-tab — quay lại tab cũ trừ khi user click "← Settings" rồi quay lại
Permissionpancake_crm_integration.access cho cả route container — Hidden hoàn toàn nếu thiếu
Keyboard←→ chuyển tab; Tab phím tự nhiên focus next field
Header badgeSync với pancake_connection.status realtime (polling 60s)

B2.SCR-06) Dropdown ticket source slot 9 (FE delta on 3 màn existing)

B2.SCR-06.1) Ngữ cảnh

Đây là FE delta trên 3 màn hiện hữu của module CRM (DEC-017, FR-018). Slot 9 = ticket_source_pancake với label "Pancake CRM".

3 file cần thay đổi:

  1. diva-admin/src/modules/crm/types.ts:146-164 — ADD const + array entry
  2. diva-admin/src/modules/crm/i18n/vi.ts:130-137 — ADD i18n key
  3. diva-admin/src/modules/crm/pages/Tickets.tsx:128-149 — ADD sourceDescriptions entry

Sau khi 3 file thay đổi, dropdown filter ở Tickets.tsx, dropdown form ở TicketMultipleAdd.tsx, dropdown drawer ở CustomerTicketManager.tsx tự render slot 9 mà KHÔNG cần code change thêm.

B2.SCR-06.2) Wireframe gắn vào layout existing

Demo trên Tickets.tsx — Filter dropdown

text
/crm/tickets

┌─────────────────────────────────────┬───────────────────────────────────────────┐
│ Panel filter trái (existing KEEP)   │ Panel data table (existing KEEP)          │
├─────────────────────────────────────┼───────────────────────────────────────────┤
│                                     │ Toolbar:                                  │
│ "Nguồn ticket"                      │ [+ Thêm] [Sửa] [Xóa]   Chip filter active│
│ ┌─────────────────────────────────┐ │                                           │
│ │ ☐ Tất cả              [Áp dụng]│ │ # │ Mã    │ Trạng thái│ Liên hệ      │... │
│ │ ☐ Nguồn 1  ⓘ                   │ │───┼───────┼───────────┼──────────────┼...│
│ │ ☐ Nguồn 2                       │ │ 1 │TK-001│ Mới       │Nguyễn Thị Mai│   │
│ │ ☐ Nguồn 3                       │ │ 2 │TK-002│ Mới       │Trần Văn Hùng │   │
│ │ ☐ Nguồn 4                       │ │ 3 │TK-003│ Đang gọi  │Lê Thị Hoa    │   │
│ │ ☐ Nguồn 5                       │ │   │      │           │              │   │
│ │ ☐ Nguồn 6                       │ │ Chi nhánh: Diva HCM Q1 - Q3              │
│ │ ☐ Nguồn 7  ⓘ                   │ │ Người phụ trách: telesale_a, _b, _c     │
│ │ ☐ Nguồn 8  ⓘ                   │ │                                           │
│ │ >>> ☑ Pancake CRM  ⓘ <<<        │ │                                           │
│ │     (slot 9 mới — selected)     │ │ Pagination: 1/100 (20/page)              │
│ └─────────────────────────────────┘ │                                           │
│                                     │                                           │
│ ⓘ Hover slot 9 → tooltip:           │                                           │
│ "Lead từ Pancake CRM (40+ kênh:     │                                           │
│  FB, Zalo, TikTok, Google Ads,      │                                           │
│  Whatsapp, Shopee...)"               │                                           │
│                                     │                                           │
│ "Trạng thái" (existing KEEP)        │                                           │
│ "Người phụ trách" (existing KEEP)   │                                           │
│ "Người nhận bàn giao" (KEEP)        │                                           │
│                                     │                                           │
└─────────────────────────────────────┴───────────────────────────────────────────┘

Khi user chọn "Pancake CRM" trong filter:
→ Table phải hiển thị các ticket có source_id='ticket_source_pancake'
→ Chip filter active hiển thị "Nguồn: Pancake CRM" (xóa được)

Demo trên TicketMultipleAdd.tsx — Form Tạo Ticket dropdown

text
/crm/tickets — Click [+ Thêm] → Drawer phải mở:

╔════════════════════════════════════════════════════════════╗
║ Tạo ticket mới                                       [✕]  ║
║ ────────────────────────────────────────────────────────── ║
║                                                            ║
║ Khách hàng *           [Nguyễn Thị Mai (0912345678)    ▾]  ║
║ Liên hệ *              [+84 912 345 678                ▾]  ║
║ Nhóm sản phẩm          [Liệu trình da liễu             ▾]  ║
║ Dịch vụ                [Liệu trình trị mụn 5 buổi      ▾]  ║
║ Chiến dịch tiếp thị    [Promotion 30/04                ▾]  ║
║ Chi nhánh *            [Diva HCM Q1                    ▾]  ║
║ Ngày hết hạn *         [15/05/2026 23:59               📅] ║
║ Nhóm đối tượng *       [Telesale                       ▾]  ║
║ Người phụ trách        [— Tự động phân công —          ▾]  ║
║ Người nhận bàn giao    [—                              ▾]  ║
║ Ghi chú                [_______________________________]   ║
║                                                            ║
║ >>> Nguồn  [Pancake CRM ▾] (slot 9 mới — chọn được) <<<   ║
║     Dropdown 9 option: Nguồn 1, ..., Nguồn 8, Pancake CRM ║
║                                                            ║
║                              [Hủy]      [Tạo ticket]      ║
╚════════════════════════════════════════════════════════════╝

(Note: Khi telesale tạo ticket manual với Nguồn = "Pancake CRM",
 ticket vẫn được tạo bình thường; chỉ là field source_id metadata.
 Tự động flow vẫn là webhook → auto-create ticket, KHÔNG qua form này.)

Demo trên CustomerTicketManager.tsx — Drawer KH

text
/customers/c8f7a3e4-... — Khi click "Lịch sử ticket" panel phải:

                                ╔════════════════════════════════════════╗
                                ║ Khách hàng: Nguyễn Thị Mai        [✕] ║
                                ║ SĐT: +84 912 345 678                  ║
                                ║ ────────────────────────────────────── ║
                                ║                                        ║
                                ║ Lịch sử ticket (5 gần nhất)            ║
                                ║                                        ║
                                ║ • TK-100 [Đã hoàn thành]              ║
                                ║   Nguồn: Pancake CRM                   ║
                                ║   Ngày: 10/05/2026                     ║
                                ║                                        ║
                                ║ • TK-085 [Đã hoàn thành]              ║
                                ║   Nguồn: Nguồn 1                       ║
                                ║   Ngày: 25/04/2026                     ║
                                ║                                        ║
                                ║                  [+ Thêm ticket]       ║
                                ║                                        ║
                                ║ (Click "+ Thêm ticket" mở             ║
                                ║  TicketMultipleAdd với khách hàng     ║
                                ║  pre-filled. Dropdown source render    ║
                                ║  9 option như SCR-06.2 trên.)          ║
                                ╚════════════════════════════════════════╝

B2.SCR-06.3) Phân loại reuse

FilePhân loạiDelta
types.ts:146-164🔧 ExtendADD const TICKET_SOURCE_PANCAKE = "ticket_source_pancake" + append vào array TicketSources
i18n/vi.ts:130-137🔧 ExtendADD key ticket_source_pancake: "Pancake CRM"
i18n/en.ts (nếu có)🔧 ExtendADD key ticket_source_pancake: "Pancake CRM" (giữ EN brand name)
Tickets.tsx:128-149🔧 ExtendADD entry vào sourceDescriptions: ticket_source_pancake: '<span>Lead từ Pancake CRM (40+ kênh: FB, Zalo, TikTok, Google Ads, Whatsapp, Shopee...)</span>'
Tickets.tsx:851-865 (filter dropdown)✅ ReuseAuto-render slot 9 (no code change)
TicketMultipleAdd.tsx:758 (form dropdown)✅ ReuseAuto-render
CustomerTicketManager.tsx drawer✅ ReuseAuto-render
Module report (customer-service, telesales) filtersTBD-verify Phase 4Grep audit 'ticket_source_' — nếu hardcode → +0.5d delta

B2.SCR-06.4) Quy ước

MụcQuy ước
Migration orderingMigration INSERT crm_master_data row ticket_source_pancake PHẢI run TRƯỚC FE deploy — tránh dropdown render text undefined
PermissionMọi user có ticket_management.access đều thấy slot 9 trong filter; khác với Settings/Pancake (Admin only)
i18n fallbackNếu i18n key thiếu → fallback display "ticket_source_pancake" (technical) — KHÔNG render trống
TooltipHover icon ⓘ cạnh "Pancake CRM" hiển thị mô tả (HTML span) như B2.SCR-06.2

B2.SCR-06.5) Variants

Variant IDĐiều kiệnWireframeQA ref
Variant ADefault — slot 9 hiển thị 9 option(B2.SCR-06.2)TC-UI-SCR06-001
Variant BMigration chưa run, FE đã deployFallback display "ticket_source_pancake" + log warningTC-UI-SCR06-002
Variant CUser không có ticket_management.accessDropdown hidden hoàn toànTC-PERM-SCR06-001

B2.SCR-06.6) Hành vi tương tác (đã liệt kê ở B0.9 SCR-06)


B3) Luồng người dùng (Flows — BẮT BUỘC Profile L)

Luồng 1 — Setup ban đầu (Admin first-time)

Thời gian ước tính: 15 phút (giả định Admin đã có Pancake credentials).

Luồng 2 — Replay DLQ event (admin recovery)

Luồng bulk replay:

Luồng 3 — Outage detected (auto + admin verify)

MTTR target: ≤5 phút sau khi Pancake recovery.

Luồng 4 — Telesale nhận lead Pancake (auto-rendered, ref SCR-06)

Lưu ý: Telesale flow chạy trên UI existing (Tickets.tsx + ticket detail page) — SCR-06 chỉ là delta dropdown source. Không thuộc UI Spec này về detail.

Luồng 5 — Loose mode (branch=NULL, admin manual assign)


B4) Đặc tả thông báo (Notification Spec)

3 template mới (DEC-024) + dispatch qua notification-v2-api Hasura action sendNotifications.

Điều kiện kích hoạtTên templateKênhRecipientMẫu nội dungChống trùng (dedupe)
Ticket Pancake được auto-assign cho telesale (FR-011 STEP 12)noti_ticket_assigned_pancakeIn-app push + (email/SMS theo preference telesale)telesale assignee_id"Bạn vừa nhận lead Pancake CRM: {customer_name} ({phone}). Nguồn: {source_name}. Phản hồi trong 30 phút."1 notification / ticket_id (không gửi lại nếu re-assign)
Source mới detect chưa map branch (Cron 3) HOẶC ticket tạo với branch=NULL (Loose mode)noti_pancake_unmapped_branchIn-app pushAdmin role + branch-manager NULL"Có nguồn Pancake mới chưa map chi nhánh: '{source_name}'. {N} ticket đang chờ map. Click để xem."Dedupe 1h per source_id
Outage detected (Cron 4 status → outage_started)noti_pancake_outageIn-app push (DEC-012 default)Admin role"Pancake không nhận event {duration} phút. Đã chuyển sang polling fallback (Cron 6). Click xem chi tiết."1 notification / outage_started event (PD-012 confirm)
Outage recovered (status → outage_recovered)noti_pancake_outage (variant)In-app pushAdmin role"Pancake đã khôi phục. Outage tổng {duration_min} phút. Đã rescue {N} event miss."1 / recovery event
Bulk replay xong (partial fail)(existing pattern, reuse)In-app pushAdmin (who triggered)"Bulk replay hoàn tất: {X}/{N} thành công. {Y} vẫn lỗi DLQ."1 / bulk_replay_id
DLQ tồn đọng cao bất thường (≥50 trong 1h — Cron health-alert)(extend noti_pancake_outage variant dlq_high)In-app pushAdmin role"Cảnh báo: {N} sự kiện DLQ trong 1 giờ. Có thể có lỗi hệ thống. Click xem."Dedupe 30 phút

B4.1) Template seed (master data migration)

sql
INSERT INTO notification_template (id, name, channel, ...)
VALUES
  ('noti_ticket_assigned_pancake', 'Lead Pancake CRM được phân công', 'push', ...),
  ('noti_pancake_unmapped_branch', 'Nguồn Pancake chưa map chi nhánh', 'push', ...),
  ('noti_pancake_outage', 'Pancake outage cảnh báo', 'push', ...);

Detail SQL → dev-spec.md C4 + C7 migration.

B4.2) UI notification badge

  • Telesale noti_ticket_assigned_pancake: hiện trên notification bell + badge count; click → deeplink /crm/tickets/{ticket_id} (detail page).
  • Admin noti_pancake_unmapped_branch: click → deeplink /settings/pancake-crm/source-routing?filter=unmapped.
  • Admin noti_pancake_outage: click → deeplink /settings/pancake-crm/health.

B5) Ma trận phân quyền (Permission Matrix — BẮT BUỘC M+)

Mỗi action × role × screen có 3 cột: Render condition + Denied feedback + Khi thu hồi quyền.

B5.1) Module pancake_crm_integration (admin portal)

ModuleActionRoleRender conditionDenied feedbackKhi thu hồi quyền
pancake_crm_integrationaccessAdminalways renderẩn sau refetch/relogin (toast "Quyền của bạn đã thay đổi")
pancake_crm_integrationaccessManagerKHÔNG render (default seed)hidden (menu Sidebar không có DOM)giữ hidden
pancake_crm_integrationaccessTelesaleKHÔNG renderhiddengiữ hidden
pancake_crm_integrationaccessPOS-onlyKHÔNG renderhiddengiữ hidden
pancake_crm_integrationcreateAdminrender SCR-02 button "Đồng bộ", SCR-03 button "Phát lại"ẩn sau refetch
pancake_crm_integrationcreatenon-AdminKHÔNG renderhidden
pancake_crm_integrationupdateAdminrender SCR-01 form save, SCR-02 inline-edit, SCR-01 toggle kill switchinline-edit disabled + tooltip "Quyền đã thay đổi"
pancake_crm_integrationupdatenon-AdminKHÔNG renderhidden
pancake_crm_integrationdeleteAdminrender SCR-02 hover action "Vô hiệu hóa source"ẩn sau refetch
pancake_crm_integrationdeletenon-AdminKHÔNG renderhidden
pancake_crm_integrationview_allAdminrender SCR-03 full audit list + drawer raw_payloaddrawer raw_payload trả null từ API
pancake_crm_integrationview_allnon-AdminKHÔNG renderhidden (kể cả URL direct)
pancake_crm_integrationreplay_dlqAdminrender SCR-03 button "Phát lại" + bulk actionbutton hidden sau refetch
pancake_crm_integrationreplay_dlqnon-AdminKHÔNG renderhidden (button không có DOM)

B5.2) Module ticket_management (SCR-06 — không thay đổi)

ModuleActionRoleRender conditionDenied feedback
ticket_managementaccessAdmin/Manager/Telesalerender dropdown 9 source (slot 9 = Pancake CRM)
ticket_managementaccessPOS-onlyKHÔNG render Tickets modulehidden (toàn route)

SCR-06 không tạo quyền mới — reuse ticket_management existing permission.

B5.3) 3 mode Denied feedback canonical (chọn 1 cho mỗi action)

ModeKhi dùngDiva pattern
hiddenUser KHÔNG nên biết feature tồn tại (Settings/Pancake cho non-admin)Menu không có DOM + route guard redirect /403
disabledUser nên biết feature có nhưng cần quyền (vd inline-edit branch khi network fail, KHÔNG phải permission case)Grey button + tooltip lý do
redirectUser gõ URL direct mà không có quyềnRoute guard → /403 + toast

Trong feature này: Toàn bộ permission denied feedback = hidden (Diva rule "ẩn menu/button, không disable").

B5.4) EXT-4 RBAC field-level (defer MVP-2 ready)

FieldAdminManagerTelesalePortalView modeAPI behavior
api_keyfull (encrypted display sau reveal)hiddenhiddenadminnull cho non-adminHasura permission filter cột
webhook_tokenfull sau revealhiddenhiddenadminnullHasura permission
raw_payload (audit)fullhiddenhiddenadminnullHasura permission
vip_tag_names[]fullhiddenhiddenadminnullHasura permission
Source name (Pancake)fullfull (defer Marketing role MVP-2 read-only)hiddenadminfull hoặc summaryHasura

Enforcement: FE hide chỉ là UX; backend Hasura permission canonical — field nhạy cảm trả null cho non-admin (KHÔNG mask ●●●●●● trong DOM để tránh DOM cache leak).


B6) Ma trận trạng thái (State Matrix — BẮT BUỘC khi có lifecycle)

B6.1) pancake_webhook_event.status lifecycle (12 trạng thái — LIFECYCLE-001)

Ref PRD LIFECYCLE-001. Section này map UI/action theo state.

Trạng thái (code)Display VIBadge intentUI badgeAction cho phép (SCR-03 row)Action ẩn/khóaField cho phép editSide effect
ingestedĐã nhận (chưa xác thực)muted[Đã nhận] xám"Sao chép JSON", "Xem cùng record""Phát lại" (chưa xử lý gì)(audit immutable)non-terminal — chờ verify
receivedĐã xác thựcwarning[Đã xác thực] cam"Sao chép JSON", "Xem cùng record""Phát lại" (đang xử lý)non-terminal — Hasura trigger fired
processingĐang xử lýwarning[Đang xử lý] cam-spin"Sao chép JSON""Phát lại"non-terminal — handler running
processedĐã xử lýsuccess[Đã xử lý ✓] xanh"Sao chép JSON", "Xem cùng record", "Xem ticket đã tạo""Phát lại" (idempotency — không cho replay)terminal — ticket created
auth_failedLỗi xác thựcnegative[Lỗi xác thực ⚠] đỏ"Sao chép JSON", "Phát lại" (sau khi fix token)terminal — alert ops nếu > 10/min
ip_blockedBị chặn IPnegative[Bị chặn IP ⚠] đỏ"Sao chép JSON""Phát lại" (cần fix IP whitelist trước)terminal
parse_errorLỗi cấu trúc dữ liệunegative[Lỗi cấu trúc ⚠] đỏ"Sao chép JSON", "Phát lại" (sau khi Pancake fix payload)terminal
skipped_duplicateBỏ qua (trùng lặp)muted[Bỏ qua (trùng)] xám"Sao chép JSON", "Xem cùng record""Phát lại" (đúng nghiệp vụ)terminal — last_received_at++
skipped_source_disabledBỏ qua (nguồn đã tắt)muted[Bỏ qua (nguồn tắt)] xám"Sao chép JSON", "Xem cùng record""Phát lại" (cần bật source trước qua SCR-02)terminal
skipped_kill_switchBỏ qua (đã tắt hệ thống)muted[Bỏ qua (kill switch)] xám"Sao chép JSON""Phát lại" (cần bật kill switch SCR-01)terminal
skipped_opt_outBỏ qua (khách từ chối marketing)muted[Bỏ qua (opt-out)] xám"Sao chép JSON", "Xem cùng record""Phát lại" (đúng compliance)terminal
dead_letterLỗi DLQ (chờ phát lại)negative[Lỗi DLQ ⚠] đỏ"Phát lại", "Sao chép JSON", "Xem cùng record", "Đánh dấu permanently_failed"non-terminal — bulk select OK
permanently_failedLỗi vĩnh viễn (cần xem xét)negative-dark[Lỗi vĩnh viễn ⚠] đỏ đậm"Sao chép JSON", (manual escalate)"Phát lại" (đã hết retry budget)terminal — alert manual review

Mermaid lifecycle (ref PRD):

B6.2) pancake_outage_state.status lifecycle (3 trạng thái — LIFECYCLE-002)

Trạng tháiDisplay VIBadge intentUI renderCron behavior
healthyKhỏe mạnhsuccessSCR-04 card "✓ Khỏe mạnh" + chart xanhCron 6 không chạy
outage_startedMất kết nốinegativeSCR-04 banner đỏ sticky + card "🚨 Mất kết nối"Cron 6 adaptive polling kích hoạt (60-900s)
outage_recoveredĐang phục hồiwarningSCR-04 banner xanh "Đang phục hồi (chờ sustain 5 phút)"Cron 6 stop sau 5 phút sustain

B6.3) pancake_connection.status (4 trạng thái — không phải lifecycle phức tạp nhưng cần map UI)

Trạng tháiDisplay VIBadgeSCR-01 UI behavior
activeĐang hoạt độngsuccess ✓ xanhDefault Variant A; tất cả field editable; kill switch hoạt động
pausedTạm dừngwarning ⏸ vàngForm vẫn editable; banner cam "Đã tạm dừng — webhook không nhận event"; button "Bật lại"
suspended_by_pancakeBị Pancake khóanegative 🚫 đỏVariant F; banner đỏ + button "Kích hoạt lại webhook"; kill switch disabled
errorLỗinegative ⚠ đỏBanner "Lỗi cấu hình: {error_message}"; button "Kiểm tra kết nối" để re-verify

B6.4) Ticket source slot 9 (KHÔNG có lifecycle riêng)

Ticket Pancake follow ticket lifecycle existing (ticket_status_new → ... → ticket_status_done). KHÔNG đổi state machine ticket. Chỉ field source_id='ticket_source_pancake' tăng coverage trong filter UI.

B6.5) EXT-3 Lifecycle mở rộng — action × state × persona

Đã cover trong B6.1 trên (12 trạng thái + action allowed). Không cần bảng riêng vì chỉ Admin persona dùng UI (chứ không có multi-role approval flow phức tạp).


B7) Từ điển nội dung hiển thị (Copy Text Dictionary)

Liệt kê mọi copy mới/thay đổi xuất hiện trong UI Spec. Cross-ref từ wireframes B2 và variant.

B7.1) Header / Tabs / Sidebar

KeyTiếng ViệtEN (i18n nếu cần)Ngữ cảnh
pancake.sidebar.menuTích hợp Pancake CRMPancake CRM IntegrationSidebar Settings entry
pancake.scr05.headerTích hợp Pancake CRMPancake CRM IntegrationSCR-05 page title
pancake.tab.connectionKết nốiConnectionTab 1 label
pancake.tab.source_routingMap nguồnSource mappingTab 2 label
pancake.tab.auditLịch sử + DLQAudit + DLQTab 3 label
pancake.tab.healthSức khỏeHealthTab 4 label

B7.2) SCR-01 Connection — labels + buttons + copy

KeyTiếng ViệtNgữ cảnh
scr01.section.workspaceThông tin workspace PancakeSection header
scr01.field.workspace_idWorkspace IDField label
scr01.field.workspace_id.placeholderMã workspace PancakePlaceholder
scr01.field.api_keyAPI keyField label
scr01.field.api_key.placeholderNhập API key từ Pancake adminPlaceholder
scr01.btn.test_connectionKiểm tra kết nốiPrimary CTA
scr01.btn.reveal_passwordHiện 5sToggle reveal API key
scr01.section.webhookCấu hình webhook (paste vào Pancake admin)Section header
scr01.field.webhook_urlURL webhookReadonly label
scr01.btn.copy_urlSao chép URLIcon button
scr01.field.webhook_tokenToken webhookReadonly label
scr01.btn.copy_tokenSao chépIcon button
scr01.btn.regenerate_tokenTạo lạiDestructive button
scr01.section.kill_switchTắt khẩn cấp toàn hệ thống (kill switch)Section header
scr01.kill_switch.labelĐang nhận event PancakeToggle label
scr01.kill_switch.tooltipTắt sẽ chuyển mọi event sang trạng thái "Bỏ qua (đã tắt hệ thống)". Pancake không bị suspend (vẫn trả 200).Tooltip
scr01.section.vip_tagsDanh sách tag VIP (theo tên, không phân biệt hoa thường)Section header
scr01.vip_tags.helperMỗi dòng = 1 tag. Khi Pancake gửi event chứa tag trùng tên, hệ thống coi khách là VIP và luôn tạo ticket dù chỉ update info.Helper text
scr01.vip_tags.counter{X} / 100 dòngChar counter
scr01.btn.saveLưu cấu hìnhPrimary CTA
scr01.btn.cancelHủy thay đổiSecondary CTA
scr01.last_modifiedLưu lần cuối: {datetime} bởiFooter info
scr01.dirty_indicatorĐã chỉnh sửaIndicator
scr01.suspended.banner.titlePancake đã tự động khóa webhookVariant F
scr01.suspended.banner.bodyLý do: 80% error / 30 phút (suspension rule). Phát hiện lúc: {datetime} (Cron 4 detect). Mọi event mới sẽ KHÔNG được Pancake gửi tới Diva.Variant F body
scr01.suspended.action_requiredHành động cần làm:\n1. Vào Pancake admin > Tích hợp > Webhook → Bật lại thủ công\n2. Sau đó bấm nút bên dưới để cập nhật trạng thái DivaVariant F instructions
scr01.btn.reactivate_webhookKích hoạt lại webhookVariant F CTA
scr01.first_time.welcome.titleChào mừng đến với Tích hợp Pancake CRMVariant B welcome
scr01.first_time.welcome.bodyNhập thông tin workspace để bắt đầu nhận lead tự động từ Pancake. Cần Workspace ID + API key (lấy từ Pancake admin > Tích hợp).Variant B body
scr01.first_time.webhook_pendingURL + Token webhook sẽ được tự động tạo sau khi lưu thành công.Variant B helper
scr01.toast.save_successĐã lưu cấu hình Pancake CRM.Toast success
scr01.toast.save_first_timeĐã lưu. Hệ thống đã tạo URL webhook. Tiếp theo: map nguồn → chi nhánh.Toast first-time
scr01.toast.test_successKết nối thành công. Đã phát hiện {N} nguồn từ Pancake workspace.Toast test OK
scr01.error.test_failKhông thể kết nối Pancake. Kiểm tra api_key + thử lại. Mã:Banner test fail
scr01.modal.regen_token.titleTạo lại Token webhookConfirm modal title
scr01.modal.regen_token.bodyToken mới sẽ vô hiệu hóa cấu hình Pancake hiện tại. Mọi event tới URL cũ sẽ bị từ chối. Gõ 'TẠO LẠI' để xác nhận.Modal body
scr01.modal.regen_token.confirm_wordTẠO LẠIConfirm word user phải gõ
scr01.modal.kill_switch_on.titleTắt nhận event PancakeConfirm modal
scr01.modal.kill_switch_on.bodyTắt sẽ ngừng nhận lead Pancake. Mọi event sẽ chuyển trạng thái 'Bỏ qua (đã tắt hệ thống)'. Xác nhận?Modal body
scr01.validation.workspace_idWorkspace ID không hợp lệ. Vui lòng kiểm tra (≤64 ký tự, UUID Pancake).Inline error
scr01.validation.api_key_shortAPI key phải ≥16 ký tự.Inline error
scr01.validation.api_key_invalidAPI key sai. Kiểm tra trên trang Pancake admin.Server validation

B7.3) SCR-02 Source routing

KeyTiếng ViệtNgữ cảnh
scr02.summaryTổng: {N} nguồn • Đã map chi nhánh: {M} • Đang bật: {K} • Mới chưa map:Summary chip
scr02.search.placeholderTìm theo tên nguồn...Search input
scr02.filter.status.allTất cảFilter default
scr02.filter.status.activeĐang bậtFilter option
scr02.filter.status.inactiveĐang tắtFilter option
scr02.filter.branch.allTất cảFilter default
scr02.btn.syncĐồng bộ từ PancakePrimary CTA
scr02.col.idx#Column header
scr02.col.source_nameTên nguồn PancakeColumn header
scr02.col.branchChi nhánh DivaColumn header
scr02.col.is_activeBật/TắtColumn header
scr02.col.last_sync_atCập nhật cuốiColumn header
scr02.branch.loose_modeĐể trống (Loose mode)Branch dropdown option
scr02.branch.choose— Chọn chi nhánhDefault placeholder dropdown
scr02.row.new_badgeMớiBadge cho source chưa map
scr02.row.inactive_labelĐang tắt — KHÔNG nhận eventRow helper khi is_active=false
scr02.row.helperClick vào dòng để xem chi tiết nguồn + lịch sử event 7 ngàyHelper text
scr02.empty.titleChưa có nguồn nào được đồng bộ từ PancakeVariant B title
scr02.empty.bodyKhi bạn click "Đồng bộ từ Pancake", hệ thống sẽ gọi Pancake API và tự động thêm danh sách nguồn (FB page, Zalo OA, TikTok...). Sau đó bạn map từng nguồn sang chi nhánh Diva tương ứng.Variant B body
scr02.empty.preconditionCần Tab 1 đã thiết lập kết nối thành công.Variant B helper
scr02.empty_filter.titleKhông có nguồn nào khớp bộ lọc. {N} nguồn tổng.Empty filter
scr02.error.titleKhông thể đồng bộ từ PancakeVariant E
scr02.error.bodyPancake API không phản hồi (timeout 30s). Hiển thị danh sách cache từ {timestamp} ({ago} trước). Mã sự cố: {trace_id}. Sẽ tự động thử lại sau 30 phút.Variant E body
scr02.precondition.titleChưa thiết lập kết nối PancakeVariant F
scr02.precondition.bodyVui lòng hoàn tất Tab 1 (Kết nối) trước khi map nguồn → chi nhánh.Variant F body
scr02.btn.goto_tab1Sang Tab 1Variant F CTA
scr02.toast.branch_updatedĐã cập nhật chi nhánh cho '{source_name}' → '{branch_name}'.Toast inline-edit success
scr02.toast.toggle_offĐã tắt nguồn '{source_name}'. Sẽ ngừng nhận event.Toast + undo
scr02.toast.undoHoàn tácUndo toast button
scr02.toast.sync_successĐã đồng bộ. Phát hiện {N} nguồn ({M} nguồn mới). Vui lòng map nguồn mới sang chi nhánh.Toast sync OK
scr02.modal.loose_mode.titleLoose mode (chi nhánh trống)Confirm modal
scr02.modal.loose_mode.bodyLoose mode: ticket sẽ tạo với chi nhánh NULL, leader manual assign sau. Xác nhận?Modal body
scr02.modal.disable_with_traffic.titleTắt nguồn đang có trafficConfirm modal
scr02.modal.disable_with_traffic.bodyNguồn '{source_name}' có {N} event trong 24h gần nhất. Tắt sẽ ngừng nhận event. Xác nhận?Modal body
scr02.drawer.titleChi tiết nguồn PancakeDrawer title
scr02.drawer.activity_7dHoạt động 7 ngày gần nhấtDrawer section
scr02.drawer.btn.view_auditXem nhật ký event 7 ngàyDrill-down sang SCR-03

B7.4) SCR-03 Audit + DLQ

KeyTiếng ViệtNgữ cảnh
scr03.filter.date_rangeKhoảng thời gianFilter label
scr03.filter.date_range.default7 ngày gần nhấtDefault display
scr03.filter.statusTrạng tháiFilter label
scr03.filter.sourceNguồnFilter label
scr03.filter.include_testBao gồm test eventsCheckbox
scr03.btn.clear_filterXóa bộ lọcSecondary CTA
scr03.summaryTổng: {N} sự kiện • Đã xử lý: {M} ({pct}%) • Lỗi DLQ: {K} • Bỏ qua:Summary chip
scr03.col.checkbox(no header text)Bulk select
scr03.col.event_idMã sự kiệnColumn header
scr03.col.record_idRecord IDColumn header
scr03.col.statusTrạng tháiColumn header
scr03.col.sourceNguồnColumn header
scr03.col.created_atThời gianColumn header
scr03.row.actions_hintHover một dòng để hiện hành động: Phát lại / Sao chép JSON / Xem cùng recordHelper text
scr03.empty.titleChưa nhận sự kiện nào từ Pancake trong khoảng đã chọnVariant B
scr03.empty.checklistKiểm tra:\n• Tab 1 Kết nối: trạng thái có 'Đang hoạt động' không?\n• Tab 2 Map nguồn: ≥1 nguồn được bật?\n• Pancake admin: URL webhook đã paste đúng?Variant B body
scr03.empty.btn.expand_rangeMở rộng khoảng thời gian (30 ngày)Variant B CTA
scr03.empty_filter.titleKhông có sự kiện nào khớp bộ lọcVariant B2
scr03.empty_filter.body{N} sự kiện tổng trong khoảng {timeframe} — có lẽ bộ lọc quá hẹpVariant B2 body
scr03.bulk.progressĐang phát lại {X} / {N} sự kiện DLQ...Variant F
scr03.bulk.rate_helperRate: 5 sự kiện / giây (ước tính {N} giây còn lại)Variant F
scr03.bulk.btn.cancelHủy bulkVariant F
scr03.bulk.successĐã phát lại {X}/{N} sự kiện thành công. {Y} sự kiện vẫn lỗi (chuyển sang DLQ). Lý do thường gặp: STEP 9 round-robin fail (không có telesale active).Variant F success
scr03.bulk.view_failuresXem chi tiết {N} lỗiVariant F
scr03.dlq_alert.titleCảnh báo: {N} sự kiện DLQ trong 1 giờ gần nhất (bình thường <5/giờ)Variant G
scr03.dlq_alert.bodyCó thể có vấn đề hệ thống (round-robin, telesale roster). Kiểm tra Tab 4 Sức khỏe + báo Tech Lead nếu cần.Variant G body
scr03.dlq_alert.btn.filterLọc chỉ DLQVariant G CTA
scr03.dlq_alert.btn.healthSang Tab 4Variant G CTA
scr03.btn.replay_singlePhát lại sự kiệnRow action / drawer CTA
scr03.btn.copy_jsonSao chép JSONRow action
scr03.btn.view_same_recordXem cùng recordRow action
scr03.drawer.titleChi tiết sự kiệnDrawer title
scr03.drawer.tab.payloadPayloadTab
scr03.drawer.tab.headersHeadersTab
scr03.drawer.tab.historyLịch sử trạng tháiTab
scr03.drawer.error_messageThông báo lỗiSection
scr03.drawer.raw_payloadRaw payload (JSON)Section
scr03.drawer.field.event_idMã sự kiệnField
scr03.drawer.field.record_idRecord IDField
scr03.drawer.field.statusTrạng tháiField
scr03.drawer.field.event_typeLoạiField
scr03.drawer.field.is_testTest eventField (Yes/No)
scr03.drawer.field.retry_countSố lần retryField
scr03.drawer.field.created_atTạo lúcField
scr03.drawer.field.processed_atXử lý lúcField
scr03.drawer.field.source_ipIP nguồnField
scr03.modal.replay.titlePhát lại sự kiệnConfirm modal
scr03.modal.replay.bodyPhát lại sự kiện {event_id_short}? Trạng thái sẽ chuyển '{current_status}' → 'Đã xác thực'.Modal body
scr03.modal.bulk_replay.titlePhát lại bulk {N} sự kiệnConfirm modal
scr03.modal.bulk_replay.bodyPhát lại {N} sự kiện? Sẽ chạy với rate 5 sự kiện/giây (~{seconds} giây).Modal body
scr03.toast.replay_successĐã phát lại sự kiện. Trạng thái chuyển 'Lỗi DLQ' → 'Đã xác thực'. Sẽ xử lý trong vài giây.Toast
scr03.toast.replay_not_allowedKhông thể phát lại sự kiện trạng thái '{status_vi}'. Chỉ áp dụng cho 'Lỗi DLQ', 'Lỗi xác thực', 'Lỗi cấu trúc dữ liệu'.Toast
scr03.toast.copy_json_successĐã sao chép JSON payload vào clipboard.Toast
scr03.validation.date_range_too_wideKhoảng thời gian tối đa 90 ngày. Vui lòng thu hẹp.Inline error
scr03.error.titleKhông thể tải audit log. {error}. Mã sự cố: {trace_id}.Variant E

B7.5) SCR-04 Health

KeyTiếng ViệtNgữ cảnh
scr04.timeframe.24h24 giờToggle
scr04.timeframe.7d7 ngàyToggle
scr04.last_updatedCập nhật cuối:Header info
scr04.card.success_rate.titleTỉ lệ thành côngMetric card
scr04.card.success_rate.targetMục tiêu ≥ 99,5%Metric card target
scr04.card.latency.titleĐộ trễ webhook → ticket (p95)Metric card
scr04.card.latency.targetMục tiêu ≤ 30 giâyMetric card target
scr04.card.auto_assign.titleAuto-assign telesale (chính xác)Metric card
scr04.card.auto_assign.targetMục tiêu ≥ 90%Metric card target
scr04.card.pancake_state.titleTrạng thái PancakeMetric card
scr04.card.pancake_state.healthyKhỏe mạnhStatus
scr04.card.pancake_state.healthy_helperNhận event cuối:Helper
scr04.card.pancake_state.outageMất kết nốiStatus
scr04.card.pancake_state.outage_durationĐã {minutes} phútHelper
scr04.card.pancake_state.polling_helperPolling: {seconds}s fallback activeHelper
scr04.card.target_passedĐạtBadge ✓
scr04.card.target_warningCần xem xétBadge ⚠
scr04.card.btn.detailXem chi tiếtDrill-down CTA
scr04.chart.titleXu hướng 7 ngày (Tỉ lệ thành công + Số event)Chart title
scr04.chart.legend.success_rateTỉ lệ thành côngLegend
scr04.chart.legend.event_countEvent nhận / ngàyLegend
scr04.outage_history.titleLịch sử outage (10 gần nhất)Section
scr04.outage_history.col.started_atBắt đầuColumn
scr04.outage_history.col.ended_atKết thúcColumn
scr04.outage_history.col.durationThời lượngColumn
scr04.outage_history.col.miss_rescuedEvent miss đã rescueColumn
scr04.outage_history.row.via_polling(Cron 6 rescue)Sub-label
scr04.outage_history.row.via_reconciliation(Cron 7 rescue)Sub-label
scr04.mttrMTTR (Mean Time To Recovery): {minutes} phút (target ≤ 5 phút)Footer
scr04.outage_banner.titlePancake outage — không nhận event trong 5 phút gần nhấtVariant B banner
scr04.outage_banner.bodyBắt đầu: {datetime} ({ago} trước). Trạng thái: outage_started. Cron 6 đã kích hoạt adaptive polling fallback (interval {seconds}s). Đã rescue: {N} event miss tới giờ.Variant B body
scr04.outage_banner.btn.snoozeTạm tắt cảnh báo (1h)Banner CTA
scr04.outage_banner.btn.detailXem chi tiết outageBanner CTA
scr04.warning.success_low.titleTỉ lệ thành công thấp bất thường ({pct}% — bình thường ≥99,5%)Variant F
scr04.warning.success_low.bodyKhả năng: tăng DLQ trong vài giờ qua. Kiểm tra Tab 3 lọc status 'Lỗi DLQ'.Variant F body
scr04.warning.success_low.btnSang Tab 3 lọc DLQVariant F CTA

B7.6) SCR-06 Dropdown delta

KeyTiếng ViệtNgữ cảnh
ticket_source_pancakePancake CRMi18n key — dropdown label slot 9
sourceDescriptions.ticket_source_pancakeLead từ Pancake CRM (40+ kênh: FB, Zalo, TikTok, Google Ads, Whatsapp, Shopee...)sourceDescriptions HTML span hover

B7.7) Notification copy

TemplateTitle (push)BodyDeeplink
noti_ticket_assigned_pancakeLead mới từ Pancake CRMBạn vừa nhận lead Pancake: {customer_name} ({phone}). Nguồn: {source_name}. Phản hồi trong 30 phút./crm/tickets/{ticket_id}
noti_pancake_unmapped_branchNguồn Pancake chưa mapCó nguồn '{source_name}' chưa map chi nhánh. {N} ticket đang chờ map./settings/pancake-crm/source-routing?filter=unmapped
noti_pancake_outagePancake outage cảnh báoPancake không nhận event {duration_min} phút. Đã chuyển sang polling fallback./settings/pancake-crm/health
noti_pancake_outage (recovery)Pancake đã khôi phụcOutage tổng {duration_min} phút. Đã rescue {N} event miss./settings/pancake-crm/health

B7.8) Mapping thuật ngữ chuẩn (A9) ↔ UI label

Canonical (A9 PRD)UI label người dùng thấyTooltip/helpLý do
Pancake CRMPancake CRM (giữ EN brand)"Sản phẩm CRM của công ty Pancake (≠ Pancake POS)"Brand name
Record (Pancake)"Sự kiện record" (trong UI) hoặc "Lead Pancake" (trong notification)"Mỗi 'record' Pancake = 1 khách hàng tiềm năng"Tiếng Việt thân thiện hơn
Source (Pancake)"Nguồn Pancake" hoặc "Nguồn" (trong context)"Page Pancake (FB page, Zalo OA, TikTok...)"Phân biệt rõ với "Nguồn ticket" của Diva
Idempotency 3-tuple(không hiện UI — backend only)Không user-facing
Smart update Q2.b"Chỉ tạo ticket khi thay đổi quan trọng"(chi tiết trong B9)Q2.b là code name, không nên hiện UI
Loose mode"Để trống (Loose mode)" trong dropdown branch"Ticket sẽ tạo với chi nhánh NULL, leader assign sau"Có giải thích trong tooltip
Round-robin per branch"Tự động phân công" trong placeholder"Hệ thống tự xoay vòng telesale trong chi nhánh"Đơn giản hơn
Advisory lock(không hiện UI — backend only)
Fail-open(không hiện UI — backend behavior)
Pancake suspension"Bị Pancake khóa" (badge)"Pancake tự khóa khi error >80%/30 phút"
DLQ replay"Phát lại sự kiện" (button) + "Lỗi DLQ (chờ phát lại)" (badge)"Dead Letter Queue — sự kiện lỗi đang chờ xử lý lại"Phát lại = ngôn ngữ tự nhiên
Webhook receiver(không hiện UI — Settings/Pancake là wrap context)
VIP tag"Tag VIP (theo tên)""Match theo tên tag, không phân biệt hoa thường"Giải thích NAME (không phải ID)
Test mode event"Test event" + "Bao gồm test events" (checkbox)"Event đánh dấu is_test=true. KHÔNG tính KPI, tự xóa sau 24h."
Opt-out marketing"Khách từ chối marketing""Khách có consent_data.marketing=false"

B7.9) Empty / Loading / Error chung

MụcTiếng ViệtNgữ cảnh
Loading skeleton(no copy, skeleton bars)All variants
Error genericKhông thể tải dữ liệu. Vui lòng thử lại. Mã:All SCR error
Network timeoutKhông thể kết nối Diva server. Đang thử lại...Toast banner
Permission denied (URL direct)Bạn không có quyền truy cập Tích hợp Pancake CRM. Liên hệ Admin nếu cần.Toast /403

B8) Sự kiện phân tích (Analytics — BẮT BUỘC L)

Track key user actions để đo adoption, friction setup, success vận hành.

Sự kiện (event_name)Điều kiện kích hoạtThuộc tính (properties)KPI liên quan
pancake.setup.tab1_openedAdmin mở SCR-01 lần đầuuser_id, branch_id, is_first_time (bool), connection_exists (bool), client_ts, server_ts, correlation_id, feature_flag_stateAdoption funnel: % admin mở Tab 1
pancake.setup.test_connection_clickedClick button "Kiểm tra kết nối"user_id, attempt_count (session), result{success, fail_auth, fail_timeout, fail_other}, latency_msSetup friction
pancake.setup.completedLần đầu lưu config thành công + có ≥1 source mapped + activeuser_id, time_from_first_open_minutes, sources_countKPI Manager adoption (PRD A8) — target 100% sau W4
pancake.source_routing.syncedClick "Đồng bộ từ Pancake" thành cônguser_id, sources_total, sources_new, sources_updated, latency_msSources discovery rate
pancake.source_routing.branch_mappedInline-edit branch dropdown autosave thành cônguser_id, source_id, branch_id, loose_mode (bool, branch_id IS NULL)% source mapped (mỗi week)
pancake.source_routing.toggledInline-toggle is_activeuser_id, source_id, is_active_new, event_count_24h_beforePilot rollout tracking W1-W4
pancake.audit.filter_appliedUser thay đổi filter SCR-03user_id, filter_status[], filter_source_count, filter_date_range_days, include_testDebug UX usage
pancake.audit.dlq_replay_singleClick "Phát lại sự kiện" singleuser_id, event_id, status_before, retry_count_beforeDLQ recovery rate
pancake.audit.dlq_replay_bulkBulk replay xonguser_id, bulk_count, success_count, fail_count, latency_msBulk replay efficiency
pancake.audit.json_copiedClick "Sao chép JSON"user_id, event_idDebug pattern
pancake.health.timeframe_toggledToggle 24h/7duser_id, timeframe_new{24h, 7d}Health monitoring usage
pancake.health.metric_drilldownClick metric carduser_id, metric_name, target_screen (SCR-03 with filter)Drill-down usage
pancake.kill_switch.toggledToggle kill switchuser_id, kill_switch_new (bool), reason (free text optional)Pilot rollback events
pancake.webhook.outage_detectedCron 4 detect (backend event, không phải UI)outage_started_at, event_count_5min_beforeKPI outage MTTR (FORMULA-006)
pancake.webhook.outage_recoveredCron status → outage_recoveredoutage_started_at, outage_ended_at, duration_min, miss_count_rescuedKPI MTTR
pancake.scr06.dropdown_usedUser chọn "Pancake CRM" trong filter Tickets / Formuser_id, role, surface{tickets_filter, form_add, customer_drawer}Adoption FE delta

B8.1) Schema event chuẩn (ref EXT B7A)

json
{
  "event_name": "pancake.setup.completed",
  "user_id": "uuid",
  "branch_id": "uuid",
  "portal": "admin",
  "correlation_id": "uuid-v4",
  "feature_flag_state": "enabled",
  "client_ts": "2026-05-15T09:42:18+07:00",
  "server_ts": "2026-05-15T09:42:18.345+07:00",
  "properties": {
    "time_from_first_open_minutes": 15,
    "sources_count": 12
  },
  "event_version": 1
}

B8.2) Sampling & retention

EventSamplingRetention
pancake.setup.*100%Hot 90 ngày + warehouse 2 năm
pancake.source_routing.*100%90d + 2y
pancake.audit.*100% (critical for compliance)90d + 2y
pancake.audit.dlq_replay_*100% (audit-compliance critical)90d + 2y
pancake.health.*10% (UI rendering — không critical)90d
pancake.kill_switch.toggled100% (vận hành critical)90d + 2y
pancake.webhook.outage_*100% (backend)90d + 2y
pancake.scr06.dropdown_used10% (UI metric, không critical)90d

B8.3) PII rules

  • KHÔNG track raw phone_number, customer_name trong analytics event (đã được track trong pancake_webhook_event audit DB).
  • KHÔNG track api_key, webhook_token raw.
  • branch_id OK (không phải PII).
  • correlation_id reuse từ webhook handler để cross-trace.

B9) Từ điển tooltip (BẮT BUỘC M+ tightened, 3-part cho calculated)

3 case BẮT BUỘC tooltip: (1) Calculated/Derived field — 3-part định nghĩa + công thức + ví dụ. (2) Status/Lifecycle term. (3) Icon-only button.

#MànField/IconLoạiĐịnh nghĩaCông thức / Điều kiệnVí dụHiện khi
1SCR-01Workspace IDText termMã định danh workspace Pancake CRM (1 workspace = 1 doanh nghiệp dùng Pancake)Lấy từ Pancake admin > Cài đặt > Tích hợpws_diva_2026_a8c3d4f5Hover icon ⓘ
2SCR-01API keyText termChìa khóa xác thực với Pancake REST API (đọc danh sách nguồn)Lấy từ Pancake admin > Cài đặt > API. Lưu encrypted at rest trong DB Diva(mask)Hover icon ⓘ + reveal toggle
3SCR-01URL webhookText termURL Pancake gửi webhook tới Diva. Paste vào Pancake admin > Tích hợp > WebhookTự động gen theo https://diva.com.vn/api/pancake/record/{token}https://diva.com.vn/api/pancake/record/abc123tokenHover icon ⓘ
4SCR-01Token webhookText termToken xác thực request từ Pancake. Là phần {token} trong URL webhookUUID v4 auto-gen. Tạo lại sẽ invalidate URL cũ(mask)Hover icon ⓘ + reveal toggle
5SCR-01Kill switch (Tắt khẩn cấp)StatusTắt tổng — toàn bộ event Pancake bị bỏ quaKhi off (toggle hiện cam) → mọi event chuyển status 'Bỏ qua (đã tắt hệ thống)'"Tạm dừng nhận lead Pancake trong giờ bảo trì"Hover icon ⓘ
6SCR-01Tag VIPTermTag NAME (text, không phân biệt hoa thường) đánh dấu khách quan trọngMatch account.pancake_metadata.pancake_tag_names[] với pancake_connection.vip_tag_names[] lowercase"VIP", "Hot Lead" → match "vip", "HOT LEAD" cũng OKHover icon ⓘ
7SCR-01Trạng thái Pancake activeStatusKết nối Pancake đang hoạt động, sẵn sàng nhận eventVào state khi save config + Kiểm tra kết nối passBadge xanh trên headerLuôn hiện trên badge
8SCR-02Pancake source IDText termMã định danh page/kênh trong Pancake (readonly)Đồng bộ tự động qua Cron 3 hourly. KHÔNG sửa đượcsrc_fb_hcm_q1_2024Hover icon ⓘ
9SCR-02Tên nguồn PancakeText termTên page/kênh hiển thị trên Pancake adminĐồng bộ qua Cron 3. Nếu Pancake đổi tên → tự update"FB Diva HCM Q1", "Zalo OA Diva Premium"(always display)
10SCR-02Chi nhánh DivaFK termChi nhánh Diva sẽ nhận ticket từ nguồn Pancake nàyNULL = Loose mode (leader assign manual)"Diva HCM Q1"Hover icon ⓘ + dropdown
11SCR-02Bật/Tắt (is_active)StatusPer-source feature flag (DEC-021 mức 3)Tắt → event status 'Bỏ qua (nguồn đã tắt)'"Bật cho pilot W1=1 source"Hover icon ⓘ
12SCR-02Cập nhật cuối (last_sync_at)DatetimeThời điểm Cron 3 sync metadata Pancake gần nhất (KHÔNG phải event nhận cuối)Auto-update Cron 3 hourly"15/05/2026 09:30 (12 phút trước)"(always display)
13SCR-03Mã sự kiện (event_id)Text termUUID identifier của row pancake_webhook_eventPK auto-gena8c3d4f5-1f8d-... (hiển thị 8 ký tự đầu)Hover full ID
14SCR-03Record IDText termID record bên Pancake (1 khách = 1 record)Pancake gửi qua webhook payloadrec_xyz_03Hover icon ⓘ
15SCR-03Trạng thái ingestedStatusĐã nhận (chưa xác thực) — webhook handler vừa INSERT raw event, chưa verifyVào: INSERT step 1. Ra: verify → received hoặc fail"Lần đầu thấy"Badge tooltip
16SCR-03Trạng thái receivedStatusĐã xác thực — pass token + IP + schema + dedup, sẵn sàng cho crm-api xử lýVào: webhook UPDATE. Ra: Hasura trigger fire → processing"Pass verify"Badge tooltip
17SCR-03Trạng thái processingStatusĐang xử lý — crm-api handler đang chạy STEP 0-14Vào: handler started. Ra: STEP 14 OK → processed, exception → dead_letter"Đang tạo account/ticket"Badge tooltip
18SCR-03Trạng thái processedStatusĐã xử lý — hoàn tất tạo account + ticket + notificationVào: STEP 14 COMMIT. Terminal."Lead đã được rescue thành ticket"Badge tooltip
19SCR-03Trạng thái auth_failedStatusLỗi xác thực — token URL saiVào: webhook verify step 2. Terminal — alert ops nếu > 10/min"Có thể spoof attempt — báo Ops"Badge tooltip
20SCR-03Trạng thái ip_blockedStatusBị chặn IP — IP nguồn không trong whitelistVào: webhook verify step 2 (sau PD-002 resolve). Terminal"Pancake không gửi từ IP đó — có thể spoof"Badge tooltip
21SCR-03Trạng thái parse_errorStatusLỗi cấu trúc dữ liệu — JSON schema invalidVào: webhook verify step 3. Terminal"Pancake gửi payload sai schema"Badge tooltip
22SCR-03Trạng thái skipped_duplicateStatusBỏ qua (trùng lặp) — 3-tuple (record_id, modified_on, payload_hash) đã tồn tạiVào: webhook step 4. Terminal — update last_received_at"Pancake retry cùng payload"Badge tooltip
23SCR-03Trạng thái skipped_source_disabledStatusBỏ qua (nguồn đã tắt) — pancake_source_routing.is_active=falseVào: webhook step 5. Terminal"Admin đã tắt nguồn này"Badge tooltip
24SCR-03Trạng thái skipped_kill_switchStatusBỏ qua (đã tắt hệ thống) — app_setting.pancake_integration.enabled=falseVào: webhook step 5. Terminal"Admin đã tắt toàn hệ thống"Badge tooltip
25SCR-03Trạng thái skipped_opt_outStatusBỏ qua (khách từ chối marketing) — customer_consent.marketing=falseVào: crm-api STEP 7. Terminal"Compliance — không tạo ticket cho khách opt-out"Badge tooltip
26SCR-03Trạng thái dead_letterStatusLỗi DLQ — STEP 1-14 exception, chờ admin replayVào: handler catch exception. Ra: admin replay → received hoặc retry_count>=3 → permanently_failed"STEP 9 round-robin fail (no telesale active)"Badge tooltip
27SCR-03Trạng thái permanently_failedStatusLỗi vĩnh viễn (cần xem xét) — đã retry 3 lần failVào: Cron retry check. Terminal — alert manual review"Cần Tech Lead xem xét root cause"Badge tooltip
28SCR-03Test event (is_test)StatusEvent được đánh dấu test (QA/Dev test)Pancake payload có is_test=true hoặc URL ?test=1. KHÔNG count KPI. Auto-cleanup 24h(icon test tube)Hover icon ⓘ
29SCR-03Số lần retry (retry_count)IntSố lần Cron retry đã thửTăng mỗi lần Cron DLQ retry. >= 3 → permanently_failed"2 / 3"(cột số)
30SCR-03Tạo lúc (created_at)DatetimeThời điểm webhook handler INSERT rowAuto NOW()"15/05/2026 09:41:55"(always display)
31SCR-03Xử lý lúc (processed_at)DatetimeThời điểm status chuyển processedAuto NOW() khi STEP 14 success. NULL nếu chưa processed"15/05/2026 09:42:08" hoặc "—"(always display)
32SCR-03Thông báo lỗi (error_message)TextException text từ handler khi dead_letter/permanently_failedAuto-capture từ catch block"STEP 9 round-robin failed: no active telesale"(drawer field)
33SCR-03Raw payloadJSONFull body JSON Pancake gửi (lossless audit)INSERT raw NOT NULL(xem drawer)(drawer)
34SCR-03Raw headersJSONFull HTTP headers Pancake gửiINSERT raw(xem drawer)(drawer)
35SCR-03IP nguồn (source_ip)INETIP server Pancake gửi request từResolve qua X-Forwarded-For reverse proxy103.20.149.117(drawer)
36SCR-04Độ trễ webhook → ticket (p95)CalculatedThời gian từ Pancake gửi webhook đến khi ticket Diva được tạo. Đo trên 95% request trong 24h gần nhấtlatency_p95 = percentile_cont(0.95) WITHIN GROUP (ORDER BY (processed_at - created_at)) FROM pancake_webhook_event WHERE status='processed' AND created_at > NOW() - 24h100 event 24h, p95 = 12,5s → "12,5s ✓" (target ≤ 30s). Outlier > 5 phút loại khỏi p95.Hover icon ⓘ
37SCR-03Khoảng thời gian (date range)FilterCửa sổ thời gian filter eventDefault 7 ngày gần nhất. Max 90 ngày để không overload query"09/05/2026 - 15/05/2026 (7 ngày)"(always display)
38SCR-03Phát lại sự kiện (action)ActionReset event status về received để Hasura trigger fire lại crm-api handlerChỉ enable cho status dead_letter, auth_failed, parse_error. KHÔNG enable cho processed (idempotency) hay skipped_* (intentional)"Replay 1 event DLQ sau khi fix telesale roster"Hover button
39SCR-04Tỉ lệ thành côngCalculatedTỉ lệ event xử lý thành công trên tổng event nhận trong window. Loại trừ skipped_* và test eventssuccess_rate = count(status='processed') / count(status NOT IN ('skipped_*') AND is_test=false) × 100 (3 chữ số thập phân)10000 event 24h, 9985 processed, 10 dead_letter, 5 skipped_* → success = 9985/(9985+10) × 100 = 99,900% ✓ (target ≥ 99,5%)Hover icon ⓘ
40SCR-04Trạng thái healthyStatusBình thường, webhook nhận event đều đặn(initial state)Card xanh ✓Badge tooltip
41SCR-04Trạng thái outage_startedStatusMất kết nối — Cron 4 detect 0 event trong 5 phút (giờ làm việc)Cron 6 adaptive polling kích hoạt (interval 60-900s)Banner đỏ "🚨 Mất kết nối"Badge tooltip
42SCR-04Trạng thái outage_recoveredStatusĐang phục hồi — webhook nhận event mới, đang sustain 5 phútSau 5 phút sustain → chuyển healthyBanner cam "Đang phục hồi"Badge tooltip
43SCR-04Nhận event cuối (last_event_received_at)DatetimeThời điểm webhook nhận event hợp lệ gần nhấtUpdate mỗi event status received"38 giây trước"(always display)
44SCR-04Lịch sử outageAudit logDanh sách 10 outage gần nhất với started/ended/duration/miss_rescuedReuse pancake_outage_state history(table row)(always display)
45SCR-04Polling interval hiện tạiInt (s)Khoảng cách giữa 2 lần Cron 6 polling Pancake REST /recordsStart 60s khi outage_started, tăng dần max 900s (15 phút)"60s fallback active"Hover icon ⓘ
46SCR-04Auto-assign telesale (chính xác)CalculatedTỉ lệ ticket Pancake được auto-assign telesale trên tổng ticket Pancake trong 7 ngàycorrect_rate = count(ticket WHERE source_id='ticket_source_pancake' AND assignee_id IS NOT NULL AND created_by='system_pancake_webhook') / count(ticket WHERE source_id='ticket_source_pancake') × 100 (2 chữ số thập phân)1000 ticket 7 ngày, 920 có assignee → 920/1000 × 100 = 92,00% ✓ (target ≥ 90%)Hover icon ⓘ
47SCR-01Kiểm tra kết nối (button)ActionGọi Pancake REST GET /workspaces/{ws}/sources để verify api_keyDisabled khi workspace_id hoặc api_key trống. Timeout 30s"Phát hiện 12 nguồn"Hover button
48SCR-01Sao chép URL (icon)ActionCopy URL webhook vào clipboardnavigator.clipboard.writeText"Đã sao chép" toastHover icon
49SCR-01Tạo lại token (button)ActionTạo token mới + invalidate URL cũConfirm 2 bước gõ 'TẠO LẠI'. Sau đó admin phải update Pancake admin(destructive)Hover button
50SCR-01Kích hoạt lại webhook (button)ActionReset pancake_connection.status từ suspended_by_pancake về activeChỉ hiện khi suspended. Yêu cầu admin đã bật lại bên Pancake trước(conditional)(only Variant F)
51SCR-02Đồng bộ từ Pancake (button)ActionManual call Cron 3 logic — pull source list từ Pancake RESTLoading 5-30s. Update last_sync_at + auto-insert source mới"Phát hiện 12 nguồn (2 nguồn mới)"Hover button

Quy tắc lock-in:

  • 100% Calculated/Derived field (rows 36, 39, 46) có 3-part đầy đủ
  • 100% Status text trong wireframe (rows 7, 11, 15-27, 28, 40-42) có row B9 giải thích
  • 100% Icon-only button (rows 47-51) có row B9 + aria-label tương ứng (xem B-Microcopy)
  • Mọi cell Định nghĩa ≤80 ký tự — chi tiết → ref A9 PRD hoặc dev-spec C3
  • Mọi cell Ví dụ có số liệu thật (không placeholder)

B-Microcopy) Quy ước vi-copy form

MẫuQuy ước SCR-01..06
Required marker* đỏ sau label cho workspace_id, api_key ở SCR-01
Optional markerKHÔNG dùng (Tuỳ chọn) — context rõ ràng
Help textDưới field VIP tag textarea: "Mỗi dòng = 1 tag..."
TooltipHover icon ⓘ; chi tiết phụ; ref B9
Char count{X}/100 dòng ở textarea VIP tag; đỏ khi >90%
Placeholder"Mã workspace Pancake", "Nhập API key từ Pancake admin", "Tìm theo tên nguồn..."
Inline error"API key phải ≥16 ký tự.", "Workspace ID không hợp lệ. Vui lòng kiểm tra (≤64 ký tự, UUID Pancake)."
Inline success"Đã đồng bộ. Phát hiện 12 nguồn." (toast — không dùng inline)
Reveal passwordIcon mắt 👁; tooltip "Hiện/Ẩn API key"
Auto-formatTrim whitespace + lowercase tag NAME khi blur
Reset fieldSearch input có nút × clear; date range picker "Đặt lại"
aria-label icon-onlyIcon Sao chép URL: aria-label "Sao chép URL webhook". Icon Sao chép token: "Sao chép token webhook". Icon mắt: "Hiện/Ẩn API key". Icon ⓘ: "Xem giải thích". Icon Phát lại: "Phát lại sự kiện". Icon ⋮: "Tùy chọn khác"

B-Voice) Quy ước giọng thương hiệu

Tình huốngToneVí dụ đúngVí dụ sai
Hướng dẫn setupTrang trọng, hỗ trợ"Vui lòng hoàn tất Tab 1 (Kết nối) trước khi map nguồn → chi nhánh.""Phải làm Tab 1 trước nha bạn!"
Toast thành côngKhẳng định, súc tích"Đã lưu cấu hình Pancake CRM.""Yay, lưu xong rồi!"
Banner cảnh báo outageKhẩn cấp, rõ ràng"Pancake outage — không nhận event trong 5 phút gần nhất""Có lỗi gì đó với Pancake"
Lỗi user-inducedTrung tính, hướng dẫn"API key phải ≥16 ký tự.""API key sai rồi"
Lỗi systemXin lỗi nhẹ + trace"Không thể lưu. Mã sự cố: TRC-2026-05-15-a8c3d4. Vui lòng báo bộ phận Ops với mã.""Server lỗi không xác định"
Cảnh báo destructiveRõ ràng, có tác động"Token mới sẽ vô hiệu hóa cấu hình Pancake hiện tại. Gõ 'TẠO LẠI' để xác nhận.""Bạn có chắc không?"
Hỏi xác nhậnLễ phép, rõ tác động"Tắt sẽ ngừng nhận lead Pancake. Mọi event sẽ chuyển trạng thái 'Bỏ qua (đã tắt hệ thống)'. Xác nhận?""Tắt thật à?"
Empty stateHướng dẫn next step"Chưa có nguồn nào. Click 'Đồng bộ từ Pancake' để bắt đầu.""Không có dữ liệu"

Quy tắc chung:

  • KHÔNG dùng emoji trong copy hành chính (form label, error, validation). Cho phép dùng badge icon trong wireframe.
  • KHÔNG viết tắt nghệ thuật ("kb", "đc")
  • KHÔNG dùng English UI ("Submit", "Cancel", "OK") — dùng "Lưu", "Hủy", "Xác nhận"
  • "Bạn" cho user nội bộ (Admin) — formal-professional
  • Câu chủ động, ngắn gọn

B-i18n) Quy ước quốc tế hóa / format

Khía cạnhQuy ước Diva (vi-VN)
NgàyDD/MM/YYYY (KHÔNG MM/DD)
Ngày + giờDD/MM/YYYY HH:mm hoặc DD/MM/YYYY HH:mm:ss (audit log) — 24h
GiờHH:mm:ss
TimezoneAsia/Ho_Chi_Minh (UTC+7); KHÔNG có DST
Số nguyên1.500 (dấu chấm phân ngàn)
Số thập phân12,5 (dấu phẩy thập phân)
Phần trăm99,950% (3 thập phân cho success rate), 92,00% (2 thập phân cho auto-assign), 37,50% (2 thập phân chung)
Thời gian khoảng (latency)12,5s (1 thập phân), 4,40 phút (2 thập phân)
SĐT+84 912 345 678 hoặc 0912 345 678 (cách nhóm 4-3-3)
Tên KHCapitalize từng chữ; giữ dấu (Nguyễn Thị Mai, Đặng Thị Ánh Sương)
Sort tiếng ViệtCollation vi_VN (Postgres)
Tooltip "ago""38 giây trước", "12 phút trước", "2 giờ trước", "3 ngày trước" — relative format

B-i18n canonical keys (vi.ts module crm)

typescript
// diva-admin/src/modules/crm/i18n/vi.ts (line 130-137 — chỉ delta)
export default {
  // ... existing keys ticket_source_1 .. ticket_source_8
  ticket_source_pancake: 'Pancake CRM',  // NEW slot 9
};

B-i18n canonical keys (vi.ts module settings — NEW)

typescript
// diva-admin/src/modules/settings/i18n/vi.ts (NEW section pancake)
export default {
  // ... existing settings keys
  pancake: {
    sidebar: { menu: 'Tích hợp Pancake CRM' },
    scr05: { header: 'Tích hợp Pancake CRM' },
    tab: {
      connection: 'Kết nối',
      source_routing: 'Map nguồn',
      audit: 'Lịch sử + DLQ',
      health: 'Sức khỏe',
    },
    scr01: { /* ... keys từ B7.2 ... */ },
    scr02: { /* ... keys từ B7.3 ... */ },
    scr03: { /* ... keys từ B7.4 ... */ },
    scr04: { /* ... keys từ B7.5 ... */ },
    common: {
      loading: 'Đang tải',
      no_data: 'Chưa có dữ liệu',
      error: 'Không thể tải dữ liệu. Vui lòng thử lại.',
    },
  },
};

B-i18n EN fallback (en.ts — minimal)

typescript
// diva-admin/src/modules/settings/i18n/en.ts
pancake: {
  sidebar: { menu: 'Pancake CRM Integration' },
  // ... brand name giữ EN
}

Quy tắc: Diva chủ yếu vi-VN. en.ts giữ minimal fallback cho nhân sự nước ngoài (rare case Admin). MVP-1 SCR-01..05 chỉ cần vi-VN đầy đủ.


B-Versioning) Quy ước UI cho feature flag / staged rollout

Feature deploy theo pilot 4 tuần (DEC-022).

Tình huốngQuy ước UI
User ngoài pilot (W1-W3)(Admin Diva luôn trong pilot — không có concept "user ngoài"). Telesale ngoài source W1 → không thấy ticket source Pancake CRM (vì source is_active=false)
User trong pilotHiện feature đầy đủ
Staged rollout per-sourceDEC-021 mức 3 pancake_source_routing.is_active — Admin toggle per row SCR-02
Kill switch tổngDEC-021 mức 1 app_setting.pancake_integration.enabled — SCR-01 toggle
Rollback W2→W1Admin tắt is_active của 4 source ở W2 → quay về 1 source W1; không ảnh hưởng ticket đã tạo
Migration periodKHÔNG có old UI vs new UI (feature mới hoàn toàn) — Settings/Pancake là route mới

KHÔNG có version doc cho settings (live edit): thay đổi config (workspace_id, vip_tag_names, branch mapping) áp dụng ngay — KHÔNG có draft/review workflow. Lý do: số lượng admin nhỏ (1-3 người), audit log đủ tracking; nếu sai → rollback nhanh bằng inline-edit ngược lại. Kill switch là rollback nhanh nhất khi cần.


B-Help) Quy ước điểm hỗ trợ / Help Touchpoint

Khía cạnhQuy ước
Help link cuối SCR-01..04Footer mỗi tab: "Cần hỗ trợ? → [Xem hướng dẫn Tích hợp Pancake CRM]" — deeplink https://docs.diva-group.com/admin-guide/integrations/pancake-crm
Tooltip ⓘMọi field/term phức tạp có icon ⓘ + tooltip B9
Onboarding tourLần đầu Admin vào SCR-05 (Variant B SCR-01 first-time) → tour 4 step: (1) Welcome card SCR-01, (2) Sau khi save lần đầu → auto-redirect SCR-02 + tooltip "Tiếp theo: map nguồn", (3) Sau map ≥1 source → tooltip SCR-04 "Verify health", (4) Done
Empty state with hintMỗi empty state có CTA + link "Xem hướng dẫn"
Error with support hintError 5xx kèm correlation_id + "Báo bộ phận Ops với mã: {trace_id}"
Contact supportFloating "Cần hỗ trợ?" button góc phải dưới chỉ ở SCR-05 (defer MVP-2 if needed)
Feedback channel"Phản hồi tính năng" link footer Settings (defer)
Runbook opsDevOps runbook: https://docs.diva-group.com/runbook/pancake-integration — link từ banner outage Variant B SCR-04

B-Trường hợp cá biệt (Edge Cases — 12 nhóm G1-G12 BẮT BUỘC L)

Nhóm G1 — Quyền và phân quyền

#Trường hợpHành vi kỳ vọng
G1.1Manager gõ URL /settings/pancake-crm trực tiếpRoute guard redirect /403 + toast "Bạn không có quyền truy cập Tích hợp Pancake CRM. Liên hệ Admin nếu cần."
G1.2Admin bị thu hồi quyền pancake_crm_integration.access giữa sessionRequest kế tiếp 401/refetch → menu Sidebar ẩn ngay; nếu đang ở SCR-01..04 → redirect home + toast "Quyền đã thay đổi"
G1.3Admin được cấp view_all nhưng KHÔNG có replay_dlqSCR-03 list xem được + drawer xem được; button "Phát lại" hidden hoàn toàn (kể cả bulk select bar)
G1.4Admin chuyển sang Manager role giữa session đang xem SCR-03API request kế trả 401/403 → redirect + toast
G1.5Multi-role (Admin + Manager)Effective permission = union; xem được SCR-01..04 + ticket dashboard Manager
G1.6Dynamic Permission UI override (defer MVP-2)Default seed Admin only; admin có thể grant view_all cho Marketing role qua DP UI sau — UI Spec MVP-1 chỉ chuẩn bị structure

Nhóm G2 — Mạng và độ tin cậy

#Trường hợpHành vi kỳ vọng
G2.1Mất mạng giữa "Lưu cấu hình" SCR-01Banner "Mất kết nối. Đã lưu nháp local IndexedDB. Đang thử lại..." (autoretry); KHÔNG mất form data
G2.2Có mạng lại sau outage 30sAuto-retry queue → toast "Đã đồng bộ thay đổi"
G2.3Pancake REST /sources timeout 30s khi "Đồng bộ"Banner "Pancake không phản hồi. Hiển thị cache. Thử lại sau 30 phút." (Variant E SCR-02)
G2.4Diva API 5xx khi load SCR-03Toast "Có lỗi từ hệ thống. Mã: {trace_id}." + nút Thử lại
G2.5Rate limit Pancake 429 khi "Kiểm tra kết nối" gọi nhanhBanner đếm ngược "Bạn đã thử quá nhanh. Vui lòng chờ {N}s."
G2.6Bulk replay 100 event timeout giữa chừngProgress dừng + banner "Đã thực hiện X/100. {Y} còn lại sẽ retry tự động."

Nhóm G3 — Dữ liệu biên / boundary

#Trường hợpHành vi kỳ vọng
G3.1Pancake gửi record_id rất dài (>200 ký tự)Cột Record ID truncate ... cuối với tooltip full ID; drawer hiển thị full text
G3.21M+ event trong audit log (≥1 năm retention)Server-side pagination + index (created_at, status) — không client filter > 5k row
G3.31000+ source routing rows (extreme)Pagination 100/page; defer MVP-2 nếu thực tế cần (volume hiện 40-50 source)
G3.4VIP tag list 100+ entriesBlock keystroke + char counter đỏ ">90%" + toast 1 lần "Tối đa 100 dòng"
G3.5Tên KH có dấu Unicode (Đặng Thị Ánh Sương)Render đúng dấu trong wireframe + drawer; sort theo collation vi_VN
G3.6Tên source Pancake có emoji (vd "FB Diva 💎 Premium")Render thuần text (emoji OK) — không filter; sort theo Unicode
G3.7SĐT Pancake gửi với prefix +84 vs 0Hiển thị normalized E.164 trong drawer raw_payload; UI label dùng +84 912 345 678
G3.8Workspace_id rỗng → SaveInline error "Workspace ID là bắt buộc."
G3.9API key 1000 ký tự (vượt giới hạn)Block keystroke max 64 + toast
G3.10Branch dropdown: 50+ branchesSearchable dropdown (debounce 200ms); render top 20 mặc định

Nhóm G4 — Thời gian / múi giờ

#Trường hợpHành vi kỳ vọng
G4.1Cron 4 chạy lúc đổi giờ DST (Asia/HCM không có DST)N/A — VN không có DST; consistent UTC+7
G4.2due_date today 23:59:59 Asia/Ho_Chi_Minh khi webhook đến lúc 23:58Ticket due_date = today 23:59:59 → telesale có 1 phút để xử lý? → defer rule: nếu webhook đến sau 22:00 → due_date = tomorrow 23:59:59 (out of scope UI Spec, ref dev-spec STEP 10)
G4.3Year boundary 31/12 → 01/01 trong audit logFilter date range cross-year hoạt động đúng
G4.4last_event_received_at hiển thị "1 năm trước"Relative format "1 năm trước" (không absolute)
G4.5Outage kéo dài > 1 giờ vắt sang ngày mớiHistory row hiển thị 2 datetime đầy đủ (started 23:55 → ended 01:30 hôm sau, duration 95 phút)

Nhóm G5 — Concurrency

#Trường hợpHành vi kỳ vọng
G5.12 Admin cùng mở SCR-01 đang editBanner presence "Đang chỉnh sửa cùng: NGUYEN VAN A" cuối form
G5.2A save SCR-01 trước, B save sauB nhận modal conflict "Cấu hình đã thay đổi. [Tải lại] / [Ghi đè]"
G5.32 Admin cùng inline-edit cùng row SCR-02A save trước → optimistic update B; B save thấy banner "Bản ghi đã đổi. [Tải lại]"
G5.42 Admin cùng replay 1 event SCR-03A click trước → status đã chuyển received; B click thấy "Sự kiện đã được phát lại bởi {A} lúc {time}"
G5.5Admin mở 2 tab cùng SCR-03 → bulk replay tab 1, tab 2 viewBroadcastChannel notify tab 2 "Đang có bulk replay từ tab khác"
G5.6Admin A toggle kill switch off, A2 cùng thấy kill switch hiện vẫn onRealtime polling 60s → A2 thấy update sau ≤60s; KHÔNG cần WebSocket cho settings (DEC-014 PD-009)

Nhóm G6 — Validation

#Trường hợpHành vi kỳ vọng
G6.1api_key 15 ký tự (under min 16)Inline error "API key phải ≥16 ký tự." + focus field
G6.2api_key đúng format nhưng Pancake rejectBanner đỏ "API key sai. Kiểm tra trên trang Pancake admin." + correlation_id
G6.3Pancake gửi payload schema mới (field unknown)Backend tolerant — vẫn process; field unknown lưu trong raw_payload; log warning
G6.4Phone Pancake format edge 912abc345Backend libphonenumber parse fail → log warning, fallback raw; UI drawer hiển thị raw text
G6.5Date range filter SCR-03 > 90 ngàyInline error "Khoảng thời gian tối đa 90 ngày." + reset về 7 ngày
G6.6VIP tag mỗi dòng > 60 ký tựInline warning "Tag dài bất thường — có thể không match. Kiểm tra lại?"

Nhóm G7 — i18n / locale

#Trường hợpHành vi kỳ vọng
G7.1Admin browser locale en-USDiva admin default vi-VN; en-US user thấy en.ts fallback minimal — primary VN
G7.2Source name Unicode (FB Diva ★ Premium)Render đúng Unicode; sort vi_VN collation
G7.3Branch name có dấu (Diva HCM Cầu Giấy)Dropdown render đúng dấu; search debounce match dấu/không dấu (TBD-verify)

Nhóm G8 — Print / Export

#Trường hợpHành vi kỳ vọng
G8.1Export audit log SCR-03 (defer MVP-2)UI placeholder button "Xuất Excel"; alert "Tính năng đang phát triển — sẽ có ở MVP-2"
G8.2Bulk export 100k rowDefer MVP-2 — async background job pattern (ref B5A Export Deep Contract)
G8.3Print PDF outage historyDefer MVP-2

Nhóm G9 — Accessibility

#Trường hợpHành vi kỳ vọng
G9.1Keyboard only nav SCR-01Tab order: workspace_id → api_key → reveal eye → Test → URL copy → token copy → token reveal → token regenerate → kill switch → VIP textarea → Cancel → Save; Esc đóng confirm modal
G9.2Screen reader cho drawer SCR-03role="dialog" + aria-labelledby="drawer-title"; focus trap; close khi Esc
G9.3Color contrast badge statusMọi badge status có icon + text (không chỉ dựa vào màu); contrast ≥4.5:1 WCAG AA
G9.4aria-live cho toastToast success: aria-live="polite"; error: aria-live="assertive"
G9.5Banner outage SCR-04role="alert" + auto-announce screen reader

Nhóm G10 — Mobile / responsive

#Trường hợpHành vi kỳ vọng
G10.1SCR-01..05 trên tablet (768-1024px)Responsive: tab strip horizontal scroll nếu cần; form 1 column; drawer SCR-03 full width
G10.2SCR-01..05 trên mobile (<768px)OUT OF SCOPE MVP-1 — Diva admin desktop-first; mobile defer MVP-2 (Pancake settings không cần mobile vì Admin desktop)
G10.3SCR-06 dropdown trên mobile crm (telesale Flutter app)OUT OF SCOPE UI Spec này — Flutter app có UI riêng (handled bởi diva-flutter team)

Nhóm G11 — Search / Filter

#Trường hợpHành vi kỳ vọng
G11.1Date range SCR-03 > 90 ngàyInline error + reset; lý do: performance (query 1M+ row)
G11.2Filter combo phức tạp (5 status + 10 source + date 30d + checkbox test)Server-side query; auto-apply debounce 500ms; loading skeleton
G11.3Search source SCR-02 không khớpEmpty filter "Không có nguồn nào khớp." + CTA "Xóa bộ lọc"
G11.4Search trong raw_payload SCR-03 drawerDefer MVP-2 (text search JSON cần Postgres tsvector index)

Nhóm G12 — Business

#Trường hợpHành vi kỳ vọng
G12.1Pancake outage > 1hBanner SCR-04 vẫn đỏ; admin có thể bấm "Tạm tắt cảnh báo (1h)" nếu đã nắm; KHÔNG auto-recovery cho đến khi webhook nhận event mới
G12.2Diva DB downtime — webhook không INSERT đượcwebhook trả 500 (Pancake retry); KHÔNG count vào outage detection vì Cron 4 cũng không chạy được; recovery khi DB lên lại
G12.3Manual intervention cần (vd permanently_failed >10 row)Tech Lead xem qua SCR-03 filter status permanently_failed; mỗi event 1 lý do khác; KHÔNG có bulk action — fix root cause + manual reset từ DB
G12.4Pilot rollback W3 → W1Admin tắt 14/15 source ở SCR-02 toggle is_active → còn 1 source active
G12.5Pancake suspension > 24h (lỡ rule)Variant F SCR-01 hiển thị tiếp; Cron 6 polling vẫn rescue được lead trong window; Cron 7 reconciliation catch miss
G12.6Khách opt-out marketing rồi opt-in lạiWebhook đến → STEP 7 query customer_consent.marketing → nếu mới opt-in → tạo ticket bình thường (DEC-014 không có grace period)
G12.7Source được map sang branch không active (branch.disabled=true)Defer MVP-2 — UI cảnh báo "Branch đã vô hiệu hóa" trong dropdown; ticket vẫn tạo với branch_id (data integrity, leader manual xử lý)

B-POST) Verification (BẮT BUỘC trước DELIVER)

B-POST.1) Checkpoint completeness (As-Is + Delta)

  • [x] As-Is đầy đủ: B0.1 inventory bao phủ 100% UI hiện hữu của Tickets + TicketMultipleAdd + CustomerTicketManager (3 màn SCR-06 delta đụng) + Settings sidebar — kèm Evidence file:line từ EVIDENCE_PACK
  • [x] Delta status đầy đủ: 11/11 row B0.1.A-D có Delta Status (KEEP/UPDATE/NEW)
  • [x] Field × Surface matrix B0.4 (4 nhóm A-E, 30+ field) — mọi cell có giá trị
  • [x] State × Screen matrix B0.5: 6 state canonical × 6 SCR × variant (Admin/non-admin)
  • [x] Reuse classification: SCR-01..04 = 🆕 Build mới, SCR-05 = ✅ Reuse XDetailLayout, SCR-06 = 🔧 Extend
  • [x] Wireframe context: SCR-06 vẽ vùng KEEP (filter panel + table) xung quanh UPDATE (slot 9 dropdown)
  • [x] UX principles: mỗi SCR có B2.x.1A (5-giây, ưu tiên, dữ liệu nhạy cảm, pending, canonical term, intent token, no-suy-diễn)
  • [x] Variant coverage: B2.x.2 có A (default) + B (empty/first-time) + C (no-perm) + D (loading) + E (error) + F (lifecycle special) per SCR
  • [x] Role × Variant: B5.1 + EXT-4 B5.4 không mâu thuẫn PRD
  • [x] CTA hierarchy: mỗi SCR có primary CTA + secondary + denied feedback
  • [x] Validation đầy đủ: B0.4 mọi field input có rule + error copy
  • [x] Permission matrix B5: cover module × action × role × portal × denied feedback
  • [x] Confirm modal: destructive actions (Tạo lại token, kill switch off, vô hiệu hóa source có traffic, bulk replay) có copy đầy đủ
  • [x] Empty với CTA: SCR-02 Variant B + SCR-03 Variant B đều có CTA next step
  • [x] Format VN: tiền không applicable, ngày DD/MM/YYYY, giờ HH:mm:ss, % 99,950%/92,00%/37,50%
  • [x] Design-token intent: KHÔNG hard-code color/font; dùng primary emphasis/success/warning/negative-value/muted

B-POST.2) Cross-spec consistency check

  • [x] Mọi field B0.4 xuất hiện trong PRD FR-002 (audit fields), FR-012 (settings UI fields), FR-013 (REST fields), FR-014 (outage state fields), FR-018 (FE delta fields)
  • [x] Mọi field B0.4 xuất hiện trong dev-spec C4 data model (4 bảng pancake_* + ALTER account + ticket master data + customer_consent + contact_book)
  • [x] Mọi action mới (Phát lại, Đồng bộ, Kiểm tra kết nối, Tạo lại token, Kích hoạt lại) sẽ có TC trong qa-test-plan D4
  • [x] Mọi state lifecycle B6.1 (12 state) + B6.2 (3 state) sẽ có TC trong qa-test-plan
  • [x] Mọi variant B2.x.2 (A-F) có PRD AC hoặc UI-only state đã ghi rõ
  • [x] Mọi variant có QA TC ref hoặc N/A có lý do
  • [x] Role/permission B5 + B5.4 EXT-4 khớp PRD Z DEC-016 + Permission Matrix
  • [x] FE delta SCR-06 (3 file) khớp PRD FR-018 + dev-spec C2 Impact Map

B-POST.3) Lint nhanh

bash
SLUG=pancake-crm-integration

# 1. Không có style hard-code / English UI copy
rg -n "cam đỏ|màu đỏ|font-size|font-weight|Submit|Confirm|Loading|No data|Forbidden|Save Now|Delete|Edit Now|View Now" docs/features/$SLUG/ui-spec.md || true

# 2. Không TBD/Lorem
rg -n "TBD|TODO|Lorem|\{placeholder\}|XXX|FIXME" docs/features/$SLUG/ui-spec.md || true

# 3. Không dịch enum vào diagram
rg -n "stateDiagram.*đã xác thực|stateDiagram.*đã xử lý" docs/features/$SLUG/ui-spec.md || true

# 4. Role/permission flow nhất quán
rg -n "Manager.*Pancake|Telesale.*Settings" docs/features/$SLUG/ui-spec.md | grep -v "hidden" || true

# 5. "Click" verb chưa Việt hóa
rg -n '"Click"' docs/features/$SLUG/ui-spec.md || true

# 6. Format VN sai
rg -n '\$[0-9]|MM/DD/YYYY|hh:mm A|[0-9],[0-9]{3}đ' docs/features/$SLUG/ui-spec.md || true

# 7. Schema NOT NULL field mà UI cho phép null
rg -n "_id=NULL|=NULL OK" docs/features/$SLUG/ui-spec.md
# → chỉ B0.8 có `branch_id NULL` (Loose mode hợp lệ — schema allow NULL)

B-POST.4) Cross-spec diff bot

bash
SLUG=pancake-crm-integration

# Diff PRD FR vs UI SCR
PRD_FR=$(rg -o '^### FR-[0-9]+' docs/features/$SLUG/prd.md | sort)
UI_SCR=$(rg -o 'SCR-0[1-6]' docs/features/$SLUG/ui-spec.md | sort -u)
# Expected: 18 FR, 6 SCR. FR-012 → SCR-01..05. FR-018 → SCR-06. FR-001..011/013..017 = no UI.

# Mỗi DEC trong SOURCE_OF_TRUTH phải ref ở ui-spec hoặc note "N/A backend only"
DECs=$(rg -o 'DEC-0[0-2][0-9]' docs/features/$SLUG/SOURCE_OF_TRUTH.md | sort -u)
for dec in $DECs; do
  rg -q "$dec" docs/features/$SLUG/ui-spec.md && echo "$dec: OK" || echo "$dec: MISSING (verify N/A)"
done

# Mỗi permission action trong UI Spec phải có ở dev-spec C8 Security
rg -o "pancake_crm_integration\.[a-z_]+" docs/features/$SLUG/ui-spec.md | sort -u
# Expected: access, create, update, delete, view_all, replay_dlq (6 actions)

DEC coverage ui-spec: DEC-016 (Tab structure ✓), DEC-021 (3-mức flag ✓ B5/SCR-01/SCR-02), DEC-017 (slot 9 ✓ SCR-06), DEC-008 (VIP tag NAME ✓ B9 row 6), DEC-009 (test event ✓ SCR-03), DEC-010 (notification ✓ B4), DEC-014 (opt-out ✓ B6.1 status), DEC-024 (3 template ✓ B4), DEC-015 (4-layer recovery ✓ SCR-04 + SCR-03 DLQ replay), DEC-025 (reconciliation ✓ B6.1 event_type), DEC-013 (Loose mode ✓ SCR-02). DEC backend-only (002/003/004/005/006/011/012/018/019/020) đã note "N/A — backend only".


B-QUALITY) Rà soát rủi ro thiếu sót (≥30 rủi ro UI — BẮT BUỘC L)

Rủi ro QA thường gặp

#Rủi roĐã cover ở section
1Thiếu test cho field mớiB0.4 (Field × Surface 30+ field với Required/Format/Business rule)
2Thiếu state empty/loading/errorB0.5 (6 state × 6 SCR × variant) + B2.x.2 Variant Matrix
3Thiếu permission denied testB5.1 (mọi action có denied feedback hidden cho non-admin) + B5.4 EXT-4 field-level
4Thiếu confirm modalB2.SCR-01.10 (Tạo lại token, kill switch), SCR-02.10 (Loose mode, disable with traffic), SCR-03.10 (replay, bulk)
5Thiếu format VNB-i18n + B2.SCR-04 metric cards (99,950% / 12,5s / 92,00%)
6Boundary value không cụ thểB0.4 max 100 dòng VIP tag, ≥16 ký tự api_key, ≤64 ký tự workspace_id, date range ≤90 ngày

Rủi ro UI/UX thường gặp

#Rủi roĐã cover ở section
7Thêm field không nói nằm đâuB0.4 cột "List/Detail/Export/Search" + B2.x.6 bảng cấu hình cột với Vị trí
8Wireframe không vẽ vùng KEEPB2.SCR-06.2 gắn vào layout Tickets/TicketMultipleAdd/CustomerTicketManager existing
9Field không có defaultB0.4 cột Required + Format; mọi field có default rõ ràng (date range = 7 ngày, status filter = "Tất cả", include_test = unchecked)
10Empty state không có CTAB2.SCR-02 Variant B + B2.SCR-03 Variant B đều có CTA "Đồng bộ" / "Mở rộng khoảng thời gian"
11Modal không nói triggerB2.SCR-01.10 + SCR-02.10 + SCR-03.10 mọi confirm modal có trigger event rõ
12Filter không nói defaultB2.SCR-03.5 default 7 ngày, status "Tất cả", source "Tất cả", include_test = false
13Chỉ có happy-path wireframeB2.x.3 wireframes có Variant A (default) + B (empty) + D (loading) + E (error) + F (special state)
14Role/permission flow lệch nhauB5.1 + B-POST.2 + B-POST.4 cross-spec diff
15UI spec hard-code styleB2.x.1A nguyên tắc dùng intent token (primary/success/warning/negative-value/muted)

Rủi ro PO/BA thường gặp

#Rủi roĐã cover
16Spec mới ghi đè behavior cũ mà không khai báoB0.1 Delta Status KEEP/UPDATE/NEW/HIDE/REMOVE cho 100% UI hiện hữu
17Out-of-scope không rõB-Versioning (mobile out scope, export defer MVP-2), G10 mobile out, G11 search payload defer
18Decision không có ≥2 phương ánRef PRD Z 25 DEC (mỗi DEC có alternatives) — UI Spec không re-list

Rủi ro chuyên sâu

#Rủi roĐã cover
19Wireframe ASCII vỡ alignmentB0.6 Wireframe Quality Contract
20Search-replace lỗi enum vào diagramB0.7 Code/Display Bilingual Pairing (12+3+4+3 = 22 cặp)
21UI cho phép null field schema NOT NULLB0.8 Schema Cross-Check — chỉ Loose mode branch_id NULL hợp lệ
22Stepper số bước inconsistencyN/A — feature không có wizard nhiều bước
23Form thiếu autosave/paste/IME contractB2.SCR-01.7B Form Interaction Deep
24Concurrency lờ điB2.SCR-01.7C + SCR-02.7C + SCR-03.7C + EXT-12 reference
25Mạng yếu / offline không ghiB2.SCR-01.7D + B-EdgeCases G2
26Lỗi gộp chung "Có lỗi xảy ra"B2.SCR-01.10A Error Taxonomy + SCR-03.10A
27Print/PDF thiếu page break / version stampN/A — feature không có in pháp lý
28Token UI thiếu countdown / revokeN/A — không phải token portal
29File upload thiếu chunk/retryN/A — không có upload
30Search debounce ad-hocB2.SCR-02.7E rule 5 + SCR-03.7E rule 5 (debounce 300ms search, 500ms date range)
31Bulk action thiếu undo / partialB2.SCR-03 bulk replay có progress + partial success + per-row failure detail (EXT-13)
32Export thiếu masking theo quyềnDefer MVP-2 — đã note ở G8 + B5.4 EXT-4
33Copy length vỡ layoutB6A reference; copy đã ngắn gọn cho buttons (≤24 ký tự VI), tabs (≤16 ký tự VI)
34Format VN saiB-i18n + lint B-POST.3
35Brand voice không nhất quánB-Voice tone formal-professional toàn bộ
36Microcopy form lộn xộnB-Microcopy
37A11y bỏ sótG9 (5 rule keyboard, screen reader, contrast, aria-live, role=alert)
38Performance không nóiB0.4 (audit log capacity 100k/tháng), G3.2 (1M+ event server-side pagination), G3.10 (50+ branches searchable)
39Edge case không phân nhómB-EdgeCases 12 nhóm G1-G12 với ≥5 case mỗi nhóm
40Feature flag / staged rollout không có UIB-Versioning + DEC-021 3-mức (kill switch SCR-01, per-source SCR-02)
41Help touchpoint thiếuB-Help (deeplink docs.diva-group + runbook)
42Lifecycle ≥4 trạng thái không có EXT-3EXT-3 đã load — B6.1 cover 12 trạng thái event + B6.2 cover 3 trạng thái outage
43RBAC field-level không có EXT-4EXT-4 đã load — B5.4 (api_key/token/raw_payload masked qua Hasura permission)
44Audit/compliance UI thiếuEXT-9 reference — SCR-03 audit log cover (event_id, record_id, status, source_ip, correlation_id, retry_count)
45Real-time update không nóiSCR-04 polling 60s; outage banner sticky; defer WebSocket MVP-2
46Notification template không thống nhấtB4 + DEC-024 3 template với placeholder rõ ràng
47Wireframe data placeholder thay vì realisticB0.6 + wireframes thực tế: "Nguyễn Thị Mai 0912345678", "FB Diva HCM Q1", "Diva HN Cầu Giấy"
48Loose mode UI không rõ vs Strict modeSCR-02 dropdown branch có option "Để trống (Loose mode)" + tooltip + confirm modal
49Kill switch vs per-source toggle confusionB6.3 connection_status 4 state + B5.1 + B9 rows 5, 11 phân biệt rõ
50DLQ replay có thể tạo duplicate ticketB6.1 status processed KHÔNG cho replay (idempotency); chỉ dead_letter/auth_failed/parse_error

B-Đối soát DEC (DEC Cross-Reference)

DECStatus trong UI SpecSection ref
DEC-001 (Inbound 1 chiều)N/A — backend only, UI assume inbound(no UI)
DEC-002 (Split webhook + crm-api)N/A — backend architecture(no UI)
DEC-003 (Fail-open 200)Cover qua B6.1 lifecycle 12 status (auth_failed, ip_blocked, parse_error, ... đều terminal trả 200)B6.1
DEC-004 (Idempotency 3-tuple)Cover qua B6.1 skipped_duplicateB6.1
DEC-005 (Phone E.164)N/A — backend only(no UI)
DEC-006 (contact_book ALTER)N/A — backend only(no UI)
DEC-007 (4 bảng pancake_* dùng disabled)Cover gián tiếp qua SCR-02 inline-toggle (is_active) thay vì deleteSCR-02
DEC-008 (VIP tag NAME case-insensitive)Cover B0.4.A row vip_tag_names + B9 row 6 + SCR-01 textarea helper textSCR-01 + B9
DEC-009 (Test event is_test)Cover SCR-03 filter checkbox + B9 row 28SCR-03 + B9
DEC-010 (Notification in-app push admin)Cover B4B4
DEC-011 (Smart update Q2.b)Cover qua B7.8 mapping thuật ngữ "Chỉ tạo ticket khi thay đổi quan trọng"B7.8
DEC-012 (Reuse GetTicketUpdates)N/A — backend only(no UI)
DEC-013 (Auto-assign 1-tier + Loose mode)Cover SCR-02 Loose mode dropdown + B5.4 modalSCR-02
DEC-014 (Opt-out marketing)Cover B6.1 status skipped_opt_out + B7.8B6.1 + B7.8
DEC-015 (4-layer outage recovery)Cover SCR-03 DLQ replay + SCR-04 outage banner + B6.2 lifecycleSCR-03 + SCR-04 + B6.2
DEC-016 (Settings 4 tabs Admin only)Cover B5.1 + SCR-05 + SCR-01..04B5 + B2
DEC-017 (Ticket source slot 9)Cover SCR-06 + B7.6 + B-i18nSCR-06
DEC-018 (REST /sources only)Cover SCR-01 button "Kiểm tra kết nối" + SCR-02 button "Đồng bộ"SCR-01 + SCR-02
DEC-019 (Circuit breaker gobreaker)N/A — backend only(no UI)
DEC-020 (Advisory lock phone)N/A — backend only(no UI)
DEC-021 (3-mức feature flag)Cover SCR-01 kill switch (mức 1) + SCR-02 is_active (mức 3) + B6.3 connection.status (mức 2)SCR-01 + SCR-02 + B6.3
DEC-022 (Pilot 4 tuần)Cover B-Versioning + SCR-02 toggle pilot rollout flowB-Versioning
DEC-023 (5 KPI metric)Cover SCR-04 4 metric cards + B9 calculated rows 36, 39, 46 + 2 chartSCR-04 + B9
DEC-024 (3 notification template)Cover B4B4
DEC-025 (25h overlap reconciliation)Cover B6.1 event_type reconciled + SCR-04 outage history rescue labelB6.1 + SCR-04

DEC backend-only: DEC-001/002/005/006/012/019/020 không có UI surface — note ở ui-spec OK, dev-spec là canonical.


Hết UI Spec.

Sẵn sàng cho dev-spec.md (C1-C12) + qa-test-plan.md (D1-D5) + handoff.md + go-live-checklist.md (E1-E4). Cross-spec consistency check (_CONSISTENCY_MATRIX.md) sẽ chạy sau khi đủ 4 spec.


B-Resolutions) Pass 1 (Phase 5.2 Multi-Perspective Review fixes)

Date: 15/05/2026 | Trigger: FE Lead + UI/UX Designer review.

B-R.1) F18 delta CORRECTED — 4 file (không phải 3)

Verified diva-admin/src/modules/user/components/customer/CustomerTicketManager.tsx:111-158bản sao sourceDescriptions (không import từ Tickets.tsx).

F18 thật:

#FileDelta
1crm/types.ts:146-164ADD TICKET_SOURCE_PANCAKE + append TicketSources
2crm/i18n/vi.ts:129-138ADD nested key crm.label.ticket.ticket_source.ticket_source_pancake: "Pancake CRM"
3crm/pages/Tickets.tsx:128-158ADD entry sourceDescriptions.ticket_source_pancake
4user/components/customer/CustomerTicketManager.tsx:111-158 (NEW)ADD entry sourceDescriptions.ticket_source_pancake — duplicate source-of-truth tạm thời, M2 refactor extract single-source

B-R.2) Component library audit (FE P0-FE-3)

Spec cũRealAction
XPasswordXInputPasswordRename trong B2.SCR-01 + B-i18n
XTextarea(no) QInput type="textarea"Use Quasar
XBadge(no) QBadgeUse Quasar
XDrawer(no) QDrawer / QDialog seamlessVerify pattern Settings
XSearchInput(no) XInput + clear iconUse existing
XDateRangePickerXInputDateRangeRename
XJsonViewerBUILD NEW (~1d FE)New components/core/display/XJsonViewer.tsx
XMetricCardBUILD NEW (~0.5d FE)New components/core/display/XMetricCard.tsx
XChartREUSE modules/dashboard/components/{BarChart,LineChart}Update ref

B-R.3) DEC-026 — Slot 9 trong báo cáo (PO/BA + FE P1-FE-1)

Verified TelesaleReportFilter.tsx:140 + CustomerServiceReportFilter.tsx:195 auto-render slot 9 qua TicketSources.map. RSK-005 PRD A7 giảm severity từ TB → Thấp (auto-render confirm).

B-R.4) Banner outage auto-fade (UX P0-UX-1)

Spec cũ: Banner đỏ persistent đến khi outage_recovered → banner xanh sustain 5 phút.

Revised B6.2:

  • outage_started → banner đỏ persistent (đang đau)
  • outage_recovered → banner xanh auto-fade 30s (báo OK rồi)
  • healthy sau 5 phút sustained → không banner

B-R.5) Replay copy clarity (UX P0-UX-2)

Giữ "Phát lại" canonical (B7.8). Tooltip B9-18 đã có rõ ràng. KHÔNG đổi sang "Xử lý lại" (avoid breaking change downstream).

B-R.6) Banner emoji removal (UX I12)

B-Voice rule: KHÔNG emoji trong copy hành chính. Banner outage bỏ emoji 🚨, dùng intent token + icon SVG.

B-R.7) Status badge icon disambiguation (UX P0-UX-4)

5 status skipped_* icon disambiguation (G9.5 color blind support):

  • skipped_duplicate → 🔁
  • skipped_source_disabled → 🚧
  • skipped_kill_switch → 🛑
  • skipped_opt_out → 🚫
  • skipped_parse_error → ⚠

Hoặc giữ icon đơn, dùng text VN rõ trong B6.1 (đã có).

B-R.8) i18n vi.ts canonical path fix (FE I1)

Spec dev-spec C6 line đơn giản hóa ticket_source_pancake: 'Pancake CRM' thực ra phải nest đầy đủ:

ts
// crm/i18n/vi.ts
{
  crm: {
    label: {
      ticket: {
        ticket_source: {
          ticket_source_1: "Nguồn 1",
          // ...
          ticket_source_pancake: "Pancake CRM"  // CORRECT path
        }
      }
    }
  }
}

Bỏ sessionStorage pancake-crm-last-tab. Dùng URL route ?tab=<name> only — đơn giản, không stale, multi-tab browser support.

B-R.10) Pinia vs composition pattern (FE P1-FE-2)

Pancake settings dùng composition pattern theo compositions/useSettings.ts (cùng module settings) — consistent. KHÔNG mix Pinia store.

B-R.11) Permission check refactor (DEC-027)

UI Spec B5.1 replay_dlq action → REPLACE bằng pancake_crm_integration.update:

tsx
v-if="globalStore.hasPermission('pancake_crm_integration', 'update')"

B-R.12) Polling guard tab visibility (FE P1-FE-3)

SCR-04 polling 60s phải check document.visibilityState === 'visible' trước mỗi refetch. Tránh waste khi 10-20 admin tab mở cùng.

B-R.13) Concurrent inline-edit conflict strategy (FE P1-FE-4)

pancake_source_routing UPDATE với where: { updated_at: { _eq: $oldUpdatedAt } } — nếu 0 affected rows → conflict modal "Bản ghi đã thay đổi. Refetch?".

KHÔNG add version column riêng.

B-R.14) Telesale survey n≥3 (W1 exit — QA D7)

W1 source phải có ≥3 telesale active để telesale survey statistically meaningful. PO/Marketing chọn source W1 theo criteria này.


Hết Pass 1 Resolutions UI Spec. 13 fix consolidated từ FE/UX review.