// ─────────────────────────────────────────────────────────── // Huio Control Panel — System & DB View (cp-system-view.jsx) // ─────────────────────────────────────────────────────────── if (!window.CP_STYLES) throw new Error('[cp-system-view] window.CP_STYLES not loaded'); function renderSystem({ lang = 'vi', t: rawT, appStyles: rawAppStyles, dbStats = [], auctionStreakAlerts = [], bankIntegrations = [], setIsSyncDocModalOpen, handleBankPause, handleBankResume, }) { const appStyles = rawAppStyles || window.CP_STYLES || {}; const t = typeof rawT === 'function' ? rawT : (k) => k; const statsList = Array.isArray(dbStats) ? dbStats : []; const streakList = Array.isArray(auctionStreakAlerts) ? auctionStreakAlerts : []; const bankList = Array.isArray(bankIntegrations) ? bankIntegrations : []; // Safe clipboard helper with browser fallback const handleCopyWebhook = (url) => { if (!url) return; if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { navigator.clipboard.writeText(url) .then(() => alert(lang === 'vi' ? 'Đã sao chép Webhook URL!' : lang === 'en' ? 'Webhook URL copied!' : 'Webhook URL скопирован!')) .catch(() => { prompt(lang === 'vi' ? 'Sao chép URL:' : lang === 'en' ? 'Copy URL:' : 'Скопируйте URL:', url); }); } else { prompt(lang === 'vi' ? 'Sao chép URL:' : lang === 'en' ? 'Copy URL:' : 'Скопируйте URL:', url); } }; return (

{t('db.title')}

{t('db.sub')}

{/* Database Tables statistics */}
{statsList.length === 0 ? ( ) : ( statsList.map((s, idx) => { if (!s) return null; const tableName = s.table || `table_${idx}`; const rowCount = typeof s.rows === 'number' ? s.rows.toLocaleString() : (s.rows ?? '—'); const isRlsActive = s.rls === 'active'; return ( ); }) )}
{t('db.col.table')} {t('db.col.rows')} {t('db.col.rls')}
{lang === 'vi' ? 'Chưa có thông tin bảng CSDL.' : lang === 'en' ? 'No database statistics available.' : 'Нет данных таблиц БД.'}
{tableName} {rowCount} {isRlsActive ? t('db.rls.active') : t('db.rls.inactive')}
{/* Documentation & AI Assistant Section */}

{lang === 'vi' ? 'Tài liệu & Trợ lý AI' : lang === 'en' ? 'Documentation & AI Assistant' : 'Справочник и ИИ-ассистент'}

{lang === 'vi' ? 'Đồng bộ hóa cơ sở dữ liệu vector (embeddings) cho Chatbot hỗ trợ người dùng.' : lang === 'en' ? 'Synchronize vector database embeddings for the user-facing AI support chatbot.' : 'Синхронизация векторной базы знаний (эмбеддингов) для пользовательского ИИ-ассистента.'}

{/* Auction streak antifraud (soft analytics) */}

{lang === 'vi' ? 'Cảnh báo đấu thầu (hốt liên tiếp)' : lang === 'en' ? 'Auction streak alerts' : 'Предупреждения аукциона (серия побед)'}

{lang === 'vi' ? 'Dây có cùng hội viên hốt ≥3 kỳ liên tiếp (chỉ cảnh báo, không chặn).' : lang === 'en' ? 'Groups where the same member won ≥3 consecutive periods (alert only, no block).' : 'Кассы, где один участник выиграл ≥3 периода подряд (только предупреждение).'}

{streakList.length === 0 ? (
{lang === 'vi' ? 'Không có chuỗi bất thường.' : lang === 'en' ? 'No unusual streaks.' : 'Необычных серий нет.'}
) : ( {streakList.map((a, idx) => { if (!a) return null; const rowKey = a.groupId && a.winnerMemberId ? `${a.groupId}:${a.winnerMemberId}` : `streak_${idx}`; return ( ); })}
{lang === 'vi' ? 'Dây' : 'Group'} {lang === 'vi' ? 'Hội viên' : 'Member'} {lang === 'vi' ? 'Chuỗi' : 'Streak'} {lang === 'vi' ? 'Kỳ gần nhất' : 'Last period'}
{a.groupName || a.groupId || '—'} {a.winnerName || a.winnerMemberId || '—'} {a.streak ?? 0} {a.lastPeriod ? `K${a.lastPeriod}` : '—'}
)}
{/* Bank Integration Setup Section */}

{lang === 'vi' ? 'Tích hợp Ngân hàng (Casso/SePay)' : lang === 'en' ? 'Bank Integration (Casso/SePay)' : 'Интеграция с банком (Casso/SePay)'}

{lang === 'vi' ? 'Cấu hình Webhook, theo dõi trạng thái HMAC, tạm dừng hoặc khôi phục kết nối.' : lang === 'en' ? 'Configure Webhook URL, monitor HMAC status, pause/resume connections.' : 'Настройка Webhook, статус HMAC, пауза/возобновление интеграции.'}

{bankList.length === 0 ? (
{lang === 'vi' ? 'Chưa có tích hợp nào.' : lang === 'en' ? 'No integrations found.' : 'Интеграции не найдены.'}
) : ( bankList.map((bi, idx) => { if (!bi) return null; const provider = bi.provider || 'casso'; const baseWebhook = `https://qjuuaqdvhzzclsdwubll.supabase.co/functions/v1/bank-webhook?provider=${encodeURIComponent(provider)}`; const token = typeof bi.webhook_endpoint_token === 'string' ? bi.webhook_endpoint_token : ''; const webhookUrl = provider === 'casso' && token ? `${baseWebhook}&token=${encodeURIComponent(token)}` : baseWebhook; // Masked URL display: token partially hidden (last 8 chars) to avoid leak in screenshots const maskedToken = token.length > 8 ? `...${token.slice(-8)}` : (token ? '...' : ''); const webhookUrlDisplay = provider === 'casso' && token ? `${baseWebhook}&token=${maskedToken}` : baseWebhook; const hmacConfigured = !!bi.webhook_secret_id; const itemKey = bi.id || `bank_item_${idx}`; let pausedStatusText = `${bi.failed_webhook_count || 0} ${lang === 'vi' ? 'lỗi' : lang === 'en' ? 'errors' : 'ошибок'}`; if (bi.auto_confirm_paused_at) { try { const d = new Date(bi.auto_confirm_paused_at); const timeStr = isNaN(d.getTime()) ? String(bi.auto_confirm_paused_at) : d.toLocaleString(); pausedStatusText = `${lang === 'vi' ? 'Tạm dừng lúc' : lang === 'en' ? 'Paused at' : 'Приостановлено'} ${timeStr}`; } catch (_) { pausedStatusText = `${lang === 'vi' ? 'Tạm dừng' : lang === 'en' ? 'Paused' : 'Приостановлено'} (${bi.auto_confirm_paused_at})`; } } return (
{bi.profiles?.name || 'Unknown'} ({bi.profiles?.phone || bi.organizer_id || '—'}) {bi.is_active ? 'Active' : 'Inactive'}
Provider {provider}
Webhook URL
{webhookUrlDisplay}
HMAC Status {hmacConfigured ? ( Configured ) : ( Missing )}
{!hmacConfigured && (
{lang === 'vi' ? 'Cảnh báo bảo mật:' : lang === 'en' ? 'Security Warning:' : 'Критическая уязвимость:'}{' '} {lang === 'vi' ? 'Webhook HMAC Secret chưa được cấu hình! Tích hợp sẽ từ chối các webhook gửi đến trên production.' : lang === 'en' ? 'Webhook HMAC Secret is not configured! Integration will reject incoming webhooks in production.' : 'Webhook HMAC Secret не настроен! Интеграция будет отклонять входящие вебхуки в production.'}
)}
Status / Errors = 3 || bi.auto_confirm_paused_at) ? 'var(--hu-coral, #A8412A)' : 'var(--hu-fg)', fontSize: '13px' }}> {pausedStatusText}
); }) )}
); } window.CpSystemView = { renderSystem, };