fix(admin): polish mobile layout and actions

This commit is contained in:
2026-06-12 21:35:17 +03:30
parent 4611c8d63b
commit 1e302d2aa2
5 changed files with 153 additions and 76 deletions

View File

@@ -11,6 +11,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useToast } from "@/hooks/use-toast";
import { formatJalali, resolveErrorMessage } from "@/lib/utils";
@@ -91,37 +92,41 @@ export default function AdminBlog() {
};
return (
<div className="space-y-6" dir="ltr">
<div className="space-y-6" dir="rtl">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<Button asChild>
<Link to="/admin/blog/new/edit">
<Plus className="ml-2 h-4 w-4" />
نوشته جدید
</Link>
</Button>
<div className="text-right">
<h2 className="text-2xl font-bold">مدیریت بلاگ</h2>
<p className="mt-1 text-sm text-muted-foreground">
پیشنویسها، صف بررسی، انتشار و اصلاح نوشتهها.
</p>
</div>
<Button asChild>
<Link to="/admin/blog/new/edit">
<Plus className="ml-2 h-4 w-4" />
نوشته جدید
</Link>
</Button>
</div>
<div className="grid gap-4 md:grid-cols-4">
<Card><CardContent className="flex items-center flex-row-reverse gap-2 p-4"><BookOpenText className="h-5 w-5 text-primary" /><span>کل: {posts.length}</span></CardContent></Card>
<Card><CardContent className="flex items-center flex-row-reverse gap-2 p-4"><Clock3 className="h-5 w-5 text-amber-600" /><span>بررسی: {stats.submitted ?? 0}</span></CardContent></Card>
<Card><CardContent className="flex items-center flex-row-reverse gap-2 p-4"><CheckCircle2 className="h-5 w-5 text-emerald-600" /><span>منتشر: {stats.published ?? 0}</span></CardContent></Card>
<Card><CardContent className="flex items-center flex-row-reverse gap-2 p-4"><XCircle className="h-5 w-5 text-rose-600" /><span>اصلاح: {stats.changes_requested ?? 0}</span></CardContent></Card>
<Card><CardContent className="flex items-center flex-row gap-2 p-4"><BookOpenText className="h-5 w-5 text-primary" /><span>کل: {posts.length}</span></CardContent></Card>
<Card><CardContent className="flex items-center flex-row gap-2 p-4"><Clock3 className="h-5 w-5 text-amber-600" /><span>بررسی: {stats.submitted ?? 0}</span></CardContent></Card>
<Card><CardContent className="flex items-center flex-row gap-2 p-4"><CheckCircle2 className="h-5 w-5 text-emerald-600" /><span>منتشر: {stats.published ?? 0}</span></CardContent></Card>
<Card><CardContent className="flex items-center flex-row gap-2 p-4"><XCircle className="h-5 w-5 text-rose-600" /><span>اصلاح: {stats.changes_requested ?? 0}</span></CardContent></Card>
</div>
<Card>
<CardHeader>
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div className="flex gap-2">
<div className="text-right">
<CardTitle>نوشتهها</CardTitle>
<CardDescription>دسترسی نویسندهها به نوشتههای خودشان محدود میشود.</CardDescription>
</div>
<div className="flex flex-col gap-2 sm:flex-row">
<Button variant="outline" onClick={loadPosts}>جستجو</Button>
<Input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="جستجو..." className="w-64 text-right" />
<Input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="جستجو..." className="w-full text-right sm:w-64" />
<Select value={status} onValueChange={setStatus}>
<SelectTrigger className="w-48"><SelectValue /></SelectTrigger>
<SelectTrigger className="w-full sm:w-48"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="all">همه وضعیتها</SelectItem>
<SelectItem value="draft">پیشنویس</SelectItem>
@@ -132,10 +137,6 @@ export default function AdminBlog() {
</SelectContent>
</Select>
</div>
<div className="text-right">
<CardTitle>نوشتهها</CardTitle>
<CardDescription>دسترسی نویسندهها به نوشتههای خودشان محدود میشود.</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-3">
@@ -143,35 +144,87 @@ export default function AdminBlog() {
<div className="flex justify-center py-10"><Loader2 className="h-5 w-5 animate-spin" /></div>
) : posts.length ? (
posts.map((post) => (
<div key={post.id} className="flex flex-col gap-3 rounded-2xl border p-4 md:flex-row md:items-center md:justify-between">
<div className="flex flex-wrap gap-2">
<Button variant="outline" size="sm" asChild>
<Link to={`/admin/blog/${post.id}/preview`}><Eye className="h-4 w-4" /></Link>
</Button>
<Button variant="secondary" size="sm" asChild>
<Link to={`/admin/blog/${post.id}/edit`}><Edit className="h-4 w-4" /></Link>
</Button>
{post.status === "draft" || post.status === "changes_requested" ? (
<Button size="sm" onClick={() => submitPost(post.id)} disabled={actingId === post.id}>
<Send className="h-4 w-4" />
</Button>
) : null}
{canReview && post.status === "submitted" ? (
<>
<Button size="sm" onClick={() => reviewPost(post.id, "publish")} disabled={actingId === post.id}>انتشار</Button>
<Button size="sm" variant="outline" onClick={() => reviewPost(post.id, "request_changes")} disabled={actingId === post.id}>درخواست اصلاح</Button>
</>
) : null}
</div>
<div key={post.id} className="flex gap-3 rounded-2xl border p-4 flex-row items-center justify-between">
<div className="text-right">
<div className="flex flex-wrap items-center justify-end gap-2">
<Badge variant={post.status === "published" ? "default" : "secondary"}>{statusLabels[post.status] ?? post.status}</Badge>
<div className="flex flex-col-reverse md:flex-row flex-wrap items-start gap-2">
<h3 className="font-semibold">{post.title}</h3>
<Badge variant={post.status === "published" ? "default" : "secondary"}>{statusLabels[post.status] ?? post.status}</Badge>
</div>
<p className="mt-1 text-xs text-muted-foreground">
{post.updated_at ? formatJalali(post.updated_at, false) : ""}
</p>
</div>
<div className="grid grid-cols-2 grid-flow-col grid-rows-2 gap-2 md:flex md:flex-row md:flex-wrap" dir="ltr">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="sm" asChild className="w-full md:w-auto">
<Link
to={`/admin/blog/${post.id}/preview`}
aria-label="پیش‌نمایش"
className="flex justify-center"
>
<Eye className="h-4 w-4" />
</Link>
</Button>
</TooltipTrigger>
<TooltipContent>پیشنمایش</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="secondary" size="sm" asChild className="w-full md:w-auto">
<Link
to={`/admin/blog/${post.id}/edit`}
aria-label="ویرایش"
className="flex justify-center"
>
<Edit className="h-4 w-4" />
</Link>
</Button>
</TooltipTrigger>
<TooltipContent>ویرایش</TooltipContent>
</Tooltip>
{post.status === "draft" || post.status === "changes_requested" ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
size="sm"
onClick={() => submitPost(post.id)}
disabled={actingId === post.id}
aria-label="ارسال برای بررسی"
className="w-full md:w-auto"
>
<Send className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>ارسال برای بررسی</TooltipContent>
</Tooltip>
) : null}
{canReview && post.status === "submitted" ? (
<>
<Button
size="sm"
onClick={() => reviewPost(post.id, "publish")}
disabled={actingId === post.id}
className="w-full md:w-auto"
>
انتشار
</Button>
<Button
size="sm"
variant="outline"
onClick={() => reviewPost(post.id, "request_changes")}
disabled={actingId === post.id}
className="w-full md:w-auto"
>
درخواست اصلاح
</Button>
</>
) : null}
</div>
</div>
))
) : (