feat: add customer search and scrollable list to orderwizard
All checks were successful
Staging Build / build (push) Successful in 3m37s

This commit is contained in:
DanielS
2026-07-01 09:37:30 +02:00
parent a3498fee66
commit 023c789baf

View File

@@ -11,7 +11,7 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Separator } from '@/components/ui/separator'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import { Check, ChevronRight, ChevronLeft, ShoppingCart, User, ShieldCheck, Cloud, Utensils, HardDrive, Package, AlertCircle, Loader2, UserPlus, Building2, CreditCard, Calendar } from 'lucide-react'
import { Check, ChevronRight, ChevronLeft, ShoppingCart, User, ShieldCheck, Cloud, Utensils, HardDrive, Package, AlertCircle, Loader2, UserPlus, Building2, CreditCard, Calendar, Search } from 'lucide-react'
import { submitOrder } from '@/lib/actions/orders'
import { createEndCustomer } from '@/lib/actions/end-customers'
import { Category } from '@/lib/types'
@@ -100,6 +100,32 @@ export function OrderWizard({
)
const selectedEndCustomer = endCustomers.find(c => c.id === selectedEndCustomerId) ?? null
const [searchTerm, setSearchTerm] = useState('')
const filteredEndCustomers = useMemo(() => {
const activeCustomers = endCustomers.filter(c => !c.is_anonymized)
const term = searchTerm.toLowerCase().trim()
if (!term) return activeCustomers
return activeCustomers.filter(customer => {
const company = customer.company_name?.toLowerCase() || ''
const firstName = customer.first_name?.toLowerCase() || ''
const lastName = customer.last_name?.toLowerCase() || ''
const street = customer.street?.toLowerCase() || ''
const zip = customer.zip?.toLowerCase() || ''
const city = customer.city?.toLowerCase() || ''
return (
company.includes(term) ||
firstName.includes(term) ||
lastName.includes(term) ||
street.includes(term) ||
zip.includes(term) ||
city.includes(term)
)
})
}, [endCustomers, searchTerm])
// Modus: 'select' = Bestandskunde wählen | 'create' = Neuen anlegen
const [customerMode, setCustomerMode] = useState<'select' | 'create'>('select')
const [isCreatingCustomer, setIsCreatingCustomer] = useState(false)
@@ -398,7 +424,7 @@ export function OrderWizard({
{/* Modus A: Bestandskunde wählen */}
{customerMode === 'select' && (
<div className="space-y-4">
{endCustomers.length === 0 ? (
{endCustomers.filter(c => !c.is_anonymized).length === 0 ? (
<div className="p-6 rounded-xl bg-white/5 border border-white/10 text-center space-y-3">
<Building2 className="w-10 h-10 text-slate-600 mx-auto" />
<p className="text-slate-400">Noch keine Endkunden angelegt.</p>
@@ -407,37 +433,54 @@ export function OrderWizard({
</Button>
</div>
) : (
<div className="space-y-2">
{endCustomers.filter(c => !c.is_anonymized).map(customer => (
<label
key={customer.id}
className={`flex items-start p-4 rounded-xl border-2 cursor-pointer transition-all ${
selectedEndCustomerId === customer.id
? 'border-primary bg-primary/5'
: 'border-white/5 bg-white/5 hover:bg-white/10'
}`}
>
<input
type="radio"
name="end_customer"
value={customer.id}
checked={selectedEndCustomerId === customer.id}
onChange={() => setSelectedEndCustomerId(customer.id)}
className="sr-only"
/>
<div className="flex-1">
<p className="font-semibold text-white">{customer.company_name}</p>
<p className="text-sm text-slate-400">
{[customer.first_name, customer.last_name].filter(Boolean).join(' ')}
{customer.first_name || customer.last_name ? ' · ' : ''}
{[customer.street, customer.zip, customer.city].filter(Boolean).join(', ')}
</p>
</div>
{selectedEndCustomerId === customer.id && (
<Check className="w-5 h-5 text-primary mt-0.5 shrink-0" />
)}
</label>
))}
<div className="space-y-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<Input
placeholder="Kunden suchen nach Name, Ort, PLZ..."
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
className="pl-9 bg-white/5 border-white/10 text-white placeholder:text-slate-500"
/>
</div>
{filteredEndCustomers.length === 0 ? (
<div className="p-6 rounded-xl bg-white/5 border border-white/10 text-center">
<p className="text-slate-400">Keine passenden Kunden gefunden.</p>
</div>
) : (
<div className="space-y-2 max-h-72 overflow-y-auto pr-1">
{filteredEndCustomers.map(customer => (
<label
key={customer.id}
className={`flex items-start p-4 rounded-xl border-2 cursor-pointer transition-all ${
selectedEndCustomerId === customer.id
? 'border-primary bg-primary/5'
: 'border-white/5 bg-white/5 hover:bg-white/10'
}`}
>
<input
type="radio"
name="end_customer"
value={customer.id}
checked={selectedEndCustomerId === customer.id}
onChange={() => setSelectedEndCustomerId(customer.id)}
className="sr-only"
/>
<div className="flex-1">
<p className="font-semibold text-white">{customer.company_name}</p>
<p className="text-sm text-slate-400">
{[customer.first_name, customer.last_name].filter(Boolean).join(' ')}
{customer.first_name || customer.last_name ? ' · ' : ''}
{[customer.street, customer.zip, customer.city].filter(Boolean).join(', ')}
</p>
</div>
{selectedEndCustomerId === customer.id && (
<Check className="w-5 h-5 text-primary mt-0.5 shrink-0" />
)}
</label>
))}
</div>
)}
</div>
)}
</div>