const $ = (sel, root = document) => root.querySelector(sel);
const root = $('#app');
const CATEGORIES = ['بازار جهانی', 'سیاستگذاری', 'انرژی و ESG', 'صادرات', 'تولید داخلی', 'ریسک و بحران'];
const TYPES = [
{ value: 'quarterly', label: 'فصلی' },
{ value: 'flash', label: 'فلش' },
{ value: 'risk', label: 'هشدار ریسک' },
{ value: 'free', label: 'رایگان' },
{ value: 'special', label: 'ویژه' },
];
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) {
return `
پنل اندیشکده فولاد
`;
}
function wireTabs() {
document.querySelectorAll('.tabs button').forEach(btn => {
btn.addEventListener('click', () => {
if (btn.dataset.tab === 'articles') renderList();
else if (btn.dataset.tab === 'risks') renderRisksList();
});
});
$('#logoutBtn').addEventListener('click', async () => {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {});
renderLogin();
});
}
async function renderList() {
root.innerHTML = `
${topbarHtml('articles', '+ مطلب جدید')}
مطالب
`;
wireTabs();
$('#newBtn').addEventListener('click', () => renderEditor(null));
try {
const items = await api('/api/articles?limit=200');
if (!items.length) {
$('#list').innerHTML = `هنوز مطلبی ثبت نشده است.
`;
return;
}
const safeCoverUrl = (u) => /^\/uploads\/[a-zA-Z0-9._-]+$/.test(String(u || '')) ? u : '';
$('#list').innerHTML = items.map((a) => `
${a.featured ? 'ویژه' : ''}
${a.category ? `${escapeHtml(a.category)}` : ''}
${a.publishDate ? `${escapeHtml(a.publishDate)}` : ''}
${escapeHtml(a.title)}
${escapeHtml(a.summary)}
`).join('');
$('#list').addEventListener('click', async (e) => {
const btn = e.target.closest('button[data-action]');
if (!btn) return;
const row = btn.closest('.row');
const id = row.dataset.id;
if (btn.dataset.action === 'edit') {
const a = await api(`/api/articles/${id}`);
renderEditor(a);
} else if (btn.dataset.action === 'delete') {
if (!confirm('این مطلب حذف شود؟')) return;
await api(`/api/articles/${id}`, { method: 'DELETE' });
renderList();
}
});
} catch (err) {
console.error(err);
$('#list').innerHTML = `خطا در بارگذاری مطالب.
`;
}
}
function renderEditor(article) {
const a = article || {
title: '', category: '', type: '', author: '', authorRole: '', authorInitial: '',
publishDate: '', pages: 0, price: 0, isFree: true, summary: '', body: '',
coverImage: '', tags: [], featured: false,
};
const isEdit = !!article;
root.innerHTML = `
`;
$('#backBtn').addEventListener('click', () => renderList());
$('#coverFile').addEventListener('change', async (e) => {
const file = e.target.files?.[0];
if (!file) return;
const fd = new FormData();
fd.append('file', file);
try {
const { url } = await api('/api/uploads', { method: 'POST', body: fd, isForm: true });
$('input[name="coverImage"]').value = url;
$('#coverPreview').src = url;
} catch (err) {
alert('آپلود ناموفق بود: ' + err.message);
}
});
$('#editorForm').addEventListener('submit', async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
const tagsRaw = String(fd.get('tags') || '').split(/[,،]/).map(s => s.trim()).filter(Boolean);
const payload = {
title: fd.get('title'),
category: fd.get('category') || null,
type: fd.get('type') || null,
author: fd.get('author') || null,
authorRole: fd.get('authorRole') || null,
authorInitial: fd.get('authorInitial') || null,
publishDate: fd.get('publishDate') || null,
pages: Number(fd.get('pages') || 0),
price: Number(fd.get('price') || 0),
isFree: fd.get('isFree') === 'on',
featured: fd.get('featured') === 'on',
summary: fd.get('summary') || null,
body: fd.get('body') || null,
coverImage: fd.get('coverImage') || null,
tags: tagsRaw,
};
try {
if (isEdit) {
await api(`/api/articles/${article.id}`, { method: 'PUT', body: payload });
} else {
await api('/api/articles', { method: 'POST', body: payload });
}
renderList();
} catch (err) {
alert('ذخیره ناموفق بود: ' + err.message);
}
});
}
function escapeAttr(s) {
return String(s ?? '').replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>');
}
const RISK_LEVELS = [
{ value: 'critical', label: 'بحرانی' },
{ value: 'high', label: 'بالا' },
{ value: 'medium', label: 'متوسط' },
{ value: 'low', label: 'پایین' },
{ value: 'opportunity', label: 'فرصت' },
];
async function renderRisksList() {
root.innerHTML = `
${topbarHtml('risks', '+ سیگنال جدید')}
سیگنالهای ریسک
`;
wireTabs();
$('#newBtn').addEventListener('click', () => renderRiskEditor(null));
try {
const items = await api('/api/risks?limit=200');
if (!items.length) {
$('#list').innerHTML = `هنوز سیگنالی ثبت نشده است.
`;
return;
}
const SAFE_LEVELS = new Set(['critical', 'high', 'medium', 'low', 'opportunity']);
const labelOf = (lvl) => (RISK_LEVELS.find(r => r.value === lvl)?.label) || escapeHtml(lvl);
$('#list').innerHTML = items.map(r => `
${labelOf(r.level)}
${escapeHtml(r.name)}
${r.date ? `${escapeHtml(r.date)}` : ''}
${escapeHtml(r.quote)}
`).join('');
$('#list').addEventListener('click', async (e) => {
const btn = e.target.closest('button[data-action]');
if (!btn) return;
const id = btn.closest('.row').dataset.id;
if (btn.dataset.action === 'edit') {
const r = await api(`/api/risks/${id}`);
renderRiskEditor(r);
} else if (btn.dataset.action === 'delete') {
if (!confirm('این سیگنال حذف شود؟')) return;
await api(`/api/risks/${id}`, { method: 'DELETE' });
renderRisksList();
}
});
} catch (err) {
console.error(err);
$('#list').innerHTML = `خطا در بارگذاری سیگنالها.
`;
}
}
function renderRiskEditor(risk) {
const r = risk || { quote: '', name: '', date: '', level: 'high', sortOrder: 0 };
const isEdit = !!risk;
root.innerHTML = `
`;
$('#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);
}
});
}
api('/api/auth/me').then(() => renderList()).catch(() => renderLogin());