diff --git a/app/dashboard/media/categories/page.tsx b/app/dashboard/media/categories/page.tsx index 2ee1396..0cd7b02 100644 --- a/app/dashboard/media/categories/page.tsx +++ b/app/dashboard/media/categories/page.tsx @@ -10,6 +10,7 @@ import { ConfirmDialog, Field, Input, + Select, Textarea, Modal, PageHeader, @@ -19,24 +20,42 @@ import { MediaPreview } from "@/components/MediaPreview"; import { pickUrl } from "@/lib/media"; import { toFa } from "@/lib/utils"; +type CategoryType = "media" | "playlist" | "breathing_template"; + interface Category { id: number; name: string; + type: CategoryType; description?: string | null; icon?: string | null; subcategories_count?: number; } +// General category types, shared across media, playlists and breathing templates. +const TYPE_OPTIONS: { value: CategoryType; label: string }[] = [ + { value: "media", label: "رسانه" }, + { value: "playlist", label: "پلی‌لیست" }, + { value: "breathing_template", label: "قالب تنفس" }, +]; + +const TYPE_LABELS: Record = Object.fromEntries( + TYPE_OPTIONS.map((o) => [o.value, o.label]), +) as Record; + // The icon is a direct image-file field on the category (not an image_id ref). const ICON_KEYS = ["icon", "icon_url"]; export default function MediaCategoriesPage() { - const { data, loading, error, reload } = useList("/categories"); + const [typeFilter, setTypeFilter] = useState("media"); + const { data, loading, error, reload } = useList("/categories", { + type: typeFilter, + }); const toast = useToast(); const [editing, setEditing] = useState(null); const [open, setOpen] = useState(false); const [name, setName] = useState(""); + const [type, setType] = useState("media"); const [description, setDescription] = useState(""); const [icon, setIcon] = useState(null); const [saving, setSaving] = useState(false); @@ -47,6 +66,7 @@ export default function MediaCategoriesPage() { function openCreate() { setEditing(null); setName(""); + setType(typeFilter); setDescription(""); setIcon(null); setOpen(true); @@ -54,6 +74,7 @@ export default function MediaCategoriesPage() { function openEdit(row: Category) { setEditing(row); setName(row.name ?? ""); + setType(row.type ?? "media"); setDescription(row.description ?? ""); setIcon(null); setOpen(true); @@ -68,6 +89,7 @@ export default function MediaCategoriesPage() { const body = toFormData({ _method: "PUT", name, + type, description, icon, }); @@ -76,7 +98,7 @@ export default function MediaCategoriesPage() { } else { await apiFetch("/categories", { method: "POST", - body: toFormData({ name, description, icon }), + body: toFormData({ name, type, description, icon }), }); toast.success("دسته‌بندی افزوده شد."); } @@ -114,6 +136,12 @@ export default function MediaCategoriesPage() { ), }, { key: "name", header: "نام دسته‌بندی" }, + { + key: "type", + header: "نوع", + render: (r) => TYPE_LABELS[r.type] ?? r.type, + className: "w-28", + }, { key: "description", header: "توضیحات", @@ -130,12 +158,26 @@ export default function MediaCategoriesPage() { return (
} onClick={openCreate}> - دسته‌بندی جدید - +
+ + +
} /> @@ -184,6 +226,19 @@ export default function MediaCategoriesPage() { } >
+ + + + ("/media", { search }); - const { data: categories } = useList("/categories"); + const { data: categories } = useList("/categories", { + type: "media", + }); const { data: subCategories } = useList("/sub-categories"); const toast = useToast();