From 826cf33183f05c56f4100dd0c1a029d3720c4beb Mon Sep 17 00:00:00 2001 From: DanielS Date: Fri, 1 May 2026 00:57:33 +0200 Subject: [PATCH] feat: add full CRUD management for product categories in admin panel --- shop/app/admin/categories/page.tsx | 46 +++++++ shop/components/admin/category-dialog.tsx | 160 ++++++++++++++++++++++ shop/components/admin/category-list.tsx | 97 +++++++++++++ shop/lib/actions/products.ts | 38 +++++ 4 files changed, 341 insertions(+) create mode 100644 shop/app/admin/categories/page.tsx create mode 100644 shop/components/admin/category-dialog.tsx create mode 100644 shop/components/admin/category-list.tsx diff --git a/shop/app/admin/categories/page.tsx b/shop/app/admin/categories/page.tsx new file mode 100644 index 0000000..1814127 --- /dev/null +++ b/shop/app/admin/categories/page.tsx @@ -0,0 +1,46 @@ +import { getCategories } from '@/lib/actions/products' +import { CategoryList } from '@/components/admin/category-list' +import { CategoryDialog } from '@/components/admin/category-dialog' +import { LayoutGrid, Plus } from 'lucide-react' +import { Suspense } from 'react' + +export default async function AdminCategoriesPage() { + return ( +
+
+
+

+ + Kategorien +

+

+ Verwalten Sie die Struktur Ihres Webshops. +

+
+
+ }> + + +
+
+ }> + + +
+ ) +} + +async function CategoryDataWrapper() { + const categories = await getCategories() + return +} + +async function CreateCategoryAction() { + return ( + + + + ) +} diff --git a/shop/components/admin/category-dialog.tsx b/shop/components/admin/category-dialog.tsx new file mode 100644 index 0000000..786c317 --- /dev/null +++ b/shop/components/admin/category-dialog.tsx @@ -0,0 +1,160 @@ +'use client' + +import { useState } from 'react' +import { useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import * as z from 'zod' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog' +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { createCategory, updateCategory } from '@/lib/actions/products' +import { Category } from '@/lib/types' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' +import { Cloud, Utensils, HardDrive, Package } from 'lucide-react' + +const categorySchema = z.object({ + name: z.string().min(2, 'Name muss mindestens 2 Zeichen lang sein'), + description: z.string().optional(), + icon: z.string().default('Package'), +}) + +type CategoryFormValues = z.infer + +export function CategoryDialog({ + children, + category +}: { + children: React.ReactNode, + category?: Category +}) { + const [open, setOpen] = useState(false) + + const form = useForm({ + resolver: zodResolver(categorySchema) as any, + defaultValues: { + name: category?.name || '', + description: category?.description || '', + icon: category?.icon || 'Package', + }, + }) + + async function onSubmit(values: CategoryFormValues) { + try { + if (category) { + await updateCategory(category.id, values) + } else { + await createCategory(values) + } + setOpen(false) + form.reset() + } catch (error) { + console.error('Error saving category:', error) + } + } + + const icons = [ + { name: 'Cloud', Icon: Cloud }, + { name: 'Utensils', Icon: Utensils }, + { name: 'HardDrive', Icon: HardDrive }, + { name: 'Package', Icon: Package }, + ] + + return ( + + {children} + + + {category ? 'Kategorie bearbeiten' : 'Neue Kategorie erstellen'} + + Definieren Sie eine Kategorie für Ihre Produkte. + + + +
+ + ( + + Name + + + + + + )} + /> + ( + + Beschreibung + + + + + + )} + /> + ( + + Icon + + + + )} + /> + + + + + +
+
+ ) +} diff --git a/shop/components/admin/category-list.tsx b/shop/components/admin/category-list.tsx new file mode 100644 index 0000000..6893cf5 --- /dev/null +++ b/shop/components/admin/category-list.tsx @@ -0,0 +1,97 @@ +'use client' + +import { Category } from '@/lib/types' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Edit2, Trash2, Cloud, Utensils, HardDrive, Package } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { deleteCategory } from '@/lib/actions/products' +import { useState } from 'react' +import { CategoryDialog } from './category-dialog' + +export function CategoryList({ initialCategories }: { initialCategories: Category[] }) { + const [categories, setCategories] = useState(initialCategories) + + const handleDelete = async (id: string) => { + if (confirm('Möchten Sie diese Kategorie wirklich löschen? Alle zugehörigen Produkte werden nicht mehr dieser Kategorie zugeordnet sein.')) { + await deleteCategory(id) + setCategories(categories.filter(c => c.id !== id)) + } + } + + const getIcon = (name?: string | null) => { + switch (name) { + case 'Cloud': return + case 'Utensils': return + case 'HardDrive': return + default: return + } + } + + return ( + + + Kategorienübersicht + + Verwalten Sie die Hauptkategorien für Ihre Produkte. + + + + + + + Icon + Name + Beschreibung + Aktionen + + + + {categories.map((category) => ( + + +
+ {getIcon(category.icon)} +
+
+ {category.name} + {category.description} + +
+ + + + +
+
+
+ ))} + {categories.length === 0 && ( + + + Keine Kategorien gefunden. + + + )} +
+
+
+
+ ) +} diff --git a/shop/lib/actions/products.ts b/shop/lib/actions/products.ts index e2d6960..7fabf95 100644 --- a/shop/lib/actions/products.ts +++ b/shop/lib/actions/products.ts @@ -23,9 +23,47 @@ export async function getCategories() { .order('name', { ascending: true }) if (error) throw error + return data as Category[] +} + +export async function createCategory(category: Omit) { + const supabase = await createClient() + const { data, error } = await supabase + .from('categories') + .insert([category]) + .select() + .single() + + if (error) throw error + revalidatePath('/admin/categories') + revalidatePath('/order') return data } +export async function updateCategory(id: string, category: Partial) { + const supabase = await createClient() + const { error } = await supabase + .from('categories') + .update(category) + .eq('id', id) + + if (error) throw error + revalidatePath('/admin/categories') + revalidatePath('/order') +} + +export async function deleteCategory(id: string) { + const supabase = await createClient() + const { error } = await supabase + .from('categories') + .delete() + .eq('id', id) + + if (error) throw error + revalidatePath('/admin/categories') + revalidatePath('/order') +} + export async function createProduct(product: Omit, modules: Omit[]) { const supabase = await createClient()