From e0c97d9158c3907d44f690a945b80e6d4f154640 Mon Sep 17 00:00:00 2001 From: Amirmahdi Nourkazemi Date: Wed, 24 Jun 2026 18:10:49 +0330 Subject: [PATCH] feat:add version --- app/dashboard/versions/page.tsx | 289 ++++++++++++++++++++++++++++++++ lib/nav.ts | 5 + 2 files changed, 294 insertions(+) create mode 100644 app/dashboard/versions/page.tsx diff --git a/app/dashboard/versions/page.tsx b/app/dashboard/versions/page.tsx new file mode 100644 index 0000000..9efc7e3 --- /dev/null +++ b/app/dashboard/versions/page.tsx @@ -0,0 +1,289 @@ +"use client"; + +import { useState } from "react"; +import { apiFetch, ApiError, toFormData } from "@/lib/api"; +import { useList } from "@/lib/useResource"; +import { useToast } from "@/components/toast"; +import { DataTable, type Column } from "@/components/DataTable"; +import { + Button, + ConfirmDialog, + Field, + Input, + Modal, + PageHeader, + Textarea, +} from "@/components/ui"; +import { EditIcon, PlusIcon, TrashIcon } from "@/components/icons"; +import { toFa } from "@/lib/utils"; +import { MediaPreview } from "@/components/MediaPreview"; +import { ImagePicker } from "@/components/ImagePicker"; +import { pickUrl, IMAGE_KEYS } from "@/lib/media"; + +interface AppVersion { + id: number; + title?: string; + description?: string; + version_name?: string; + version_code?: number; + link?: string; + image_id?: number | null; + image?: unknown; +} + +export default function VersionsPage() { + const { data, loading, error, reload } = useList("/versions"); + const toast = useToast(); + + const [editing, setEditing] = useState(null); + const [open, setOpen] = useState(false); + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [versionName, setVersionName] = useState(""); + const [versionCode, setVersionCode] = useState(""); + const [link, setLink] = useState(""); + const [imageId, setImageId] = useState(null); + const [imageFile, setImageFile] = useState(null); + const [saving, setSaving] = useState(false); + + const [deleting, setDeleting] = useState(null); + const [removing, setRemoving] = useState(false); + + function openCreate() { + setEditing(null); + setTitle(""); + setDescription(""); + setVersionName(""); + setVersionCode(""); + setLink(""); + setImageId(null); + setImageFile(null); + setOpen(true); + } + function openEdit(row: AppVersion) { + setEditing(row); + setTitle(row.title ?? ""); + setDescription(row.description ?? ""); + setVersionName(row.version_name ?? ""); + setVersionCode(row.version_code != null ? String(row.version_code) : ""); + setLink(row.link ?? ""); + setImageId(row.image_id ?? null); + setImageFile(null); + setOpen(true); + } + + async function save(e: React.FormEvent) { + e.preventDefault(); + setSaving(true); + try { + // Multipart (the image is a file). Edit posts to /versions/:id, which the + // backend also accepts as a multipart update. + const body = toFormData({ + title, + description, + version_name: versionName, + version_code: versionCode, + link, + image_id: imageId, + image: imageFile, + }); + if (editing) { + await apiFetch(`/versions/${editing.id}`, { method: "POST", body }); + toast.success("نسخه ویرایش شد."); + } else { + await apiFetch("/versions", { method: "POST", body }); + toast.success("نسخه افزوده شد."); + } + setOpen(false); + reload(); + } catch (err) { + toast.error(err instanceof ApiError ? err.message : "خطا در ذخیره‌سازی"); + } finally { + setSaving(false); + } + } + + async function confirmDelete() { + if (!deleting) return; + setRemoving(true); + try { + await apiFetch(`/versions/${deleting.id}`, { method: "DELETE" }); + toast.success("نسخه حذف شد."); + setDeleting(null); + reload(); + } catch (err) { + toast.error(err instanceof ApiError ? err.message : "خطا در حذف"); + } finally { + setRemoving(false); + } + } + + const columns: Column[] = [ + { key: "id", header: "شناسه", render: (r) => toFa(r.id), className: "w-20" }, + { + key: "thumbnail", + header: "تصویر", + className: "w-20", + render: (r) => ( + + ), + }, + { key: "title", header: "عنوان", render: (r) => r.title ?? "—" }, + { + key: "version_name", + header: "نام نسخه", + render: (r) => ( + + {r.version_name ?? "—"} + + ), + className: "w-28", + }, + { + key: "version_code", + header: "کد نسخه", + render: (r) => (r.version_code != null ? toFa(r.version_code) : "—"), + className: "w-24", + }, + { + key: "link", + header: "لینک", + render: (r) => + r.link ? ( + + {r.link} + + ) : ( + "—" + ), + }, + ]; + + return ( +
+ } onClick={openCreate}> + نسخه جدید + + } + /> + + } onClick={openCreate}> + نسخه جدید + + } + actions={(row) => ( + <> + + + + )} + /> + + setOpen(false)} + title={editing ? "ویرایش نسخه" : "نسخه جدید"} + footer={ + <> + + + + } + > +
+ + setTitle(e.target.value)} + placeholder="مثلاً نسخه ۱.۲.۰" + required + autoFocus + /> + + + +