diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx index 0b3ed51..76418d9 100644 --- a/admin-spa/src/App.tsx +++ b/admin-spa/src/App.tsx @@ -20,6 +20,7 @@ import ProductTags from '@/routes/Products/Tags'; import ProductAttributes from '@/routes/Products/Attributes'; import CouponsIndex from '@/routes/Coupons'; import CouponNew from '@/routes/Coupons/New'; +import CouponEdit from '@/routes/Coupons/Edit'; import CustomersIndex from '@/routes/Customers'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { LayoutDashboard, ReceiptText, Package, Tag, Users, Settings as SettingsIcon, Maximize2, Minimize2, Loader2 } from 'lucide-react'; @@ -477,6 +478,7 @@ function AppRoutes() { {/* Coupons */} } /> } /> + } /> {/* Customers */} } /> diff --git a/admin-spa/src/components/ui/multi-select.tsx b/admin-spa/src/components/ui/multi-select.tsx new file mode 100644 index 0000000..67b65dd --- /dev/null +++ b/admin-spa/src/components/ui/multi-select.tsx @@ -0,0 +1,147 @@ +import * as React from "react"; +import { X, Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +export interface MultiSelectOption { + label: string; + value: string; +} + +interface MultiSelectProps { + options: MultiSelectOption[]; + selected: string[]; + onChange: (selected: string[]) => void; + placeholder?: string; + emptyMessage?: string; + className?: string; + maxDisplay?: number; +} + +export function MultiSelect({ + options, + selected, + onChange, + placeholder = "Select items...", + emptyMessage = "No items found.", + className, + maxDisplay = 3, +}: MultiSelectProps) { + const [open, setOpen] = React.useState(false); + + const handleUnselect = (value: string) => { + onChange(selected.filter((s) => s !== value)); + }; + + const handleSelect = (value: string) => { + if (selected.includes(value)) { + onChange(selected.filter((s) => s !== value)); + } else { + onChange([...selected, value]); + } + }; + + const selectedOptions = options.filter((option) => + selected.includes(option.value) + ); + + return ( + + + + + ))} + {selectedOptions.length > maxDisplay && ( + + +{selectedOptions.length - maxDisplay} more + + )} + + + + + + + + {emptyMessage} + + {options.map((option) => ( + handleSelect(option.value)} + > + + {option.label} + + ))} + + + + + ); +}