"use client";
import { useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
AlertTriangle,
Bell,
BookOpen,
Calendar,
Eye,
Gamepad2,
Loader2,
Megaphone,
Route,
Settings2,
Trash2,
TrendingUp,
Users,
} from "lucide-react";
import { http, toUserError } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { toast } from "@/components/providers";
import { SafeMarkdown } from "@/components/safe-markdown";
const CATEGORIES = [
{ id: "event", label: "رویداد", icon: Calendar, hint: "جلسه زنده، کارگاه، ددلاین" },
{ id: "progress", label: "پیشرفت", icon: TrendingUp, hint: "درس جدید، فصل بازشده" },
{ id: "gamification", label: "بازیسازی", icon: Gamepad2, hint: "جایزه، استریک، چالش" },
{ id: "mentor", label: "منتور", icon: Users, hint: "پیام مستقیم تیم منتور" },
{ id: "urgent", label: "فوری", icon: AlertTriangle, hint: "نیاز به توجه همین حالا" },
{ id: "curriculum", label: "درس", icon: BookOpen, hint: "محتوای آموزشی" },
{ id: "community", label: "جامعه", icon: Users, hint: "فضای مشترک دوره" },
{ id: "system", label: "سیستم", icon: Settings2, hint: "وضعیت خود پرگار" },
] as const;
type Announcement = {
id: number;
title: string;
body: string;
category: string;
route: string;
pinned: boolean;
};
const ROUTES = [
{ path: "/unwrap", label: "نقشه مسیر" },
{ path: "/notifications", label: "اعلانها" },
{ path: "/leaderboard", label: "لیدربورد" },
{ path: "/cap", label: "داشبورد" },
];
function announcementExcerpt(body: string) {
const plain = body
.replace(/```[\s\S]*?```/g, " ")
.replace(/`([^`]*)`/g, "$1")
.replace(/!\[[^\]]*]\([^)]*\)/g, " ")
.replace(/\[([^\]]*)]\([^)]*\)/g, "$1")
.replace(/^#{1,6}\s+/gm, "")
.replace(/[*_~>]+/g, "")
.replace(/\s+/g, " ")
.trim();
if (plain.length <= 180) return plain;
return `${plain.slice(0, 180).trimEnd()}…`;
}
function PreviewCard({
title,
body,
category,
route,
}: {
title: string;
body: string;
category: string;
route: string;
}) {
const cat = CATEGORIES.find((c) => c.id === category);
return (
{cat?.label ?? category}
{title.trim() || "عنوان اطلاعیه"}
→ {route || "/"}
);
}
export function AnnouncePanel() {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const [category, setCategory] = useState("event");
const [route, setRoute] = useState("");
const [pinned, setPinned] = useState(false);
const [editingId, setEditingId] = useState(null);
const [busy, setBusy] = useState(false);
const qc = useQueryClient();
const listQ = useQuery({
queryKey: ["admin-announcements"],
queryFn: () => http.get<{ announcements: Announcement[] }>("/api/admin/announcements"),
});
const [confirmOpen, setConfirmOpen] = useState(false);
const [lastSent, setLastSent] = useState<{ title: string; recipients: number } | null>(null);
const titleOk = title.trim().length >= 3;
const bodyOk = body.trim().length >= 8;
const valid = titleOk && bodyOk;
const send = async () => {
if (!valid) return;
setBusy(true);
try {
const payload = { title: title.trim(), body: body.trim(), category, route: route.trim(), pinned };
if (editingId) {
await http.put(`/api/admin/announcements/${editingId}`, payload);
toast.success("اطلاعیه بهروز شد");
} else {
const res = await http.post<{ recipients: number }>("/api/admin/announce", payload);
toast.success(`اطلاعیه برای ${res.recipients} نفر ارسال شد`);
setLastSent({ title: title.trim(), recipients: res.recipients });
}
setTitle("");
setBody("");
setPinned(false);
setEditingId(null);
setConfirmOpen(false);
void qc.invalidateQueries({ queryKey: ["admin-announcements"] });
} catch (e) {
toast.error(toUserError(e, "ارسال اطلاعیه ممکن نشد"));
} finally {
setBusy(false);
}
};
return (
<>
نگارش اعلان
به همه هنرجویان فعال میرسد و در مرکز اعلانها دیده میشود.
setTitle(e.target.value)}
placeholder="مثلاً جلسه زنده هفته ۴ · پنجشنبه ۲۰:۰۰"
className={cn("h-11 rounded-2xl", !titleOk && title.length > 0 && "border-destructive")}
autoFocus
/>
{CATEGORIES.map((c) => {
const active = category === c.id;
const Icon = c.icon;
return (
);
})}
{!valid && (
عنوان و پیام را کامل کنید تا ادامه فعال شود.
)}
پیشنمایش زنده
همزمان با تایپ بهروز میشود.
{lastSent && (
آخرین ارسال موفق
{lastSent.title}
{lastSent.recipients} گیرنده
)}
اطلاعیههای ثبتشده
{(listQ.data?.announcements ?? []).length > 0 ? (
{(listQ.data?.announcements ?? []).map((a) => {
const cat = CATEGORIES.find((c) => c.id === a.category);
const excerpt = announcementExcerpt(a.body);
return (
-
{a.title}
{cat?.label ?? a.category}
{a.pinned ? (
سنجاق
) : null}
{excerpt ? (
{excerpt}
) : null}
);
})}
) : !listQ.isPending ? (
هنوز اطلاعیهای ثبت نشده است.
) : null}
>
);
}