import { useState, useEffect, useCallback } from 'react'; import { Search, Plus, Edit2, Trash2, FileText, Image as ImageIcon, CheckCircle2, XCircle } from 'lucide-react'; import { toast } from 'react-hot-toast'; import api, { BASE_DOMAIN } from '../services/api'; import Spinner from '../components/ui/Spinner'; import Pagination from '../components/ui/Pagination'; import MediaSelector from '../components/ui/MediaSelector'; import ConfirmModal from '../components/ui/ConfirmModal'; export interface BlogPost { author?: { firstName?: string; lastName?: string; }; id: string; title: string; slug: string; content: string; isPublished: boolean; metaTitle?: string; metaDescription?: string; keywords?: string; imageUrl?: string; createdAt?: string; } export default function Blogs() { const [blogs, setBlogs] = useState([]); const [isLoading, setIsLoading] = useState(true); const [search, setSearch] = useState(''); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const limit = 10; const [isModalOpen, setIsModalOpen] = useState(false); const [isMediaSelectorOpen, setIsMediaSelectorOpen] = useState(false); const [editingBlog, setEditingBlog] = useState(null); const [deleteTargetId, setDeleteTargetId] = useState(null); const [formData, setFormData] = useState({ title: '', slug: '', content: '', isPublished: true, metaTitle: '', metaDescription: '', keywords: '', imageUrl: '' }); const fetchBlogs = useCallback(async () => { try { setIsLoading(true); const res = await api.get('/admin/blogs', { params: { page, limit, search } }); if (res.data?.data) { setBlogs(res.data.data); setTotalPages(res.data.meta?.lastPage || 1); } } catch (err) { console.error(err); } finally { setIsLoading(false); } }, [page, search]); useEffect(() => { const timer = setTimeout(() => { fetchBlogs(); }, 500); return () => clearTimeout(timer); }, [fetchBlogs]); const handleSave = async (e: React.FormEvent) => { e.preventDefault(); try { if (editingBlog) { await api.put(`/admin/blogs/${editingBlog.id}`, formData); toast.success('مقاله با موفقیت ویرایش شد'); } else { await api.post('/admin/blogs', formData); toast.success('مقاله جدید با موفقیت ذخیره شد'); } setIsModalOpen(false); fetchBlogs(); } catch (error) { console.error('Save failed', error); toast.error('خطا در ذخیره مقاله'); } }; const confirmDelete = async () => { if (!deleteTargetId) return; try { await api.delete(`/admin/blogs/${deleteTargetId}`); toast.success('مقاله با موفقیت حذف شد'); fetchBlogs(); } catch (error) { console.error('Delete failed', error); toast.error('خطا در حذف مقاله'); } finally { setDeleteTargetId(null); } }; const openModal = (blog: BlogPost | null = null) => { if (blog) { setEditingBlog(blog); setFormData({ title: blog.title, slug: blog.slug, content: blog.content, isPublished: blog.isPublished, metaTitle: blog.metaTitle || '', metaDescription: blog.metaDescription || '', keywords: blog.keywords || '', imageUrl: blog.imageUrl || '' }); } else { setEditingBlog(null); setFormData({ title: '', slug: '', content: '', isPublished: true, metaTitle: '', metaDescription: '', keywords: '', imageUrl: '' }); } setIsModalOpen(true); }; return (

وبلاگ و مقالات

مدیریت محتوای آموزشی و مقالات علمی سایت

{ setSearch(e.target.value); setPage(1); }} className="w-full pl-4 pr-12 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none transition-all" />
{isLoading ? ( ) : blogs.length === 0 ? ( ) : ( blogs.map((blog) => ( )) )}
تصویر عنوان مقاله نویسنده وضعیت انتشار عملیات
هیچ مقاله‌ای یافت نشد
{blog.imageUrl ? ( blog ) : (
)}
{blog.title} {(blog.author?.firstName && blog.author?.lastName) ? `${blog.author.firstName} ${blog.author.lastName}` : 'نامشخص'} {blog.isPublished ? ( منتشر شده ) : ( پیش‌نویس )}
{!isLoading && totalPages > 1 && (
)}
{isModalOpen && (

{editingBlog ? 'ویرایش مقاله' : 'افزودن مقاله جدید'}

setFormData({ ...formData, title: e.target.value })} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none" />
setFormData({ ...formData, slug: e.target.value })} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-sm" />
{formData.imageUrl ? ( preview ) : (
)}

تنظیمات سئو (SEO)

setFormData({ ...formData, metaTitle: e.target.value })} className="w-full px-3 py-2 rounded-lg border border-gray-200 focus:border-purple-500 outline-none text-sm" />
setFormData({ ...formData, keywords: e.target.value })} className="w-full px-3 py-2 rounded-lg border border-gray-200 focus:border-purple-500 outline-none text-sm" />
)} setIsMediaSelectorOpen(false)} onSelect={(url) => setFormData({ ...formData, imageUrl: url })} /> setDeleteTargetId(null)} />
); }