From d1adb07f404c8c25b17a2a3888b71ac5355fa0fb Mon Sep 17 00:00:00 2001 From: Citali Date: Sun, 21 Dec 2025 16:47:19 +0100 Subject: [PATCH] Add animal bool to tags --- .../20251221153124_artwork_6/migration.sql | 2 + prisma/schema.prisma | 5 +- src/actions/categories/deleteCategory.ts | 37 +++++++++ src/actions/tags/createTag.ts | 1 + src/actions/tags/updateTag.ts | 3 +- src/app/categories/page.tsx | 15 +++- src/components/categories/CategoryTable.tsx | 82 +++++++++++++++++++ src/components/global/TopNav.tsx | 4 + src/components/tags/EditTagForm.tsx | 21 ++++- src/components/tags/NewTagForm.tsx | 21 ++++- src/components/tags/TagTable.tsx | 6 ++ src/schemas/artworks/tagSchema.ts | 1 + 12 files changed, 190 insertions(+), 8 deletions(-) create mode 100644 prisma/migrations/20251221153124_artwork_6/migration.sql create mode 100644 src/actions/categories/deleteCategory.ts create mode 100644 src/components/categories/CategoryTable.tsx 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))