feat: split step-3 into independent left/right grid columns with sticky local stepper
All checks were successful
Staging Build / build (push) Successful in 2m51s

This commit is contained in:
DanielS
2026-07-10 16:10:25 +02:00
parent d37a6b4348
commit 0b4755d438
3 changed files with 81 additions and 150 deletions

View File

@@ -15,6 +15,7 @@ import { StepCustomer } from './wizard/step-customer'
import { StepBilling } from './wizard/step-billing' import { StepBilling } from './wizard/step-billing'
import { StepSoftware } from './wizard/step-software' import { StepSoftware } from './wizard/step-software'
import { StepSummary } from './wizard/step-summary' import { StepSummary } from './wizard/step-summary'
import { SummarySidebar } from './wizard/summary-sidebar'
type CategorySelection = { type CategorySelection = {
productId: string | null // selected product in this category productId: string | null // selected product in this category
@@ -664,16 +665,9 @@ export function OrderWizard({
return ( return (
<div className="max-w-5xl mx-auto px-4"> <div className="max-w-5xl mx-auto px-4">
{/* Sticky Header — Stepper für Step 3, normaler Stepper für andere Steps */} {/* Global Stepper for Steps 1, 2, 4 */}
{step === 3 ? ( {step !== 3 && (
<div className="sticky top-0 z-30 w-full bg-slate-950/80 backdrop-blur-md pt-6 pb-4 border-b border-white/5 mb-6"> <div className="pt-12 pb-6">
<div className="text-center mb-4">
<p className="text-slate-400 text-sm">Schritt 3 von 4 Software konfigurieren</p>
</div>
<ProgressStepper step={step} basketItemsCount={basketItems.length} />
</div>
) : (
<div className="pt-12">
<ProgressStepper step={step} basketItemsCount={basketItems.length} /> <ProgressStepper step={step} basketItemsCount={basketItems.length} />
</div> </div>
)} )}
@@ -739,8 +733,17 @@ export function OrderWizard({
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }} exit={{ opacity: 0, x: -20 }}
className="space-y-6"
> >
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 items-start">
{/* LEFT: Sticky header (stepper) + scrollable categories */}
<div className="lg:col-span-2 space-y-0">
{/* Sticky stepper header — only covers left column */}
<div className="sticky top-0 z-20 bg-slate-950/80 backdrop-blur-md pb-4 pt-6 border-b border-white/5 mb-6">
<p className="text-center text-slate-400 text-sm mb-4">Schritt 3 von 4 Software konfigurieren</p>
<ProgressStepper step={step} basketItemsCount={basketItems.length} />
</div>
{/* Scrollable category content */}
<StepSoftware <StepSoftware
visibleCategories={visibleCategories} visibleCategories={visibleCategories}
products={products} products={products}
@@ -754,6 +757,17 @@ export function OrderWizard({
selectedBillingInterval={selectedBillingInterval} selectedBillingInterval={selectedBillingInterval}
billingLabel={billingLabel} billingLabel={billingLabel}
billingBadgeClass={billingBadgeClass} billingBadgeClass={billingBadgeClass}
/>
</div>
{/* RIGHT: Independent full-height sidebar column */}
<div className="lg:col-span-1 sticky top-6">
<SummarySidebar
visibleCategories={visibleCategories}
selections={selections}
products={products}
moduleQuantities={moduleQuantities}
billingLabel={billingLabel}
oneTimeTotal={oneTimeTotal} oneTimeTotal={oneTimeTotal}
monthlyTotal={monthlyTotal} monthlyTotal={monthlyTotal}
oneTimeNet={oneTimeNet} oneTimeNet={oneTimeNet}
@@ -777,6 +791,8 @@ export function OrderWizard({
nextStep={nextStep} nextStep={nextStep}
prevStep={prevStep} prevStep={prevStep}
/> />
</div>
</div>
</motion.div> </motion.div>
)} )}

View File

@@ -11,7 +11,6 @@ import { Badge } from '@/components/ui/badge'
import { ShoppingCart, Check, AlertCircle } from 'lucide-react' import { ShoppingCart, Check, AlertCircle } from 'lucide-react'
import * as Icons from 'lucide-react' import * as Icons from 'lucide-react'
import { Category, Product, CategorySelection } from '@/lib/types' import { Category, Product, CategorySelection } from '@/lib/types'
import { SummarySidebar } from './summary-sidebar'
interface StepSoftwareProps { interface StepSoftwareProps {
visibleCategories: Category[] visibleCategories: Category[]
@@ -26,30 +25,6 @@ interface StepSoftwareProps {
selectedBillingInterval: 'one_time' | 'monthly' selectedBillingInterval: 'one_time' | 'monthly'
billingLabel: (interval: string) => string billingLabel: (interval: string) => string
billingBadgeClass: (interval: string) => string billingBadgeClass: (interval: string) => string
// Sidebar props
oneTimeTotal: number
monthlyTotal: number
oneTimeNet: number
oneTimeTax: number
oneTimeGross: number
monthlyNet: number
monthlyTax: number
monthlyGross: number
updatePriceModifier: { label?: string }
allCategoriesFilled: boolean
productValidationErrors: string[]
basketItems: any[]
editingIdx: number | null
editBasketItem: (idx: number) => void
deleteBasketItem: (idx: number) => void
deviceName: string
setDeviceName: (name: string) => void
addToBasket: () => void
isNextStepDisabled: boolean
hasActiveSelection: boolean
nextStep: () => void
prevStep: () => void
} }
function CategoryIcon({ icon, className }: { icon?: string | null; className?: string }) { function CategoryIcon({ icon, className }: { icon?: string | null; className?: string }) {
@@ -71,37 +46,12 @@ export function StepSoftware({
selectedBillingInterval, selectedBillingInterval,
billingLabel, billingLabel,
billingBadgeClass, billingBadgeClass,
oneTimeTotal,
monthlyTotal,
oneTimeNet,
oneTimeTax,
oneTimeGross,
monthlyNet,
monthlyTax,
monthlyGross,
updatePriceModifier,
allCategoriesFilled,
productValidationErrors,
basketItems,
editingIdx,
editBasketItem,
deleteBasketItem,
deviceName,
setDeviceName,
addToBasket,
isNextStepDisabled,
hasActiveSelection,
nextStep,
prevStep,
}: StepSoftwareProps) { }: StepSoftwareProps) {
return ( return (
<div className="grid md:grid-cols-3 gap-6">
{/* Left: Category sections */}
<div className="md:col-span-2 space-y-8">
<Card className="glass-dark border-white/10"> <Card className="glass-dark border-white/10">
<CardHeader> <CardHeader>
<CardTitle className="text-2xl flex items-center gap-2 text-white"> <CardTitle className="text-2xl flex items-center gap-2 text-white">
<ShoppingCart className="w-6 h-6 text-primary" /> <ShoppingCart className="w-6 h-6 text-blue-400" />
Software wählen Software wählen
</CardTitle> </CardTitle>
<CardDescription className="text-slate-300"> <CardDescription className="text-slate-300">
@@ -341,38 +291,5 @@ export function StepSoftware({
})} })}
</CardContent> </CardContent>
</Card> </Card>
</div>
{/* Right: Summary sidebar */}
<SummarySidebar
visibleCategories={visibleCategories}
selections={selections}
products={products}
moduleQuantities={moduleQuantities}
billingLabel={billingLabel}
oneTimeTotal={oneTimeTotal}
monthlyTotal={monthlyTotal}
oneTimeNet={oneTimeNet}
oneTimeTax={oneTimeTax}
oneTimeGross={oneTimeGross}
monthlyNet={monthlyNet}
monthlyTax={monthlyTax}
monthlyGross={monthlyGross}
updatePriceModifier={updatePriceModifier}
allCategoriesFilled={allCategoriesFilled}
productValidationErrors={productValidationErrors}
basketItems={basketItems}
editingIdx={editingIdx}
editBasketItem={editBasketItem}
deleteBasketItem={deleteBasketItem}
deviceName={deviceName}
setDeviceName={setDeviceName}
addToBasket={addToBasket}
isNextStepDisabled={isNextStepDisabled}
hasActiveSelection={hasActiveSelection}
nextStep={nextStep}
prevStep={prevStep}
/>
</div>
) )
} }

View File

@@ -69,8 +69,7 @@ export function SummarySidebar({
prevStep, prevStep,
}: SummarySidebarProps) { }: SummarySidebarProps) {
return ( return (
<div className="sticky top-32 max-h-[70vh] flex flex-col"> <Card className="glass-dark border-primary/20 backdrop-blur-lg bg-slate-950/75 flex flex-col max-h-[80vh]">
<Card className="glass-dark border-primary/20 backdrop-blur-lg bg-slate-950/75 flex flex-col max-h-[70vh]">
<CardHeader className="shrink-0"> <CardHeader className="shrink-0">
<CardTitle className="text-white">Zusammenfassung</CardTitle> <CardTitle className="text-white">Zusammenfassung</CardTitle>
</CardHeader> </CardHeader>
@@ -294,6 +293,5 @@ export function SummarySidebar({
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>
</div>
) )
} }