/* AAU CRM — QC Engine › QC Review Hàng đợi hội thoại do AI quét tự động (chat đa kênh) + chi tiết chấm điểm theo tiêu chí + bằng chứng trích dẫn. */ const QC_RS = AAU.qcRulesets.find(r => r.id === 'qc1'); // Chuẩn chat tư vấn — 5 tiêu chí const QC_CRIT = QC_RS.criteria; // [Chào hỏi 15, Hỏi nhu cầu 25, Báo giá 30, CTA 20, Chính tả 10] function qcColor(v) { return v >= 8 ? '#0e7c4a' : v >= 6.5 ? '#b06a00' : '#c4320a'; } // ---- mock: hội thoại đã quét ---- const QC_ITEMS = [ { id: 'q1', leadId: 'L1', ch: 'fb', agent: 'u9', score: 9.2, verdict: 'pass', status: 'reviewed', sev: false, at: '8 phút trước', crit: [9, 9, 9, 10, 9], summary: 'Hội thoại mẫu mực: qualify đủ 4 điều kiện trước khi báo giá, chốt CTA gửi payment link rõ ràng.', tx: [ { w: 'c', t: 'Em ơi cho anh hỏi khóa vận hành chuỗi học phí bao nhiêu?' }, { w: 'a', t: 'Dạ em chào anh Khoa ạ 🌿 Trước khi báo giá em xin hỏi nhanh để tư vấn đúng nhất — bên anh đang có mấy chi nhánh và pain chính là gì ạ?' }, { w: 'c', t: '6 chi nhánh, khổ nhất là chi phí nhân sự với chưa chuẩn SOP.' }, { w: 'a', t: 'Dạ chuẩn rồi ạ. Với quy mô 6 CN thì khóa Vận hành Chuỗi (18.9tr) sẽ tối ưu phần SOP + định biên nhân sự. Anh muốn em gửi proposal chi tiết + lock slot khóa T6 không ạ?' }, { w: 'c', t: 'Ok em gửi đi.' }, { w: 'a', t: 'Dạ em gửi payment link + proposal ngay trong hôm nay, anh xem rồi mình confirm slot nhé ạ 🙌', flag: { tone: 'ok', crit: 'Chốt bước tiếp theo (CTA)', note: 'CTA rõ ràng: gửi payment link + lock slot, có khung thời gian.' } }, ] }, { id: 'q2', leadId: 'L3', ch: 'fb', agent: 'u4', score: 5.8, verdict: 'fail', status: 'disputed', sev: true, at: '34 phút trước', crit: [7, 5, 4, 6, 8], summary: 'Báo giá ngay khi khách vừa hỏi, chưa khai thác nhu cầu/ngân sách. Vi phạm nghiêm trọng quy trình qualify.', tx: [ { w: 'c', t: 'Khóa marketing giá nhiêu em?' }, { w: 'a', t: '11.9tr chị nhé.', flag: { tone: 'critical', crit: 'Báo giá & xử lý phản đối', note: 'Báo giá ngay khi chưa qualify nhu cầu, quy mô, ngân sách — sai bước bắt buộc.' } }, { w: 'c', t: 'Mắc vậy, thôi để chị suy nghĩ thêm.' }, { w: 'a', t: 'Dạ chị cân nhắc nha.', flag: { tone: 'warning', crit: 'Chốt bước tiếp theo (CTA)', note: 'Không đặt lịch follow-up, không xử lý phản đối về giá — để hội thoại trôi.' } }, ] }, { id: 'q3', leadId: 'L8', ch: 'zalo', agent: 'u5', score: 5.2, verdict: 'fail', status: 'ai', sev: true, at: '52 phút trước', crit: [6, 5, 4, 5, 7], summary: 'Hứa cam kết sai chính sách ("đảm bảo tăng doanh thu 2 lần") — rủi ro pháp lý & uy tín. Cần manager review gấp.', tx: [ { w: 'c', t: 'Học khóa này có hiệu quả thật không em?' }, { w: 'a', t: 'Anh yên tâm, học xong đảm bảo doanh thu tăng gấp đôi trong 3 tháng ạ!', flag: { tone: 'critical', crit: 'Báo giá & xử lý phản đối', note: 'Cam kết kết quả tuyệt đối không có trong chính sách — rủi ro pháp lý, vi phạm nghiêm trọng.' } }, { w: 'c', t: 'Gấp đôi luôn hả, vậy chốt đi.' }, { w: 'a', t: 'Dạ vâng để em gửi thông tin.' }, ] }, { id: 'q4', leadId: 'L6', ch: 'fb', agent: 'u9', score: 6.4, verdict: 'fail', status: 'ai', sev: false, at: '1 giờ trước', crit: [8, 6, 6, 4, 8], summary: 'Tư vấn ổn nhưng kết thúc lửng — không có lời kêu gọi hành động, bỏ lỡ cơ hội lấy số / hẹn gọi.', tx: [ { w: 'c', t: 'Anh đang muốn nhượng quyền mảng pizza.' }, { w: 'a', t: 'Dạ vậy khóa Nhượng quyền & SOP sẽ hợp với anh ạ, nội dung gồm chuẩn hóa quy trình + tài chính franchise.' }, { w: 'c', t: 'Ừ nghe cũng hay.' }, { w: 'a', t: 'Dạ ạ.', flag: { tone: 'warning', crit: 'Chốt bước tiếp theo (CTA)', note: 'Khách đang có tín hiệu quan tâm nhưng agent không chốt bước tiếp theo (xin SĐT / hẹn gọi).' } }, ] }, { id: 'q5', leadId: 'L2', ch: 'zalo', agent: 'u4', score: 8.6, verdict: 'pass', status: 'ai', sev: false, at: '2 giờ trước', crit: [9, 8, 9, 8, 9], summary: 'Qualify tốt, xử lý phản đối ngân sách bằng option trả góp, chốt gửi proposal cá nhân hóa.', tx: [ { w: 'c', t: 'Bên chị có 3 khách sạn, mảng F&B đang lỗ.' }, { w: 'a', t: 'Dạ em hiểu ạ. Vậy mục tiêu chính của chị là cải thiện lãi gộp F&B hay chuẩn hóa vận hành trước ạ?' }, { w: 'c', t: 'Cả hai nhưng F&B lỗ là gấp nhất.' }, { w: 'a', t: 'Dạ vậy em đề xuất khóa P&L & Vận hành (14.9tr). Nếu ngân sách cần linh hoạt mình có trả góp 3 kỳ ạ. Em gửi proposal riêng cho nhóm KS của chị nhé?' }, { w: 'c', t: 'Ừ em gửi.' }, ] }, { id: 'q6', leadId: 'L5', ch: 'instagram', agent: 'u9', score: 7.4, verdict: 'pass', status: 'ai', sev: false, at: '3 giờ trước', crit: [8, 7, 7, 7, 9], summary: 'Đạt chuẩn, qualify cơ bản tốt; phần xử lý timeline ("muốn làm Q3") có thể khai thác sâu hơn.', tx: [ { w: 'c', t: 'Shop trà sữa mình có 4 chi nhánh, đang lộn xộn vụ nhượng quyền.' }, { w: 'a', t: 'Dạ em chào chị Ngọc 🌿 4 CN mà nhượng quyền chưa chuẩn thì đúng là rủi ro chất lượng ạ. Chị dự định mở rộng tiếp trong năm nay chứ ạ?' }, { w: 'c', t: 'Ừ tầm Q3 muốn làm bài bản.' }, { w: 'a', t: 'Dạ vậy mình còn thời gian chuẩn bị tốt. Em gửi lộ trình khóa Nhượng quyền nhé, mình set 1 buổi tư vấn sâu trước Q3 ạ.' }, ] }, { id: 'q7', leadId: 'L16', ch: 'fb', agent: 'u9', score: 8.1, verdict: 'pass', status: 'reviewed', sev: false, at: '4 giờ trước', crit: [9, 8, 8, 8, 8], summary: 'Tận dụng tốt context webinar khách đã xem, dẫn dắt sang call qualify tự nhiên.', tx: [ { w: 'c', t: 'Chị xem webinar nhượng quyền của bên em rồi.' }, { w: 'a', t: 'Dạ em cảm ơn chị Hương đã theo dõi ạ 🙏 Sau webinar chị thấy phần SOP hay phần tài chính franchise cần làm rõ thêm ạ?' }, { w: 'c', t: 'Phần tài chính, chị chưa rõ vốn cần bao nhiêu.' }, { w: 'a', t: 'Dạ phần đó em xin 10 phút gọi để tính nhanh theo mô hình bún bò của chị nhé, chiều nay 3h được không ạ?' }, ] }, { id: 'q8', leadId: 'L12', ch: 'fb', agent: 'u4', score: 6.9, verdict: 'fail', status: 'ai', sev: false, at: '5 giờ trước', crit: [8, 7, 6, 6, 8], summary: 'Sát ngưỡng PASS (7.0). Hỏi nhu cầu ổn nhưng chưa chốt lấy SĐT / chuyển Zalo để nuôi tiếp.', tx: [ { w: 'c', t: 'Mình có 3 chi nhánh chè, muốn mở rộng thêm.' }, { w: 'a', t: 'Dạ chị muốn mở rộng theo hướng tự mở hay nhượng quyền ạ?' }, { w: 'c', t: 'Chưa rõ, đang tìm hiểu.' }, { w: 'a', t: 'Dạ vậy em gửi tài liệu so sánh 2 hướng cho chị tham khảo nhé.', flag: { tone: 'warning', crit: 'Chốt bước tiếp theo (CTA)', note: 'Nên xin SĐT / add Zalo để nuôi tiếp thay vì chỉ gửi tài liệu rồi để ngỏ.' } }, ] }, ]; function QcChannelBadge({ ch }) { const c = AAU.channels[ch] || { short: ch, color: '#888' }; return {c.label || c.short}; } function QCReview() { const [open, setOpen] = useState(null); const [fCh, setFCh] = useState('all'); const [fVerdict, setFVerdict] = useState('all'); const [fStatus, setFStatus] = useState('all'); const live = !!(window.API && window.API.enabled); const [scan, setScan] = useState(null); const [qcCfg, setQcCfg] = useState(null); const [aiCfg, setAiCfg] = useState(null); const [scanning, setScanning] = useState(false); const [scCh, setScCh] = useState('all'); const [scFrom, setScFrom] = useState(''); const [scTo, setScTo] = useState(''); useEffect(() => { if (!live) return; if (window.API.qcScan) window.API.qcScan().then(setScan).catch(() => { }); if (window.API.qcGetConfig) window.API.qcGetConfig().then(setQcCfg).catch(() => { }); if (window.API.aiConfig) window.API.aiConfig().then(setAiCfg).catch(() => { }); }, [live]); const SRC = (scan && scan.items) || QC_ITEMS; const sum = scan && scan.summary; const engineOn = !!(qcCfg && qcCfg.autoScan && qcCfg.autoScan.enabled); const QC_MODELS = { claude: [{ value: 'claude-opus-4-8', label: 'Claude Opus 4.8' }, { value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' }, { value: 'claude-haiku-4-5', label: 'Claude Haiku 4.5' }], gemini: [{ value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' }, { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' }] }; const runScan = () => { if (!live || scanning) return; setScanning(true); const qs = []; if (scFrom) qs.push('from=' + scFrom); if (scTo) qs.push('to=' + scTo); if (scCh !== 'all') qs.push('channel=' + scCh); window.API.qcRun(qs.join('&')).then(r => { setScan(r); setScanning(false); if (window.API.qcGetConfig) window.API.qcGetConfig().then(setQcCfg); }).catch(() => setScanning(false)); }; const saveAi = patch => { setAiCfg(c => ({ ...c, ...patch })); if (window.API.saveAiConfig) window.API.saveAiConfig(patch).then(setAiCfg).catch(() => { }); }; const saveQc = patch => { setQcCfg(c => ({ ...c, ...patch })); if (window.API.qcSaveConfig) window.API.qcSaveConfig(patch).then(setQcCfg).catch(() => { }); }; const enrich = q => { const u = AAU.users.find(x => x.id === q.agent); return { ...q, lead: AAU.leadById(q.leadId) || { name: q.name || 'Khách', company: '' }, agentName: (u && u.name) || q.agentName || 'AI Bot', agentColor: (u && u.color) || '#8a8f98' }; }; const rows = SRC.map(enrich).filter(q => (fCh === 'all' || q.ch === fCh) && (fVerdict === 'all' || q.verdict === fVerdict) && (fStatus === 'all' || q.status === fStatus)); const statusMeta = { ai: { l: 'AI tự động', tone: 'neutral' }, reviewed: { l: 'Đã duyệt', tone: 'success' }, disputed: { l: 'Kháng nghị', tone: 'critical' } }; const cur = open ? enrich(open) : null; return (
| Khách hàng | Kênh | Agent | Điểm | Verdict | Trạng thái | Quét lúc | |
|---|---|---|---|---|---|---|---|
{q.lead.company} |
{q.agentName} | {q.score.toFixed(1)} | {q.at} |