import { api, BASE_URL } from './api.js'; import { auth } from './auth.js'; console.log('--- main.js Version 2.1 Loaded ---'); // Application State const state = { activePage: 'dashboard', users: [], campaigns: [], chart: null, activeMetrics: null, activeCampaignId: null }; // --- Initialization --- async function init() { initDates(); setupNavigation(); setupEventListeners(); setupAuthEvents(); setupAutomationEvents(); if (auth.isAuthenticated()) { showApp(); } else { showLogin(); } } // Modules run after DOM is ready by default init(); function initDates() { // Standard initialization with Flatpickr const commonConfig = { dateFormat: "Y-m-d", theme: "dark", locale: "ar" // Flatpickr will look for 'ar' in the included locale script }; flatpickr("#date-start", { ...commonConfig, defaultDate: "2025-11-01" }); flatpickr("#date-end", { ...commonConfig, defaultDate: "2026-03-25" }); } async function loadInitialData() { try { // 1. Fetch Users const users = await api.getAllUsers().catch(err => { console.error('Failed to fetch users:', err); return []; }); state.users = users; renderUserSelector(); // 2. Load Accounts and Refresh if we have a user if (state.users.length > 0) { await loadAccounts(); await refreshDashboard(); } else { console.warn('No users found in system'); showErrorState({ message: 'لا يوجد مستخدمين متاحين حالياً.' }); } } catch (error) { console.error('Failed to load initial data:', error); } } async function loadAccounts() { const select = document.getElementById('ad-account-select'); if (!select) return; try { const accounts = await api.getConnectedAccounts().catch(() => []); select.innerHTML = '' + accounts.map(acc => ``).join(''); } catch (err) { console.error('Failed to load accounts:', err); } } // --- UI Rendering --- function renderUserSelector() { const userInfo = document.getElementById('user-info-display'); if (!userInfo) return; const user = auth.getUser(); if (user) { userInfo.innerHTML = `
مرحباً،
${user.email}
المستوى: ${user.subscriptionTier.toUpperCase()}
`; document.getElementById('logout-btn')?.addEventListener('click', () => auth.logout()); lucide.createIcons(); } } function showLogin() { document.getElementById('login-overlay').classList.remove('hidden'); document.getElementById('main-app').classList.add('hidden'); } function showApp() { document.getElementById('login-overlay').classList.add('hidden'); document.getElementById('main-app').classList.remove('hidden'); loadInitialData(); } function setupAuthEvents() { const loginForm = document.getElementById('login-form'); const loginError = document.getElementById('login-error'); loginForm?.addEventListener('submit', async (e) => { e.preventDefault(); const email = document.getElementById('login-email').value; const submitBtn = loginForm.querySelector('button'); try { submitBtn.disabled = true; submitBtn.innerHTML = 'جاري التحقق...'; loginError.classList.add('hidden'); const user = await api.login(email); auth.login(user); showApp(); } catch (err) { loginError.textContent = err.message || 'فشل تسجيل الدخول'; loginError.classList.remove('hidden'); } finally { submitBtn.disabled = false; submitBtn.innerHTML = 'دخول للمنصة'; } }); document.getElementById('btn-google-login')?.addEventListener('click', () => { alert('تسجيل الدخول عبر Google سيتوفر قريباً بعد إعداد مفاتيح API الخاصة بك.'); // In real app: window.location.href = `${BASE_URL}/auth/google/app-login`; }); } async function refreshDashboard(demo = false) { const dashboard = document.getElementById('dashboard-page'); if (dashboard.classList.contains('hidden')) return; const refreshBtn = document.getElementById('refresh-data'); if (refreshBtn) refreshBtn.innerHTML = ' جاري التحميل...'; const params = { dateStart: document.getElementById('date-start').value, dateEnd: document.getElementById('date-end').value, adAccountId: document.getElementById('ad-account-select').value || undefined }; try { let data; if (demo) { data = await api.getSampleData(params); } else { data = await api.getInsights(params); } if (data && Array.isArray(data)) { state.campaigns = data; renderDynamicDashboard(); // New function for premium UI hideErrorState(); } } catch (err) { console.error('Dashboard refresh failed:', err); showErrorState(err); } finally { if (refreshBtn) { refreshBtn.innerHTML = ' تحديث البيانات'; lucide.createIcons(); } } } function renderDynamicDashboard() { const activeFilter = document.querySelector('.platform-btn.active')?.dataset.platform || 'all'; let filteredData = [...state.campaigns]; if (activeFilter !== 'all') { if (activeFilter === 'instagram') { filteredData = filteredData.filter(c => c.platform === 'instagram'); } else if (activeFilter === 'meta') { filteredData = filteredData.filter(c => c.source === 'meta' && c.platform !== 'instagram'); } else { filteredData = filteredData.filter(c => c.source === activeFilter); } } // Sort: ACTIVE/ENABLED first | ترتيب: النشط أولاً filteredData.sort((a, b) => { const aActive = a.status === 'ACTIVE' || a.status === 'ENABLED'; const bActive = b.status === 'ACTIVE' || b.status === 'ENABLED'; if (aActive && !bActive) return -1; if (!aActive && bActive) return 1; return 0; }); populatePremiumKPIs(filteredData); populatePremiumTable(filteredData); renderChart(filteredData); } function populatePremiumKPIs(data) { const container = document.getElementById('kpi-cards'); if (!container) return; const stats = { impressions: data.reduce((sum, c) => sum + (c.impressions || 0), 0), clicks: data.reduce((sum, c) => sum + (c.clicks || 0), 0), spend: data.reduce((sum, c) => sum + (c.spend || 0), 0), ctr: data.length > 0 ? (data.reduce((sum, c) => sum + (c.ctr || 0), 0) / data.length) : 0 }; const cards = [ { label: 'إجمالي الظهور', value: stats.impressions.toLocaleString(), icon: 'eye', color: 'accent' }, { label: 'النقرات المستهدفة', value: stats.clicks.toLocaleString(), icon: 'mouse-pointer-2', color: 'success' }, { label: 'الإنفاق الذكي', value: `$${stats.spend.toLocaleString()}`, icon: 'dollar-sign', color: 'warning' }, { label: 'متوسط الأداء (CTR)', value: `${stats.ctr.toFixed(2)}%`, icon: 'zap', color: 'danger' } ]; container.innerHTML = cards.map(c => `
${c.label}
${c.value}
`).join(''); lucide.createIcons(); } function populatePremiumTable(data) { const tbody = document.querySelector('#campaign-table tbody'); if (!tbody) return; if (data.length === 0) { tbody.innerHTML = 'لا توجد حملات لهذه المنصة حالياً'; return; } tbody.innerHTML = data.map(c => { const rec = getAIRecommendation(c); const statusClass = (c.status === 'ACTIVE' || c.status === 'ENABLED') ? 'status-active' : 'status-paused'; const statusText = (c.status === 'ACTIVE' || c.status === 'ENABLED') ? 'نشط' : (c.status === 'PAUSED' ? 'متوقف' : (c.status || 'غير معروف')); return `
${c.campaignName || 'Unnamed'} ${statusText} ${formatCompact(c.impressions || 0)} ${formatCompact(c.clicks || 0)} $${(c.spend || 0).toLocaleString()} ${rec.text} `; }).join(''); lucide.createIcons(); } function getAIRecommendation(c) { if (c.aiRecommendation) { return { text: c.aiRecommendation.substring(0, 50) + '...', type: 'excellent' }; } if (c.ctr > 2.5) return { text: 'أداء مذهل - استمر', type: 'excellent' }; if (c.spend > 100 && c.clicks < 10) return { text: 'إنفاق عالي - راقب', type: 'danger' }; if (c.ctr < 1.0) return { text: 'تحسين العرض مطلوب', type: 'warning' }; return { text: 'أداء مستقر', type: 'excellent' }; } function formatCompact(num) { if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; return num; } window.analyzeCampaign = (id) => { console.log('analyzeCampaign triggered for ID:', id); if (!state.campaigns || state.campaigns.length === 0) { console.warn('analyzeCampaign aborted: state.campaigns is empty.'); return; } const campaign = state.campaigns.find(c => String(c.campaignId) === String(id)); if (!campaign) { console.error('analyzeCampaign: Campaign not found for ID:', id); console.table(state.campaigns.map(c => ({ id: c.campaignId, name: c.campaignName }))); return; } console.log('Found campaign:', campaign.campaignName); // Clear previous AI state before switching try { clearAILab(); } catch (e) { console.error('Error in clearAILab:', e); } state.activeCampaignId = id; let imageUrl = campaign.imageUrl || ''; if (imageUrl && !imageUrl.startsWith('http')) { const serverBase = BASE_URL.replace('/api', ''); imageUrl = serverBase + imageUrl; } // Switch to AI Lab and pre-fill context switchPage('ai-lab'); document.getElementById('ai-image-url').value = imageUrl; // Use real ad copy if available, otherwise fallback to stats // استخدام نص الإعلان الحقيقي إذا وجد، وإلا نستخدم الإحصائيات const content = campaign.adCopy || `تحليل الحملة: ${campaign.campaignName}\nالمنصة: ${campaign.source}\nالظهور: ${formatCompact(campaign.impressions || 0)}\nالإنفاق: $${(campaign.spend || 0).toLocaleString()}`; document.getElementById('ai-copy').value = content; // Store metrics for the analysis button | تخزين المقاييس لزر التحليل state.activeMetrics = { impressions: campaign.impressions, clicks: campaign.clicks, spend: campaign.spend, ctr: campaign.ctr, cpc: campaign.cpc, cpm: campaign.cpm, conversions: campaign.conversions, costPerResult: campaign.costPerResult, objective: campaign.objective, reach: campaign.reach, frequency: campaign.frequency }; console.log('Campaign context set for AI Lab:', state.activeMetrics); }; function renderChart(data) { const ctx = document.getElementById('campaign-chart').getContext('2d'); if (state.chart) { state.chart.destroy(); } state.chart = new Chart(ctx, { type: 'bar', data: { labels: data.map(c => c.campaignName), datasets: [ { label: 'الإنفاق (USD)', data: data.map(c => c.spend), backgroundColor: 'rgba(59, 130, 246, 0.5)', borderColor: '#3b82f6', borderWidth: 1 }, { label: 'النقرات', data: data.map(c => c.clicks), backgroundColor: 'rgba(16, 185, 129, 0.5)', borderColor: '#10b981', borderWidth: 1 } ] }, options: { responsive: true, plugins: { legend: { labels: { color: '#94a3b8' } } }, scales: { y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8' } }, x: { grid: { display: false }, ticks: { color: '#94a3b8' } } } } }); } // --- Navigation --- function setupNavigation() { const navItems = document.querySelectorAll('.nav-item'); navItems.forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); const page = item.getAttribute('data-page'); switchPage(page); }); }); } function switchPage(pageId) { console.log('Switching to page:', pageId); const targetNav = document.querySelector(`[data-page="${pageId}"]`); const targetPage = document.getElementById(`${pageId}-page`); if (!targetNav || !targetPage) { console.error(`switchPage Error: Element not found for pageId: ${pageId}`, { targetNav, targetPage }); return; } // UI update document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active')); targetNav.classList.add('active'); document.querySelectorAll('.page-content').forEach(p => p.classList.add('hidden')); targetPage.classList.remove('hidden'); document.getElementById('page-title').textContent = targetNav.querySelector('span').textContent; state.activePage = pageId; if (pageId === 'dashboard') refreshDashboard(); if (pageId === 'automation') refreshRules(); // Re-init icons for the new page if (window.lucide) lucide.createIcons(); } // --- Event Listeners --- function setupEventListeners() { document.getElementById('refresh-data')?.addEventListener('click', () => refreshDashboard(false)); // Demo Mode Buttons document.querySelectorAll('.btn-demo').forEach(btn => { btn.onclick = () => refreshDashboard(true); }); // Platform Filters document.querySelectorAll('.platform-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.platform-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderDynamicDashboard(); }); }); document.getElementById('btn-connect-meta')?.addEventListener('click', async () => { const data = { name: document.getElementById('acc-name').value, adAccountId: document.getElementById('acc-id').value, // Fixed from externalAdAccountId accessToken: document.getElementById('acc-token').value, platform: 'meta' }; try { await api.connectMeta(data); alert('تم ربط الحساب بنجاح!'); switchPage('dashboard'); } catch (err) { console.error(err); alert(`فشل الربط: ${err.message || 'خطأ غير معروف'}`); } }); document.getElementById('btn-oauth-meta')?.addEventListener('click', () => { const userId = api.getCurrentUserId(); if (!userId) { alert('يرجى اختيار مستخدم أولاً'); return; } window.location.href = `${BASE_URL}/auth/meta?userId=${userId}`; }); document.getElementById('btn-oauth-google')?.addEventListener('click', () => { const userId = api.getCurrentUserId(); if (!userId) { alert('يرجى اختيار مستخدم أولاً'); return; } window.location.href = `${BASE_URL}/auth/google?userId=${userId}`; }); document.getElementById('btn-oauth-tiktok')?.addEventListener('click', () => { const userId = api.getCurrentUserId(); if (!userId) { alert('يرجى اختيار مستخدم أولاً'); return; } window.location.href = `${BASE_URL}/auth/tiktok?userId=${userId}`; }); } // Correct closing brace for setupEventListeners // --- AI Lab Functions and Listeners --- // 1. دالة التنسيق المخصصة (Zero-Dependency) function formatGeminiText(text) { if (!text) return ''; let html = text; // تنسيق العناوين (Headers) html = html.replace(/^### (.*$)/gim, '

$1

'); html = html.replace(/^## (.*$)/gim, '

$1

'); html = html.replace(/^# (.*$)/gim, '

$1

'); // تنسيق الخط العريض (Bold) html = html.replace(/\*\*(.*?)\*\*/gim, '$1'); // تنسيق القوائم والنقاط (Lists) html = html.replace(/^\* (.*$)/gim, '
  • $1
  • '); html = html.replace(/^- (.*$)/gim, '
  • $1
  • '); // تنسيق الفواصل (Horizontal Rules) html = html.replace(/^---/gim, '
    '); // سطر جديد (Line Breaks) - هذا ما سيمنع تكتل النص ككتلة واحدة html = html.replace(/\n/gim, '
    '); return html; } // 2. زر التحليل document.getElementById('btn-analyze-visual')?.addEventListener('click', async () => { const url = document.getElementById('ai-image-url')?.value; const copy = document.getElementById('ai-copy')?.value; const out = document.getElementById('ai-result-box'); const btn = document.getElementById('btn-analyze-visual'); const originalBtnText = btn?.innerHTML || 'تحليل التصميم بالذكاء الاصطناعي'; console.log('--- AI Analysis Clicked ---'); if (!url) { console.warn('Analysis aborted: No image URL provided.'); if (out) out.innerHTML = '

    الرجاء إدخال رابط الصورة أولاً.

    '; return; } // Disable button and show loader | تعطيل الزر وإظهار مؤشر التحميل if (btn) { btn.disabled = true; btn.innerHTML = ' جاري التحميل...'; } if (out) { out.innerHTML = `

    جاري تحليل التصميم واستخراج النتائج...

    `; } if (window.lucide) lucide.createIcons(); console.log('Requesting analysis with:', { url, copy, metrics: state.activeMetrics, campaignId: state.activeCampaignId }); try { const res = await api.analyzeVisual(url, copy, state.activeMetrics, state.activeCampaignId); console.log('Analysis Response Received:', res); let rawText = res.feedback || res || ''; // 1. إصلاح مشكلة الأسطر (في حال كان السيرفر يرسلها كـ النص الحرفي \n) rawText = rawText.replace(/\\n/g, '\n'); // 2. استيراد مكتبة marked برمجياً وبشكل ديناميكي (مضمونة 100% في الـ ES Modules) console.log('Importing marked...'); const module = await import('https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js'); // 3. تحويل النص (العناوين، القوائم، والجداول) const formattedHTML = module.marked.parse(rawText); // 4. عرض النتيجة مع CSS داخلي لتجميل الجداول والنصوص if (out) { out.innerHTML = `
    ${formattedHTML}
    `; } } catch (err) { console.error("Analysis Error:", err); if (out) { out.innerHTML = `

    فشل التحليل. تأكد من الرابط أو حاول لاحقاً.

    `; } } finally { // Re-enable button and restore text | إعادة تفعيل الزر واستعادة النص if (btn) { btn.disabled = false; btn.innerHTML = originalBtnText; if (window.lucide) lucide.createIcons(); } } }); function clearAILab() { const urlInput = document.getElementById('ai-image-url'); const copyInput = document.getElementById('ai-copy'); const resultBox = document.getElementById('ai-result-box'); state.activeCampaignId = null; state.activeMetrics = null; if (urlInput) urlInput.value = ''; if (copyInput) copyInput.value = ''; if (resultBox) { resultBox.innerHTML = `
    التقرير سيظهر هنا بعد التحليل
    `; } if (window.lucide) lucide.createIcons(); } // Attach back button logic document.getElementById('btn-back-to-dashboard')?.addEventListener('click', () => { console.log('Back button clicked! Navigation to dashboard started.'); switchPage('dashboard'); }); function showErrorState(err) { const tableBody = document.querySelector('#campaign-table tbody'); let message = 'حدث خطأ أثناء جلب البيانات'; if (err.status === 502) { message = 'انتهت صلاحية الـ Access Token مع ميتـا. يرجى تجديده أو استخدام "وضع التجربة".'; } tableBody.innerHTML = `

    ${message}

    `; lucide.createIcons(); } function hideErrorState() { // Reset handled by populateTable } function checkAuthRedirect() { const urlParams = new URLSearchParams(window.location.search); if (urlParams.has('auth')) { const status = urlParams.get('auth'); const platform = urlParams.get('platform'); if (status === 'success') { alert(`تم ربط حساب ${platform} بنجاح!`); // Clean URL window.history.replaceState({}, document.title, window.location.pathname); } else if (status === 'failed') { alert('فشل عملية الربط، يرجى المحاولة مرة أخرى.'); window.history.replaceState({}, document.title, window.location.pathname); switchPage('connect'); // Take them back to try again } } } // --- Automation Rules --- async function refreshRules() { console.log('Refreshing automation rules...'); const tbody = document.getElementById('rules-list-body'); if (!tbody) return; try { const rules = await api.getRules(); renderRules(rules); } catch (err) { console.error('Failed to fetch rules:', err); tbody.innerHTML = `فشل تحميل القواعد.`; } } function renderRules(rules) { const tbody = document.getElementById('rules-list-body'); if (!tbody) return; if (!rules || rules.length === 0) { tbody.innerHTML = `لا توجد قواعد نشطة حالياً.`; return; } tbody.innerHTML = rules.map(rule => { const cond = rule.conditions[0] || { metric: '?', operator: '?', value: '?' }; return ` ${rule.name} ${cond.metric.toUpperCase()} ${cond.operator} ${cond.value} ${rule.action === 'notify' ? 'تنبيه' : 'إيقاف'} ${rule.isActive ? 'نشط' : 'متوقف'} `; }).join(''); lucide.createIcons(); } async function handleDeleteRule(id) { if (!confirm('هل أنت متأكد من رغبتك في حذف هذه القاعدة؟')) return; try { await api.deleteRule(id); await refreshRules(); } catch (err) { alert('فشل في حذف القاعدة: ' + (err.message || 'خطأ غير معروف')); } } window.handleDeleteRule = handleDeleteRule; function setupAutomationEvents() { const form = document.getElementById('rule-form'); if (!form) return; form.addEventListener('submit', async (e) => { e.preventDefault(); const submitBtn = form.querySelector('button'); const ruleData = { name: document.getElementById('rule-name').value, platform: 'meta', targetId: 'all', action: document.getElementById('rule-action').value, conditions: [ { metric: document.getElementById('rule-metric').value, operator: document.getElementById('rule-operator').value, value: parseFloat(document.getElementById('rule-value').value) } ], isActive: true }; try { submitBtn.disabled = true; submitBtn.innerHTML = ' جاري الحفظ...'; lucide.createIcons(); await api.createRule(ruleData); form.reset(); await refreshRules(); alert('تم إنشاء وتفعيل القاعدة بنجاح! 🚀'); } catch (err) { console.error('Create rule failed:', err); alert('فشل حفظ القاعدة: ' + (err.message || 'تأكد من اختيار مستخدم وارتباط حساب إعلاني')); } finally { submitBtn.disabled = false; submitBtn.innerHTML = 'حفظ وتفعيل القاعدة'; lucide.createIcons(); } }); }