/* AAU CRM — Work Queue "Hôm nay": rep's daily home. Data Priority Flow worklist · 3 switchable layouts · daily routine rail. */ function WQLeadRow({ lead, onOpen, dense }) { const st = AAU.stageById(lead.stage); const tier = priorityTier(lead); return (
{lead.company} {lead.hot && }
{lead.signal && }
{!dense &&
{lead.nextAction}
}
{lead.dealValue > 0 && {AAU.fmtVNDm(lead.dealValue)}} {leadPriority(lead)}
); } function WorkQueue() { const leads = useLeads(); const viewer = useViewer(); const isLeader = viewer.buScope === 'all'; const canEdit = SalesAccess.canEdit(viewer); const [fMember, setFMember] = useState('all'); const [fGroup, setFGroup] = useState('all'); const [layout, setLayout] = useState(() => localStorage.getItem('wq_layout') || 'priority'); const [open, setOpen] = useState(null); useEffect(() => localStorage.setItem('wq_layout', layout), [layout]); const groupOpts = isLeader ? [...new Set(leads.map(l => l.courseInterest).filter(Boolean))] : (viewer.buScope || []); // actionable queue scoped to viewer (exclude junk + lost) let queue = SalesAccess.scoped(leads, viewer) .filter(l => { const st = AAU.stageById(l.stage); return st && !st.flags.includes('junk') && !st.flags.includes('lost'); }); if (isLeader && fMember !== 'all') queue = queue.filter(l => (fMember === 'none' ? !l.assignedTo : l.assignedTo === fMember)); if (fGroup !== 'all') queue = queue.filter(l => l.courseInterest === fGroup); queue = queue.sort((a, b) => leadPriority(b) - leadPriority(a)); const toggleQ = (id, k) => LeadStore.toggleQualify(id, k); // KPI strip const cnt = (fn) => queue.filter(fn).length; const toClose = cnt(l => { const s = AAU.stageById(l.stage); return s && s.flags.includes('hotlane'); }); const critical = cnt(l => { const s = AAU.stageById(l.stage); return s && ['1.6', '3.1', '1.4'].includes(s.code); }); const overdue = cnt(l => l.sla === 'bad'); const hotSig = cnt(l => l.signal && l.signal.level === 'HIGH'); const openLead = (l) => setOpen(l.id); const current = open ? leads.find(l => l.id === open) : null; // grouping for layouts const tiers = { now: [], next: [], later: [] }; queue.forEach(l => tiers[priorityTier(l).key].push(l)); const laneGroups = [ { id: 'hot', label: '🔥 Hot lane — Close mode', hint: '3.5 / 3.6 / 3.7 · respond < 1h', test: s => s.flags.includes('hotlane') }, { id: 'qual', label: 'Critical window', hint: '1.6 / 3.1 / 1.4 · không để nguội', test: s => ['1.6', '3.1', '1.4'].includes(s.code) }, { id: 'newlead', label: 'Lead cần qualify', hint: '1.0 / 1.2 / 2.1 / 2.2 / 2.3 / 1.5', test: s => (s.funnel === 'lead' || s.funnel === 'leadb') && !['1.6', '1.4'].includes(s.code) }, { id: 'nurture', label: 'Nurture queue', hint: '3.0 / 3.2 / 3.3 / 3.4', test: s => ['3.0', '3.2', '3.3', '3.4'].includes(s.code) }, { id: 'won', label: 'Won & Alumni', hint: 'Onboard · cross-sell · alumni tái bán', test: s => s.funnel === 'won' || s.funnel === 'alumni' }, ]; return (
Hôm nay · {AAU.fmtDate('2026-06-02')}} subtitle="Work queue theo Data Priority — luôn từ nóng xuống nguội, không skip stage. Click để MAKE THE MOVE." actions={<> {isLeader && ({ value: g, label: (AAU.courseById(g) || {}).code || g }))]} style={{ width: 150 }} />} } />
Cần CHỐT hôm nay
{toClose}
Hot lane · close mode
Critical window
{critical}
Qualify 48h · không để nguội
Quá hạn SLA
{overdue}
Xử lý trước tiên
Hot signal HIGH
{hotSig}
Đẩy lên 3.5 ngay
{queue.length} việc cần xử lýxếp theo điểm ưu tiên
{/* LAYOUT A — single ranked list */} {layout === 'priority' && (
{queue.map(l => openLead(l)} />)} {queue.length === 0 && }
)} {/* LAYOUT B — Now / Next / Later buckets */} {layout === 'nnl' && (
{[{ k: 'now', t: 'NOW', d: 'Xử lý ngay', c: '#c4320a' }, { k: 'next', t: 'NEXT', d: 'Trong hôm nay', c: '#9a6700' }, { k: 'later', t: 'LATER', d: 'Nurture / theo dõi', c: '#5a6472' }].map(col => (
{col.t}{col.d}{tiers[col.k].length}
{tiers[col.k].map(l => openLead(l)} dense />)} {tiers[col.k].length === 0 &&
}
))}
)} {/* LAYOUT C — by lane sections */} {layout === 'lane' && (
{laneGroups.map(grp => { const items = queue.filter(l => grp.test(AAU.stageById(l.stage))); if (!items.length) return null; const val = items.reduce((s, l) => s + (l.dealValue || 0), 0); return (
{grp.label} · {grp.hint}
{items.length}{val > 0 && {AAU.fmtVNDm(val)}}
{items.map(l => openLead(l)} />)}
); })}
)}
{/* daily routine rail */}
{current && setOpen(null)} onMove={(id, to, r, t) => { LeadStore.move(id, to, r, t); setOpen(null); }} onToggleQualify={toggleQ} readOnly={!canEdit} />}
); } function DailyRoutineRail() { const flow = (window.PLAYBOOK && PLAYBOOK.dailyFlowBU) || { am: [], pm: [] }; const all = [...flow.am, ...flow.pm]; // mock "current" block — 9:00–10:30 Hot calls const activeIdx = 2; return (
{all.map((b, i) => (
{b.time}{b.dur}
{b.title}
{i === activeIdx &&
    {b.items.map((it, j) =>
  • {it}
  • )}
}
))}
Báo cáo cuối ngày
Submit trước 18:00 cho Sales Leader (Larksuite).
); } Object.assign(window, { WorkQueue });