feat: add sort_order to categories for controlling display order in the order wizard

This commit is contained in:
DanielS
2026-05-01 02:21:39 +02:00
parent 2888e6aeda
commit cb6668d614
5 changed files with 117 additions and 68 deletions

View File

@@ -32,12 +32,14 @@ import {
SelectTrigger, SelectTrigger,
SelectValue SelectValue
} from '@/components/ui/select' } from '@/components/ui/select'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Cloud, Utensils, HardDrive, Package } from 'lucide-react' import { Cloud, Utensils, HardDrive, Package } from 'lucide-react'
const categorySchema = z.object({ const categorySchema = z.object({
name: z.string().min(2, 'Name muss mindestens 2 Zeichen lang sein'), name: z.string().min(2, 'Name muss mindestens 2 Zeichen lang sein'),
description: z.string().optional(), description: z.string().optional(),
icon: z.string().default('Package'), icon: z.string().default('Package'),
sort_order: z.coerce.number().int().min(0).default(0),
}) })
type CategoryFormValues = z.infer<typeof categorySchema> type CategoryFormValues = z.infer<typeof categorySchema>
@@ -46,7 +48,7 @@ export function CategoryDialog({
children, children,
category category
}: { }: {
children: React.ReactNode, children?: React.ReactNode,
category?: Category category?: Category
}) { }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@@ -57,6 +59,7 @@ export function CategoryDialog({
name: category?.name || '', name: category?.name || '',
description: category?.description || '', description: category?.description || '',
icon: category?.icon || 'Package', icon: category?.icon || 'Package',
sort_order: category?.sort_order ?? 0,
}, },
}) })
@@ -83,25 +86,33 @@ export function CategoryDialog({
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{children}</DialogTrigger> <DialogTrigger asChild>
{children || (
<Button className="bg-primary hover:bg-primary/90">
Kategorie hinzufügen
</Button>
)}
</DialogTrigger>
<DialogContent className="glass-dark border-white/10 text-white shadow-2xl"> <DialogContent className="glass-dark border-white/10 text-white shadow-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle>{category ? 'Kategorie bearbeiten' : 'Neue Kategorie erstellen'}</DialogTitle> <DialogTitle>{category ? 'Kategorie bearbeiten' : 'Neue Kategorie erstellen'}</DialogTitle>
<DialogDescription> <DialogDescription className="text-slate-300">
Definieren Sie eine Kategorie für Ihre Produkte. Definieren Sie eine Kategorie für Ihre Produkte.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 flex-1 overflow-hidden flex flex-col min-h-0">
<ScrollArea className="max-h-[60vh] pr-4">
<div className="space-y-4 py-2">
<FormField <FormField
control={form.control} control={form.control}
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Name</FormLabel> <FormLabel className="text-white">Name</FormLabel>
<FormControl> <FormControl>
<Input placeholder="z.B. Software" className="bg-white/5 border-white/10" {...field} /> <Input placeholder="z.B. Software" className="bg-white/5 border-white/10 text-white" {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -112,9 +123,9 @@ export function CategoryDialog({
name="description" name="description"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Beschreibung</FormLabel> <FormLabel className="text-white">Beschreibung</FormLabel>
<FormControl> <FormControl>
<Input placeholder="Kurze Beschreibung" className="bg-white/5 border-white/10" {...field} /> <Input placeholder="Kurze Beschreibung" className="bg-white/5 border-white/10 text-white" {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -125,16 +136,16 @@ export function CategoryDialog({
name="icon" name="icon"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Icon</FormLabel> <FormLabel className="text-white">Icon</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}> <Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl> <FormControl>
<SelectTrigger className="bg-white/5 border-white/10"> <SelectTrigger className="bg-white/5 border-white/10 text-white">
<SelectValue placeholder="Icon wählen" /> <SelectValue placeholder="Icon wählen" />
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent className="glass-dark border-white/10 text-white"> <SelectContent className="glass-dark border-white/10 text-white">
{icons.map(({ name, Icon }) => ( {icons.map(({ name, Icon }) => (
<SelectItem key={name} value={name}> <SelectItem key={name} value={name} className="text-white hover:bg-white/10">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Icon className="w-4 h-4" /> <Icon className="w-4 h-4" />
{name} {name}
@@ -147,7 +158,29 @@ export function CategoryDialog({
</FormItem> </FormItem>
)} )}
/> />
<DialogFooter> <FormField
control={form.control}
name="sort_order"
render={({ field }) => (
<FormItem>
<FormLabel className="text-white">Reihenfolge im Bestellvorgang</FormLabel>
<FormControl>
<Input
type="number"
min={0}
placeholder="0 = zuerst"
className="bg-white/5 border-white/10 text-white"
{...field}
/>
</FormControl>
<FormMessage />
<p className="text-xs text-slate-400">Kleinere Zahl = weiter oben im Bestellformular angezeigt.</p>
</FormItem>
)}
/>
</div>
</ScrollArea>
<DialogFooter className="pt-4">
<Button type="submit" className="w-full bg-primary hover:bg-primary/90"> <Button type="submit" className="w-full bg-primary hover:bg-primary/90">
{category ? 'Aktualisieren' : 'Erstellen'} {category ? 'Aktualisieren' : 'Erstellen'}
</Button> </Button>

View File

@@ -10,7 +10,7 @@ import {
TableRow, TableRow,
} from '@/components/ui/table' } from '@/components/ui/table'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Edit2, Trash2, Cloud, Utensils, HardDrive, Package } from 'lucide-react' import { Edit2, Trash2, Cloud, Utensils, HardDrive, Package, ArrowUpDown } from 'lucide-react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { deleteCategory } from '@/lib/actions/products' import { deleteCategory } from '@/lib/actions/products'
import { useState } from 'react' import { useState } from 'react'
@@ -39,7 +39,7 @@ export function CategoryList({ initialCategories }: { initialCategories: Categor
<Card className="glass-dark border-white/10 shadow-2xl overflow-hidden"> <Card className="glass-dark border-white/10 shadow-2xl overflow-hidden">
<CardHeader> <CardHeader>
<CardTitle>Kategorienübersicht</CardTitle> <CardTitle>Kategorienübersicht</CardTitle>
<CardDescription> <CardDescription className="text-slate-400">
Verwalten Sie die Hauptkategorien für Ihre Produkte. Verwalten Sie die Hauptkategorien für Ihre Produkte.
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
@@ -47,10 +47,15 @@ export function CategoryList({ initialCategories }: { initialCategories: Categor
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="border-white/10 hover:bg-white/5"> <TableRow className="border-white/10 hover:bg-white/5">
<TableHead className="text-muted-foreground">Icon</TableHead> <TableHead className="text-white font-bold w-10">Icon</TableHead>
<TableHead className="text-muted-foreground">Name</TableHead> <TableHead className="text-white font-bold">Name</TableHead>
<TableHead className="text-muted-foreground">Beschreibung</TableHead> <TableHead className="text-white font-bold">Beschreibung</TableHead>
<TableHead className="text-right text-muted-foreground">Aktionen</TableHead> <TableHead className="text-white font-bold w-28">
<span className="flex items-center gap-1">
<ArrowUpDown className="w-3 h-3" /> Reihenfolge
</span>
</TableHead>
<TableHead className="text-right text-white font-bold">Aktionen</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@@ -61,8 +66,13 @@ export function CategoryList({ initialCategories }: { initialCategories: Categor
{getIcon(category.icon)} {getIcon(category.icon)}
</div> </div>
</TableCell> </TableCell>
<TableCell className="font-medium">{category.name}</TableCell> <TableCell className="font-medium text-white">{category.name}</TableCell>
<TableCell className="text-muted-foreground">{category.description}</TableCell> <TableCell className="text-slate-300">{category.description}</TableCell>
<TableCell>
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-primary/20 text-primary text-sm font-bold">
{category.sort_order}
</span>
</TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<CategoryDialog category={category}> <CategoryDialog category={category}>
@@ -84,7 +94,7 @@ export function CategoryList({ initialCategories }: { initialCategories: Categor
))} ))}
{categories.length === 0 && ( {categories.length === 0 && (
<TableRow> <TableRow>
<TableCell colSpan={4} className="text-center py-10 text-muted-foreground"> <TableCell colSpan={5} className="text-center py-10 text-slate-400">
Keine Kategorien gefunden. Keine Kategorien gefunden.
</TableCell> </TableCell>
</TableRow> </TableRow>

View File

@@ -20,7 +20,7 @@ export async function getCategories() {
const { data, error } = await supabase const { data, error } = await supabase
.from('categories') .from('categories')
.select('*') .select('*')
.order('name', { ascending: true }) .order('sort_order', { ascending: true })
if (error) throw error if (error) throw error
return data as Category[] return data as Category[]

View File

@@ -16,6 +16,7 @@ export type Category = {
name: string; name: string;
description?: string | null; description?: string | null;
icon?: string | null; icon?: string | null;
sort_order: number;
created_at: string; created_at: string;
}; };

View File

@@ -0,0 +1,5 @@
-- Add sort_order column to categories for controlling display order in the order wizard
ALTER TABLE public.categories ADD COLUMN IF NOT EXISTS sort_order INTEGER DEFAULT 0 NOT NULL;
-- Index for efficient ordering
CREATE INDEX IF NOT EXISTS idx_categories_sort_order ON public.categories(sort_order);