const $ = (sel, root = document) => root.querySelector(sel); const root = $('#app'); const CATEGORIES = [ 'بازار جهانی', 'سیاست‌گذاری', 'انرژی و ESG', 'صادرات', 'تولید داخلی', 'ریسک و بحران', 'رادار آینده', 'اخبار صنعت', 'گزارش کارخانه', ]; const TYPES = [ { value: 'quarterly', label: 'فصلی' }, { value: 'flash', label: 'فلش' }, { value: 'risk', label: 'هشدار ریسک' }, { value: 'free', label: 'رایگان' }, { value: 'special', label: 'ویژه' }, { value: 'video', label: 'ویدیوکست' }, { value: 'podcast', label: 'پادکست' }, { value: 'news', label: 'خبر' }, { value: 'radar', label: 'رادار آینده' }, ]; const EVENT_TYPES = [ { value: 'conference', label: 'کنگره' }, { value: 'exhibition', label: 'نمایشگاه' }, { value: 'seminar', label: 'سمینار' }, { value: 'international', label: 'جهانی' }, ]; const RADAR_CATEGORIES = [ { value: 'market', label: 'بازار' }, { value: 'tech', label: 'فناوری' }, { value: 'commodity', label: 'مواد اولیه' }, { value: 'geo', label: 'ژئوپلیتیک' }, { value: 'energy', label: 'انرژی' }, ]; const SCROLL_DIRS = [ { value: 'up', label: 'از پایین' }, { value: 'left', label: 'از چپ' }, { value: 'right', label: 'از راست' }, ]; const INTEGRATION_ICONS = [ { value: 'exchange', label: 'صرافی' }, { value: 'globe', label: 'جهانی' }, { value: 'chart', label: 'نمودار' }, { value: 'activity', label: 'فعالیت' }, { value: 'database', label: 'پایگاه داده' }, { value: 'newspaper', label: 'خبرگزاری' }, { value: 'building', label: 'سازمان' }, { value: 'trending', label: 'روند' }, { value: 'coins', label: 'کالا' }, ]; const TREND_TYPES = [ { value: 'up', label: 'صعودی ↑' }, { value: 'down', label: 'نزولی ↓' }, { value: 'flat', label: 'ثابت →' }, ]; const CHART_SERIES = [ { value: 'history', label: 'روند قیمت' }, { value: 'export', label: 'صادرات ماهانه' }, { value: 'comparison', label: 'مقایسه جهانی' }, ]; const VISION_ICONS = ['FlaskConical', 'Network', 'Leaf', 'Telescope', 'Sparkles']; async function api(path, { method = 'GET', body, isForm = false } = {}) { const headers = {}; if (body && !isForm) headers['Content-Type'] = 'application/json'; const res = await fetch(path, { method, credentials: 'include', headers, body: isForm ? body : (body ? JSON.stringify(body) : undefined), }); if (res.status === 401) { renderLogin('نیاز به ورود مجدد است'); throw new Error('unauthorized'); } if (!res.ok) { const text = await res.text(); throw new Error(text || res.statusText); } if (res.status === 204) return null; return res.json(); } function renderLogin(errorMsg = '') { root.innerHTML = `

ورود به پنل مدیریت

اندیشکده فولاد

${errorMsg ? `
${errorMsg}
` : ''}
`; $('#loginForm').addEventListener('submit', async (e) => { e.preventDefault(); const fd = new FormData(e.target); try { const data = await api('/api/auth/login', { method: 'POST', body: { username: fd.get('username'), password: fd.get('password') }, }); renderList(); } catch { renderLogin('نام کاربری یا رمز عبور اشتباه است'); } }); } function topbarHtml(active, primaryLabel) { return `
پنل اندیشکده فولاد
`; } function wireTabs() { document.querySelectorAll('.tabs button').forEach(btn => { btn.addEventListener('click', () => { const t = btn.dataset.tab; if (t === 'articles') renderList(); else if (t === 'risks') renderRisksList(); else if (t === 'events') renderEventsList(); else if (t === 'radar') renderRadarList(); else if (t === 'radarPages') renderRadarPagesList(); else if (t === 'factoryReports') renderFactoryReportsList(); else if (t === 'integrations') renderIntegrationsList(); else if (t === 'stats') renderStatsList(); else if (t === 'mprices') renderMarketPricesList(); else if (t === 'charts') renderChartPointsList(); else if (t === 'vision') renderVisionList(); else if (t === 'advisory') renderAdvisoryList(); else if (t === 'team') renderTeamList(false); else if (t === 'experts') renderTeamList(true); else if (t === 'plans') renderPlansList(); else if (t === 'banners') renderBannersList(); else if (t === 'users') renderUsersList(); }); }); $('#logoutBtn').addEventListener('click', async () => { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {}); renderLogin(); }); } async function renderList() { root.innerHTML = ` ${topbarHtml('articles', '+ مطلب جدید')}

مطالب

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderEditor(null)); try { const items = await api('/api/articles?limit=200'); if (!items.length) { $('#list').innerHTML = `

هنوز مطلبی ثبت نشده است.

`; return; } const safeCoverUrl = (u) => /^\/uploads\/[a-zA-Z0-9._-]+$/.test(String(u || '')) ? u : ''; $('#list').innerHTML = items.map((a) => `
${a.featured ? 'ویژه' : ''} ${a.category ? `${escapeHtml(a.category)}` : ''} ${a.publishDate ? `${escapeHtml(a.publishDate)}` : ''}

${escapeHtml(a.title)}

${escapeHtml(a.summary)}

`).join(''); $('#list').addEventListener('click', async (e) => { const btn = e.target.closest('button[data-action]'); if (!btn) return; const row = btn.closest('.row'); const id = row.dataset.id; if (btn.dataset.action === 'edit') { const a = await api(`/api/articles/${id}`); renderEditor(a); } else if (btn.dataset.action === 'delete') { if (!confirm('این مطلب حذف شود؟')) return; await api(`/api/articles/${id}`, { method: 'DELETE' }); renderList(); } }); } catch (err) { console.error(err); $('#list').innerHTML = `

خطا در بارگذاری مطالب.

`; } } function renderEditor(article) { const a = article || { title: '', category: '', type: '', author: '', authorRole: '', authorInitial: '', publishDate: '', pages: 0, price: 0, isFree: true, summary: '', body: '', coverImage: '', tags: [], featured: false, }; const isEdit = !!article; root.innerHTML = `
${isEdit ? 'ویرایش مطلب' : 'مطلب جدید'}
`; $('#backBtn').addEventListener('click', () => renderList()); $('#coverFile').addEventListener('change', async (e) => { const file = e.target.files?.[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); try { const { url } = await api('/api/uploads', { method: 'POST', body: fd, isForm: true }); $('input[name="coverImage"]').value = url; $('#coverPreview').src = url; } catch (err) { alert('آپلود ناموفق بود: ' + err.message); } }); $('#editorForm').addEventListener('submit', async (e) => { e.preventDefault(); const fd = new FormData(e.target); const tagsRaw = String(fd.get('tags') || '').split(/[,،]/).map(s => s.trim()).filter(Boolean); const payload = { title: fd.get('title'), category: fd.get('category') || null, type: fd.get('type') || null, author: fd.get('author') || null, authorRole: fd.get('authorRole') || null, authorInitial: fd.get('authorInitial') || null, publishDate: fd.get('publishDate') || null, pages: Number(fd.get('pages') || 0), price: Number(fd.get('price') || 0), isFree: fd.get('isFree') === 'on', featured: fd.get('featured') === 'on', summary: fd.get('summary') || null, body: fd.get('body') || null, coverImage: fd.get('coverImage') || null, tags: tagsRaw, }; try { if (isEdit) { await api(`/api/articles/${article.id}`, { method: 'PUT', body: payload }); } else { await api('/api/articles', { method: 'POST', body: payload }); } renderList(); } catch (err) { alert('ذخیره ناموفق بود: ' + err.message); } }); } function escapeAttr(s) { return String(s ?? '').replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>'); } const RISK_LEVELS = [ { value: 'critical', label: 'بحرانی' }, { value: 'high', label: 'بالا' }, { value: 'medium', label: 'متوسط' }, { value: 'low', label: 'پایین' }, { value: 'opportunity', label: 'فرصت' }, ]; async function renderRisksList() { root.innerHTML = ` ${topbarHtml('risks', '+ سیگنال جدید')}

سیگنال‌های ریسک

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderRiskEditor(null)); try { const items = await api('/api/risks?limit=200'); if (!items.length) { $('#list').innerHTML = `

هنوز سیگنالی ثبت نشده است.

`; return; } const SAFE_LEVELS = new Set(['critical', 'high', 'medium', 'low', 'opportunity']); const labelOf = (lvl) => (RISK_LEVELS.find(r => r.value === lvl)?.label) || escapeHtml(lvl); $('#list').innerHTML = items.map(r => `
${labelOf(r.level)} ${escapeHtml(r.name)} ${r.date ? `${escapeHtml(r.date)}` : ''}

${escapeHtml(r.quote)}

`).join(''); $('#list').addEventListener('click', async (e) => { const btn = e.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') { const r = await api(`/api/risks/${id}`); renderRiskEditor(r); } else if (btn.dataset.action === 'delete') { if (!confirm('این سیگنال حذف شود؟')) return; await api(`/api/risks/${id}`, { method: 'DELETE' }); renderRisksList(); } }); } catch (err) { console.error(err); $('#list').innerHTML = `

خطا در بارگذاری سیگنال‌ها.

`; } } function renderRiskEditor(risk) { const r = risk || { quote: '', name: '', date: '', level: 'high', sortOrder: 0 }; const isEdit = !!risk; root.innerHTML = `
${isEdit ? 'ویرایش سیگنال ریسک' : 'سیگنال ریسک جدید'}
`; $('#backBtn').addEventListener('click', () => renderRisksList()); $('#editorForm').addEventListener('submit', async (e) => { e.preventDefault(); const fd = new FormData(e.target); const payload = { name: fd.get('name'), quote: fd.get('quote'), date: fd.get('date') || null, level: fd.get('level'), sortOrder: Number(fd.get('sortOrder') || 0), }; try { if (isEdit) await api(`/api/risks/${risk.id}`, { method: 'PUT', body: payload }); else await api('/api/risks', { method: 'POST', body: payload }); renderRisksList(); } catch (err) { alert('ذخیره ناموفق بود: ' + err.message); } }); } // ---------- users ---------- async function renderUsersList() { root.innerHTML = ` ${topbarHtml('users', '+ کاربر جدید')}

کاربران پنل

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderUserEditor(null)); try { const items = await api('/api/users'); if (!items.length) { $('#list').innerHTML = `

هنوز کاربری ثبت نشده است.

`; return; } $('#list').innerHTML = items.map(u => `
${escapeHtml(u.username)} ${u.createdAt ? `عضویت: ${escapeHtml(u.createdAt)}` : ''}
`).join(''); $('#list').addEventListener('click', async (e) => { const btn = e.target.closest('button[data-action]'); if (!btn) return; const row = btn.closest('.row'); const id = row.dataset.id; if (btn.dataset.action === 'edit') { renderUserEditor({ id, username: row.dataset.username }); } else if (btn.dataset.action === 'delete') { if (!confirm('این کاربر حذف شود؟')) return; try { await api(`/api/users/${id}`, { method: 'DELETE' }); renderUsersList(); } catch (err) { alert('حذف ناموفق بود: ' + friendlyUserError(err.message)); } } }); } catch (err) { console.error(err); $('#list').innerHTML = `

خطا در بارگذاری کاربران.

`; } } function renderUserEditor(user) { const u = user || { username: '' }; const isEdit = !!user; root.innerHTML = `
${isEdit ? 'ویرایش کاربر' : 'کاربر جدید'}
`; $('#backBtn').addEventListener('click', () => renderUsersList()); $('#editorForm').addEventListener('submit', async (e) => { e.preventDefault(); const fd = new FormData(e.target); const username = String(fd.get('username') || '').trim(); const password = String(fd.get('password') || ''); const payload = { username }; if (password) payload.password = password; try { if (isEdit) await api(`/api/users/${user.id}`, { method: 'PUT', body: payload }); else await api('/api/users', { method: 'POST', body: { username, password } }); renderUsersList(); } catch (err) { alert('ذخیره ناموفق بود: ' + friendlyUserError(err.message)); } }); } function friendlyUserError(msg) { const m = String(msg || ''); if (m.includes('username_taken')) return 'این نام کاربری قبلاً ثبت شده است.'; if (m.includes('username_too_short')) return 'نام کاربری باید حداقل ۳ کاراکتر باشد.'; if (m.includes('password_too_short')) return 'رمز عبور باید حداقل ۸ کاراکتر باشد.'; if (m.includes('cannot_delete_self')) return 'نمی‌توانید حساب خودتان را حذف کنید.'; if (m.includes('cannot_delete_last_user')) return 'آخرین کاربر را نمی‌توان حذف کرد.'; return m; } // ═══════════════════════════════════════════════ // EVENTS // ═══════════════════════════════════════════════ async function renderEventsList() { root.innerHTML = ` ${topbarHtml('events', '+ رویداد جدید')}

رویدادها

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderEventEditor(null)); try { const items = await api('/api/events?limit=200'); if (!items.length) { $('#list').innerHTML = `

هنوز رویدادی ثبت نشده.

`; return; } $('#list').innerHTML = items.map(e => `
${e.type ? `${escapeHtml(EVENT_TYPES.find(t=>t.value===e.type)?.label||e.type)}` : ''} ${e.cityFa ? `${escapeHtml(e.cityFa)}` : ''} ${e.dateFa ? `${escapeHtml(e.dateFa)}` : ''} ${e.registrationOpen?'ثبت‌نام باز':'بسته'}

${escapeHtml(e.titleFa)}

${escapeHtml(e.descriptionFa||'')}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderEventEditor(await api(`/api/events/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این رویداد حذف شود؟')) return; await api(`/api/events/${id}`, { method: 'DELETE' }); renderEventsList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderEventEditor(event) { const e = event || { titleFa:'', titleEn:'', dateFa:'', dateEn:'', monthFa:'', monthEn:'', year:'', type:'', locationFa:'', locationEn:'', cityFa:'', cityEn:'', descriptionFa:'', descriptionEn:'', registrationOpen:true, sortOrder:0 }; const isEdit = !!event; root.innerHTML = `
${isEdit?'ویرایش رویداد':'رویداد جدید'}
`; $('#backBtn').addEventListener('click', () => renderEventsList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { titleFa: fd.get('titleFa'), titleEn: fd.get('titleEn')||null, dateFa: fd.get('dateFa')||null, dateEn: fd.get('dateEn')||null, monthFa: fd.get('monthFa')||null, monthEn: fd.get('monthEn')||null, year: fd.get('year')||null, type: fd.get('type')||null, locationFa: fd.get('locationFa')||null, locationEn: fd.get('locationEn')||null, cityFa: fd.get('cityFa')||null, cityEn: fd.get('cityEn')||null, descriptionFa: fd.get('descriptionFa')||null, descriptionEn: fd.get('descriptionEn')||null, registrationOpen: fd.get('registrationOpen') === 'on', sortOrder: Number(fd.get('sortOrder')||0), }; try { if (isEdit) await api(`/api/events/${event.id}`, { method:'PUT', body:payload }); else await api('/api/events', { method:'POST', body:payload }); renderEventsList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // TEAM & EXPERTS (shared UI, isExpert flag) // ═══════════════════════════════════════════════ async function renderTeamList(isExpert) { const tab = isExpert ? 'experts' : 'team'; const label = isExpert ? 'خبرگان' : 'تیم'; root.innerHTML = ` ${topbarHtml(tab, `+ ${isExpert?'خبره':'عضو'} جدید`)}

${isExpert?'شبکه خبرگان':'اعضای تیم'}

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderTeamEditor(null, isExpert)); try { const items = await api(`/api/team?expert=${isExpert?1:0}`); if (!items.length) { $('#list').innerHTML = `

هنوز ${label}ی ثبت نشده.

`; return; } $('#list').innerHTML = items.map(m => `
${escapeHtml(m.initial||m.nameFa?.charAt(0)||'؟')}
${m.roleFa ? `${escapeHtml(m.roleFa)}` : ''} ${m.expertRoomFa ? `${escapeHtml(m.expertRoomFa)}` : ''} ${m.reportCount ? `${m.reportCount} گزارش` : ''}

${escapeHtml(m.nameFa)}

${escapeHtml(m.bioFa||'')}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderTeamEditor(await api(`/api/team/${id}`), isExpert); else if (btn.dataset.action === 'delete') { if (!confirm('این عضو حذف شود؟')) return; await api(`/api/team/${id}`, { method:'DELETE' }); renderTeamList(isExpert); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderTeamEditor(member, isExpert) { const m = member || { nameFa:'', nameEn:'', roleFa:'', roleEn:'', bioFa:'', bioEn:'', expertise:[], email:'', initial:'', photo:'', reportCount:0, expertRoomFa:'', expertRoomEn:'', telegram:'', linkedin:'', twitter:'', website:'', sortOrder:0 }; const isEdit = !!member; const expertiseStr = Array.isArray(m.expertise) ? m.expertise.join('، ') : ''; root.innerHTML = `
${isEdit?'ویرایش':'جدید'} — ${isExpert?'خبره':'عضو تیم'}
${isExpert ? ` ` : ''}
`; $('#backBtn').addEventListener('click', () => renderTeamList(isExpert)); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const expertiseRaw = String(fd.get('expertise')||'').split(/[,،]/).map(s=>s.trim()).filter(Boolean); const payload = { nameFa: fd.get('nameFa'), nameEn: fd.get('nameEn')||null, roleFa: fd.get('roleFa')||null, roleEn: fd.get('roleEn')||null, bioFa: fd.get('bioFa')||null, bioEn: fd.get('bioEn')||null, expertise: expertiseRaw, email: fd.get('email')||null, initial: fd.get('initial')||null, reportCount: Number(fd.get('reportCount')||0), isExpert: isExpert, expertRoomFa: fd.get('expertRoomFa')||null, expertRoomEn: fd.get('expertRoomEn')||null, telegram: fd.get('telegram')||null, linkedin: fd.get('linkedin')||null, twitter: fd.get('twitter')||null, website: fd.get('website')||null, sortOrder: Number(fd.get('sortOrder')||0), }; try { if (isEdit) await api(`/api/team/${member.id}`, { method:'PUT', body:payload }); else await api('/api/team', { method:'POST', body:payload }); renderTeamList(isExpert); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // PLANS // ═══════════════════════════════════════════════ async function renderPlansList() { root.innerHTML = ` ${topbarHtml('plans', '+ پلان جدید')}

پلان‌های اشتراک

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderPlanEditor(null)); try { const items = await api('/api/plans'); if (!items.length) { $('#list').innerHTML = `

هنوز پلانی ثبت نشده.

`; return; } $('#list').innerHTML = items.map(p => `
${p.isFeatured ? 'ویژه' : ''} ${p.badgeFa ? `${escapeHtml(p.badgeFa)}` : ''} ${Number(p.price).toLocaleString()} ریال / ${escapeHtml(p.periodFa||'')}

${escapeHtml(p.nameFa)}

${Array.isArray(p.features) ? p.features.length : 0} ویژگی

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderPlanEditor(await api(`/api/plans/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این پلان حذف شود؟')) return; await api(`/api/plans/${id}`, { method:'DELETE' }); renderPlansList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderPlanEditor(plan) { const p = plan || { nameFa:'', nameEn:'', price:0, periodFa:'', periodEn:'', features:[], badgeFa:'', badgeEn:'', isFeatured:false, ctaFa:'', ctaEn:'', sortOrder:0 }; const isEdit = !!plan; const featStr = Array.isArray(p.features) ? p.features.map(f => typeof f === 'object' ? f.fa||'' : f).join('\n') : ''; root.innerHTML = `
${isEdit?'ویرایش پلان':'پلان جدید'}
`; $('#backBtn').addEventListener('click', () => renderPlansList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const featLines = String(fd.get('features')||'').split('\n').map(s=>s.trim()).filter(Boolean); const payload = { nameFa: fd.get('nameFa'), nameEn: fd.get('nameEn')||null, price: Number(fd.get('price')||0), periodFa: fd.get('periodFa')||null, periodEn: fd.get('periodEn')||null, features: featLines, badgeFa: fd.get('badgeFa')||null, badgeEn: fd.get('badgeEn')||null, isFeatured: fd.get('isFeatured') === 'on', ctaFa: fd.get('ctaFa')||null, ctaEn: fd.get('ctaEn')||null, sortOrder: Number(fd.get('sortOrder')||0), }; try { if (isEdit) await api(`/api/plans/${plan.id}`, { method:'PUT', body:payload }); else await api('/api/plans', { method:'POST', body:payload }); renderPlansList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // RADAR (رادار آینده) // ═══════════════════════════════════════════════ async function renderRadarList() { root.innerHTML = ` ${topbarHtml('radar', '+ سیگنال جدید')}

رادار آینده

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderRadarEditor(null)); try { const items = await api('/api/radar?limit=200'); if (!items.length) { $('#list').innerHTML = `

هنوز سیگنالی ثبت نشده.

`; return; } const safeImgUrl = (u) => /^(\/uploads\/[a-zA-Z0-9._-]+|\/[a-zA-Z0-9._\/-]+)$/.test(String(u || '')) ? u : ''; $('#list').innerHTML = items.map(r => `
${r.category ? `${escapeHtml(RADAR_CATEGORIES.find(c=>c.value===r.category)?.label||r.category)}` : ''} ${r.authorName ? `${escapeHtml(r.authorName)}` : ''} ${r.dateFa ? `${escapeHtml(r.dateFa)}` : ''}

${escapeHtml(r.titleFa||'')}

${escapeHtml(r.excerptFa||'')}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderRadarEditor(await api(`/api/radar/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این سیگنال حذف شود؟')) return; await api(`/api/radar/${id}`, { method: 'DELETE' }); renderRadarList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderRadarEditor(item) { const r = item || { category:'', img:'', authorName:'', authorAvatar:'', titleFa:'', dateFa:'', excerptFa:'', titleEn:'', dateEn:'', excerptEn:'', sortOrder:0 }; const isEdit = !!item; root.innerHTML = `
${isEdit?'ویرایش سیگنال':'سیگنال جدید'}
`; $('#backBtn').addEventListener('click', () => renderRadarList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { titleFa: fd.get('titleFa'), titleEn: fd.get('titleEn')||null, category: fd.get('category')||null, img: fd.get('img')||null, authorName: fd.get('authorName')||null, authorAvatar: fd.get('authorAvatar')||null, dateFa: fd.get('dateFa')||null, dateEn: fd.get('dateEn')||null, excerptFa: fd.get('excerptFa')||null, excerptEn: fd.get('excerptEn')||null, sortOrder: Number(fd.get('sortOrder')||0), }; try { if (isEdit) await api(`/api/radar/${item.id}`, { method:'PUT', body:payload }); else await api('/api/radar', { method:'POST', body:payload }); renderRadarList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // RADAR PAGES (صفحات رادار — hero + banner per category) // ═══════════════════════════════════════════════ const RADAR_PAGE_SLUGS = [ { slug: 'foresight', label: 'آینده‌پژوهی و رصد هوشمند' }, { slug: 'market', label: 'بازار و زنجیره فولاد' }, { slug: 'sustainability', label: 'پایداری و فولاد سبز' }, { slug: 'geopolitics', label: 'ژئوپلیتیک و اقتصاد جهانی' }, { slug: 'technology', label: 'فناوری و نوآوری' }, ]; async function renderRadarPagesList() { root.innerHTML = ` ${topbarHtml('radarPages', '')}

صفحات رادار آینده

۵ صفحه ثابت. محتوای هیرو (کارت ویژه) و بنر هر دسته را اینجا ویرایش کنید.

در حال بارگذاری…

`; wireTabs(); const newBtn = $('#newBtn'); if (newBtn) newBtn.style.display = 'none'; try { const saved = await api('/api/radar-pages'); const bySlug = {}; saved.forEach(p => { bySlug[p.slug] = p; }); $('#list').innerHTML = RADAR_PAGE_SLUGS.map(s => { const p = bySlug[s.slug]; return `
${escapeHtml(s.slug)} ${p ? 'ذخیره‌شده' : 'پیش‌فرض (هنوز ذخیره نشده)'}

${escapeHtml((p && p.labelFa) || s.label)}

${escapeHtml((p && p.bannerTitleFa) || '')}

`; }).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const slug = btn.closest('.row').dataset.slug; const def = RADAR_PAGE_SLUGS.find(x => x.slug === slug); renderRadarPageEditor(bySlug[slug] || { slug, labelFa: def.label }, def); }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderRadarPageEditor(p, def) { const r = Object.assign({ slug: def.slug, sourceCategories: [], labelFa: def.label, labelEn: '', latestHeadingFa: '', latestHeadingEn: '', featuredTitleFa: '', featuredTitleEn: '', featuredDateFa: '', featuredDateEn: '', featuredImg: '', bannerKickerFa: 'تحلیل ویژه', bannerKickerEn: 'Special Analysis', bannerTitleFa: '', bannerTitleEn: '', bannerDescFa: '', bannerDescEn: '', bannerBookImg: '', sortOrder: 0, }, p || {}); const catChecked = (c) => (r.sourceCategories || []).includes(c) ? 'checked' : ''; root.innerHTML = `
ویرایش صفحه: ${escapeHtml(r.labelFa)}

کارت ویژه (هیرو)

بنر تحلیل ویژه

`; $('#backBtn').addEventListener('click', () => renderRadarPagesList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { sourceCategories: fd.getAll('srcCat'), labelFa: fd.get('labelFa'), labelEn: fd.get('labelEn')||null, latestHeadingFa: fd.get('latestHeadingFa')||null, latestHeadingEn: fd.get('latestHeadingEn')||null, featuredTitleFa: fd.get('featuredTitleFa')||null, featuredTitleEn: fd.get('featuredTitleEn')||null, featuredDateFa: fd.get('featuredDateFa')||null, featuredDateEn: fd.get('featuredDateEn')||null, featuredImg: fd.get('featuredImg')||null, bannerKickerFa: fd.get('bannerKickerFa')||null, bannerKickerEn: fd.get('bannerKickerEn')||null, bannerTitleFa: fd.get('bannerTitleFa')||null, bannerTitleEn: fd.get('bannerTitleEn')||null, bannerDescFa: fd.get('bannerDescFa')||null, bannerDescEn: fd.get('bannerDescEn')||null, bannerBookImg: fd.get('bannerBookImg')||null, sortOrder: Number(fd.get('sortOrder')||0), }; try { await api(`/api/radar-pages/${r.slug}`, { method:'PUT', body:payload }); renderRadarPagesList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // FACTORY REPORTS (گزارش کارخانه) // ═══════════════════════════════════════════════ async function renderFactoryReportsList() { root.innerHTML = ` ${topbarHtml('factoryReports', '+ گزارش جدید')}

گزارش کارخانه

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderFactoryReportEditor(null)); try { const items = await api('/api/factory-reports?limit=50'); if (!items.length) { $('#list').innerHTML = `

هنوز گزارشی ثبت نشده.

`; return; } $('#list').innerHTML = items.map(r => `
${r.tagFa ? `${escapeHtml(r.tagFa)}` : ''} ${r.dateFa ? `${escapeHtml(r.dateFa)}` : ''} ${escapeHtml(SCROLL_DIRS.find(d=>d.value===r.scrollDir)?.label||r.scrollDir||'')}

${escapeHtml(r.titleFa)}

${escapeHtml(r.excerptFa||'')}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderFactoryReportEditor(await api(`/api/factory-reports/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این گزارش حذف شود؟')) return; await api(`/api/factory-reports/${id}`, { method: 'DELETE' }); renderFactoryReportsList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderFactoryReportEditor(report) { const r = report || { img:'', tagFa:'', tagEn:'', titleFa:'', titleEn:'', dateFa:'', dateEn:'', excerptFa:'', excerptEn:'', scrollDir:'up', sortOrder:0 }; const isEdit = !!report; root.innerHTML = `
${isEdit?'ویرایش گزارش':'گزارش جدید'}
`; $('#backBtn').addEventListener('click', () => renderFactoryReportsList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { img: fd.get('img')||null, tagFa: fd.get('tagFa')||null, tagEn: fd.get('tagEn')||null, titleFa: fd.get('titleFa'), titleEn: fd.get('titleEn')||null, dateFa: fd.get('dateFa')||null, dateEn: fd.get('dateEn')||null, excerptFa: fd.get('excerptFa')||null, excerptEn: fd.get('excerptEn')||null, scrollDir: fd.get('scrollDir')||'up', sortOrder: Number(fd.get('sortOrder')||0), }; try { if (isEdit) await api(`/api/factory-reports/${report.id}`, { method:'PUT', body:payload }); else await api('/api/factory-reports', { method:'POST', body:payload }); renderFactoryReportsList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // INTEGRATIONS (منابع داده) // ═══════════════════════════════════════════════ async function renderIntegrationsList() { root.innerHTML = ` ${topbarHtml('integrations', '+ منبع جدید')}

منابع داده

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderIntegrationEditor(null)); try { const items = await api('/api/integrations?limit=50'); if (!items.length) { $('#list').innerHTML = `

هنوز منبعی ثبت نشده.

`; return; } $('#list').innerHTML = items.map(it => `
${it.icon ? `${escapeHtml(INTEGRATION_ICONS.find(i=>i.value===it.icon)?.label||it.icon)}` : ''} سلول ${it.gridCol}×${it.gridRow} ${it.accent ? ` ` : ''}

${escapeHtml(it.name)}

${it.logo ? `

${escapeHtml(it.logo)}

` : ''}
`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderIntegrationEditor(await api(`/api/integrations/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این منبع حذف شود؟')) return; await api(`/api/integrations/${id}`, { method: 'DELETE' }); renderIntegrationsList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderIntegrationEditor(integration) { const it = integration || { name:'', icon:'globe', logo:'', accent:'', gridCol:1, gridRow:1, sortOrder:0 }; const isEdit = !!integration; root.innerHTML = `
${isEdit?'ویرایش منبع':'منبع جدید'}
`; $('#backBtn').addEventListener('click', () => renderIntegrationsList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { name: fd.get('name'), icon: fd.get('icon')||'globe', logo: fd.get('logo')||null, accent: fd.get('accent')||null, gridCol: Number(fd.get('gridCol')||1), gridRow: Number(fd.get('gridRow')||1), sortOrder: Number(fd.get('sortOrder')||0), }; try { if (isEdit) await api(`/api/integrations/${integration.id}`, { method:'PUT', body:payload }); else await api('/api/integrations', { method:'POST', body:payload }); renderIntegrationsList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // INSTITUTE STATS (آمار) // ═══════════════════════════════════════════════ async function renderStatsList() { root.innerHTML = ` ${topbarHtml('stats', '+ آمار جدید')}

آمار اندیشکده

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderStatEditor(null)); try { const items = await api('/api/institute-stats'); if (!items.length) { $('#list').innerHTML = `

هنوز آماری ثبت نشده.

`; return; } $('#list').innerHTML = items.map(s => `
${s.value}${s.suffixFa ? escapeHtml(s.suffixFa) : ''}

${escapeHtml(s.labelFa)}

${s.labelEn ? `

${escapeHtml(s.labelEn)}

` : ''}
`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderStatEditor(await api(`/api/institute-stats/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این آمار حذف شود؟')) return; await api(`/api/institute-stats/${id}`, { method: 'DELETE' }); renderStatsList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderStatEditor(stat) { const s = stat || { id: '', labelFa: '', labelEn: '', value: 0, suffixFa: '', sortOrder: 0 }; const isEdit = !!stat; root.innerHTML = `
${isEdit ? 'ویرایش آمار' : 'آمار جدید'}
`; $('#backBtn').addEventListener('click', () => renderStatsList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { id: fd.get('id'), labelFa: fd.get('labelFa'), labelEn: fd.get('labelEn') || null, value: Number(fd.get('value') || 0), suffixFa: fd.get('suffixFa') || null, sortOrder: Number(fd.get('sortOrder') || 0), }; try { if (isEdit) await api(`/api/institute-stats/${stat.id}`, { method: 'PUT', body: payload }); else await api('/api/institute-stats', { method: 'POST', body: payload }); renderStatsList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // MARKET PRICES (قیمت‌ها) // ═══════════════════════════════════════════════ async function renderMarketPricesList() { root.innerHTML = ` ${topbarHtml('mprices', '+ قیمت جدید')}

قیمت‌های بازار

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderMarketPriceEditor(null)); try { const items = await api('/api/market-prices'); if (!items.length) { $('#list').innerHTML = `

هنوز قیمتی ثبت نشده.

`; return; } const trendLabel = (t) => (TREND_TYPES.find(x => x.value === t)?.label) || t; $('#list').innerHTML = items.map(p => `
${p.value} ${escapeHtml(p.unit || '')} ${escapeHtml(trendLabel(p.trend))} ${p.changePercent}%

${escapeHtml(p.name)}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderMarketPriceEditor(await api(`/api/market-prices/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این قیمت حذف شود؟')) return; await api(`/api/market-prices/${id}`, { method: 'DELETE' }); renderMarketPricesList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderMarketPriceEditor(price) { const p = price || { id: '', name: '', value: 0, unit: '', change: 0, changePercent: 0, trend: 'flat', sortOrder: 0 }; const isEdit = !!price; root.innerHTML = `
${isEdit ? 'ویرایش قیمت' : 'قیمت جدید'}
`; $('#backBtn').addEventListener('click', () => renderMarketPricesList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { id: fd.get('id'), name: fd.get('name'), value: Number(fd.get('value') || 0), unit: fd.get('unit') || null, change: Number(fd.get('change') || 0), changePercent: Number(fd.get('changePercent') || 0), trend: fd.get('trend'), sortOrder: Number(fd.get('sortOrder') || 0), }; try { if (isEdit) await api(`/api/market-prices/${price.id}`, { method: 'PUT', body: payload }); else await api('/api/market-prices', { method: 'POST', body: payload }); renderMarketPricesList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // MARKET CHART POINTS (نمودارها) // ═══════════════════════════════════════════════ async function renderChartPointsList() { root.innerHTML = ` ${topbarHtml('charts', '+ نقطه جدید')}

نقاط نمودار بازار

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderChartPointEditor(null)); try { const items = await api('/api/market-chart-points?limit=500'); if (!items.length) { $('#list').innerHTML = `

هنوز نقطه‌ای ثبت نشده.

`; return; } const seriesLabel = (s) => (CHART_SERIES.find(x => x.value === s)?.label) || s; $('#list').innerHTML = items.map(c => `
${escapeHtml(seriesLabel(c.series))} ${c.value} ${c.meta ? `${escapeHtml(c.meta)}` : ''}

${escapeHtml(c.label)}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderChartPointEditor(await api(`/api/market-chart-points/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این نقطه حذف شود؟')) return; await api(`/api/market-chart-points/${id}`, { method: 'DELETE' }); renderChartPointsList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderChartPointEditor(point) { const c = point || { id: '', series: 'history', label: '', value: 0, meta: '', sortOrder: 0 }; const isEdit = !!point; root.innerHTML = `
${isEdit ? 'ویرایش نقطه نمودار' : 'نقطه نمودار جدید'}
`; $('#backBtn').addEventListener('click', () => renderChartPointsList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { series: fd.get('series'), label: fd.get('label'), value: Number(fd.get('value') || 0), meta: fd.get('meta') || null, sortOrder: Number(fd.get('sortOrder') || 0), }; try { if (isEdit) await api(`/api/market-chart-points/${point.id}`, { method: 'PUT', body: payload }); else await api('/api/market-chart-points', { method: 'POST', body: payload }); renderChartPointsList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // VISION ITEMS (چشم‌انداز — About) // ═══════════════════════════════════════════════ async function renderVisionList() { root.innerHTML = ` ${topbarHtml('vision', '+ آیتم جدید')}

چشم‌انداز

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderVisionEditor(null)); try { const items = await api('/api/vision-items?limit=200'); if (!items.length) { $('#list').innerHTML = `

هنوز آیتمی ثبت نشده.

`; return; } $('#list').innerHTML = items.map(v => `
${v.icon ? `${escapeHtml(v.icon)}` : ''} ${v.titleEn ? `${escapeHtml(v.titleEn)}` : ''}

${escapeHtml(v.titleFa)}

${escapeHtml(v.descFa||'')}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderVisionEditor(await api(`/api/vision-items/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این آیتم حذف شود؟')) return; await api(`/api/vision-items/${id}`, { method: 'DELETE' }); renderVisionList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderVisionEditor(item) { const v = item || { icon:'', titleFa:'', titleEn:'', descFa:'', descEn:'', sortOrder:0 }; const isEdit = !!item; root.innerHTML = `
${isEdit?'ویرایش آیتم':'آیتم جدید'}
`; $('#backBtn').addEventListener('click', () => renderVisionList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { icon: fd.get('icon')||null, titleFa: fd.get('titleFa'), titleEn: fd.get('titleEn')||null, descFa: fd.get('descFa')||null, descEn: fd.get('descEn')||null, sortOrder: Number(fd.get('sortOrder')||0), }; try { if (isEdit) await api(`/api/vision-items/${item.id}`, { method:'PUT', body:payload }); else await api('/api/vision-items', { method:'POST', body:payload }); renderVisionList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // ADVISORY BOARD (شورای مشورتی — About) // ═══════════════════════════════════════════════ async function renderAdvisoryList() { root.innerHTML = ` ${topbarHtml('advisory', '+ عضو جدید')}

شورای مشورتی

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderAdvisoryEditor(null)); try { const items = await api('/api/advisory-board?limit=200'); if (!items.length) { $('#list').innerHTML = `

هنوز عضوی ثبت نشده.

`; return; } $('#list').innerHTML = items.map(m => `
${escapeHtml(m.nameFa?.charAt(0)||'؟')}
${m.roleFa ? `${escapeHtml(m.roleFa)}` : ''} ${m.nameEn ? `${escapeHtml(m.nameEn)}` : ''}

${escapeHtml(m.nameFa)}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderAdvisoryEditor(await api(`/api/advisory-board/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این عضو حذف شود؟')) return; await api(`/api/advisory-board/${id}`, { method: 'DELETE' }); renderAdvisoryList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderAdvisoryEditor(member) { const m = member || { nameFa:'', nameEn:'', roleFa:'', roleEn:'', sortOrder:0 }; const isEdit = !!member; root.innerHTML = `
${isEdit?'ویرایش عضو':'عضو جدید'}
`; $('#backBtn').addEventListener('click', () => renderAdvisoryList()); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { nameFa: fd.get('nameFa'), nameEn: fd.get('nameEn')||null, roleFa: fd.get('roleFa')||null, roleEn: fd.get('roleEn')||null, sortOrder: Number(fd.get('sortOrder')||0), }; try { if (isEdit) await api(`/api/advisory-board/${member.id}`, { method:'PUT', body:payload }); else await api('/api/advisory-board', { method:'POST', body:payload }); renderAdvisoryList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ═══════════════════════════════════════════════ // BANNERS // ═══════════════════════════════════════════════ const POSITIONS = [ { value: 'featured', label: 'گزارش ویژه (صفحه اصلی)' }, { value: 'hero', label: 'هیرو (بالای صفحه اصلی)' }, { value: 'announcement', label: 'اطلاع‌رسانی (نوار بالا)' }, { value: 'promo', label: 'تبلیغاتی (درون صفحه)' }, ]; async function renderBannersList() { root.innerHTML = ` ${topbarHtml('banners', '+ بنر جدید')}

بنرها

در حال بارگذاری…

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderBannerEditor(null)); try { const items = await api('/api/banners'); if (!items.length) { $('#list').innerHTML = `

هنوز بنری ثبت نشده.

`; return; } $('#list').innerHTML = items.map(b => `
${b.image ? `
` : ''}
${b.active ? 'فعال' : 'غیرفعال'} ${escapeHtml(POSITIONS.find(p=>p.value===b.position)?.label || b.position)}

${escapeHtml(b.titleFa)}

${escapeHtml(b.subtitleFa||'')}

`).join(''); $('#list').addEventListener('click', async ev => { const btn = ev.target.closest('button[data-action]'); if (!btn) return; const id = btn.closest('.row').dataset.id; if (btn.dataset.action === 'edit') renderBannerEditor(await api(`/api/banners/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این بنر حذف شود؟')) return; await api(`/api/banners/${id}`, { method: 'DELETE' }); renderBannersList(); } }); } catch (err) { $('#list').innerHTML = `

خطا: ${escapeHtml(err.message)}

`; } } function renderBannerEditor(banner) { const b = banner || { overlineFa:'', overlineEn:'', titleFa:'', titleEn:'', subtitleFa:'', subtitleEn:'', ctaLabelFa:'', ctaLabelEn:'', ctaUrl:'', image:'', position:'featured', active:true, sortOrder:0 }; const isEdit = !!banner; root.innerHTML = `
${isEdit ? 'ویرایش بنر' : 'بنر جدید'}
${b.image ? `` : ''}
`; $('#backBtn').addEventListener('click', () => renderBannersList()); $('#imageFile').addEventListener('change', async ev => { const file = ev.target.files[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); try { const res = await api('/api/uploads', { method:'POST', body:fd, isForm:true }); $('#imageUrl').value = res.url; } catch (err) { alert('آپلود ناموفق: ' + err.message); } }); $('#editorForm').addEventListener('submit', async ev => { ev.preventDefault(); const fd = new FormData(ev.target); const payload = { overlineFa: fd.get('overlineFa') || null, overlineEn: fd.get('overlineEn') || null, titleFa: fd.get('titleFa'), titleEn: fd.get('titleEn') || null, subtitleFa: fd.get('subtitleFa') || null, subtitleEn: fd.get('subtitleEn') || null, ctaLabelFa: fd.get('ctaLabelFa') || null, ctaLabelEn: fd.get('ctaLabelEn') || null, ctaUrl: fd.get('ctaUrl') || null, image: fd.get('imageUrl') || null, position: fd.get('position') || 'hero', active: fd.get('active') === 'on', sortOrder: Number(fd.get('sortOrder') || 0), }; try { if (isEdit) await api(`/api/banners/${banner.id}`, { method:'PUT', body:payload }); else await api('/api/banners', { method:'POST', body:payload }); renderBannersList(); } catch (err) { alert('ذخیره ناموفق: ' + err.message); } }); } // ───────────────────────────────────────────── api('/api/auth/me').then(() => renderList()).catch(() => renderLogin());