feat: implement product categories with tabbed filtering and admin configuration
This commit is contained in:
@@ -29,25 +29,36 @@ import { Plus, Trash2, X, PlusCircle } from 'lucide-react'
|
||||
import { createProduct } from '@/lib/actions/products'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Category } from '@/lib/types'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
|
||||
const moduleSchema = z.object({
|
||||
name: z.string().min(2, 'Name muss mindestens 2 Zeichen lang sein'),
|
||||
description: z.string().optional(),
|
||||
additional_price: z.coerce.number().min(0, 'Preis darf nicht negativ sein'),
|
||||
price: z.coerce.number().min(0, 'Preis darf nicht negativ sein'),
|
||||
is_required: z.boolean().default(false),
|
||||
requirements: z.string().optional().default(''), // Will be converted to array
|
||||
exclusions: z.string().optional().default(''), // Will be converted to array
|
||||
})
|
||||
|
||||
const productSchema = z.object({
|
||||
name: z.string().min(2, 'Name muss mindestens 2 Zeichen lang sein'),
|
||||
description: z.string().optional(),
|
||||
base_price: z.coerce.number().min(0, 'Preis darf nicht negativ sein'),
|
||||
category_id: z.string().min(1, 'Bitte wählen Sie eine Kategorie'),
|
||||
is_active: z.boolean().default(true),
|
||||
modules: z.array(moduleSchema).default([]),
|
||||
})
|
||||
|
||||
type ProductFormValues = z.infer<typeof productSchema>
|
||||
|
||||
export function CreateProductDialog({ children }: { children: React.ReactNode }) {
|
||||
export function CreateProductDialog({ categories }: { categories: Category[] }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const form = useForm<ProductFormValues>({
|
||||
@@ -56,6 +67,7 @@ export function CreateProductDialog({ children }: { children: React.ReactNode })
|
||||
name: '',
|
||||
description: '',
|
||||
base_price: 0,
|
||||
category_id: '',
|
||||
is_active: true,
|
||||
modules: [],
|
||||
},
|
||||
@@ -69,7 +81,12 @@ export function CreateProductDialog({ children }: { children: React.ReactNode })
|
||||
async function onSubmit(values: ProductFormValues) {
|
||||
try {
|
||||
const { modules, ...productData } = values
|
||||
await createProduct(productData, modules)
|
||||
const formattedModules = modules.map(m => ({
|
||||
...m,
|
||||
requirements: m.requirements ? m.requirements.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)
|
||||
setOpen(false)
|
||||
form.reset()
|
||||
} catch (error) {
|
||||
@@ -79,7 +96,11 @@ export function CreateProductDialog({ children }: { children: React.ReactNode })
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-primary hover:bg-primary/90">
|
||||
<Plus className="mr-2 h-4 w-4" /> Produkt hinzufügen
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-hidden flex flex-col glass-dark border-white/10 text-white shadow-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-bold">Neues Produkt erstellen</DialogTitle>
|
||||
@@ -121,6 +142,29 @@ export function CreateProductDialog({ children }: { children: React.ReactNode })
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="category_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Kategorie</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-white/5 border-white/10">
|
||||
<SelectValue placeholder="Kategorie wählen" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent className="glass-dark border-white/10 text-white">
|
||||
{categories.map(cat => (
|
||||
<SelectItem key={cat.id} value={cat.id}>{cat.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
@@ -148,7 +192,7 @@ export function CreateProductDialog({ children }: { children: React.ReactNode })
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-primary/50 text-primary hover:bg-primary/10"
|
||||
onClick={() => append({ name: '', additional_price: 0, is_required: false, description: '' })}
|
||||
onClick={() => append({ name: '', price: 0, is_required: false, description: '', requirements: '', exclusions: '' })}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" /> Modul hinzufügen
|
||||
</Button>
|
||||
@@ -182,7 +226,7 @@ export function CreateProductDialog({ children }: { children: React.ReactNode })
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`modules.${index}.additional_price`}
|
||||
name={`modules.${index}.price`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Aufpreis (€)</FormLabel>
|
||||
@@ -195,6 +239,35 @@ export function CreateProductDialog({ children }: { children: React.ReactNode })
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`modules.${index}.requirements`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Benötigt (IDs, kommagetrennt)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="UUIDs..." className="bg-white/10 border-white/10 text-xs" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`modules.${index}.exclusions`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Schließt aus (IDs, kommagetrennt)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="UUIDs..." className="bg-white/10 border-white/10 text-xs" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
Reference in New Issue
Block a user