'use client' import { useState, useMemo } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { Product, ProductModule, Profile } from '@/lib/types' import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' 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 } from 'lucide-react' import { submitOrder } from '@/lib/actions/orders' import { Category } from '@/lib/types' import { Badge } from '@/components/ui/badge' // ─── Types ──────────────────────────────────────────────────────────────────── type CategorySelection = { productId: string | null // selected product in this category moduleIds: string[] // selected module IDs for this product } // ─── Billing interval helpers ───────────────────────────────────────────────── function billingLabel(interval?: string) { if (interval === 'one_time') return 'einmalig' return '/ Monat' } function billingBadgeClass(interval?: string) { if (interval === 'one_time') return 'bg-slate-500/20 text-slate-300 border-slate-500/30' return 'bg-blue-500/20 text-blue-400 border-blue-500/30' } // ─── Icon helper ────────────────────────────────────────────────────────────── function CategoryIcon({ icon, className }: { icon?: string | null; className?: string }) { const Icon = icon === 'Cloud' ? Cloud : icon === 'Utensils' ? Utensils : icon === 'HardDrive' ? HardDrive : Package return } // ─── Module toggle logic ─────────────────────────────────────────────────────── function toggleModuleInList( modules: ProductModule[], currentIds: string[], toggleId: string ): string[] { const isSelected = currentIds.includes(toggleId) if (isSelected) { const next = currentIds.filter(id => id !== toggleId) // cascade: remove modules that required this one return next.filter(mId => { const m = modules.find(mod => mod.id === mId) return !m?.requirements || m.requirements.every(reqId => next.includes(reqId) || reqId === toggleId) }).filter(id => id !== toggleId) } else { const module = modules.find(m => m.id === toggleId) if (module?.exclusions?.some(exId => currentIds.includes(exId))) return currentIds return [...currentIds, toggleId] } } function isModuleDisabled(module: ProductModule, selectedModules: string[]): boolean { const requirementsMet = !module.requirements?.length || module.requirements.every(reqId => selectedModules.includes(reqId)) const isExcluded = module.exclusions?.some(exId => selectedModules.includes(exId)) return !requirementsMet || !!isExcluded } // ─── Main Wizard ────────────────────────────────────────────────────────────── export function OrderWizard({ products, categories, initialProfile, }: { products: Product[] categories: Category[] initialProfile: Profile | null }) { const [step, setStep] = useState(1) const [customerData, setCustomerData] = useState>( initialProfile || { company_name: '', vat_id: '', first_name: '', last_name: '', address: '', city: '', zip_code: '', } ) // Per-category selection map: categoryId -> { productId, moduleIds } const [selections, setSelections] = useState>(() => { const init: Record = {} categories.forEach(cat => { const first = products.find(p => p.category_id === cat.id) init[cat.id] = { productId: first?.id ?? null, moduleIds: [] } }) return init }) // Only REQUIRED categories must have a product selected const allCategoriesFilled = useMemo( () => categories.filter(cat => cat.is_required).every(cat => !!selections[cat.id]?.productId), [categories, selections] ) // Total price across all selections const totalPrice = useMemo(() => { let total = 0 for (const cat of categories) { const sel = selections[cat.id] if (!sel?.productId) continue const prod = products.find(p => p.id === sel.productId) if (!prod) continue total += Number(prod.base_price) sel.moduleIds.forEach(mId => { const mod = prod.modules?.find(m => m.id === mId) if (mod) total += Number(mod.price) }) } return total }, [selections, products, categories]) // Helper: update product selection for a category (resets modules) function selectProduct(catId: string, productId: string) { setSelections(prev => ({ ...prev, [catId]: { productId, moduleIds: [] }, })) } // Helper: toggle module for a category's selected product function toggleModule(catId: string, moduleId: string) { setSelections(prev => { const sel = prev[catId] if (!sel?.productId) return prev const prod = products.find(p => p.id === sel.productId) const newModuleIds = toggleModuleInList(prod?.modules ?? [], sel.moduleIds, moduleId) return { ...prev, [catId]: { ...sel, moduleIds: newModuleIds } } }) } const nextStep = () => setStep(s => s + 1) const prevStep = () => setStep(s => s - 1) const handleSubmit = async () => { try { // Build multi-product order payload – submit each category item const items = categories .map(cat => { const sel = selections[cat.id] if (!sel?.productId) return null const prod = products.find(p => p.id === sel.productId)! return { category: cat.name, product_id: sel.productId, product_name: prod.name, base_price: prod.base_price, selected_modules: sel.moduleIds, } }) .filter(Boolean) await submitOrder({ product_id: items[0]!.product_id, // primary (first category) selected_modules: items.flatMap(i => i!.selected_modules), total_amount: totalPrice, customer_details: customerData, // @ts-ignore extended payload items, }) alert('Bestellung erfolgreich aufgegeben!') window.location.href = '/' } catch (error) { console.error(error) alert('Fehler bei der Bestellung.') } } return (
{/* Progress Stepper */}
{[1, 2, 3].map(s => (
= s ? 'bg-primary text-white scale-110 shadow-[0_0_15px_rgba(59,130,246,0.5)]' : 'bg-slate-800 text-slate-400' }`} > {step > s ? : s}
))}
{/* ───── Step 1: Product Selection per Category ───── */} {step === 1 && (
{/* Left: Category sections */}
Software wählen Wählen Sie pro Kategorie mindestens einen Artikel aus. {categories.map((cat, idx) => { const catProducts = products.filter(p => p.category_id === cat.id) const sel = selections[cat.id] const selectedProduct = catProducts.find(p => p.id === sel?.productId) ?? null return (
{idx > 0 && } {/* Category header */}

{cat.name}

{cat.description && (

{cat.description}

)}
{sel?.productId ? ( Ausgewählt ) : cat.is_required ? ( Pflichtfeld ) : ( Optional )}
{catProducts.length === 0 ? (

Keine Produkte in dieser Kategorie vorhanden.

) : ( selectProduct(cat.id, id)} className="grid gap-3" > {catProducts.map(product => (
))}
)} {/* Modules for selected product in this category */} {selectedProduct?.modules && selectedProduct.modules.length > 0 && (

Zusatzmodule:

{selectedProduct.modules.map(module => { const disabled = isModuleDisabled(module, sel?.moduleIds ?? []) const checked = sel?.moduleIds.includes(module.id) ?? false return (
toggleModule(cat.id, module.id)} disabled={disabled} />
{module.description && (

{module.description}

)} {disabled && (

{module.requirements?.some( reqId => !sel?.moduleIds.includes(reqId) ) ? 'Benötigt weitere Module' : 'Nicht kombinierbar mit aktueller Auswahl'}

)}
) })}
)}
) })}
{/* Right: Summary */}
Zusammenfassung {categories.map(cat => { const sel = selections[cat.id] const prod = products.find(p => p.id === sel?.productId) if (!prod) return (
{cat.is_required ? ( ) : ( )} {cat.name}: {cat.is_required ? 'nicht gewählt' : 'nicht gewählt (optional)'}
) return (
{cat.name}
{prod.name} {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(prod.base_price)} {' '}{billingLabel(prod.billing_interval)}
{sel.moduleIds.map(mId => { const mod = prod.modules?.find(m => m.id === mId) return mod ? (
+ {mod.name} {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(mod.price)}
) : null })}
) })}
Gesamt: {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(totalPrice)}
{!allCategoriesFilled && (

Bitte aus jeder Pflichtkategorie einen Artikel wählen.

)}
)} {/* ───── Step 2: Customer Data ───── */} {step === 2 && ( Kundendaten Wir benötigen Ihre Daten für die Rechnungsstellung. {[ { label: 'Firmenname', key: 'company_name', placeholder: 'GmbH / Einzelunternehmen', span: false }, { label: 'USt-IdNr.', key: 'vat_id', placeholder: 'DE123456789', span: false }, { label: 'Vorname', key: 'first_name', placeholder: '', span: false }, { label: 'Nachname', key: 'last_name', placeholder: '', span: false }, { label: 'Straße & Hausnummer', key: 'address', placeholder: '', span: true }, { label: 'Postleitzahl', key: 'zip_code', placeholder: '', span: false }, { label: 'Ort', key: 'city', placeholder: '', span: false }, ].map(({ label, key, placeholder, span }) => (
setCustomerData({ ...customerData, [key]: e.target.value })} placeholder={placeholder} className="bg-white/5 border-white/10 text-white placeholder:text-slate-500" />
))}
)} {/* ───── Step 3: Review & Submit ───── */} {step === 3 && (
Bestellung prüfen Fast fertig! Bitte überprüfen Sie Ihre Auswahl.
{categories.map(cat => { const sel = selections[cat.id] const prod = products.find(p => p.id === sel?.productId) if (!prod) return null const catTotal = Number(prod.base_price) + sel.moduleIds.reduce((acc, mId) => { const mod = prod.modules?.find(m => m.id === mId) return acc + Number(mod?.price ?? 0) }, 0) return (
{prod.name} {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(catTotal)} {' '}{billingLabel(prod.billing_interval)}
{sel.moduleIds.length > 0 && (

Module: {sel.moduleIds .map(id => prod.modules?.find(m => m.id === id)?.name) .filter(Boolean) .join(', ')}

)}
) })}

{customerData.company_name}

{customerData.first_name} {customerData.last_name}

{customerData.address}, {customerData.zip_code} {customerData.city}

Gesamtbetrag: {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(totalPrice)} / Monat

Mit dem Klick auf „Kostenpflichtig bestellen" akzeptieren Sie unsere AGB und die Datenschutzerklärung.

)}
) }