const $ = (sel, root = document) => root.querySelector(sel); const root = $('#app'); const TAB_GROUPS = [ { label: 'محتوا', tabs: ['glance', 'radarFuture', 'media', 'events', 'banners'] }, ]; const TAB_LABELS = { glance: 'در یک نگاه', radarFuture: 'رادار آینده', media: 'ویدیوکست و پادکست', events: 'رویدادها', banners: 'بنرها', }; 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 CONTENT_TYPES = [ { key: 'report', label: 'گزارش تحلیلی', icon: '📊', desc: 'گزارش فصلی، فلش یا ویژه با تحلیل بازار', defaultType: 'quarterly', defaultCategory: '' }, { key: 'news', label: 'خبر', icon: '📰', desc: 'اخبار صنعت فولاد', defaultType: 'news', defaultCategory: 'اخبار صنعت' }, { key: 'video', label: 'ویدیوکست', icon: '🎬', desc: 'ویدیوی تحلیلی یا مصاحبه', defaultType: 'video', defaultCategory: '' }, { key: 'podcast', label: 'پادکست', icon: '🎙️', desc: 'اپیزود پادکست صوتی', defaultType: 'podcast', defaultCategory: '' }, { key: 'factory', label: 'گزارش کارخانه', icon: '🏭', desc: 'گزارش تولید و عملیات کارخانه', defaultType: 'factoryReport', defaultCategory: 'گزارش کارخانه' }, { key: 'radarFuture', label: 'رادار آینده', icon: '🔭', desc: 'مقالهٔ بلوکی (متن/عکس) برای ۵ زیرمنوی رادار آینده', defaultType: 'radarfuture', defaultCategory: '' }, ]; // the 5 رادار آینده sub-sections (block-article categories) const RADAR_FUTURE_CATEGORIES = [ 'آینده‌پژوهی و رصد هوشمند', 'پایداری و فولاد سبز', 'ژئوپلیتیک و اقتصاد جهانی', 'فناوری و نوآوری', 'بازار و زنجیره فولاد', ]; function inferContentType(a) { if (!a) return 'report'; if (a.type === 'video') return 'video'; if (a.type === 'podcast') return 'podcast'; if (a.type === 'radarfuture') return 'radarFuture'; if (a.type === 'factoryReport' || a.category === 'گزارش کارخانه') return 'factory'; if (a.type === 'news') return 'news'; return 'report'; } 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') }, }); renderRadarList(); } catch { renderLogin('نام کاربری یا رمز عبور اشتباه است'); } }); } function topbarHtml(active, primaryLabel) { const activeGroup = TAB_GROUPS.find(g => g.tabs.includes(active)) || TAB_GROUPS[0]; const groupBtns = TAB_GROUPS.map(g => ` `).join(''); const subTabs = activeGroup.tabs.map(t => ` `).join(''); return `
پنل اندیشکده فولاد
`; } function wireTabs() { document.querySelectorAll('.tabs button').forEach(btn => { btn.addEventListener('click', () => { const t = btn.dataset.tab; if (t === 'glance') renderRadarList(); else if (t === 'radarFuture') renderRadarFutureList(); else if (t === 'media') renderMediaList(); else if (t === 'events') renderEventsList(); else if (t === 'banners') renderBannersList(); }); }); $('#logoutBtn').addEventListener('click', async () => { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {}); renderLogin(); }); } async function renderList() { root.innerHTML = ` ${topbarHtml('articles', '+ مطلب جدید')}

مطالب

${CATEGORIES.map(c => ``).join('')}

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

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderEditor(null)); let allItems = []; let activeCat = ''; const safeCoverUrl = (u) => /^\/uploads\/[a-zA-Z0-9._-]+$/.test(String(u || '')) ? u : ''; function renderItems() { const filtered = activeCat ? allItems.filter(a => a.category === activeCat) : allItems; if (!filtered.length) { $('#list').innerHTML = `

مطلبی در این دسته‌بندی یافت نشد.

`; return; } $('#list').innerHTML = filtered.map((a) => `
${a.featured ? 'ویژه' : ''} ${a.category ? `${escapeHtml(a.category)}` : ''} ${a.publishDate ? `${escapeHtml(a.publishDate)}` : ''}

${escapeHtml(a.title)}

${escapeHtml(a.summary)}

`).join(''); } $('#cat-bar').addEventListener('click', (e) => { const btn = e.target.closest('.cat-btn'); if (!btn) return; activeCat = btn.dataset.cat; document.querySelectorAll('.cat-btn').forEach(b => b.classList.toggle('active', b === btn)); renderItems(); }); $('#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(); } }); try { allItems = await api('/api/articles?limit=500'); if (!allItems.length) { $('#list').innerHTML = `

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

`; return; } renderItems(); } catch (err) { console.error(err); $('#list').innerHTML = `

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

`; } } async function renderRadarFutureList() { root.innerHTML = ` ${topbarHtml('radarFuture', '+ رادار آینده جدید')}

رادار آینده

محتوای صفحات «رادار آینده» سایت (‎/radar/*‎) — مقالهٔ بلوکی به تفکیک ۵ زیرمنو.

${RADAR_FUTURE_CATEGORIES.map(c => ``).join('')}

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

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderEditor(null, { contentType: 'radarFuture', type: 'radarfuture', category: '' })); let allItems = [], activeCat = ''; function renderItems() { const filtered = activeCat ? allItems.filter(a => a.category === activeCat) : allItems; if (!filtered.length) { $('#list').innerHTML = `

مطلبی در این بخش یافت نشد.

`; return; } $('#list').innerHTML = filtered.map(a => `
${a.category?`${escapeHtml(a.category)}`:''}${a.publishDate?`${escapeHtml(a.publishDate)}`:''}

${escapeHtml(a.title)}

${escapeHtml(a.summary||'')}

`).join(''); } $('#cat-bar').addEventListener('click', e => { const b = e.target.closest('.cat-btn'); if (!b) return; activeCat = b.dataset.cat; document.querySelectorAll('.cat-btn').forEach(x => x.classList.toggle('active', x === b)); renderItems(); }); $('#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') renderEditor(await api(`/api/articles/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این مطلب حذف شود؟')) return; await api(`/api/articles/${id}`, { method: 'DELETE' }); renderRadarFutureList(); } }); try { const all = await api('/api/articles?limit=500'); allItems = all.filter(a => a.type === 'radarfuture'); if (!allItems.length) { $('#list').innerHTML = `

هنوز مطلبی ثبت نشده — با «+ رادار آینده جدید» شروع کن.

`; return; } renderItems(); } catch (err) { $('#list').innerHTML = `

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

`; } } async function renderMediaList() { root.innerHTML = ` ${topbarHtml('media', '+ ویدیوکست جدید')}

ویدیوکست و پادکست

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

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderEditor(null, { contentType: 'video', type: 'video', category: '' })); $('#newPod').addEventListener('click', () => renderEditor(null, { contentType: 'podcast', type: 'podcast', category: '' })); $('#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') renderEditor(await api(`/api/articles/${id}`)); else if (btn.dataset.action === 'delete') { if (!confirm('این مورد حذف شود؟')) return; await api(`/api/articles/${id}`, { method: 'DELETE' }); renderMediaList(); } }); try { const all = await api('/api/articles?limit=500'); const items = all.filter(a => a.type === 'video' || a.type === 'podcast'); if (!items.length) { $('#list').innerHTML = `

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

`; return; } $('#list').innerHTML = items.map(a => `
${a.type==='video'?'ویدیوکست':'پادکست'}${a.publishDate?`${escapeHtml(a.publishDate)}`:''}

${escapeHtml(a.title)}

${escapeHtml(a.summary||'')}

`).join(''); } catch (err) { $('#list').innerHTML = `

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

`; } } function renderTypeSelector() { root.innerHTML = `
مطلب جدید

چه نوع محتوایی می‌خواهید منتشر کنید؟

${CONTENT_TYPES.map(ct => ` `).join('')}
`; $('#backBtn').addEventListener('click', () => renderList()); document.querySelectorAll('.type-card').forEach(btn => { btn.addEventListener('click', () => { const ct = CONTENT_TYPES.find(c => c.key === btn.dataset.ctype); if (ct.externalTab === 'radar') { renderRadarList(); return; } renderEditor(null, { contentType: ct.key, type: ct.defaultType, category: ct.defaultCategory }); }); }); } function renderEditor(article, draft = {}) { if (!article && !draft.contentType) { renderTypeSelector(); return; } const contentType = draft.contentType || inferContentType(article); const ctInfo = CONTENT_TYPES.find(c => c.key === contentType) || CONTENT_TYPES[0]; const isEdit = !!article; const a = article || { title: '', category: draft.category || ctInfo.defaultCategory, type: draft.type || ctInfo.defaultType, author: '', authorRole: '', authorInitial: '', publishDate: '', pages: 0, price: 0, isFree: true, summary: '', body: '', coverImage: '', tags: [], featured: false, }; // content blocks (interleaved text + chart/image) and citation sources let blocks = (Array.isArray(a.blocks) && a.blocks.length) ? a.blocks.map(b => ({ ...b })) : (a.body ? [{ type: 'text', value: a.body }] : []); let sources = Array.isArray(a.sources) ? a.sources.map(s => ({ ...s })) : []; const REPORT_TYPES = TYPES.filter(t => ['quarterly','flash','special','risk','free'].includes(t.value)); const isRadarFuture = contentType === 'radarFuture'; const hasCover = ['report','news','video','podcast','factory','radarFuture'].includes(contentType); const hasCategory = ['report','news','radarFuture'].includes(contentType); const hasAuthor = ['report','video','podcast'].includes(contentType); const hasPages = ['report','factory'].includes(contentType); const hasPrice = contentType === 'report'; const hasBody = ['report','news','factory','radarFuture'].includes(contentType); const hasMediaLink = ['video','podcast'].includes(contentType); const hasReportType = contentType === 'report'; const categoryOptions = isRadarFuture ? RADAR_FUTURE_CATEGORIES : CATEGORIES; root.innerHTML = `
${ctInfo.icon} ${isEdit ? 'ویرایش' : 'ایجاد'} — ${ctInfo.label}
${hasCategory ? ` ` : ``} ${hasReportType ? ` ` : ``} ${hasAuthor ? ` ` : ``} ${hasPages ? ` ` : ``} ${hasPrice ? ` ` : ``} ${hasCover ? `
` : ``}
${hasBody ? `
` : ``} ${hasMediaLink ? `
` : (hasBody ? `` : ``)}
${!isEdit ? `` : ''}
`; const backToList = () => { if (contentType === 'radarFuture') return renderRadarFutureList(); if (contentType === 'video' || contentType === 'podcast') return renderMediaList(); return renderRadarFutureList(); }; $('#backBtn').addEventListener('click', backToList); document.getElementById('changeTypeBtn')?.addEventListener('click', () => renderTypeSelector()); const coverFile = document.getElementById('coverFile'); if (coverFile) { coverFile.addEventListener('change', async (e) => { const file = e.target.files?.[0]; if (!file) return; const fd = new FormData(); fd.append('file', file); try { const data = await api('/api/uploads', { method: 'POST', body: fd, isForm: true }); const url = data.url; const input = $('input[name="coverImage"]'); if (input) input.value = url; const prev = document.getElementById('coverPreview'); if (prev) { prev.src = url; prev.style.display = ''; } } catch (err) { alert('آپلود ناموفق بود: ' + err.message); } }); } // video / podcast media file → object storage (fills the media URL field) const mediaFile = document.getElementById('mediaFile'); if (mediaFile) { mediaFile.addEventListener('change', async (e) => { const file = e.target.files?.[0]; if (!file) return; const status = document.getElementById('mediaStatus'); if (status) status.textContent = 'در حال آپلود فایل… (بسته به حجم چند لحظه طول می‌کشد)'; const fd = new FormData(); fd.append('file', file); try { const { url } = await api('/api/uploads/raw', { method: 'POST', body: fd, isForm: true }); document.getElementById('mediaUrl').value = url; if (status) status.textContent = '✓ آپلود شد و در آبجکت استوریج ذخیره شد'; } catch (err) { if (status) status.textContent = ''; alert('آپلود فایل ناموفق بود: ' + err.message); } }); } // ── content blocks (text + chart/image, reorderable) ────────────────── function renderBlocks() { const el = document.getElementById('blocksEditor'); if (!el) return; if (!blocks.length) { el.innerHTML = `
هنوز محتوایی اضافه نشده — یک پاراگراف یا تصویر/نمودار اضافه کنید.
`; return; } el.innerHTML = blocks.map((b, i) => { const ctrls = `
`; if (b.type === 'image') { return `
${ctrls}
${b.url ? `` : `
تصویر / نمودار
`}
`; } return `
${ctrls}
`; }).join(''); el.querySelectorAll('[data-txt]').forEach(t => t.addEventListener('input', e => { blocks[+e.target.dataset.txt].value = e.target.value; })); el.querySelectorAll('[data-cap]').forEach(t => t.addEventListener('input', e => { blocks[+e.target.dataset.cap].caption = e.target.value; })); el.querySelectorAll('[data-imgfile]').forEach(f => f.addEventListener('change', async e => { const file = e.target.files?.[0]; if (!file) return; const i = +e.target.dataset.imgfile; const fd2 = new FormData(); fd2.append('file', file); try { const { url } = await api('/api/uploads', { method: 'POST', body: fd2, isForm: true }); blocks[i].url = url; renderBlocks(); } catch (err) { alert('آپلود ناموفق بود: ' + err.message); } })); el.querySelectorAll('button[data-act]').forEach(btn => btn.addEventListener('click', e => { const i = +e.currentTarget.dataset.i, act = e.currentTarget.dataset.act; if (act === 'del') blocks.splice(i, 1); else if (act === 'up' && i > 0) { [blocks[i - 1], blocks[i]] = [blocks[i], blocks[i - 1]]; } else if (act === 'down' && i < blocks.length - 1) { [blocks[i + 1], blocks[i]] = [blocks[i], blocks[i + 1]]; } renderBlocks(); })); } document.getElementById('addTextBlock')?.addEventListener('click', () => { blocks.push({ type: 'text', value: '' }); renderBlocks(); }); document.getElementById('addImageBlock')?.addEventListener('click', () => { blocks.push({ type: 'image', url: '', caption: '' }); renderBlocks(); }); renderBlocks(); // ── sources / citations ─────────────────────────────────────────────── function renderSources() { const el = document.getElementById('sourcesEditor'); if (!el) return; el.innerHTML = sources.length ? sources.map((s, i) => `
`).join('') : `
منبعی اضافه نشده.
`; el.querySelectorAll('[data-stitle]').forEach(t => t.addEventListener('input', e => { sources[+e.target.dataset.stitle].title = e.target.value; })); el.querySelectorAll('[data-surl]').forEach(t => t.addEventListener('input', e => { sources[+e.target.dataset.surl].url = e.target.value; })); el.querySelectorAll('[data-sdel]').forEach(b => b.addEventListener('click', e => { sources.splice(+e.currentTarget.dataset.sdel, 1); renderSources(); })); } document.getElementById('addSource')?.addEventListener('click', () => { sources.push({ title: '', url: '' }); renderSources(); }); renderSources(); $('#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 isFreeVal = fd.get('isFree'); 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: isFreeVal === 'on' || isFreeVal === 'true', featured: fd.get('featured') === 'on', summary: fd.get('summary') || null, body: hasBody ? blocks.filter(b => b.type === 'text' && b.value && b.value.trim()).map(b => b.value.trim()).join('\n\n') : (fd.get('body') || null), coverImage: fd.get('coverImage') || null, blocks: hasBody ? blocks.filter(b => (b.type === 'text' && b.value && b.value.trim()) || (b.type === 'image' && b.url)) : [], sources: sources.filter(s => (s.title && s.title.trim()) || (s.url && s.url.trim())), tags: tagsRaw, }; try { if (isEdit) { await api(`/api/articles/${article.id}`, { method: 'PUT', body: payload }); } else { await api('/api/articles', { method: 'POST', body: payload }); } backToList(); } 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 (رادار آینده) // ═══════════════════════════════════════════════ // section pages on the site → which raw categories feed them const RADAR_PAGE_SLUGS = [ { slug: 'foresight', label: 'آینده‌پژوهی و رصد هوشمند', cats: ['market','tech','commodity','geo','energy'] }, { slug: 'market', label: 'بازار و زنجیره فولاد', cats: ['market','commodity'] }, { slug: 'sustainability', label: 'پایداری و فولاد سبز', cats: ['energy','commodity'] }, { slug: 'geopolitics', label: 'ژئوپلیتیک و اقتصاد جهانی', cats: ['geo','market'] }, { slug: 'technology', label: 'فناوری و نوآوری', cats: ['tech'] }, ]; function sectionLabelsForCategory(cat) { return RADAR_PAGE_SLUGS.filter(s => s.cats.includes(cat)).map(s => s.label); } async function renderRadarList() { root.innerHTML = ` ${topbarHtml('glance', '+ در یک نگاه جدید')}

در یک نگاه

محتوای صفحهٔ «در یک نگاه» سایت (‎/at-a-glance‎) — به تفکیک دسته.

${RADAR_CATEGORIES.map(c => ``).join('')}

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

`; wireTabs(); $('#newBtn').addEventListener('click', () => renderRadarEditor(null)); let allItems = []; let activeCat = ''; const safeImgUrl = (u) => /^(\/uploads\/[a-zA-Z0-9._-]+|\/[a-zA-Z0-9._\/-]+)$/.test(String(u || '')) ? u : ''; const catLabel = (v) => (RADAR_CATEGORIES.find(c => c.value === v) || {}).label || v; function renderItems() { const filtered = activeCat ? allItems.filter(r => r.category === activeCat) : allItems; if (!filtered.length) { $('#list').innerHTML = `

مطلبی در این دسته یافت نشد.

`; return; } $('#list').innerHTML = filtered.map(r => { return `
${r.category ? `${escapeHtml(catLabel(r.category))}` : ''} ${r.dateFa ? `${escapeHtml(r.dateFa)}` : ''}

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

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

`; }).join(''); } $('#section-bar').addEventListener('click', e => { const btn = e.target.closest('.cat-btn'); if (!btn) return; activeCat = btn.dataset.cat; document.querySelectorAll('#section-bar .cat-btn').forEach(b => b.classList.toggle('active', b === btn)); renderItems(); }); $('#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(); } }); try { allItems = await api('/api/radar?limit=500'); if (!allItems.length) { $('#list').innerHTML = `

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

`; return; } renderItems(); } catch (err) { $('#list').innerHTML = `

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

`; } } function renderRadarEditor(item) { const r = item || { category:'', img:'', authorName:'', authorAvatar:'', titleFa:'', dateFa:'', excerptFa:'', bodyFa:'', analysisFa:'', titleEn:'', dateEn:'', excerptEn:'', bodyEn:'', analysisEn:'', tags:[], source:'', sortOrder:0 }; const isEdit = !!item; function sectionBadges(cat) { const labels = sectionLabelsForCategory(cat); if (!labels.length) return ''; return labels.map(l => `${escapeHtml(l)}`).join(' '); } root.innerHTML = `
📌 ${isEdit?'ویرایش':'مطلب جدید'} — در یک نگاه
نمایش در بخش‌های سایت: ${sectionBadges(r.category)}
فارسی
English
`; $('#backBtn').addEventListener('click', () => renderRadarList()); document.getElementById('catSelect').addEventListener('change', e => { const cat = e.target.value; const badgesEl = document.getElementById('sectionBadges'); badgesEl.innerHTML = `نمایش در بخش‌های سایت: ${sectionBadges(cat)}`; }); document.getElementById('radarCoverFile').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="img"]').value = url; const prev = document.getElementById('radarCoverPreview'); prev.src = url; prev.style.display = ''; } catch (err) { alert('آپلود ناموفق: ' + err.message); } }); $('#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: r.authorAvatar || null, dateFa: fd.get('dateFa')||null, dateEn: fd.get('dateEn')||null, excerptFa: fd.get('excerptFa')||null, excerptEn: fd.get('excerptEn')||null, bodyFa: fd.get('bodyFa')||null, bodyEn: fd.get('bodyEn')||null, analysisFa: fd.get('analysisFa')||null, analysisEn: fd.get('analysisEn')||null, source: fd.get('source')||null, tags: String(fd.get('tags')||'').split(/[,،]/).map(s=>s.trim()).filter(Boolean), 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); } }); } 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: 'radar-foresight', label: 'صفحه رادار: آینده‌پژوهی و رصد هوشمند' }, { value: 'radar-sustainability', label: 'صفحه رادار: پایداری و فولاد سبز' }, { value: 'radar-geopolitics', label: 'صفحه رادار: ژئوپلیتیک و اقتصاد جهانی' }, { value: 'radar-technology', label: 'صفحه رادار: فناوری و نوآوری' }, { value: 'radar-market', 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') || 'featured', 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); } }); } // ───────────────────────────────────────────── function renderImport() { root.innerHTML = ` ${topbarHtml('import', '')}

⬇️ ایمپورت از Gemini

خروجی JSON که Gemini داد رو اینجا paste کن و دکمه ایمپورت رو بزن. گزارش توی مطالب و آیتم‌های رادار توی رادار ذخیره می‌شن.

`; setupTopbar(); const jsonTA = document.getElementById('import-json'); const statusEl = document.getElementById('import-status'); const resultEl = document.getElementById('import-result'); const countEl = document.getElementById('import-count'); jsonTA.addEventListener('input', () => { try { const d = JSON.parse(jsonTA.value); const ra = d.radar_items?.length || 0; countEl.textContent = `✓ JSON معتبر — گزارش: ${d.report?.title ? '1' : '0'} | رادار: ${ra}`; countEl.style.color = '#22c55e'; } catch { countEl.textContent = 'JSON نامعتبر'; countEl.style.color = '#ef4444'; } }); document.getElementById('import-clear').onclick = () => { jsonTA.value = ''; countEl.textContent = ''; resultEl.innerHTML = ''; }; document.getElementById('import-btn').onclick = async () => { let data; try { data = JSON.parse(jsonTA.value); } catch { statusEl.innerHTML = '
JSON نامعتبر است
'; return; } const btn = document.getElementById('import-btn'); btn.disabled = true; btn.textContent = 'در حال ایمپورت...'; statusEl.innerHTML = ''; try { const result = await api('/api/admin/import', { method:'POST', body: data }); resultEl.innerHTML = `
✓ ایمپورت موفق
📄 گزارش: ${result.article}
${result.radar.length ? `
🔭 آیتم‌های رادار: ${result.radar.length} مورد اضافه شد
` : ''}
مشاهده در مطالب ← ${result.radar.length ? `مشاهده در رادار ←` : ''}
`; jsonTA.value = ''; countEl.textContent = ''; } catch (err) { statusEl.innerHTML = `
خطا: ${err.message}
`; } finally { btn.disabled = false; btn.textContent = 'ایمپورت'; } }; } // ───────────────────────────────────────────── function renderPdfAnalyze() { root.innerHTML = ` ${topbarHtml('pdfAnalyze', '')}

آنالیز و دسته‌بندی PDF

فایل PDF گزارش را آپلود کنید — سیستم متن را استخراج کرده و پیشنهاد بخش می‌دهد.

`; wireTabs(); $('#newBtn') && ($('#newBtn').style.display = 'none'); $('#pdfForm').addEventListener('submit', async (e) => { e.preventDefault(); const file = e.target.pdf.files[0]; if (!file) return; const result = $('#pdfResult'); result.innerHTML = `

در حال پردازش PDF… (${(file.size/1024).toFixed(0)} KB)

`; try { const base64 = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result.split(',')[1]); reader.onerror = reject; reader.readAsDataURL(file); }); // session travels in the HttpOnly cookie (credentials:'include'); no token in JS const _res = await fetch('/api/pdf-analyze', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ base64 }), }); if (!_res.ok) { const errText = await _res.text(); result.innerHTML = `
خطای سرور ${_res.status}:
${escapeHtml(errText)}
`; return; } const data = await _res.json(); const SECTION_LABELS = { market: 'بازار و زنجیره فولاد', technology: 'فناوری صنعتی', geopolitics: 'ژئواکونومی', sustainability: 'پایداری محیطی', foresight: 'آینده‌نگاری', }; if (data.is_garbled) { // PDF has non-standard font encoding — offer manual text input result.innerHTML = `
⚠️ متن PDF قابل استخراج نیست

این PDF از رمزگذاری فونت غیراستاندارد استفاده می‌کند (رایج در نشریات فارسی). متن به‌درستی خوانده نمی‌شود و نیاز به OCR دارد.
می‌توانید متن رو دستی paste کنید تا بخش پیشنهادی و خلاصه ساخته بشه.

${data.page_count} صفحه · ${(data.text_length/1000).toFixed(1)}k کاراکتر (ناخوانا)

عنوان گزارش

متن / خلاصه (paste کنید)

بخش مناسب

`; async function runGenerate(textSrc, titleSrc, catKey) { const gr = $('#generateResult'); gr.innerHTML = '

در حال تولید محتوا... ⏳

'; // session travels in the HttpOnly cookie (credentials:'include'); no token in JS try { const r = await fetch('/api/pdf-generate', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: textSrc, category: SECTION_LABELS[catKey] || catKey }), }); const d = await r.json(); if (!r.ok) { gr.innerHTML = `
${escapeHtml(d.error || 'خطا')}
`; return; } const generated = d.generated || ''; gr.innerHTML = `

محتوای تولیدشده

${escapeHtml(generated)}
`; $('#useGeneratedBtn').addEventListener('click', () => { const lines = generated.split('\n').filter(l => l.trim()); const autoTitle = titleSrc || lines[0]?.replace(/^#+\s*|\*\*/g,'').slice(0,100) || ''; window._pdfDraft = { title: autoTitle, summary: generated.slice(0, 600), category: SECTION_LABELS[catKey] || catKey, type: 'quarterly' }; renderEditor(null, window._pdfDraft); }); } catch(e) { gr.innerHTML = `
${escapeHtml(e.message)}
`; } } $('#generateContentBtn').addEventListener('click', () => { const t = $('#manualText').value.trim(); if (!t) { alert('لطفاً ابتدا متن را paste کنید'); return; } runGenerate(t, $('#manualTitle').value.trim(), $('#manualCategory').value); }); $('#createArticleBtn').addEventListener('click', () => { const manualTitle = $('#manualTitle').value.trim(); const manualText = $('#manualText').value.trim(); const catKey = $('#manualCategory').value; window._pdfDraft = { title: manualTitle, summary: manualText.slice(0, 300), category: SECTION_LABELS[catKey] || catKey, type: 'quarterly', }; renderEditor(null, window._pdfDraft); }); return; } const c = data.classification; const allRows = Object.entries(c.allScores) .sort((a,b) => b[1].score - a[1].score) .map(([cat, d]) => ` ${cat === c.category ? '✅' : ''} ${SECTION_LABELS[cat] || cat}
${d.score}
`).join(''); result.innerHTML = `
${escapeHtml(c.label)} اطمینان: ${c.confidence}٪ ${data.page_count} صفحه · ${(data.text_length/1000).toFixed(1)}k کاراکتر

عنوان پیشنهادی

${escapeHtml(data.title)}

کلیدواژه‌های شناسایی‌شده

${c.keywords.map(kw => `${escapeHtml(kw)}`).join('')}

امتیاز بخش‌ها

${allRows}

متن استخراج‌شده (۸۰۰ کاراکتر اول)

${escapeHtml(data.excerpt)}

ایجاد مقاله از این PDF

`; $('#createArticleBtn').addEventListener('click', () => { window._pdfDraft = { title: data.title, summary: data.excerpt.slice(0, 300), category: c.label, type: 'quarterly', }; renderEditor(null, window._pdfDraft); }); $('#generateContentBtn').addEventListener('click', async () => { const gr = $('#generateResult'); gr.innerHTML = '

در حال تولید محتوا... ⏳

'; // session travels in the HttpOnly cookie (credentials:'include'); no token in JS try { const r = await fetch('/api/pdf-generate', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: data.excerpt + '\n\n' + (data.text || '').slice(0, 5000), category: c.label }), }); const d = await r.json(); if (!r.ok) { gr.innerHTML = `
${escapeHtml(d.error || 'خطا')}
`; return; } const generated = d.generated || ''; gr.innerHTML = `

محتوای تولیدشده

${escapeHtml(generated)}
`; $('#useGeneratedBtn').addEventListener('click', () => { const lines = generated.split('\n').filter(l => l.trim()); const autoTitle = data.title || lines[0]?.replace(/^#+\s*|\*\*/g,'').slice(0,100) || ''; window._pdfDraft = { title: autoTitle, summary: generated.slice(0, 600), category: c.label, type: 'quarterly' }; renderEditor(null, window._pdfDraft); }); } catch(e) { gr.innerHTML = `
${escapeHtml(e.message)}
`; } }); } catch (err) { result.innerHTML = `
خطا: ${escapeHtml(err.message)}
`; } }); } // ───────────────────────────────────────────── api('/api/auth/me').then(() => renderRadarList()).catch(() => renderLogin());