"use client"; import * as React from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ImagePlus, Trash2, Upload } from "lucide-react"; import AdminDateTimeField from "@/components/AdminDateTimeField"; import ConfirmAction from "@/components/ConfirmAction"; import Markdown from "@/components/Markdown"; import MarkdownEditor from "@/components/MarkdownEditor"; import ProgressiveImage from "@/components/ProgressiveImage"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Link, Navigate, useNavigate, useParams } from "@/lib/router"; import type { EventCreateSchema, EventDetailSchema, EventGalleryItem } from "@/lib/types"; import { api } from "@/lib/api"; import { getEventCardImageUrl, resolveErrorMessage } from "@/lib/utils"; import { useAuth } from "@/contexts/AuthContext"; import { useToast } from "@/hooks/use-toast"; type Mode = "create" | "edit"; const emptyForm = { title: "", slug: "", status: "draft" as EventCreateSchema["status"], event_type: "on_site" as EventCreateSchema["event_type"], price: "", capacity: "", start_time: null as string | null, end_time: null as string | null, registration_start_date: null as string | null, registration_end_date: null as string | null, location: "", address: "", online_link: "", description: "", registration_success_markdown: "", }; function toForm(event: EventDetailSchema) { return { title: event.title || "", slug: event.slug || "", status: event.status || "draft", event_type: event.event_type || "on_site", price: event.price ? String(Math.floor(Number(event.price) / 10)) : "", capacity: event.capacity != null ? String(event.capacity) : "", start_time: event.start_time || null, end_time: event.end_time || null, registration_start_date: event.registration_start_date || null, registration_end_date: event.registration_end_date || null, location: event.location || "", address: event.address || "", online_link: event.online_link || "", description: event.description || "", registration_success_markdown: event.registration_success_markdown || "", }; } export default function AdminEventForm({ mode }: { mode: Mode }) { const { user, isAuthenticated, loading } = useAuth(); const { id } = useParams<{ id?: string }>(); const eventId = Number(id); const navigate = useNavigate(); const { toast } = useToast(); const queryClient = useQueryClient(); const [form, setForm] = React.useState(emptyForm); const [previewMode, setPreviewMode] = React.useState<"editor" | "preview">("editor"); const [galleryPreview, setGalleryPreview] = React.useState(null); const detailQuery = useQuery({ queryKey: ["admin", "edit-event", eventId], queryFn: () => api.getEventAdminDetail(eventId), enabled: mode === "edit" && Number.isFinite(eventId) && isAuthenticated, }); const galleryQuery = useQuery({ queryKey: ["admin", "event", eventId, "gallery"], queryFn: () => api.listEventGallery(eventId), enabled: mode === "edit" && Number.isFinite(eventId) && isAuthenticated, }); React.useEffect(() => { if (detailQuery.data) setForm(toForm(detailQuery.data)); }, [detailQuery.data]); const makePayload = (): EventCreateSchema => ({ title: form.title, slug: form.slug || null, status: form.status, event_type: form.event_type, price: form.price ? Number(form.price) * 10 : 0, capacity: form.capacity ? Number(form.capacity) : null, start_time: form.start_time || new Date().toISOString(), end_time: form.end_time || form.start_time || new Date().toISOString(), registration_start_date: form.registration_start_date, registration_end_date: form.registration_end_date, location: form.location || null, address: form.address || null, online_link: form.online_link || null, description: form.description || "", registration_success_markdown: form.registration_success_markdown || null, }); const saveMutation = useMutation({ mutationFn: async () => { const payload = makePayload(); return mode === "edit" ? api.updateEvent(eventId, payload) : api.createEvent(payload); }, onSuccess: (event) => { queryClient.invalidateQueries({ queryKey: ["admin", "events"] }); toast({ title: "رویداد ذخیره شد", variant: "success" }); navigate(`/admin/events/${event.id}/edit`); }, onError: (error) => toast({ title: "خطا در ذخیره رویداد", description: resolveErrorMessage(error), variant: "destructive" }), }); const posterMutation = useMutation({ mutationFn: (file: File) => api.uploadEventFeaturedImage(eventId, file), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["admin", "edit-event", eventId] }); toast({ title: "پوستر ذخیره شد", variant: "success" }); }, onError: (error) => toast({ title: "خطا در آپلود پوستر", description: resolveErrorMessage(error), variant: "destructive" }), }); const galleryUploadMutation = useMutation({ mutationFn: (file: File) => api.uploadEventGalleryImage(eventId, file, { title: file.name }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["admin", "event", eventId, "gallery"] }); toast({ title: "تصویر گالری افزوده شد", variant: "success" }); }, onError: (error) => toast({ title: "خطا در آپلود گالری", description: resolveErrorMessage(error), variant: "destructive" }), }); const galleryDeleteMutation = useMutation({ mutationFn: (imageId: number) => api.deleteEventGalleryImage(eventId, imageId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["admin", "event", eventId, "gallery"] }); toast({ title: "تصویر حذف شد", variant: "success" }); }, onError: (error) => toast({ title: "خطا در حذف تصویر", description: resolveErrorMessage(error), variant: "destructive" }), }); if (loading) return
در حال بررسی دسترسی...
; if (!isAuthenticated || !(user?.is_staff || user?.is_superuser)) return ; if (mode === "edit" && !Number.isFinite(eventId)) return
شناسه رویداد معتبر نیست.
; const event = detailQuery.data; const gallery = galleryQuery.data ?? event?.gallery_images ?? []; return (

{mode === "edit" ? "ویرایش رویداد" : "افزودن رویداد"}

فرم کامل رویداد با توضیحات Markdown، زمان‌بندی، پوستر و گالری

{detailQuery.isLoading ?

در حال بارگذاری...

: null} اطلاعات اصلی عنوان، وضعیت، نوع رویداد، ظرفیت و هزینه
setForm((current) => ({ ...current, title: event.target.value }))} />
setForm((current) => ({ ...current, slug: event.target.value }))} />
setForm((current) => ({ ...current, price: event.target.value }))} />
setForm((current) => ({ ...current, capacity: event.target.value }))} />
setForm((current) => ({ ...current, location: event.target.value }))} />
setForm((current) => ({ ...current, address: event.target.value }))} />
setForm((current) => ({ ...current, online_link: event.target.value }))} />
زمان‌بندی تاریخ شمسی و زمان جداگانه نمایش داده می‌شود. setForm((current) => ({ ...current, start_time: value }))} /> setForm((current) => ({ ...current, end_time: value }))} /> setForm((current) => ({ ...current, registration_start_date: value }))} /> setForm((current) => ({ ...current, registration_end_date: value }))} /> پوستر تصویر شاخص رویداد {event ? ( ) : (
)} {mode === "edit" ? ( ) : (

پس از ذخیره اولیه، امکان آپلود پوستر فعال می‌شود.

)}
متن رویداد ویرایشگر Markdown و پیش‌نمایش
setForm((current) => ({ ...current, description: value }))} minHeight="520px" onSave={() => saveMutation.mutate()} />
پیام موفقیت ثبت‌نام متنی که بعد از ثبت‌نام موفق به کاربر نمایش داده می‌شود.