const $ = (sel, root = document) => root.querySelector(sel);
const root = $('#app');
const TAB_GROUPS = [
{ label: 'محتوا', tabs: ['articles', 'events', 'radar', 'radarPages', 'factoryReports'] },
{ label: 'داده', tabs: ['risks', 'mprices', 'stats', 'charts', 'integrations'] },
{ label: 'سایت', tabs: ['vision', 'advisory', 'team', 'experts', 'plans', 'banners'] },
{ label: 'ابزار', tabs: ['pdfAnalyze', 'import', 'users'] },
];
const TAB_LABELS = {
articles: 'مطالب', risks: 'ریسکها', events: 'رویدادها',
radar: 'رادار', radarPages: 'صفحات رادار', factoryReports: 'گزارش کارخانه',
integrations: 'منابع داده', stats: 'آمار', mprices: 'قیمتها',
charts: 'نمودارها', vision: 'چشمانداز', advisory: 'شورای مشورتی',
team: 'تیم', experts: 'خبرگان', plans: 'پلانها', banners: 'بنرها',
pdfAnalyze: '📄 PDF', import: '⬇️ ایمپورت', users: 'کاربران',
};
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: 'radar', label: 'رادار آینده', icon: '🔭', desc: 'از تب «رادار» در منوی بالا مدیریت میشود', defaultType: '', defaultCategory: '', externalTab: 'radar' },
];
function inferContentType(a) {
if (!a) return 'report';
if (a.type === 'video') return 'video';
if (a.type === 'podcast') return 'podcast';
if (a.type === 'radar') return 'radar';
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 = `
`;
$('#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) {
const activeGroup = TAB_GROUPS.find(g => g.tabs.includes(active)) || TAB_GROUPS[0];
const groupBtns = TAB_GROUPS.map(g => `
${g.label}
`).join('');
const subTabs = activeGroup.tabs.map(t => `
${TAB_LABELS[t]}
`).join('');
return `
پنل اندیشکده فولاد
${groupBtns}
${primaryLabel}
خروج
${subTabs}
`;
}
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 === 'pdfAnalyze') renderPdfAnalyze();
else if (t === 'import') renderImport();
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', '+ مطلب جدید')}
مطالب
همه
${CATEGORIES.map(c => `${escapeHtml(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 = `خطا در بارگذاری مطالب.
`;
}
}
function renderTypeSelector() {
root.innerHTML = `
چه نوع محتوایی میخواهید منتشر کنید؟
${CONTENT_TYPES.map(ct => `
${ct.icon}
${ct.label}
${ct.desc}
`).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,
};
const REPORT_TYPES = TYPES.filter(t => ['quarterly','flash','special','risk','free'].includes(t.value));
const hasCover = ['report','news','video','podcast','factory'].includes(contentType);
const hasCategory = ['report','news'].includes(contentType);
const hasAuthor = ['report','video','podcast'].includes(contentType);
const hasPages = ['report','factory'].includes(contentType);
const hasPrice = contentType === 'report';
const hasBody = ['report','news','factory'].includes(contentType);
const hasMediaLink = ['video','podcast'].includes(contentType);
const hasReportType = contentType === 'report';
root.innerHTML = `
`;
$('#backBtn').addEventListener('click', () => renderList());
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 { url } = await api('/api/uploads', { method: 'POST', body: fd, isForm: true });
$('input[name="coverImage"]').value = url;
const prev = document.getElementById('coverPreview');
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);
}
});
}
$('#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: 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 = `
عنوان / منبع
سطح
${RISK_LEVELS.map(l => `${l.label} `).join('')}
تاریخ / افق (شمسی)
ترتیب نمایش (عدد کوچکتر اول)
متن سیگنال
${escapeHtml(r.quote)}
${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 ? '(برای تغییر پر کنید، خالی = بدون تغییر)' : '(حداقل ۸ کاراکتر)'}
${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 = `
عنوان (فارسی)
عنوان (انگلیسی)
نوع
—
${EVENT_TYPES.map(t=>`${t.label} `).join('')}
ثبتنام باز است
تاریخ (فارسی)
تاریخ (انگلیسی)
ماه (فارسی)
ماه (انگلیسی)
سال
ترتیب نمایش
شهر (فارسی)
شهر (انگلیسی)
مکان (فارسی)
مکان (انگلیسی)
توضیحات (فارسی) ${escapeHtml(e.descriptionFa||'')}
توضیحات (انگلیسی) ${escapeHtml(e.descriptionEn||'')}
${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 = `
نام (فارسی)
نام (انگلیسی)
سمت (فارسی)
سمت (انگلیسی)
حرف آواتار
تعداد گزارش
ایمیل
ترتیب نمایش
تخصصها (با ویرگول جدا کنید)
${isExpert ? `
اتاق فکر (فارسی)
اتاق فکر (انگلیسی)
تلگرام
لینکدین
توییتر
وبسایت ` : ''}
بیوگرافی (فارسی) ${escapeHtml(m.bioFa||'')}
بیوگرافی (انگلیسی) ${escapeHtml(m.bioEn||'')}
${isEdit?'ذخیره':'ایجاد'}
`;
$('#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 = `
نام (فارسی)
نام (انگلیسی)
قیمت (ریال)
دوره (فارسی)
دوره (انگلیسی)
برچسب (فارسی)
برچسب (انگلیسی)
دکمه CTA (فارسی)
دکمه CTA (انگلیسی)
ترتیب نمایش
پلان ویژه (هایلایت)
ویژگیها (هر ویژگی در یک خط — فارسی)
${escapeHtml(featStr)}
${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('radar', '+ مطلب رادار جدید')}
رادار آینده
همه بخشها
${RADAR_PAGE_SLUGS.map(s => `${s.label} `).join('')}
`;
wireTabs();
$('#newBtn').addEventListener('click', () => renderRadarEditor(null));
let allItems = [];
let activeSlug = '';
const safeImgUrl = (u) => /^(\/uploads\/[a-zA-Z0-9._-]+|\/[a-zA-Z0-9._\/-]+)$/.test(String(u || '')) ? u : '';
function renderItems() {
const activePage = RADAR_PAGE_SLUGS.find(s => s.slug === activeSlug);
const filtered = activePage ? allItems.filter(r => activePage.cats.includes(r.category)) : allItems;
if (!filtered.length) { $('#list').innerHTML = `مطلبی در این بخش یافت نشد.
`; return; }
$('#list').innerHTML = filtered.map(r => {
const sections = sectionLabelsForCategory(r.category);
return `
${sections.map(s => `${escapeHtml(s)} `).join('')}
${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;
activeSlug = btn.dataset.slug;
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:'', 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 = `
بخش سایت
انتخاب کنید…
${RADAR_CATEGORIES.map(c=>`${c.label} `).join('')}
نمایش در بخشهای سایت:
${sectionBadges(r.category)}
فارسی
عنوان (فارسی)
تاریخ (فارسی)
نام نویسنده
چکیده (فارسی) ${escapeHtml(r.excerptFa||'')}
متن کامل (فارسی) ${escapeHtml(r.bodyFa||'')}
تحلیل استراتژیک (فارسی — برای اعضا) ${escapeHtml(r.analysisFa||'')}
English
Title (English)
Date (English)
Excerpt (English) ${escapeHtml(r.excerptEn||'')}
Body (English) ${escapeHtml(r.bodyEn||'')}
Strategic Analysis (English) ${escapeHtml(r.analysisEn||'')}
ترتیب نمایش
${isEdit?'ذخیره تغییرات':'انتشار مطلب رادار'}
`;
$('#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,
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 = `
عنوان دسته (فارسی)
عنوان دسته (انگلیسی)
تیتر تازهترینها (فارسی)
تیتر تازهترینها (انگلیسی)
کارت ویژه (هیرو)
عنوان ویژه (فارسی)
عنوان ویژه (انگلیسی)
تاریخ ویژه (فارسی)
تاریخ ویژه (انگلیسی)
تصویر ویژه (URL)
بنر تحلیل ویژه
برچسب بنر (فارسی)
برچسب بنر (انگلیسی)
عنوان بنر (فارسی)
عنوان بنر (انگلیسی)
توضیح بنر (فارسی) ${escapeHtml(r.bannerDescFa||'')}
توضیح بنر (انگلیسی) ${escapeHtml(r.bannerDescEn||'')}
تصویر کتاب بنر (URL)
ترتیب نمایش
ذخیره
`;
$('#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 = `
عنوان (فارسی)
عنوان (انگلیسی)
برچسب (فارسی)
برچسب (انگلیسی)
تاریخ (فارسی)
تاریخ (انگلیسی)
جهت ورود
${SCROLL_DIRS.map(d=>`${d.label} `).join('')}
ترتیب نمایش
مسیر تصویر
خلاصه (فارسی) ${escapeHtml(r.excerptFa||'')}
خلاصه (انگلیسی) ${escapeHtml(r.excerptEn||'')}
${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 = `
نام
آیکن
${INTEGRATION_ICONS.map(i=>`${i.label} `).join('')}
رنگ تأکید
ستون شبکه (۱-۶)
ردیف شبکه (۱-۵)
ترتیب نمایش
مسیر لوگو (اختیاری)
${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 = `
شناسه (لاتین)
نام محصول (فارسی)
مقدار / قیمت
واحد (مثل تومان/کیلو)
تغییر (مقدار)
تغییر (درصد)
روند
${TREND_TYPES.map(t => `${t.label} `).join('')}
ترتیب نمایش
${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 = `
سری داده
${CHART_SERIES.map(s => `${s.label} `).join('')}
ترتیب نمایش
برچسب (ماه فارسی / نام کشور)
مقدار (قیمت / تناژ)
متادیتا (فقط «مقایسه جهانی» — JSON: {"flag":"🇮🇷","currency":"IRR","unit":"تومان/کیلو"})
${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 = `
عنوان (فارسی)
عنوان (انگلیسی)
آیکون
—
${VISION_ICONS.map(ic=>`${ic} `).join('')}
ترتیب نمایش
توضیحات (فارسی) ${escapeHtml(v.descFa||'')}
توضیحات (انگلیسی) ${escapeHtml(v.descEn||'')}
${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 = `
برچسب بالای عنوان (فارسی) — مثلاً «ماهنامه شماره ۱۲ منتشر شد…»
برچسب بالای عنوان (انگلیسی)
عنوان (فارسی)
عنوان (انگلیسی)
زیرعنوان (فارسی)
زیرعنوان (انگلیسی)
متن دکمه (فارسی)
متن دکمه (انگلیسی)
لینک دکمه
موقعیت
${POSITIONS.map(p=>`${p.label} `).join('')}
ترتیب نمایش
فعال (نمایش داده شود)
${isEdit ? 'ذخیره' : 'ایجاد'}
`;
$('#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); }
});
}
// ─────────────────────────────────────────────
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} مورد اضافه شد
` : ''}
`;
jsonTA.value = ''; countEl.textContent = '';
} catch (err) {
statusEl.innerHTML = `خطا: ${err.message}
`;
} finally {
btn.disabled = false; btn.textContent = 'ایمپورت';
}
};
}
// ─────────────────────────────────────────────
function renderPdfAnalyze() {
root.innerHTML = `
${topbarHtml('pdfAnalyze', '')}
آنالیز و دستهبندی PDF
فایل 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 کاراکتر (ناخوانا)
`;
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}
`).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('')}
امتیاز بخشها
متن استخراجشده (۸۰۰ کاراکتر اول)
${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(() => renderList()).catch(() => renderLogin());