import { useState, useEffect, useCallback } from 'react';
import { useAuth, useNav, useToast, RoleBadge, EmptyState, pillBtn, inputStyle } from '../../teknav-ui.jsx';
import { adminApi } from '../lib/admin-api.js';
import { iconNode } from '../lib/icon.jsx';
import triangleAlertIcon from '../assets/icons/triangle-alert.svg';
import { SIDEBAR_ITEMS, BRAND, PanelState, actionBtn, ADMIN_ROLE_NAMES } from './Shared.jsx';
import { ArticleEditor } from './ArticleEditor.jsx';
import { ArticlesManagement } from './ArticleList.jsx';
import { UserManagement, CategoryManagement, TagsManagement, AuthorsManagement } from './UserManagement.jsx';
import { MediaLibrary } from './MediaLibrary.jsx';
import { CommentsModeration } from './CommentModeration.jsx';
import { Phase9AnalyticsPanel, SeoAuditPanel, JobsPanel, ClientErrorsPanel } from './AnalyticsPanel.jsx';
import { ReviewsPanel } from './ReviewPanel.jsx';
import { NewsletterPanel } from './NewsletterPanel.jsx';
import { SeriesManagementPanel } from './SeriesPanel.jsx';
function AdminSidebar({ activeTab, onTab, user, open, onClose }) {
const items = SIDEBAR_ITEMS[user?.role] || SIDEBAR_ITEMS.writer;
return (
<>
{open &&
}
>
);
}
function DashboardStats({ role }) {
const [statsData, setStatsData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(false);
adminApi.getDashboard()
.then(res => { if (!cancelled) setStatsData(res); })
.catch(() => { if (!cancelled) { setStatsData(null); setError(true); } })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, []);
if (loading || error || !statsData || statsData.totalArticles === 0) {
return (
);
}
const published = statsData.published ?? 0;
const pending = statsData.pending ?? 0;
const drafts = statsData.drafts ?? 0;
const totalViews = statsData.totalViews ?? 0;
const adminStats = [
{ label: 'مقاله منتشرشده', value: published, color: '#2F8F6B', icon: '◉' },
{ label: 'در انتظار بررسی', value: pending, color: '#D08A22', icon: '◷' },
{ label: 'پیشنویس', value: drafts, color: '#5F6B6D', icon: '◐' },
{ label: 'مجموع بازدید', value: totalViews.toLocaleString('fa'), color: '#0F6B73', icon: '◈' },
{ label: 'کاربران', value: statsData.totalUsers ?? 0, color: '#3A7D5E', icon: '◐' },
{ label: 'رسانهها', value: statsData.totalMedia ?? 0, color: '#C46A4D', icon: '▣' },
];
const editorStats = [
{ label: 'در انتظار تأیید', value: pending, color: '#D08A22', icon: '◷' },
{ label: 'منتشرشده', value: published, color: '#2F8F6B', icon: '◉' },
{ label: 'نیازمند اصلاح', value: statsData.needsRevision ?? 0, color: '#C94C4C', icon: triangleAlertIcon },
];
const writerStats = [
{ label: 'مقاله منتشرشده', value: published, color: '#2F8F6B', icon: '◉' },
{ label: 'پیشنویس', value: drafts, color: '#5F6B6D', icon: '◐' },
{ label: 'در انتظار بررسی', value: pending, color: '#D08A22', icon: '◷' },
];
const stats = role === 'admin' ? adminStats : role === 'editor' ? editorStats : writerStats;
return (
);
}
function LiveAdminFeed() {
const [liveData, setLiveData] = useState({ count: 0, guests: 0, users: 0, commentsPerMin: 0 });
const [events, setEvents] = useState([]);
const [connected, setConnected] = useState(false);
useEffect(() => {
const es = new EventSource('/api/v1/admin/stream', { withCredentials: true });
es.onopen = () => setConnected(true);
es.onerror = () => setConnected(false);
es.addEventListener('visitor_update', event => {
try {
const parsed = JSON.parse(event.data);
setLiveData({
count: parsed.count || 0,
guests: parsed.guests || 0,
users: parsed.users || 0,
commentsPerMin: parsed.commentsPerMin || 0
});
} catch {}
});
es.addEventListener('activity', event => setEvents(list => [JSON.parse(event.data), ...list].slice(0, 50)));
return () => es.close();
}, []);
return (
وضعیت زنده
{connected ? 'متصل' : 'قطع'}
{liveData.count.toLocaleString('fa-IR')}
بازدید کل
{liveData.users.toLocaleString('fa-IR')}
کاربر لاگینشده
{liveData.guests.toLocaleString('fa-IR')}
مهمان
{liveData.commentsPerMin.toLocaleString('fa-IR')}
نظر بر دقیقه
آخرین رخدادها
{events.length === 0 ?
هنوز رخدادی ثبت نشده...
: null}
{events.slice(0, 6).map((event, idx) => (
{event.actor}: {event.action || event.type} روی {event.target}
))}
);
}
function ActivityLog() {
const [log, setLog] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(false);
adminApi.listActivity({ limit: 50 })
.then(items => { if (!cancelled) { setLog(Array.isArray(items) ? items : []); setLoading(false); } })
.catch(() => { if (!cancelled) { setLog([]); setError(true); setLoading(false); } });
return () => { cancelled = true; };
}, []);
const typeColors = { publish: '#2F8F6B', submit: '#D49A2A', draft: '#5F6B6D', approve: '#2F8F6B', reject: '#C94C4C', user: '#0F6B73', tag: '#C76D4A', category: '#D49A2A', edit: '#0F6B73', role: '#C76D4A', delete: '#C94C4C', author: '#C76D4A', upload: '#2F8F6B', media: '#C46A4D', schedule: '#D49A2A' };
if (loading) return در حال بارگذاری...
;
if (error) return ;
if (log.length === 0) return هنوز فعالیتی ثبت نشده است.
;
return (
{log.slice(0, 20).map(item => (
{typeof item.user === 'string' ? item.user : (item.user?.name ?? 'کاربر')}
— {item.action}:
{item.target}
{item.time || (item.createdAt ? new Date(item.createdAt).toLocaleDateString('fa-IR') : '')}
))}
);
}
function DashboardCategoryStats() {
const [cats, setCats] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(false);
adminApi.listCategories()
.then(items => { if (!cancelled) setCats(items ?? []); })
.catch(() => { if (!cancelled) { setCats([]); setError(true); } })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, []);
if (loading || error || cats.length === 0) {
return ;
}
return cats.map(c => (
));
}
function DashboardTopArticles() {
const { navigate } = useNav();
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(false);
adminApi.getDashboard()
.then(res => { if (!cancelled) setItems(res?.topArticles ?? []); })
.catch(() => { if (!cancelled) { setItems([]); setError(true); } })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, []);
if (loading || error || items.length === 0) {
return ;
}
return items.map(article => (
));
}
function RoleMetricsPanel({ role }) {
const [metrics, setMetrics] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const load = useCallback(() => {
setError(false);
return adminApi.getPanelMetrics()
.then(setMetrics)
.catch(() => { setMetrics(null); setError(true); })
.finally(() => setLoading(false));
}, []);
useEffect(() => {
let cancelled = false;
load();
const timer = setInterval(() => { if (!cancelled) load(); }, 15000);
let es;
if (role === 'admin' || role === 'editor') {
es = new EventSource('/api/v1/admin/stream', { withCredentials: true });
['activity', 'view_update', 'visitor_update'].forEach((eventName) => {
es.addEventListener(eventName, () => load());
});
}
return () => { cancelled = true; clearInterval(timer); es?.close(); };
}, [load, role]);
if (loading || error || !metrics || metrics.statuses?.totalArticles === 0) {
return ;
}
const eventTotal = (metrics.realtime?.eventsByType ?? []).reduce((sum, item) => sum + Number(item.count ?? 0), 0);
const sets = {
admin: [
['مقاله منتشرشده', metrics.statuses?.published, '#2F8F6B'],
['در انتظار بررسی', metrics.statuses?.pending, '#D49A2A'],
['نیازمند اصلاح', metrics.statuses?.needsRevision, '#C94C4C'],
['بازدید زنده', metrics.engagement?.views, '#0F6B73'],
['تعاملها', (metrics.engagement?.comments ?? 0) + (metrics.engagement?.saved ?? 0), '#3A7D5E'],
['ایراد کیفیت', metrics.quality?.recentWithIssues, '#C46A4D'],
],
editor: [
['در انتظار تایید', metrics.statuses?.pending, '#D49A2A'],
['منتشرشده', metrics.statuses?.published, '#2F8F6B'],
['نیازمند اصلاح', metrics.statuses?.needsRevision, '#C94C4C'],
['مشترک خبرنامه', metrics.growth?.newsletterSubscribers, '#0F6B73'],
['رویدادهای زنده', eventTotal, '#3A7D5E'],
],
writer: [
['منتشرشده', metrics.statuses?.published, '#2F8F6B'],
['پیشنویس', metrics.statuses?.drafts, '#5F6B6D'],
['در انتظار بررسی', metrics.statuses?.pending, '#D49A2A'],
['بازدید نوشتهها', metrics.engagement?.views, '#0F6B73'],
['دنبالکننده', metrics.engagement?.writerFollows, '#3A7D5E'],
],
reviewer: [
['در صف بررسی', metrics.statuses?.pending, '#D49A2A'],
['تاییدشده', metrics.workflow?.reviewsApproved, '#2F8F6B'],
['نیازمند اصلاح', metrics.statuses?.needsRevision, '#C94C4C'],
['ایراد کیفیت اخیر', metrics.quality?.recentWithIssues, '#0F6B73'],
],
};
const stats = sets[role] ?? sets.writer;
return (
{stats.map(([label, value, color]) => (
{Number(value ?? 0).toLocaleString('fa-IR')}
{label}
))}
پوشش زنده پنل
{(metrics.realtime?.eventsByType ?? []).slice(0, 8).map((item) => (
{item.type}: {Number(item.count ?? 0).toLocaleString('fa-IR')}
))}
اعلان خواندهنشده: {Number(metrics.realtime?.unreadNotifications ?? 0).toLocaleString('fa-IR')}
);
}
function AdminPanel() {
const { user, logout } = useAuth();
const { navigate } = useNav();
const [tab, setTab] = useState('dashboard');
const [editingArticle, setEditingArticle] = useState(null);
const [sidebarOpen, setSidebarOpen] = useState(false);
useEffect(() => { if (!user) navigate('/login'); }, [user]);
if (!user) return null;
const sectionTitle = (t) => {t}
;
const renderContent = () => {
if (tab === 'create' || editingArticle) return (
{ setEditingArticle(null); setTab('articles'); }} />
);
if (tab === 'articles') return (
<>
{sectionTitle(user.role === 'reviewer' ? 'مقالههای در انتظار بررسی' : 'مدیریت مقالهها')}
{ setEditingArticle(a); setTab('edit'); }} />
>
);
if (tab === 'users' && user.role === 'admin') return <>{sectionTitle('مدیریت کاربران')}>;
if (tab === 'authors' && (user.role === 'admin' || user.role === 'editor')) return <>{sectionTitle('مدیریت نویسندگان')}>;
if (tab === 'media' && (user.role === 'admin' || user.role === 'editor')) return <>{sectionTitle('کتابخانه رسانه')}>;
if (tab === 'comments' && (user.role === 'admin' || user.role === 'editor')) return <>{sectionTitle('مدیریت نظرها')}>;
if (tab === 'analytics') return <>{sectionTitle('تحلیل محتوا')}>;
if (tab === 'reviews') return <>{sectionTitle('گردش بررسی مقالهها')}>;
if (tab === 'newsletter' && (user.role === 'admin' || user.role === 'editor')) return <>{sectionTitle('خبرنامه')}>;
if (tab === 'series' && (user.role === 'admin' || user.role === 'editor')) return <>{sectionTitle('سری مقالهها')}>;
if (tab === 'categories') return <>{sectionTitle('مدیریت دستهبندیها')}>;
if (tab === 'tags' && user.role === 'admin') return <>{sectionTitle('مدیریت برچسبها')}>;
if (tab === 'seo-audit' && user.role === 'admin') return <>{sectionTitle('ممیزی SEO')}>;
if (tab === 'jobs' && (user.role === 'admin' || user.role === 'editor')) return <>{sectionTitle('آگهیهای شغلی')}>;
if (tab === 'client-errors' && user.role === 'admin') return <>{sectionTitle('خطاهای کلاینت')}>;
if (tab === 'activity' && user.role === 'admin') return <>{sectionTitle('گزارش فعالیت')}>;
return (
خوش آمدید، {user.name}
نقش شما:
{(user.role === 'admin' || user.role === 'editor') &&
}
{user.role === 'admin' && (
آمار دستهبندیها
پربازدیدترین مقالهها
)}
);
};
return (
{ setTab(t); setSidebarOpen(false); }} user={user} open={sidebarOpen} onClose={() => setSidebarOpen(false)} />
پنل مدیریت تکناو
محیط تولید، بازبینی و انتشار مقاله
{renderContent()}
);
}
export { AdminPanel, DashboardStats, LiveAdminFeed, ActivityLog, DashboardCategoryStats, DashboardTopArticles, RoleMetricsPanel, AdminSidebar };