feat: add multi-device basket loop and add-another-device button to wizard
All checks were successful
Staging Build / build (push) Successful in 2m46s

This commit is contained in:
DanielS
2026-07-09 22:48:31 +02:00
parent 8bd47905b0
commit 2b92626bf9

View File

@@ -98,6 +98,8 @@ export function OrderWizard({
const [selectedCompanyId, setSelectedCompanyId] = useState<string | null>(
initialOrder?.company_id ?? null
)
const [basketItems, setBasketItems] = useState<any[]>([])
const [deviceName, setDeviceName] = useState<string>('')
// Partner-Profil (Fallback)
const [customerData] = useState<Partial<Profile>>(initialProfile || {})
@@ -549,40 +551,73 @@ export function OrderWizard({
}
}
const addToBasket = () => {
const currentItem = {
deviceName: deviceName || `Kasse ${basketItems.length + 1}`,
selections: JSON.parse(JSON.stringify(selections)),
moduleQuantities: { ...moduleQuantities },
billingInterval: selectedBillingInterval,
}
setBasketItems(prev => [...prev, currentItem])
setModuleQuantities({})
setDeviceName('')
// Reset selections
const resetSels: Record<string, CategorySelection> = {}
categories.forEach(cat => {
const isVisible = selectedBillingInterval === 'one_time' ? cat.show_in_kauf !== false : cat.show_in_abo !== false
if (!isVisible || cat.allow_multiselect || cat.preselect === false) {
resetSels[cat.id] = { productId: null, productIds: [], moduleIds: [] }
} else {
const first = products.find(p => p.category_id === cat.id && (selectedBillingInterval === 'one_time' ? p.show_in_kauf !== false : p.show_in_abo !== false))
resetSels[cat.id] = { productId: first?.id ?? null, productIds: first ? [first.id] : [], moduleIds: [] }
}
})
setSelections(resetSels)
}
const handleSubmit = async () => {
if (isSubmitting) return // Doppelklick-Guard
if (isSubmitting) return
setIsSubmitting(true)
try {
let order
if (initialOrder) {
order = await updateOrder(initialOrder.id, {
let finalItems = [...basketItems]
if (finalItems.length === 0 && allCategoriesFilled && productValidationErrors.length === 0) {
finalItems.push({
deviceName: deviceName || 'Kasse 1',
selections,
moduleQuantities,
customerProfile: customerData,
endCustomerId: selectedEndCustomerId,
endCustomer: selectedEndCustomer,
billingInterval: selectedBillingInterval,
lastLicenseDate: lastLicenseDate || null,
companyId: selectedCompanyId,
})
} else {
order = await submitOrder({
selections,
moduleQuantities,
products,
categories,
customerProfile: customerData,
endCustomerId: selectedEndCustomerId,
endCustomer: selectedEndCustomer,
billingInterval: selectedBillingInterval,
lastLicenseDate: lastLicenseDate || null,
billingInterval: selectedBillingInterval
})
}
router.push(`/order/success?id=${order.id}`)
if (finalItems.length === 0) {
throw new Error('Bitte konfigurieren Sie mindestens eine Kasse.')
}
const res = await fetch('/api/orders/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: finalItems,
customerProfile: customerData,
endCustomerId: selectedEndCustomerId,
endCustomer: selectedEndCustomer,
})
})
const result = await res.json()
if (result.error) throw new Error(result.error)
const firstOrderId = result.orders?.[0]?.id
if (firstOrderId) {
router.push(`/order/success?id=${firstOrderId}`)
} else {
router.push('/my-orders')
}
} catch (error: any) {
console.error(error)
alert(error?.message || (initialOrder ? 'Fehler beim Aktualisieren der Anfrage.' : 'Fehler bei der Bestellung. Bitte versuchen Sie es erneut.'))
setIsSubmitting(false) // Nur bei Fehler wieder entsperren
alert(error?.message || 'Fehler bei der Bestellung. Bitte versuchen Sie es erneut.')
setIsSubmitting(false)
}
}
@@ -1323,12 +1358,43 @@ export function OrderWizard({
{err}
</p>
))}
{basketItems.length > 0 && (
<div className="pt-4 border-t border-white/10 space-y-2">
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Warenkorb ({basketItems.length}):</p>
{basketItems.map((item, idx) => (
<div key={idx} className="flex justify-between text-xs bg-white/5 p-2 rounded border border-white/10">
<span className="text-white font-medium">{item.deviceName}</span>
<span className="text-slate-400 capitalize">{item.billingInterval === 'one_time' ? 'Kauf' : 'Abo'}</span>
</div>
))}
</div>
)}
</CardContent>
<CardFooter className="flex flex-col gap-2">
<CardFooter className="flex flex-col gap-3">
<div className="w-full space-y-2">
<Label htmlFor="device-name" className="text-xs text-slate-400">Gerätename (optional)</Label>
<Input
id="device-name"
placeholder="z.B. Hauptkasse, Theke"
value={deviceName}
onChange={e => setDeviceName(e.target.value)}
className="bg-white/5 border-white/10 text-white text-xs h-9 rounded-lg"
/>
</div>
<Button
type="button"
variant="outline"
className="w-full border-primary/30 text-white hover:bg-primary/10 gap-2 h-10 text-sm"
onClick={addToBasket}
disabled={isNextStepDisabled}
>
<UserPlus className="w-4 h-4" /> Noch eine Kasse hinzufügen
</Button>
<Separator className="bg-white/10 my-1" />
<Button
className="w-full h-12 text-lg"
onClick={nextStep}
disabled={isNextStepDisabled}
disabled={basketItems.length === 0 && isNextStepDisabled}
>
Weiter <ChevronRight className="ml-2 w-5 h-5" />
</Button>