feat: complete full CRUD for products with unified edit dialog and robust mapping

This commit is contained in:
DanielS
2026-05-01 01:26:12 +02:00
parent e6d801e9df
commit e6cfc45c67
3 changed files with 43 additions and 22 deletions

View File

@@ -26,7 +26,7 @@ import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
import { Plus, Trash2, X, PlusCircle } from 'lucide-react' import { Plus, Trash2, X, PlusCircle } from 'lucide-react'
import { createProduct } from '@/lib/actions/products' import { createProduct, updateProduct } from '@/lib/actions/products'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
import { Category } from '@/lib/types' import { Category } from '@/lib/types'
@@ -58,18 +58,25 @@ const productSchema = z.object({
type ProductFormValues = z.infer<typeof productSchema> type ProductFormValues = z.infer<typeof productSchema>
export function CreateProductDialog({ categories }: { categories: Category[] }) { export function CreateProductDialog({ children, categories, product }: { children?: React.ReactNode, categories: Category[], product?: Product }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const form = useForm<ProductFormValues>({ const form = useForm<ProductFormValues>({
resolver: zodResolver(productSchema) as any, resolver: zodResolver(productSchema) as any,
defaultValues: { defaultValues: {
name: '', name: product?.name || '',
description: '', description: product?.description || '',
base_price: 0, base_price: product?.base_price || 0,
category_id: '', category_id: product?.category_id || '',
is_active: true, is_active: product?.is_active ?? true,
modules: [], modules: product?.modules?.map(m => ({
name: m.name,
description: m.description || '',
price: m.price,
is_required: m.is_required,
requirements: m.requirements?.join(', ') || '',
exclusions: m.exclusions?.join(', ') || '',
})) || [],
}, },
}) })
@@ -86,24 +93,34 @@ export function CreateProductDialog({ categories }: { categories: Category[] })
requirements: m.requirements ? m.requirements.split(',').map(s => s.trim()).filter(Boolean) : [], requirements: m.requirements ? m.requirements.split(',').map(s => s.trim()).filter(Boolean) : [],
exclusions: m.exclusions ? m.exclusions.split(',').map(s => s.trim()).filter(Boolean) : [], exclusions: m.exclusions ? m.exclusions.split(',').map(s => s.trim()).filter(Boolean) : [],
})) }))
await createProduct(productData as any, formattedModules as any)
if (product) {
await updateProduct(product.id, productData as any, formattedModules as any)
} else {
await createProduct(productData as any, formattedModules as any)
}
setOpen(false) setOpen(false)
form.reset() if (!product) form.reset()
} catch (error) { } catch (error) {
console.error('Error creating product:', error) console.error('Error saving product:', error)
} }
} }
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="bg-primary hover:bg-primary/90"> {children || (
<Plus className="mr-2 h-4 w-4" /> Produkt hinzufügen <Button className="bg-primary hover:bg-primary/90">
</Button> <Plus className="mr-2 h-4 w-4" /> Produkt hinzufügen
</Button>
)}
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-hidden flex flex-col glass-dark border-white/10 text-white shadow-2xl"> <DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-hidden flex flex-col glass-dark border-white/10 text-white shadow-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-2xl font-bold">Neues Produkt erstellen</DialogTitle> <DialogTitle className="text-2xl font-bold">
{product ? 'Produkt bearbeiten' : 'Neues Produkt erstellen'}
</DialogTitle>
<DialogDescription className="text-muted-foreground"> <DialogDescription className="text-muted-foreground">
Definieren Sie das Basisprodukt und fügen Sie optionale oder erforderliche Module hinzu. Definieren Sie das Basisprodukt und fügen Sie optionale oder erforderliche Module hinzu.
</DialogDescription> </DialogDescription>
@@ -111,7 +128,7 @@ export function CreateProductDialog({ categories }: { categories: Category[] })
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 flex-1 overflow-hidden flex flex-col"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 flex-1 overflow-hidden flex flex-col">
<ScrollArea className="flex-1 pr-4"> <ScrollArea className="flex-1 max-h-[60vh] pr-4">
<div className="space-y-6 py-4"> <div className="space-y-6 py-4">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<FormField <FormField

View File

@@ -15,8 +15,10 @@ import { Edit2, Trash2, Layers } from 'lucide-react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { deleteProduct } from '@/lib/actions/products' import { deleteProduct } from '@/lib/actions/products'
import { useState } from 'react' import { useState } from 'react'
import { CreateProductDialog } from './create-product-dialog'
import { Category } from '@/lib/types'
export function ProductList({ initialProducts }: { initialProducts: Product[] }) { export function ProductList({ initialProducts, categories }: { initialProducts: Product[], categories: Category[] }) {
const [products, setProducts] = useState(initialProducts) const [products, setProducts] = useState(initialProducts)
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
@@ -75,9 +77,11 @@ export function ProductList({ initialProducts }: { initialProducts: Product[] })
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-white"> <CreateProductDialog categories={categories} product={product}>
<Edit2 className="h-4 w-4" /> <Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-white">
</Button> <Edit2 className="h-4 w-4" />
</Button>
</CreateProductDialog>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"

View File

@@ -107,8 +107,8 @@ export async function updateProduct(id: string, product: Partial<Product>, modul
if (modules.length > 0) { if (modules.length > 0) {
const modulesWithId = modules.map(m => ({ const modulesWithId = modules.map(m => ({
name: m.name, name: m.name,
description: m.description, description: m.description || null,
price: m.additional_price, // Match schema column name 'price' price: m.price,
is_required: m.is_required, is_required: m.is_required,
requirements: m.requirements || [], requirements: m.requirements || [],
exclusions: m.exclusions || [], exclusions: m.exclusions || [],