diff --git a/prisma/migrations/20251221153124_artwork_6/migration.sql b/prisma/migrations/20251221153124_artwork_6/migration.sql
new file mode 100644
index 0000000..9ef8846
--- /dev/null
+++ b/prisma/migrations/20251221153124_artwork_6/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "ArtTag" ADD COLUMN "showOnAnimalPage" BOOLEAN NOT NULL DEFAULT false;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index e60b4df..59def6a 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -101,8 +101,9 @@ model ArtTag {
updatedAt DateTime @updatedAt
sortIndex Int @default(0)
- name String @unique
- slug String @unique
+ name String @unique
+ slug String @unique
+ showOnAnimalPage Boolean @default(false)
description String?
diff --git a/src/actions/categories/deleteCategory.ts b/src/actions/categories/deleteCategory.ts
new file mode 100644
index 0000000..b5f4c62
--- /dev/null
+++ b/src/actions/categories/deleteCategory.ts
@@ -0,0 +1,37 @@
+"use server";
+
+import { prisma } from "@/lib/prisma";
+import { revalidatePath } from "next/cache";
+
+export async function deleteCategory(catId: string) {
+ const cat = await prisma.artCategory.findUnique({
+ where: { id: catId },
+ select: {
+ id: true,
+ _count: {
+ select: {
+ tags: true,
+ artworks: true
+ },
+ },
+ },
+ });
+
+ if (!cat) {
+ throw new Error("Category not found.");
+ }
+
+ if (cat._count.artworks > 0) {
+ throw new Error("Cannot delete category: it is used by artworks.");
+ }
+
+ if (cat._count.tags > 0) {
+ throw new Error("Cannot delete category: it is used by tags.");
+ }
+
+ await prisma.artCategory.delete({ where: { id: catId } });
+
+ revalidatePath("/categories");
+
+ return { success: true };
+}
\ No newline at end of file
diff --git a/src/actions/tags/createTag.ts b/src/actions/tags/createTag.ts
index b3b2c40..051a47d 100644
--- a/src/actions/tags/createTag.ts
+++ b/src/actions/tags/createTag.ts
@@ -22,6 +22,7 @@ export async function createTag(formData: TagFormInput) {
name: data.name,
slug: tagSlug,
description: data.description,
+ showOnAnimalPage: data.showOnAnimalPage,
parentId
},
});
diff --git a/src/actions/tags/updateTag.ts b/src/actions/tags/updateTag.ts
index f415b18..7d9181e 100644
--- a/src/actions/tags/updateTag.ts
+++ b/src/actions/tags/updateTag.ts
@@ -32,7 +32,8 @@ export async function updateTag(id: string, rawData: TagFormInput) {
data: {
name: data.name,
slug: tagSlug,
- description: data.description,
+ description: data.description,
+ showOnAnimalPage: data.showOnAnimalPage,
parentId,
categories: data.categoryIds
? { set: data.categoryIds.map((cid) => ({ id: cid })) }
diff --git a/src/app/categories/page.tsx b/src/app/categories/page.tsx
index 6aea465..eda2b86 100644
--- a/src/app/categories/page.tsx
+++ b/src/app/categories/page.tsx
@@ -1,10 +1,15 @@
-import ItemList from "@/components/lists/ItemList";
+import CategoryTable from "@/components/categories/CategoryTable";
import { prisma } from "@/lib/prisma";
import { PlusCircleIcon } from "lucide-react";
import Link from "next/link";
export default async function CategoriesPage() {
- const items = await prisma.artCategory.findMany({})
+ const items = await prisma.artCategory.findMany({
+ include: {
+ _count: { select: { artworks: true, tags: true } },
+ },
+ orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
+ });
return (
@@ -14,7 +19,11 @@ export default async function CategoriesPage() {
Add new category
- {items && items.length > 0 ? : There are no categories yet. Consider adding some!
}
+ {items.length > 0 ? (
+
+ ) : (
+ There are no categories yet. Consider adding some!
+ )}
);
}
\ No newline at end of file
diff --git a/src/components/categories/CategoryTable.tsx b/src/components/categories/CategoryTable.tsx
new file mode 100644
index 0000000..6abe228
--- /dev/null
+++ b/src/components/categories/CategoryTable.tsx
@@ -0,0 +1,82 @@
+"use client";
+
+import { deleteCategory } from "@/actions/categories/deleteCategory";
+import { Button } from "@/components/ui/button";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { PencilIcon, Trash2Icon } from "lucide-react";
+import Link from "next/link";
+
+type CatRow = {
+ id: string;
+ name: string;
+ slug: string;
+ _count: { artworks: number, tags: number };
+};
+
+export default function CategoryTable({ categories }: { categories: CatRow[] }) {
+ const handleDelete = (id: string) => {
+ deleteCategory(id);
+ };
+
+ return (
+
+
+
+
+ Name
+ Slug
+ Tags
+ Artworks
+
+
+
+
+
+ {categories.map((c) => (
+
+ {c.name}
+
+
+ {c.slug}
+
+
+
+ {c._count.tags}
+
+
+
+ {c._count.artworks}
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/global/TopNav.tsx b/src/components/global/TopNav.tsx
index cc9a0d1..f3682f4 100644
--- a/src/components/global/TopNav.tsx
+++ b/src/components/global/TopNav.tsx
@@ -23,6 +23,10 @@ const artworkItems = [
title: "Tags",
href: "/tags",
},
+ {
+ title: "Animals",
+ href: "/animals",
+ },
]
// const portfolioItems = [
diff --git a/src/components/tags/EditTagForm.tsx b/src/components/tags/EditTagForm.tsx
index 281e58e..3b8b7f8 100644
--- a/src/components/tags/EditTagForm.tsx
+++ b/src/components/tags/EditTagForm.tsx
@@ -2,7 +2,7 @@
import { updateTag } from "@/actions/tags/updateTag";
import { Button } from "@/components/ui/button";
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
+import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { ArtCategory, ArtTag, ArtTagAlias } from "@/generated/prisma/client";
@@ -13,6 +13,7 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import MultipleSelector from "../ui/multiselect";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
+import { Switch } from "../ui/switch";
import AliasEditor from "./AliasEditor";
export default function EditTagForm({ tag, categories, allTags }: { tag: ArtTag & { categories: ArtCategory[], aliases: ArtTagAlias[] }, categories: ArtCategory[], allTags: ArtTag[] }) {
@@ -24,6 +25,7 @@ export default function EditTagForm({ tag, categories, allTags }: { tag: ArtTag
description: tag.description || "",
categoryIds: tag.categories?.map(cat => cat.id) ?? [],
parentId: (tag as any).parentId ?? null,
+ showOnAnimalPage: tag.showOnAnimalPage ?? false,
aliases: tag.aliases?.map(a => a.alias) ?? []
}
})
@@ -147,6 +149,23 @@ export default function EditTagForm({ tag, categories, allTags }: { tag: ArtTag
)}
/>
+
+
(
+
+
+ Show on animal page
+
+
+
+
+
+
+ )}
+ />
+
diff --git a/src/components/tags/NewTagForm.tsx b/src/components/tags/NewTagForm.tsx
index 12cacd2..e8e8715 100644
--- a/src/components/tags/NewTagForm.tsx
+++ b/src/components/tags/NewTagForm.tsx
@@ -2,7 +2,7 @@
import { createTag } from "@/actions/tags/createTag";
import { Button } from "@/components/ui/button";
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
+import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { ArtCategory, ArtTag } from "@/generated/prisma/client";
@@ -13,6 +13,7 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import MultipleSelector from "../ui/multiselect";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
+import { Switch } from "../ui/switch";
import AliasEditor from "./AliasEditor";
@@ -25,6 +26,7 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
description: "",
categoryIds: [],
parentId: null,
+ showOnAnimalPage: false,
aliases: [],
}
})
@@ -148,6 +150,23 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
)}
/>
+
+
(
+
+
+ Show on animal page
+
+
+
+
+
+
+ )}
+ />
+
diff --git a/src/components/tags/TagTable.tsx b/src/components/tags/TagTable.tsx
index ccaa714..0453a53 100644
--- a/src/components/tags/TagTable.tsx
+++ b/src/components/tags/TagTable.tsx
@@ -18,6 +18,7 @@ type TagRow = {
name: string;
slug: string;
parent: { id: string; name: string } | null;
+ showOnAnimalPage: boolean;
aliases: { alias: string }[];
categories: { id: string; name: string }[];
_count: { artworks: number };
@@ -73,6 +74,7 @@ export default function TagTable({ tags }: { tags: TagRow[] }) {
Aliases
Categories
Parent
+
ShowAnimal
Artworks
@@ -105,6 +107,10 @@ export default function TagTable({ tags }: { tags: TagRow[] }) {
)}
+
+ {t.showOnAnimalPage ? "yes" : "no"}
+
+
{t._count.artworks}
diff --git a/src/schemas/artworks/tagSchema.ts b/src/schemas/artworks/tagSchema.ts
index 28e7f82..b346786 100644
--- a/src/schemas/artworks/tagSchema.ts
+++ b/src/schemas/artworks/tagSchema.ts
@@ -5,6 +5,7 @@ export const tagSchema = z.object({
description: z.string().optional(),
categoryIds: z.array(z.string()).optional(),
parentId: z.string().nullable().optional(),
+ showOnAnimalPage: z.boolean(),
aliases: z
.array(z.string().trim().min(1))