feat: add sort_order to categories for controlling display order in the order wizard
This commit is contained in:
@@ -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,71 +86,101 @@ 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">
|
||||||
<FormField
|
<ScrollArea className="max-h-[60vh] pr-4">
|
||||||
control={form.control}
|
<div className="space-y-4 py-2">
|
||||||
name="name"
|
<FormField
|
||||||
render={({ field }) => (
|
control={form.control}
|
||||||
<FormItem>
|
name="name"
|
||||||
<FormLabel>Name</FormLabel>
|
render={({ field }) => (
|
||||||
<FormControl>
|
<FormItem>
|
||||||
<Input placeholder="z.B. Software" className="bg-white/5 border-white/10" {...field} />
|
<FormLabel className="text-white">Name</FormLabel>
|
||||||
</FormControl>
|
<FormControl>
|
||||||
<FormMessage />
|
<Input placeholder="z.B. Software" className="bg-white/5 border-white/10 text-white" {...field} />
|
||||||
</FormItem>
|
</FormControl>
|
||||||
)}
|
<FormMessage />
|
||||||
/>
|
</FormItem>
|
||||||
<FormField
|
)}
|
||||||
control={form.control}
|
/>
|
||||||
name="description"
|
<FormField
|
||||||
render={({ field }) => (
|
control={form.control}
|
||||||
<FormItem>
|
name="description"
|
||||||
<FormLabel>Beschreibung</FormLabel>
|
render={({ field }) => (
|
||||||
<FormControl>
|
<FormItem>
|
||||||
<Input placeholder="Kurze Beschreibung" className="bg-white/5 border-white/10" {...field} />
|
<FormLabel className="text-white">Beschreibung</FormLabel>
|
||||||
</FormControl>
|
<FormControl>
|
||||||
<FormMessage />
|
<Input placeholder="Kurze Beschreibung" className="bg-white/5 border-white/10 text-white" {...field} />
|
||||||
</FormItem>
|
</FormControl>
|
||||||
)}
|
<FormMessage />
|
||||||
/>
|
</FormItem>
|
||||||
<FormField
|
)}
|
||||||
control={form.control}
|
/>
|
||||||
name="icon"
|
<FormField
|
||||||
render={({ field }) => (
|
control={form.control}
|
||||||
<FormItem>
|
name="icon"
|
||||||
<FormLabel>Icon</FormLabel>
|
render={({ field }) => (
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<FormItem>
|
||||||
<FormControl>
|
<FormLabel className="text-white">Icon</FormLabel>
|
||||||
<SelectTrigger className="bg-white/5 border-white/10">
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
<SelectValue placeholder="Icon wählen" />
|
<FormControl>
|
||||||
</SelectTrigger>
|
<SelectTrigger className="bg-white/5 border-white/10 text-white">
|
||||||
</FormControl>
|
<SelectValue placeholder="Icon wählen" />
|
||||||
<SelectContent className="glass-dark border-white/10 text-white">
|
</SelectTrigger>
|
||||||
{icons.map(({ name, Icon }) => (
|
</FormControl>
|
||||||
<SelectItem key={name} value={name}>
|
<SelectContent className="glass-dark border-white/10 text-white">
|
||||||
<div className="flex items-center gap-2">
|
{icons.map(({ name, Icon }) => (
|
||||||
<Icon className="w-4 h-4" />
|
<SelectItem key={name} value={name} className="text-white hover:bg-white/10">
|
||||||
{name}
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<Icon className="w-4 h-4" />
|
||||||
</SelectItem>
|
{name}
|
||||||
))}
|
</div>
|
||||||
</SelectContent>
|
</SelectItem>
|
||||||
</Select>
|
))}
|
||||||
<FormMessage />
|
</SelectContent>
|
||||||
</FormItem>
|
</Select>
|
||||||
)}
|
<FormMessage />
|
||||||
/>
|
</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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[]
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
Reference in New Issue
Block a user