diff --git a/app/src/admin/WriterWorkspace.jsx b/app/src/admin/WriterWorkspace.jsx
new file mode 100644
index 0000000..d1e20b5
--- /dev/null
+++ b/app/src/admin/WriterWorkspace.jsx
@@ -0,0 +1,657 @@
+import { useState, useEffect, useRef, useCallback, Fragment } from 'react';
+import { useAuth, useNav, useToast, StatusBadge, EmptyState, inputStyle, pillBtn } from '../../teknav-ui.jsx';
+import { adminApi } from '../lib/admin-api.js';
+import { engagementApi } from '../lib/engagement-api.js';
+import triangleAlertIcon from '../assets/icons/triangle-alert.svg';
+import { PanelState, actionBtn } from './Shared.jsx';
+import { ArticleEditor } from './ArticleEditor.jsx';
+import { ArticlesManagement } from './ArticleList.jsx';
+
+const WW = {
+ terra: '#C46A4D', terraLight: '#FAF0EB', terraBorder: 'rgba(196,106,77,0.22)',
+ gold: '#C8951C', goldLight: '#FBF6E8',
+ ink: '#1A2E30', muted: '#7B8C8E', faint: '#9AA5A6',
+ cream: '#FAF7F0', border: '#E4DDD2',
+ success: '#3A7D5E', successLight: '#EFF8F4',
+ purple: '#7B5EA7', purpleLight: '#F5F0FB',
+};
+const wwPrimaryBtn = {
+ padding: '10px 22px', borderRadius: 10, border: 'none',
+ background: WW.terra, color: '#fff', fontSize: 13, fontWeight: 700,
+ cursor: 'pointer', fontFamily: 'Vazirmatn,sans-serif',
+ boxShadow: '0 3px 12px rgba(196,106,77,0.25)',
+ display: 'inline-flex', alignItems: 'center', gap: 6,
+};
+const wwGhostBtn = {
+ padding: '9px 16px', borderRadius: 10, border: `1px solid ${WW.border}`,
+ background: '#fff', color: WW.ink, fontSize: 13, fontWeight: 600,
+ cursor: 'pointer', fontFamily: 'Vazirmatn,sans-serif',
+};
+const wwDangerBtn = {
+ padding: '9px 16px', borderRadius: 10, border: 'none',
+ background: 'rgba(201,76,76,0.08)', color: '#C94C4C',
+ fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'Vazirmatn,sans-serif',
+};
+const wwLinkBtn = {
+ background: WW.terraLight, border: 'none', cursor: 'pointer',
+ color: WW.terra, fontSize: 11, fontWeight: 700,
+ fontFamily: 'Vazirmatn,sans-serif', padding: '3px 10px', borderRadius: 6,
+};
+
+const WRITING_TIPS = [
+ { icon: '✦', tip: 'با یک سوال باز شروع کنید. بهترین مقالهها خواننده را وارد گفتگو میکنند.' },
+ { icon: '◈', tip: 'عنوان را آخر بنویسید. وقتی محتوا کامل است، عنوان دقیقتر انتخاب میشود.' },
+ { icon: '◉', tip: 'یک مفهوم پیچیده را با یک مثال واقعی توضیح دهید تا خواننده آن را به خاطر بسپارد.' },
+ { icon: '◇', tip: 'پاراگراف اول تعیینکننده است. اگر خواننده را نگه ندارد، ادامه را نمیخواند.' },
+ { icon: '◎', tip: 'از اعداد استفاده کنید. "۳ روش" قابل اعتمادتر از "چند روش" به نظر میرسد.' },
+ { icon: '✦', tip: 'مقاله را با صدای بلند بخوانید. اگر گیر کردید، جمله نیاز به بازنویسی دارد.' },
+];
+
+const WEEK_DAYS = ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج'];
+
+function WritingTipCard() {
+ const [idx, setIdx] = useState(() => Math.floor(Math.random() * WRITING_TIPS.length));
+ const [fading, setFading] = useState(false);
+ const timerRef = useRef(null);
+
+ const advance = useCallback((step = 1) => {
+ setFading(true);
+ clearInterval(timerRef.current);
+ setTimeout(() => {
+ setIdx(i => (i + step + WRITING_TIPS.length) % WRITING_TIPS.length);
+ setFading(false);
+ timerRef.current = setInterval(autoAdvance, 7000);
+ }, 350);
+ }, []);
+
+ function autoAdvance() { advance(1); }
+
+ useEffect(() => {
+ timerRef.current = setInterval(autoAdvance, 7000);
+ return () => clearInterval(timerRef.current);
+ }, []);
+
+ const { icon, tip } = WRITING_TIPS[idx];
+ return (
+
+
{icon}
+
+
+
+ {'نکته نویسندگی'}
+
+
+
+ {idx + 1}/{WRITING_TIPS.length}
+
+ advance(-1)}
+ style={{ ...wwLinkBtn, padding: '2px 7px', fontSize: 13, lineHeight: 1 }}
+ >‹
+ advance(1)}
+ style={{ ...wwLinkBtn, padding: '2px 7px', fontSize: 13, lineHeight: 1 }}
+ >›
+
+
+
{tip}
+
+
+ );
+}
+
+function WeeklyChart({ data }) {
+ const values = (data && data.length === 7) ? data : [2, 5, 3, 8, 4, 6, 1];
+ const max = Math.max(...values, 1);
+ const today = new Date().getDay();
+ return (
+
+
+ {values.map((v, i) => (
+
+ ))}
+
+
+ {WEEK_DAYS.map((d, i) => (
+
{d}
+ ))}
+
+
+ );
+}
+
+function WriterAnalyticsPanel() {
+ const [articles, setArticles] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(false);
+ const [expandedSlug, setExpandedSlug] = useState(null);
+ const [sparklineData, setSparklineData] = useState({});
+
+ useEffect(() => {
+ let cancelled = false;
+ engagementApi.writerAnalyticsArticles()
+ .then(res => { if (!cancelled) setArticles(res.items ?? []); })
+ .catch(() => { if (!cancelled) setError(true); })
+ .finally(() => { if (!cancelled) setLoading(false); });
+ return () => { cancelled = true; };
+ }, []);
+
+ useEffect(() => {
+ if (articles.length === 0) return;
+ let cancelled = false;
+ articles.forEach(a => {
+ engagementApi.writerAnalyticsArticle(a.slug)
+ .then(res => {
+ if (!cancelled) setSparklineData(prev => ({ ...prev, [a.slug]: res.viewsByDay.map(d => d.views) }));
+ })
+ .catch(() => {});
+ });
+ return () => { cancelled = true; };
+ }, [articles]);
+
+ const toggleSparkline = (slug) => {
+ setExpandedSlug(prev => prev === slug ? null : slug);
+ };
+
+ if (loading) return در حال بارگذاری تحلیلها...
;
+ if (error) return ;
+ if (articles.length === 0) return ;
+
+ return (
+
+ عملکرد نوشتههای من
+
+
+
+
+
+ عنوان مقاله
+ بازدید
+ واکنش
+ نظر
+ روند اخیر
+
+
+
+ {articles.map((a, i) => {
+ const spark = sparklineData[a.slug];
+ const max = spark ? Math.max(...spark, 1) : 1;
+ return (
+
+ toggleSparkline(a.slug)}>
+ {a.title}
+ {Number(a.views || 0).toLocaleString('fa-IR')}
+ {Number(a.reactions || 0).toLocaleString('fa-IR')}
+ {Number(a.comments || 0).toLocaleString('fa-IR')}
+
+ {(() => {
+ const s = sparklineData[a.slug];
+ if (!s) return ... ;
+ if (s.length < 2) return — ;
+ const W = 80, H = 20;
+ const mx = Math.max(...s, 1);
+ const pts = s.map((v, si) => [
+ (si / (s.length - 1)) * W,
+ H - 2 - (v / mx) * (H - 4),
+ ]);
+ const d = pts.map(([x, y], si) => `${si === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`).join(' ');
+ const last = pts[pts.length - 1];
+ return (
+
+
+
+
+ );
+ })()}
+ {expandedSlug === a.slug ? '▴' : '▾'}
+
+
+ {expandedSlug === a.slug && (
+
+
+ {spark ? (
+ spark.length < 2 ? (
+ داده کافی برای رسم نمودار ۳۰ روزه وجود ندارد.
+ ) : (() => {
+ const W = 560, H = 80, pad = 4;
+ const n = spark.length;
+ const pts = spark.map((v, si) => [
+ (si / (n - 1)) * (W - pad * 2) + pad,
+ H - pad - (v / max) * (H - pad * 2 - 4),
+ ]);
+ const line = pts.map(([x, y], si) => `${si === 0 ? 'M' : 'L'} ${x.toFixed(1)},${y.toFixed(1)}`).join(' ');
+ const area = `${line} L ${pts[pts.length-1][0].toFixed(1)},${H} L ${pts[0][0].toFixed(1)},${H} Z`;
+ return (
+
+
+
+
+
+
+
+
+
+
+ {pts[pts.length - 1] && (
+
+ )}
+
+
+ ۳۰ روز پیش
+ امروز
+
+
+ );
+ })()
+ ) : (
+ در حال بارگذاری روند...
+ )}
+
+
+ )}
+
+ );
+ })}
+
+
+
+ جمع کل
+
+ {articles.reduce((s, a) => s + (a.views || 0), 0).toLocaleString('fa-IR')}
+
+
+ {articles.reduce((s, a) => s + (a.reactions || 0), 0).toLocaleString('fa-IR')}
+
+
+ {articles.reduce((s, a) => s + (a.comments || 0), 0).toLocaleString('fa-IR')}
+
+
+
+
+
+
+
+ );
+}
+
+function WriterWorkspace() {
+ const { user, logout } = useAuth();
+ const { navigate } = useNav();
+ const [tab, setTab] = useState('overview');
+ const [editingArticle, setEditingArticle] = useState(null);
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(false);
+
+ const load = useCallback(() => {
+ setError(false);
+ return adminApi.getWriterDashboard()
+ .then(setData)
+ .catch(() => { setData(null); setError(true); })
+ .finally(() => setLoading(false));
+ }, []);
+
+ useEffect(() => { if (!user) navigate('/login'); }, [user, navigate]);
+ useEffect(() => {
+ if (!user) return undefined;
+ let cancelled = false;
+ load();
+ const timer = setInterval(() => { if (!cancelled) load(); }, 20000);
+ return () => { cancelled = true; clearInterval(timer); };
+ }, [load, user]);
+
+ if (!user) return null;
+
+ const stats = data?.stats ?? {};
+ const openEditor = (article = null) => { setEditingArticle(article); setTab('write'); };
+
+ const tabs = [
+ { id: 'overview', label: 'آمار من', icon: '◈' },
+ { id: 'analytics', label: 'تحلیلها', icon: '▤' },
+ { id: 'write', label: editingArticle ? 'ویرایش' : 'نوشتن', icon: '✦' },
+ { id: 'articles', label: 'نوشتهها', icon: '◉' },
+ { id: 'comments', label: 'نظرها', icon: '◎' },
+ ];
+
+ const statCards = [
+ { label: 'بازدید', value: stats.views, color: WW.terra, bg: WW.terraLight, icon: '◉' },
+ { label: 'دنبالکننده', value: stats.followers, color: WW.success, bg: WW.successLight, icon: '◎' },
+ { label: 'نظر', value: stats.comments, color: WW.gold, bg: WW.goldLight, icon: '◇' },
+ { label: 'در انتظار بررسی', value: stats.pending, color: WW.purple, bg: WW.purpleLight, icon: '◈' },
+ { label: 'پیشنویس', value: stats.drafts, color: WW.muted, bg: '#F2F4F4', icon: '◻' },
+ ];
+
+ const renderOverview = () => (
+
+
+ {data && (
+ <>
+ {(stats.needsRevision > 0) && (
+
+
+
+
+ {Number(stats.needsRevision).toLocaleString('fa-IR')} {'مقاله نیاز به بازبینی دارد'}
+
+
+ {'ویراستار بازخوردی ثبت کرده است. برای مشاهده توضیحات به بخش «نوشتهها» بروید.'}
+
+
+
setTab('articles')} style={{
+ ...wwLinkBtn, background: WW.gold, color: '#fff',
+ padding: '6px 14px', fontSize: 12, flexShrink: 0,
+ }}>
+ {'مشاهده →'}
+
+
+ )}
+
+
+
+
+ {statCards.map(({ label, value, color, bg, icon }) => (
+
+
+
{icon}
+
+ {Number(value ?? 0).toLocaleString('fa-IR')}
+
+
{label}
+
+ ))}
+
+
+
+
+
+
+ {'آخرین نوشتهها'}
+
+ setTab('articles')} style={wwLinkBtn}>
+ {'همه →'}
+
+
+ {(data.recentArticles ?? []).length === 0
+ ?
+ : data.recentArticles.map(article => (
+ openEditor(article)} style={{
+ width: '100%', background: 'transparent', border: 0,
+ borderBottom: `1px solid ${WW.border}`, padding: '13px 0',
+ textAlign: 'right', cursor: 'pointer', fontFamily: 'Vazirmatn,sans-serif',
+ }}>
+
+ {article.title}
+
+
+
+ {Number(article.views ?? 0).toLocaleString('fa-IR')} {'بازدید ·'} {article.date}
+
+ {article.status === 'نیازمند اصلاح' && article.reviewNote && (
+
+ {article.reviewNote}
+
+ )}
+
+ ))}
+
+
+
+
+
+ {'نظرهای تازه'}
+
+ setTab('comments')} style={wwLinkBtn}>
+ {'همه →'}
+
+
+ {(data.recentComments ?? []).length === 0
+ ?
+ : data.recentComments.slice(0, 5).map(comment => (
+
+
+
{comment.author?.name}
+
{comment.time}
+
+
{comment.body}
+
{comment.article?.title}
+
+ ))}
+
+
+
+
+
+
+ {'فعالیت هفتگی'}
+
+
d.views ?? 0)} />
+
+ {'بازدید روزانه در این هفته'}
+
+
+
+
+
+ {'هدف ماهانه'}
+
+
+ {'این ماه: '}{Number(stats.published ?? 0).toLocaleString('fa-IR')}
+ {' از ۴ مقاله'}
+
+
+
+ {[1, 2, 3, 4].map(n => (
+
{n <= (stats.published ?? 0) ? '✔' : n.toLocaleString('fa-IR')}
+ ))}
+
+
+ {'هر ماه ۴ مقاله بنویسید'}
+
+
+
+ >
+ )}
+
+ );
+
+ return (
+
+
+
+
+
+
+ {'✦ استودیوی نویسنده · تکناو'}
+
+
+ {(() => {
+ const h = new Date().getHours();
+ if (h < 6) return 'شب بخیر، ';
+ if (h < 12) return 'صبح بخیر، ';
+ if (h < 17) return 'ظهر بخیر، ';
+ if (h < 21) return 'عصر بخیر، ';
+ return 'شب بخیر، ';
+ })()}{user.name}
+
+
+ {(() => {
+ const h = new Date().getHours();
+ if (h < 6) return 'ساعت کار است یا الهام گرفتن؟';
+ if (h < 12) return 'صبح بهترین وقت نوشتن است.';
+ if (h < 17) return 'امروز چه مینویسید؟';
+ return 'شبهای خلاق را به یاد داشته باشید.';
+ })()}
+
+
+
+ openEditor(null)} style={wwPrimaryBtn}>
+ {'✦ مقاله جدید'}
+
+ navigate('/admin')} style={wwGhostBtn}>
+ {'پنل کامل'}
+
+ navigate('/')} style={wwGhostBtn}>
+ {'سایت'}
+
+ { logout(); navigate('/'); }} style={wwDangerBtn}>
+ {'خروج'}
+
+
+
+
+
+ {tabs.map(({ id, label, icon }) => (
+ setTab(id)} style={{
+ padding: '11px 22px', border: 'none', cursor: 'pointer',
+ fontFamily: 'Vazirmatn,sans-serif', fontSize: 13, fontWeight: 700,
+ background: 'transparent', whiteSpace: 'nowrap',
+ color: tab === id ? WW.terra : WW.muted,
+ borderBottom: tab === id ? `2.5px solid ${WW.terra}` : '2.5px solid transparent',
+ transition: 'color 0.18s, border-color 0.18s',
+ }}>
+ {icon}
+ {label}
+
+ ))}
+
+
+
+
+
+ {tab === 'write' ? (
+
{ setEditingArticle(null); setTab('overview'); load(); }} />
+ ) : tab === 'articles' ? (
+
+ ) : tab === 'analytics' ? (
+
+ ) : tab === 'comments' ? (
+
+
+
+ {'نظرها روی نوشتههای من'}
+
+ {(data?.recentComments ?? []).length > 0 && (
+
+ {(data.recentComments.length).toLocaleString('fa-IR')} {'نظر اخیر'}
+
+ )}
+
+
+ {(data?.recentComments ?? []).map(comment => (
+
+
+
+ {comment.author?.name}
+
+ {comment.article?.title?.slice(0, 28)}{comment.article?.title?.length > 28 ? '…' : ''}
+
+
+
{comment.time}
+
+
{comment.body}
+
navigate(`/article/${comment.article?.slug}`)} style={{ ...wwLinkBtn, fontSize: 11 }}>
+ {'رفتن به مقاله و مشاهده همه نظرها ←'}
+
+
+ ))}
+ {(data?.recentComments ?? []).length >= 8 && (
+
+ فقط ۸ نظر اخیر نمایش داده میشود. برای مشاهده همه نظرها روی مقاله مربوطه بروید.
+
+ )}
+
+ ) : renderOverview()}
+
+
+
+
+ );
+}
+
+export { WriterWorkspace };