// ─────────────────────────────────────────────────────────── // Huio Control Panel — Analytics View (cp-analytics-view.jsx) // ─────────────────────────────────────────────────────────── if (!window.CP_STYLES) throw new Error('[cp-analytics-view] window.CP_STYLES not loaded'); function renderAnalytics({ lang = 'vi', t: rawT, appStyles: rawAppStyles, analyticsEvents: rawAnalyticsEvents = [], eventFilter = 'all', setEventFilter = () => {}, platformFilter = 'all', setPlatformFilter = () => {}, searchQuery = '', setSearchQuery = () => {}, hoveredStep: rawHoveredStep = null, setHoveredStep: rawSetHoveredStep = () => {}, }) { const appStyles = rawAppStyles || window.CP_STYLES || {}; const t = typeof rawT === 'function' ? rawT : (k) => k; const analyticsEvents = Array.isArray(rawAnalyticsEvents) ? rawAnalyticsEvents : []; const hoveredStep = rawHoveredStep; const setHoveredStep = typeof rawSetHoveredStep === 'function' ? rawSetHoveredStep : () => {}; const formatPhone = window.formatPhone || ((p) => p || '—'); // Compute funnel data const funnelData = (() => { const userSteps = {}; analyticsEvents.forEach(e => { const id = e.anonymous_id; if (!userSteps[id]) userSteps[id] = new Set(); userSteps[id].add(e.event_type); }); const uniqueUsers = Object.keys(userSteps).length; const steps = [ { id: 'step1', name: lang === 'vi' ? 'Bắt đầu Onboarding' : 'Onboarding Started', check: (events) => events.has('onboarding_started'), color: '#B85C38' }, // terra { id: 'step2', name: lang === 'vi' ? 'Xem Slide / Bỏ qua' : 'Slide Viewed / Skipped', check: (events) => events.has('onboarding_slide_viewed') || events.has('onboarding_completed') || events.has('onboarding_skipped'), color: '#C19433' }, // ochre { id: 'step3', name: lang === 'vi' ? 'Yêu cầu mã OTP' : 'OTP Requested', check: (events) => events.has('auth_otp_requested') || events.has('auth_otp_verified'), color: '#4F6B3D' }, // forest { id: 'step4', name: lang === 'vi' ? 'Mã OTP khớp' : 'OTP Verified', check: (events) => events.has('auth_otp_verified'), color: '#3A8A8A' }, // teal { id: 'step5', name: lang === 'vi' ? 'Chấp nhận điều khoản' : 'Terms Accepted', check: (events) => events.has('terms_accepted'), color: '#A8412A' }, // rust { id: 'step6', name: lang === 'vi' ? 'Lựa chọn vai trò' : 'Role Selected', check: (events) => events.has('role_selected'), color: '#4A7A96' }, // sky { id: 'step7', name: lang === 'vi' ? 'Nộp hồ sơ CCCD / Hoàn tất' : 'CCCD Submitted / Completed', check: (events) => events.has('identity_verification_submitted') || events.has('identity_verification_completed'), color: '#2C2A29' } // charcoal ]; return steps.map(step => { let count = 0; Object.values(userSteps).forEach(events => { if (step.check(events)) count++; }); const percentOfTotal = uniqueUsers > 0 ? Math.round((count / uniqueUsers) * 100) : 0; return { ...step, count, percent: percentOfTotal }; }); })(); // Filter events const filteredEvents = analyticsEvents.filter(e => { // Event filter if (eventFilter === 'onboarding' && !e.event_type.startsWith('onboarding_')) return false; if (eventFilter === 'auth' && !e.event_type.startsWith('auth_') && e.event_type !== 'terms_accepted') return false; if (eventFilter === 'verification' && !e.event_type.startsWith('identity_verification_')) return false; // Platform filter const plat = e.metadata?.platform || ''; if (platformFilter !== 'all' && plat.toLowerCase() !== platformFilter.toLowerCase()) return false; // Search query if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); const eventName = e.event_type.toLowerCase(); const anonId = e.anonymous_id.toLowerCase(); const userName = e.profiles?.name?.toLowerCase() || ''; const userPhone = e.profiles?.phone || ''; if (!eventName.includes(query) && !anonId.includes(query) && !userName.includes(query) && !userPhone.includes(query)) return false; } return true; }); // Compute unique visitors const totalUniqueUsers = Object.keys(analyticsEvents.reduce((acc, e) => { acc[e.anonymous_id] = true; return acc; }, {})).length; // Compute overall conversion (step 7 / step 1) const step1Count = funnelData[0]?.count || 0; const step7Count = funnelData[6]?.count || 0; const overallConversion = step1Count > 0 ? Math.round((step7Count / step1Count) * 100) : 0; // Device breakdown const iosCount = analyticsEvents.filter(e => { const plat = e.metadata?.platform || e.platform || ''; return plat.toLowerCase() === 'ios'; }).length; const androidCount = analyticsEvents.filter(e => { const plat = e.metadata?.platform || e.platform || ''; return plat.toLowerCase() === 'android'; }).length; const totalPlatform = iosCount + androidCount; const iosPercent = totalPlatform > 0 ? Math.round((iosCount / totalPlatform) * 100) : 50; const androidPercent = totalPlatform > 0 ? Math.round((androidCount / totalPlatform) * 100) : 50; return (
{t('analytics.sub')}
{t('analytics.funnel.desc')}
{lang === 'vi' ? 'Dòng dữ liệu sự kiện thời gian thực được đẩy trực tiếp từ ứng dụng di động.' : 'Real-time telemetry event stream pushed directly from user mobile client.' }
| {t('analytics.col.event')} | {t('analytics.col.user')} | {t('analytics.col.platform')} | {t('analytics.col.time')} |
|---|---|---|---|
| {lang === 'vi' ? 'Không tìm thấy sự kiện nào.' : 'No analytics events found.'} | |||
| {e.event_type} | {displayUser} | {platform.toUpperCase()} | {timeStr} |