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 (
+
+ )
+}
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()