"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { BookOpenText, CheckCircle2, Clock3, Edit, Eye, Loader2, Plus, Send, XCircle } from "lucide-react"; import { Link } from "@/lib/router"; import BlogThumbnail from "@/components/BlogThumbnail"; import { useAuth } from "@/contexts/AuthContext"; import { api } from "@/lib/api"; import type * as Types from "@/lib/types"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useToast } from "@/hooks/use-toast"; import { formatJalali, resolveErrorMessage } from "@/lib/utils"; const statusLabels: Record = { draft: "پیش‌نویس", submitted: "در انتظار بررسی", changes_requested: "نیازمند اصلاح", published: "منتشر شده", archived: "آرشیو شده", }; export default function AdminBlog() { const { user } = useAuth(); const { toast } = useToast(); const [posts, setPosts] = useState([]); const [status, setStatus] = useState("all"); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(true); const [actingId, setActingId] = useState(null); const [changesPost, setChangesPost] = useState(null); const [changesNote, setChangesNote] = useState(""); const canReview = Boolean(user?.is_staff || user?.is_superuser || user?.can_review_blog_posts); const loadPosts = useCallback(async () => { setLoading(true); try { const data = await api.listAdminBlogPosts({ status: status === "all" ? undefined : status, search: search.trim() || undefined, limit: 100, }); setPosts(data); } catch (error) { toast({ title: "دریافت نوشته‌ها ناموفق بود", description: resolveErrorMessage(error, "دوباره تلاش کنید"), variant: "destructive", }); } finally { setLoading(false); } }, [search, status, toast]); useEffect(() => { loadPosts(); }, [loadPosts]); const stats = useMemo(() => { return posts.reduce>((acc, post) => { acc[post.status] = (acc[post.status] ?? 0) + 1; return acc; }, {}); }, [posts]); const submitPost = async (postId: number) => { setActingId(postId); try { await api.submitBlogPost(postId); await loadPosts(); toast({ title: "نوشته برای بررسی ارسال شد", variant: "success" }); } catch (error) { toast({ title: "ارسال ناموفق بود", description: resolveErrorMessage(error, "دوباره تلاش کنید"), variant: "destructive" }); } finally { setActingId(null); } }; const reviewPost = async (postId: number, action: Types.PostReviewSchema["action"], note?: string) => { setActingId(postId); try { await api.reviewBlogPost(postId, { action, note }); await loadPosts(); toast({ title: action === "publish" ? "نوشته منتشر شد" : "درخواست اصلاح ثبت شد", variant: "success" }); } catch (error) { toast({ title: "عملیات ناموفق بود", description: resolveErrorMessage(error, "دوباره تلاش کنید"), variant: "destructive" }); } finally { setActingId(null); } }; const openChangesDialog = (post: Types.PostListSchema) => { setChangesPost(post); setChangesNote(""); }; const closeChangesDialog = () => { if (actingId) return; setChangesPost(null); setChangesNote(""); }; const requestChanges = async () => { if (!changesPost) return; await reviewPost(changesPost.id, "request_changes", changesNote.trim() || undefined); setChangesPost(null); setChangesNote(""); }; return (

مدیریت بلاگ

پیش‌نویس‌ها، صف بررسی، انتشار و اصلاح نوشته‌ها.

کل: {posts.length} بررسی: {stats.submitted ?? 0} منتشر: {stats.published ?? 0} اصلاح: {stats.changes_requested ?? 0}
نوشته‌ها دسترسی نویسنده‌ها به نوشته‌های خودشان محدود می‌شود.
setSearch(event.target.value)} placeholder="جستجو..." className="w-full text-right sm:w-64" />
{loading ? (
) : posts.length ? ( posts.map((post) => (

{post.title}

{statusLabels[post.status] ?? post.status}

{post.updated_at ? formatJalali(post.updated_at, false) : ""}

پیش‌نمایش
ویرایش
{post.status === "draft" || post.status === "changes_requested" ? (
ارسال برای بررسی
) : null} {canReview && post.status === "submitted" ? ( <>
انتشار
درخواست اصلاح
) : null}
)) ) : (
نوشته‌ای پیدا نشد.
)}
(open ? undefined : closeChangesDialog())}> درخواست اصلاح نوشته توضیح کوتاهی بنویسید تا نویسنده بداند چه چیزی باید در نوشته اصلاح شود.

نوشته

{changesPost?.title}

{ event.preventDefault(); void requestChanges(); }} >