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 => `
`).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();
}
});
}