refactor(wizard): compress step 1 and step 2 layout
All checks were successful
Staging Build / build (push) Successful in 3m40s

This commit is contained in:
DanielS
2026-09-07 22:55:16 +02:00
parent 694e8dfbc1
commit 2fc4e50ae1
2 changed files with 225 additions and 231 deletions

View File

@@ -14,9 +14,7 @@ import {
ChevronRight, ChevronRight,
RefreshCw, RefreshCw,
ShoppingBag, ShoppingBag,
ShieldCheck,
CheckCircle2, CheckCircle2,
Info,
} from 'lucide-react' } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
@@ -34,197 +32,191 @@ interface StepBillingProps {
export function StepBilling({ export function StepBilling({
selectedBillingInterval, selectedBillingInterval,
handleBillingIntervalChange, handleBillingIntervalChange,
customerMode,
selectedEndCustomerId,
lastLicenseDate, lastLicenseDate,
setLastLicenseDate, setLastLicenseDate,
prevStep, prevStep,
nextStep, nextStep,
}: StepBillingProps) { }: StepBillingProps) {
// Optionale Validierung falls Datum bei Kauf Pflicht wäre, oder prüfen ob Modell gewählt ist
const isOneTime = selectedBillingInterval === 'one_time' const isOneTime = selectedBillingInterval === 'one_time'
const isMonthly = selectedBillingInterval === 'monthly' const isMonthly = selectedBillingInterval === 'monthly'
// Wenn Kauf gewählt ist, muss ein gültiges Datum vorliegen
const isNextDisabled = isOneTime && !lastLicenseDate
return ( return (
<Card className="glass-dark border-white/10 h-[calc(100vh-8.5rem)] flex flex-col justify-between overflow-hidden relative"> <Card className="glass-dark border-white/10 h-[calc(100vh-8.5rem)] flex flex-col justify-between overflow-hidden relative">
{/* ─── 1. FIXED HEADER (shrink-0) ─── */} {/* ─── 1. FIXED HEADER (shrink-0) ─── */}
<CardHeader className="pb-3 pt-4 px-6 shrink-0 border-b border-white/10 bg-slate-950/40 space-y-0"> <CardHeader className="py-2.5 px-4 shrink-0 border-b border-white/10 bg-slate-950/40 space-y-0">
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<div> <div>
<CardTitle className="text-xl font-bold flex items-center gap-2 text-white"> <CardTitle className="text-lg font-bold flex items-center gap-2 text-white">
<CreditCard className="w-5 h-5 text-primary" /> <CreditCard className="w-4 h-4 text-primary" />
Abrechnungsmodell wählen Abrechnungsmodell wählen
</CardTitle> </CardTitle>
<CardDescription className="text-xs text-slate-300 mt-0.5"> <CardDescription className="text-[11px] text-slate-300 mt-0.5">
Wählen Sie zwischen monatlicher Miete (Abonnement) oder einmaligem Softwarekauf. Wählen Sie zwischen monatlicher Miete (Abonnement) oder einmaligem Softwarekauf.
</CardDescription> </CardDescription>
</div> </div>
<Badge variant="outline" className="border-primary/30 text-primary bg-primary/10 text-xs px-3 py-1"> <Badge variant="outline" className="border-primary/30 text-primary bg-primary/10 text-xs px-2.5 py-0.5">
Schritt 2 von 4 Schritt 2 von 4
</Badge> </Badge>
</div> </div>
</CardHeader> </CardHeader>
{/* ─── 2. SCROLLABLE MIDDLE CONTENT (flex-1 min-h-0 overflow-y-auto) ─── */} {/* ─── 2. NO-SCROLL MIDDLE CONTENT (flex-1 flex flex-col justify-center items-center overflow-hidden p-4) ─── */}
<div className="flex-1 min-h-0 overflow-y-auto p-6 flex flex-col justify-center max-w-4xl mx-auto w-full scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent"> <div className="flex-1 min-h-0 flex flex-col justify-center items-center overflow-hidden p-4 w-full">
<div className="space-y-6 my-auto"> <div className="max-w-3xl w-full grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
{/* Abrechnungsmodell Cards */} {/* Option 1: Miete / Abo */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-5"> <div
{/* Option 1: Miete / Abo */} onClick={() => handleBillingIntervalChange('monthly')}
<div className={`relative p-5 rounded-xl border-2 cursor-pointer transition-all duration-300 flex flex-col justify-between select-none ${
onClick={() => handleBillingIntervalChange('monthly')} isMonthly
className={`relative p-6 rounded-2xl border-2 cursor-pointer transition-all duration-300 flex flex-col justify-between min-h-[14rem] select-none ${ ? 'border-primary bg-primary/10 shadow-[0_0_20px_rgba(59,130,246,0.2)] ring-1 ring-primary/50'
isMonthly : 'border-white/5 bg-white/5 hover:border-white/20 hover:bg-white/[0.07]'
? 'border-primary bg-primary/10 shadow-[0_0_20px_rgba(59,130,246,0.2)] ring-1 ring-primary/50' }`}
: 'border-white/5 bg-white/5 hover:border-white/20 hover:bg-white/[0.07]' >
}`} <div>
> <div className="flex items-center justify-between gap-3 mb-3">
<div> <div className={`p-2.5 rounded-lg ${isMonthly ? 'bg-primary/20 text-primary border border-primary/30' : 'bg-white/5 text-slate-400 border border-white/10'}`}>
<div className="flex items-center justify-between gap-3 mb-4"> <RefreshCw className={`w-5 h-5 ${isMonthly ? 'animate-spin-slow' : ''}`} />
<div className={`p-3.5 rounded-xl ${isMonthly ? 'bg-primary/20 text-primary border border-primary/30' : 'bg-white/5 text-slate-400 border border-white/10'}`}>
<RefreshCw className={`w-6 h-6 ${isMonthly ? 'animate-spin-slow' : ''}`} />
</div>
{isMonthly && (
<div className="w-6 h-6 rounded-full bg-primary flex items-center justify-center text-white shadow-md shadow-primary/30">
<Check className="w-3.5 h-3.5" />
</div>
)}
</div> </div>
{isMonthly && (
<div className="space-y-1"> <div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center text-white shadow-md shadow-primary/30">
<div className="flex items-center gap-2"> <Check className="w-3 h-3" />
<h3 className="text-lg font-bold text-white">Miete / Abo</h3>
<Badge variant="outline" className="text-[10px] bg-blue-500/10 border-blue-500/30 text-blue-400">
Empfohlen
</Badge>
</div> </div>
<p className="text-xs font-medium text-slate-300"> )}
Fortlaufende monatliche Abrechnung </div>
</p>
</div>
<p className="text-slate-400 text-xs mt-3 leading-relaxed"> <div className="space-y-0.5">
Maximale Flexibilität: Inklusive sämtlicher Software-Updates, Cloud-Dienste und technischem Support. Jederzeit kündbar oder anpassbar. <div className="flex items-center gap-2">
<h3 className="text-base font-bold text-white">Miete / Abo</h3>
<Badge variant="outline" className="text-[9px] px-1.5 py-0 bg-blue-500/10 border-blue-500/30 text-blue-400">
Empfohlen
</Badge>
</div>
<p className="text-[11px] font-medium text-slate-300">
Fortlaufende monatliche Abrechnung
</p> </p>
</div> </div>
<div className="mt-4 pt-3 border-t border-white/10 flex items-center justify-between text-xs"> <p className="text-slate-400 text-[11px] mt-2 leading-relaxed">
<span className="text-slate-400">Zahlungsintervall:</span> Maximale Flexibilität: Inklusive sämtlicher Software-Updates, Cloud-Dienste und Support. Jederzeit kündbar oder anpassbar.
<span className="text-primary font-semibold">monatlich</span> </p>
</div>
</div> </div>
{/* Option 2: Kauf / Einmalkauf */} <div className="mt-3 pt-2.5 border-t border-white/10 flex items-center justify-between text-[11px]">
<div <span className="text-slate-400">Zahlungsintervall:</span>
onClick={() => handleBillingIntervalChange('one_time')} <span className="text-primary font-semibold">monatlich</span>
className={`relative p-6 rounded-2xl border-2 cursor-pointer transition-all duration-300 flex flex-col justify-between min-h-[14rem] select-none ${
isOneTime
? 'border-primary bg-primary/10 shadow-[0_0_20px_rgba(59,130,246,0.2)] ring-1 ring-primary/50'
: 'border-white/5 bg-white/5 hover:border-white/20 hover:bg-white/[0.07]'
}`}
>
<div>
<div className="flex items-center justify-between gap-3 mb-4">
<div className={`p-3.5 rounded-xl ${isOneTime ? 'bg-primary/20 text-primary border border-primary/30' : 'bg-white/5 text-slate-400 border border-white/10'}`}>
<ShoppingBag className="w-6 h-6" />
</div>
{isOneTime && (
<div className="w-6 h-6 rounded-full bg-primary flex items-center justify-center text-white shadow-md shadow-primary/30">
<Check className="w-3.5 h-3.5" />
</div>
)}
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<h3 className="text-lg font-bold text-white">Kauf / Einmalkauf</h3>
<Badge variant="outline" className="text-[10px] bg-slate-500/10 border-slate-500/30 text-slate-300">
Klassisch
</Badge>
</div>
<p className="text-xs font-medium text-slate-300">
Einmalige Lizenzgebühr
</p>
</div>
<p className="text-slate-400 text-xs mt-3 leading-relaxed">
Dauerhafte Lizenzrechte für die gekaufte Version inklusive 12 Monate kostenfreier Updates. Keine laufenden monatlichen Mietgebühren.
</p>
</div>
<div className="mt-4 pt-3 border-t border-white/10 flex items-center justify-between text-xs">
<span className="text-slate-400">Zahlungsintervall:</span>
<span className="text-slate-200 font-semibold">einmalig</span>
</div>
</div> </div>
</div> </div>
{/* Dynamische Stichtagsabfrage bei Kauf */} {/* Option 2: Kauf / Einmalkauf (mit integrierter Inline-Erweiterung) */}
<AnimatePresence> <div
{isOneTime && ( onClick={() => handleBillingIntervalChange('one_time')}
<motion.div className={`relative p-5 rounded-xl border-2 cursor-pointer transition-all duration-300 flex flex-col justify-between select-none ${
initial={{ opacity: 0, y: 10, height: 0 }} isOneTime
animate={{ opacity: 1, y: 0, height: 'auto' }} ? 'border-primary bg-primary/10 shadow-[0_0_20px_rgba(59,130,246,0.2)] ring-1 ring-primary/50'
exit={{ opacity: 0, y: -10, height: 0 }} : 'border-white/5 bg-white/5 hover:border-white/20 hover:bg-white/[0.07]'
transition={{ duration: 0.25 }} }`}
className="overflow-hidden" >
> <div>
<div className="p-5 rounded-2xl bg-gradient-to-r from-slate-900/90 to-slate-900/60 border border-white/10 space-y-3 shadow-lg"> <div className="flex items-center justify-between gap-3 mb-3">
<div className="flex items-start gap-3"> <div className={`p-2.5 rounded-lg ${isOneTime ? 'bg-primary/20 text-primary border border-primary/30' : 'bg-white/5 text-slate-400 border border-white/10'}`}>
<div className="p-2.5 rounded-xl bg-primary/20 text-primary border border-primary/30 shrink-0 mt-0.5"> <ShoppingBag className="w-5 h-5" />
<Calendar className="w-4 h-4" />
</div>
<div className="space-y-1 flex-1">
<Label htmlFor="last-license-date" className="text-white text-xs font-bold flex items-center gap-2">
Datum des letzten CASPOS-Kaufs (Stichtag)
</Label>
<p className="text-[11px] text-slate-400 leading-normal">
Datum des letzten Kaufs zur automatischen Berechnung der Update-Staffel (z. B. 15% innerhalb 1 Jahr, 30% bis 2 Jahre, 50% bis 3 Jahre).
</p>
</div>
</div>
<div className="pt-2 flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
<div className="relative flex-1 max-w-sm">
<Calendar className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<Input
id="last-license-date"
type="date"
value={lastLicenseDate}
onChange={(e) => setLastLicenseDate(e.target.value)}
className="pl-9 bg-slate-950/80 border-white/10 text-white text-xs h-9 focus:border-primary"
/>
</div>
{lastLicenseDate && (
<Badge variant="outline" className="bg-emerald-500/10 border-emerald-500/30 text-emerald-400 text-xs px-2.5 py-1 flex items-center gap-1.5 self-start sm:self-auto">
<CheckCircle2 className="w-3.5 h-3.5" /> Stichtag gesetzt
</Badge>
)}
</div>
</div> </div>
</motion.div> {isOneTime && (
)} <div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center text-white shadow-md shadow-primary/30">
</AnimatePresence> <Check className="w-3 h-3" />
</div>
)}
</div>
<div className="space-y-0.5">
<div className="flex items-center gap-2">
<h3 className="text-base font-bold text-white">Kauf / Einmalkauf</h3>
<Badge variant="outline" className="text-[9px] px-1.5 py-0 bg-slate-500/10 border-slate-500/30 text-slate-300">
Klassisch
</Badge>
</div>
<p className="text-[11px] font-medium text-slate-300">
Einmalige Lizenzgebühr
</p>
</div>
<p className="text-slate-400 text-[11px] mt-2 leading-relaxed">
Dauerhafte Lizenzrechte für die gekaufte Version inklusive 12 Monate kostenfreier Updates.
</p>
{/* Inline-Erweiterung für Kaufdatum / Stichtag */}
<AnimatePresence>
{isOneTime && (
<motion.div
initial={{ opacity: 0, height: 0, marginTop: 0 }}
animate={{ opacity: 1, height: 'auto', marginTop: 12 }}
exit={{ opacity: 0, height: 0, marginTop: 0 }}
transition={{ duration: 0.25, ease: 'easeInOut' }}
className="overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
<div className="p-3 rounded-lg bg-slate-950/80 border border-white/10 space-y-2">
<div className="flex items-start gap-2">
<Calendar className="w-3.5 h-3.5 text-primary shrink-0 mt-0.5" />
<div className="space-y-0.5 flex-1">
<Label htmlFor="last-license-date" className="text-white text-[11px] font-bold block">
Datum letzter CASPOS-Kauf (Stichtag)
</Label>
<p className="text-[10px] text-slate-400 leading-tight">
Zur automatischen Berechnung der Update-Staffel (15% / 30% / 50%).
</p>
</div>
</div>
<div className="relative">
<Input
id="last-license-date"
type="date"
value={lastLicenseDate}
onChange={(e) => setLastLicenseDate(e.target.value)}
className="bg-slate-900 border-white/10 text-white text-xs h-8 focus:border-primary w-full"
/>
</div>
{lastLicenseDate && (
<div className="text-[10px] text-emerald-400 flex items-center gap-1">
<CheckCircle2 className="w-3 h-3 text-emerald-400" /> Stichtag erfasst
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
<div className="mt-3 pt-2.5 border-t border-white/10 flex items-center justify-between text-[11px]">
<span className="text-slate-400">Zahlungsintervall:</span>
<span className="text-slate-200 font-semibold">einmalig</span>
</div>
</div>
</div> </div>
</div> </div>
{/* ─── 3. FIXED BOTTOM ACTION BAR (shrink-0) ─── */} {/* ─── 3. FIXED BOTTOM ACTION BAR (shrink-0) ─── */}
<div className="shrink-0 border-t border-white/10 bg-slate-950/90 backdrop-blur-md px-6 py-3.5 flex flex-col sm:flex-row items-center justify-between gap-3 z-10"> <div className="shrink-0 border-t border-white/10 bg-slate-950/90 backdrop-blur-md px-4 py-2.5 flex flex-col sm:flex-row items-center justify-between gap-2 z-10">
{/* Back Button */} {/* Back Button */}
<Button <Button
variant="outline" variant="outline"
onClick={prevStep} onClick={prevStep}
className="w-full sm:w-auto border-white/10 text-white hover:bg-white/10 h-9 text-xs gap-1.5" className="w-full sm:w-auto border-white/10 text-white hover:bg-white/10 h-8 text-xs gap-1.5"
> >
<ChevronLeft className="w-4 h-4" /> Zurück zu Schritt 1 <ChevronLeft className="w-3.5 h-3.5" /> Zurück zu Schritt 1
</Button> </Button>
{/* Selected Model Status Chip */} {/* Selected Model Status Chip */}
<div className="flex items-center gap-2 px-3 py-1.5 rounded-xl bg-white/5 border border-white/10 text-white text-xs"> <div className="flex items-center gap-2 px-2.5 py-1 rounded-lg bg-white/5 border border-white/10 text-white text-xs">
<span className="text-slate-400">Gewähltes Modell:</span> <span className="text-slate-400">Gewähltes Modell:</span>
<Badge <Badge
variant="outline" variant="outline"
className={`text-xs px-2 py-0.5 ${ className={`text-xs px-2 py-0 ${
isMonthly isMonthly
? 'border-blue-500/30 text-blue-400 bg-blue-500/10' ? 'border-blue-500/30 text-blue-400 bg-blue-500/10'
: 'border-slate-500/30 text-slate-300 bg-slate-500/10' : 'border-slate-500/30 text-slate-300 bg-slate-500/10'
@@ -237,11 +229,13 @@ export function StepBilling({
{/* Next Button */} {/* Next Button */}
<Button <Button
onClick={nextStep} onClick={nextStep}
className="w-full sm:w-auto bg-primary hover:bg-primary/90 text-white font-semibold px-6 shadow-lg shadow-primary/20 h-9 text-xs gap-1.5" disabled={isNextDisabled}
className="w-full sm:w-auto bg-primary hover:bg-primary/90 text-white font-semibold px-5 shadow-md shadow-primary/20 h-8 text-xs gap-1.5 disabled:opacity-50"
> >
Weiter zu Schritt 3 <ChevronRight className="w-4 h-4" /> Weiter zu Schritt 3 <ChevronRight className="w-3.5 h-3.5" />
</Button> </Button>
</div> </div>
</Card> </Card>
) )
} }

View File

@@ -180,44 +180,44 @@ export function StepCustomer({
<> <>
<Card className="glass-dark border-white/10 h-[calc(100vh-8.5rem)] flex flex-col overflow-hidden relative"> <Card className="glass-dark border-white/10 h-[calc(100vh-8.5rem)] flex flex-col overflow-hidden relative">
{/* ─── 1. FIXED HEADER (shrink-0) ─── */} {/* ─── 1. FIXED HEADER (shrink-0) ─── */}
<CardHeader className="pb-3 pt-4 px-6 shrink-0 border-b border-white/10 bg-slate-950/40 space-y-0"> <CardHeader className="py-2.5 px-4 shrink-0 border-b border-white/10 bg-slate-950/40 space-y-0">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<div> <div>
<CardTitle className="text-xl font-bold flex items-center gap-2 text-white"> <CardTitle className="text-lg font-bold flex items-center gap-2 text-white">
<User className="w-5 h-5 text-primary" /> <User className="w-4 h-4 text-primary" />
Für wen bestellen Sie? Für wen bestellen Sie?
</CardTitle> </CardTitle>
<CardDescription className="text-xs text-slate-300 mt-0.5"> <CardDescription className="text-[11px] text-slate-300 mt-0.5">
Wählen Sie den Endkunden für die Lizenzierung aus oder legen Sie schnell einen neuen Datensatz an. Wählen Sie den Endkunden für die Lizenzierung aus oder legen Sie schnell einen neuen Datensatz an.
</CardDescription> </CardDescription>
</div> </div>
<Button <Button
onClick={openDrawer} onClick={openDrawer}
size="sm" size="sm"
className="gap-2 shrink-0 bg-primary hover:bg-primary/90 text-white font-medium shadow-md shadow-primary/20 h-8 text-xs" className="gap-1.5 shrink-0 bg-primary hover:bg-primary/90 text-white font-medium shadow-md shadow-primary/20 h-8 text-xs"
> >
<UserPlus className="w-4 h-4" /> Kunde anlegen <UserPlus className="w-3.5 h-3.5" /> Kunde anlegen
</Button> </Button>
</div> </div>
</CardHeader> </CardHeader>
{/* ─── 2. SCROLLABLE MIDDLE CONTENT (flex-1 min-h-0 overflow-hidden) ─── */} {/* ─── 2. SCROLLABLE MIDDLE CONTENT (flex-1 min-h-0 overflow-hidden) ─── */}
<div className="flex-1 min-h-0 overflow-hidden p-6"> <div className="flex-1 min-h-0 overflow-hidden p-3">
<div className={`h-full ${isAdmin ? 'grid grid-cols-1 lg:grid-cols-2 gap-6 items-stretch' : 'flex flex-col'}`}> <div className={`h-full ${isAdmin ? 'grid grid-cols-1 lg:grid-cols-2 gap-3 items-stretch' : 'flex flex-col'}`}>
{/* ─── 2A. ADMIN PARTNER SELECTION ─── */} {/* ─── 2A. ADMIN PARTNER SELECTION ─── */}
{isAdmin && ( {isAdmin && (
<div className="p-4 rounded-2xl bg-white/5 border border-white/10 flex flex-col h-full overflow-hidden justify-between space-y-3"> <div className="p-3 rounded-xl bg-white/5 border border-white/10 flex flex-col h-full overflow-hidden justify-between space-y-2">
<div className="flex items-center justify-between gap-4 flex-wrap shrink-0"> <div className="flex items-center justify-between gap-2 flex-wrap shrink-0">
<div> <div>
<Label className="text-white font-bold text-sm flex items-center gap-2"> <Label className="text-white font-bold text-xs flex items-center gap-1.5">
<Building2 className="w-4 h-4 text-primary" /> <Building2 className="w-3.5 h-3.5 text-primary" />
Partner-Auswahl (Admin) Partner-Auswahl (Admin)
</Label> </Label>
<p className="text-[11px] text-slate-400 mt-0.5"> <p className="text-[10px] text-slate-400">
Filtert die Kundenliste nach Partner Filtert die Kundenliste nach Partner
</p> </p>
</div> </div>
<Badge variant="outline" className="border-primary/30 text-primary bg-primary/10 text-[10px]"> <Badge variant="outline" className="border-primary/30 text-primary bg-primary/10 text-[9px] px-1.5 py-0">
Admin Modus Admin Modus
</Badge> </Badge>
</div> </div>
@@ -225,7 +225,7 @@ export function StepCustomer({
{/* Partner Search & Page Size */} {/* Partner Search & Page Size */}
<div className="flex gap-2 items-center shrink-0"> <div className="flex gap-2 items-center shrink-0">
<div className="relative flex-1"> <div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400" /> <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400" />
<Input <Input
placeholder="Partner suchen..." placeholder="Partner suchen..."
value={partnerSearchTerm} value={partnerSearchTerm}
@@ -245,34 +245,34 @@ export function StepCustomer({
</div> </div>
{/* Partner Card List (Scrollable) */} {/* Partner Card List (Scrollable) */}
<div className="space-y-1.5 flex-1 min-h-0 overflow-y-auto pr-1 py-1 scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent"> <div className="space-y-1.5 flex-1 min-h-0 overflow-y-auto pr-1 py-0.5 scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent">
{/* Reserved Fixed Card: Alle Partner */} {/* Reserved Fixed Card: Alle Partner */}
<div <div
onClick={() => { onClick={() => {
setSelectedCompanyId('all') setSelectedCompanyId('all')
setSelectedEndCustomerId(null) setSelectedEndCustomerId(null)
}} }}
className={`flex items-center justify-between p-2.5 rounded-xl border cursor-pointer transition-all select-none ${ className={`flex items-center justify-between p-2 rounded-lg border cursor-pointer transition-all select-none ${
selectedCompanyId === 'all' || !selectedCompanyId selectedCompanyId === 'all' || !selectedCompanyId
? 'border-primary bg-primary/10 shadow-sm shadow-primary/10' ? 'border-primary bg-primary/10 shadow-sm shadow-primary/10'
: 'border-white/5 bg-white/5 hover:bg-white/10' : 'border-white/5 bg-white/5 hover:bg-white/10'
}`} }`}
> >
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-lg bg-primary/20 flex items-center justify-center text-primary font-bold shrink-0"> <div className="w-6 h-6 rounded-md bg-primary/20 flex items-center justify-center text-primary font-bold shrink-0">
<Users className="w-3.5 h-3.5" /> <Users className="w-3 h-3" />
</div> </div>
<div> <div>
<p className="font-semibold text-white text-xs"> <p className="font-semibold text-white text-xs leading-tight">
Alle Partner (Firmenübergreifend) Alle Partner (Firmenübergreifend)
</p> </p>
<p className="text-[11px] text-slate-400"> <p className="text-[10px] text-slate-400 leading-tight">
{endCustomers.filter((c) => !c.is_anonymized).length} Kunden gesamt {endCustomers.filter((c) => !c.is_anonymized).length} Kunden gesamt
</p> </p>
</div> </div>
</div> </div>
{(selectedCompanyId === 'all' || !selectedCompanyId) && ( {(selectedCompanyId === 'all' || !selectedCompanyId) && (
<Check className="w-4 h-4 text-primary shrink-0" /> <Check className="w-3.5 h-3.5 text-primary shrink-0" />
)} )}
</div> </div>
@@ -288,20 +288,20 @@ export function StepCustomer({
setSelectedCompanyId(c.id) setSelectedCompanyId(c.id)
setSelectedEndCustomerId(null) setSelectedEndCustomerId(null)
}} }}
className={`flex items-center justify-between p-2.5 rounded-xl border cursor-pointer transition-all select-none ${ className={`flex items-center justify-between p-2 rounded-lg border cursor-pointer transition-all select-none ${
isSelected isSelected
? 'border-primary bg-primary/10 shadow-sm shadow-primary/10' ? 'border-primary bg-primary/10 shadow-sm shadow-primary/10'
: 'border-white/5 bg-white/5 hover:bg-white/10' : 'border-white/5 bg-white/5 hover:bg-white/10'
}`} }`}
> >
<div className="flex items-center gap-2.5 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<div className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-slate-300 font-bold shrink-0"> <div className="w-6 h-6 rounded-md bg-white/5 border border-white/10 flex items-center justify-center text-slate-300 font-bold shrink-0">
<Building2 className="w-3.5 h-3.5 text-slate-400" /> <Building2 className="w-3 h-3 text-slate-400" />
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<p className="font-semibold text-white text-xs truncate">{c.name}</p> <p className="font-semibold text-white text-xs truncate leading-tight">{c.name}</p>
{(c.street || c.zip || c.city) && ( {(c.street || c.zip || c.city) && (
<p className="text-[11px] text-slate-400 truncate"> <p className="text-[10px] text-slate-400 truncate leading-tight">
{[c.street, [c.zip, c.city].filter(Boolean).join(' ')].filter(Boolean).join(', ')} {[c.street, [c.zip, c.city].filter(Boolean).join(' ')].filter(Boolean).join(', ')}
</p> </p>
)} )}
@@ -311,7 +311,7 @@ export function StepCustomer({
<div className="flex items-center gap-1.5 shrink-0 ml-2"> <div className="flex items-center gap-1.5 shrink-0 ml-2">
<Badge <Badge
variant="outline" variant="outline"
className={`text-[10px] px-1.5 py-0 ${ className={`text-[9px] px-1.5 py-0 ${
customerCount > 0 customerCount > 0
? 'border-green-500/30 text-green-400 bg-green-500/10' ? 'border-green-500/30 text-green-400 bg-green-500/10'
: 'border-white/10 text-slate-500 bg-white/5' : 'border-white/10 text-slate-500 bg-white/5'
@@ -319,21 +319,21 @@ export function StepCustomer({
> >
{customerCount} {customerCount}
</Badge> </Badge>
{isSelected && <Check className="w-4 h-4 text-primary shrink-0" />} {isSelected && <Check className="w-3.5 h-3.5 text-primary shrink-0" />}
</div> </div>
</div> </div>
) )
})} })}
{filteredCompanies.length === 0 && ( {filteredCompanies.length === 0 && (
<div className="p-3 text-center text-slate-400 text-xs bg-white/5 rounded-xl border border-white/5 my-auto"> <div className="p-2 text-center text-slate-400 text-xs bg-white/5 rounded-lg border border-white/5 my-auto">
Keine Partner gefunden. Keine Partner gefunden.
</div> </div>
)} )}
</div> </div>
{/* Partner Pagination Footer */} {/* Partner Pagination Footer */}
<div className="flex items-center justify-between border-t border-white/10 pt-2 text-[11px] text-slate-400 shrink-0"> <div className="flex items-center justify-between border-t border-white/10 pt-1.5 text-[10px] text-slate-400 shrink-0">
<div> <div>
{Math.min(partnerCurrentPage * partnerPageSize, totalPartnerCount)} / {totalPartnerCount} Partner {Math.min(partnerCurrentPage * partnerPageSize, totalPartnerCount)} / {totalPartnerCount} Partner
</div> </div>
@@ -343,20 +343,20 @@ export function StepCustomer({
size="sm" size="sm"
onClick={() => setPartnerCurrentPage(1)} onClick={() => setPartnerCurrentPage(1)}
disabled={partnerCurrentPage === 1} disabled={partnerCurrentPage === 1}
className="h-6 px-1.5 text-slate-400 hover:text-white" className="h-5 px-1 text-slate-400 hover:text-white"
> >
<ChevronsLeft className="w-3.5 h-3.5" /> <ChevronsLeft className="w-3 h-3" />
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setPartnerCurrentPage((p) => Math.max(1, p - 1))} onClick={() => setPartnerCurrentPage((p) => Math.max(1, p - 1))}
disabled={partnerCurrentPage === 1} disabled={partnerCurrentPage === 1}
className="h-6 px-2 border-white/10 text-white text-[11px]" className="h-5 px-1.5 border-white/10 text-white text-[10px]"
> >
<ChevronLeft className="w-3 h-3 mr-0.5" /> Zurück <ChevronLeft className="w-2.5 h-2.5 mr-0.5" /> Zurück
</Button> </Button>
<span className="px-1.5 text-slate-300"> <span className="px-1 text-slate-300">
{partnerCurrentPage} / {totalPartnerPages} {partnerCurrentPage} / {totalPartnerPages}
</span> </span>
<Button <Button
@@ -364,18 +364,18 @@ export function StepCustomer({
size="sm" size="sm"
onClick={() => setPartnerCurrentPage((p) => Math.min(totalPartnerPages, p + 1))} onClick={() => setPartnerCurrentPage((p) => Math.min(totalPartnerPages, p + 1))}
disabled={partnerCurrentPage === totalPartnerPages} disabled={partnerCurrentPage === totalPartnerPages}
className="h-6 px-2 border-white/10 text-white text-[11px]" className="h-5 px-1.5 border-white/10 text-white text-[10px]"
> >
Vor <ChevronRight className="w-3 h-3 ml-0.5" /> Vor <ChevronRight className="w-2.5 h-2.5 ml-0.5" />
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => setPartnerCurrentPage(totalPartnerPages)} onClick={() => setPartnerCurrentPage(totalPartnerPages)}
disabled={partnerCurrentPage === totalPartnerPages} disabled={partnerCurrentPage === totalPartnerPages}
className="h-6 px-1.5 text-slate-400 hover:text-white" className="h-5 px-1 text-slate-400 hover:text-white"
> >
<ChevronsRight className="w-3.5 h-3.5" /> <ChevronsRight className="w-3 h-3" />
</Button> </Button>
</div> </div>
</div> </div>
@@ -383,14 +383,14 @@ export function StepCustomer({
)} )}
{/* ─── 2B. CUSTOMER SELECTION LIST (Scrollable Panel) ─── */} {/* ─── 2B. CUSTOMER SELECTION LIST (Scrollable Panel) ─── */}
<div className="p-4 rounded-2xl bg-white/5 border border-white/10 flex flex-col h-full overflow-hidden justify-between space-y-3"> <div className="p-3 rounded-xl bg-white/5 border border-white/10 flex flex-col h-full overflow-hidden justify-between space-y-2">
<div className="flex items-center justify-between gap-3 shrink-0"> <div className="flex items-center justify-between gap-2 shrink-0">
<div className="min-w-0"> <div className="min-w-0">
<Label className="text-white font-bold text-sm flex items-center gap-2"> <Label className="text-white font-bold text-xs flex items-center gap-1.5">
<User className="w-4 h-4 text-primary" /> <User className="w-3.5 h-3.5 text-primary" />
Kunden-Auswahl Kunden-Auswahl
</Label> </Label>
<p className="text-[11px] text-slate-400 mt-0.5 truncate"> <p className="text-[10px] text-slate-400 truncate">
{isAdmin && selectedCompanyId && selectedCompanyId !== 'all' {isAdmin && selectedCompanyId && selectedCompanyId !== 'all'
? `Partner: "${companies.find((c) => c.id === selectedCompanyId)?.name || selectedCompanyId}"` ? `Partner: "${companies.find((c) => c.id === selectedCompanyId)?.name || selectedCompanyId}"`
: 'Alle Endkunden'} : 'Alle Endkunden'}
@@ -408,7 +408,7 @@ export function StepCustomer({
{/* Customer Search */} {/* Customer Search */}
<div className="relative shrink-0"> <div className="relative shrink-0">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400" /> <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400" />
<Input <Input
placeholder="Kunden suchen nach Name, Ort, PLZ, E-Mail..." placeholder="Kunden suchen nach Name, Ort, PLZ, E-Mail..."
value={searchTerm} value={searchTerm}
@@ -418,10 +418,10 @@ export function StepCustomer({
</div> </div>
{/* Customer Card List (Isolated Scrollable Area) */} {/* Customer Card List (Isolated Scrollable Area) */}
<div className="flex-1 min-h-0 overflow-y-auto pr-1 space-y-2 pb-2 scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent"> <div className="flex-1 min-h-0 overflow-y-auto pr-1 space-y-1.5 py-0.5 scrollbar-thin scrollbar-thumb-slate-800 scrollbar-track-transparent">
{customersByCompany.length === 0 ? ( {customersByCompany.length === 0 ? (
<div className="p-6 rounded-xl bg-white/5 border border-white/10 text-center space-y-3 my-auto"> <div className="p-4 rounded-xl bg-white/5 border border-white/10 text-center space-y-2 my-auto">
<Building2 className="w-8 h-8 text-slate-600 mx-auto" /> <Building2 className="w-6 h-6 text-slate-600 mx-auto" />
<p className="text-slate-400 text-xs"> <p className="text-slate-400 text-xs">
{isAdmin && selectedCompanyId && selectedCompanyId !== 'all' {isAdmin && selectedCompanyId && selectedCompanyId !== 'all'
? 'Keine Endkunden für den ausgewählten Partner vorhanden.' ? 'Keine Endkunden für den ausgewählten Partner vorhanden.'
@@ -431,13 +431,13 @@ export function StepCustomer({
variant="outline" variant="outline"
size="sm" size="sm"
onClick={openDrawer} onClick={openDrawer}
className="border-white/10 text-white text-xs h-8" className="border-white/10 text-white text-xs h-7"
> >
<UserPlus className="w-3.5 h-3.5 mr-1.5" /> Ersten Kunden anlegen <UserPlus className="w-3 h-3 mr-1.5" /> Ersten Kunden anlegen
</Button> </Button>
</div> </div>
) : filteredCustomers.length === 0 ? ( ) : filteredCustomers.length === 0 ? (
<div className="p-4 text-center text-slate-400 text-xs bg-white/5 rounded-xl border border-white/5 my-auto"> <div className="p-3 text-center text-slate-400 text-xs bg-white/5 rounded-lg border border-white/5 my-auto">
Keine passenden Kunden für &quot;{searchTerm}&quot; gefunden. Keine passenden Kunden für &quot;{searchTerm}&quot; gefunden.
</div> </div>
) : ( ) : (
@@ -448,7 +448,7 @@ export function StepCustomer({
<div <div
key={customer.id} key={customer.id}
onClick={() => handleSelectCustomer(customer)} onClick={() => handleSelectCustomer(customer)}
className={`flex items-start p-3 rounded-xl border-2 cursor-pointer transition-all select-none ${ className={`flex items-start p-2 rounded-lg border cursor-pointer transition-all select-none ${
isSelected isSelected
? 'border-primary bg-primary/10 shadow-md shadow-primary/5' ? 'border-primary bg-primary/10 shadow-md shadow-primary/5'
: 'border-white/5 bg-white/5 hover:bg-white/10' : 'border-white/5 bg-white/5 hover:bg-white/10'
@@ -463,29 +463,29 @@ export function StepCustomer({
className="sr-only" className="sr-only"
/> />
<div className="flex-1 min-w-0 pr-2"> <div className="flex-1 min-w-0 pr-2">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-1.5 flex-wrap">
<p className="font-semibold text-white text-xs truncate">{customer.company_name}</p> <p className="font-semibold text-white text-xs truncate leading-tight">{customer.company_name}</p>
{isAdmin && customer.partner_id && ( {isAdmin && customer.partner_id && (
<Badge <Badge
variant="outline" variant="outline"
className="text-[9px] px-1.5 py-0 border-white/10 text-slate-400 bg-white/5" className="text-[9px] px-1 py-0 border-white/10 text-slate-400 bg-white/5 leading-tight"
> >
Partner: {companies.find((co) => co.id === customer.partner_id)?.name || customer.partner_id} Partner: {companies.find((co) => co.id === customer.partner_id)?.name || customer.partner_id}
</Badge> </Badge>
)} )}
</div> </div>
<p className="text-[11px] text-slate-400 mt-0.5 truncate"> <p className="text-[10px] text-slate-400 mt-0.5 truncate leading-tight">
{[customer.first_name, customer.last_name].filter(Boolean).join(' ')} {[customer.first_name, customer.last_name].filter(Boolean).join(' ')}
{customer.first_name || customer.last_name ? ' · ' : ''} {customer.first_name || customer.last_name ? ' · ' : ''}
{[customer.street, customer.zip, customer.city].filter(Boolean).join(', ')} {[customer.street, customer.zip, customer.city].filter(Boolean).join(', ')}
</p> </p>
{customer.email && ( {customer.email && (
<p className="text-[10px] text-slate-500 mt-0.5 flex items-center gap-1 truncate"> <p className="text-[10px] text-slate-500 mt-0.5 flex items-center gap-1 truncate leading-tight">
<Mail className="w-3 h-3 shrink-0" /> {customer.email} <Mail className="w-2.5 h-2.5 shrink-0" /> {customer.email}
</p> </p>
)} )}
</div> </div>
{isSelected && <Check className="w-4 h-4 text-primary shrink-0 mt-0.5" />} {isSelected && <Check className="w-3.5 h-3.5 text-primary shrink-0 mt-0.5" />}
</div> </div>
) )
}) })
@@ -494,19 +494,19 @@ export function StepCustomer({
{/* Customer Pagination */} {/* Customer Pagination */}
{totalCustomerCount > 0 && ( {totalCustomerCount > 0 && (
<div className="flex items-center justify-between border-t border-white/10 pt-2 text-[11px] text-slate-400 shrink-0 flex-wrap gap-2"> <div className="flex items-center justify-between border-t border-white/10 pt-1.5 text-[10px] text-slate-400 shrink-0 flex-wrap gap-1.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-1.5">
<span> <span>
{Math.min(currentPage * pageSize, totalCustomerCount)} / {totalCustomerCount} Kunden {Math.min(currentPage * pageSize, totalCustomerCount)} / {totalCustomerCount} Kunden
</span> </span>
<select <select
value={pageSize} value={pageSize}
onChange={(e) => setPageSize(Number(e.target.value))} onChange={(e) => setPageSize(Number(e.target.value))}
className="h-6 rounded border border-white/10 bg-slate-900 px-1 text-[11px] text-white focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary" className="h-5 rounded border border-white/10 bg-slate-900 px-1 text-[10px] text-white focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary"
> >
<option value={10}>10 pro Seite</option> <option value={10}>10</option>
<option value={20}>20 pro Seite</option> <option value={20}>20</option>
<option value={50}>50 pro Seite</option> <option value={50}>50</option>
</select> </select>
</div> </div>
@@ -516,20 +516,20 @@ export function StepCustomer({
size="sm" size="sm"
onClick={() => setCurrentPage(1)} onClick={() => setCurrentPage(1)}
disabled={currentPage === 1} disabled={currentPage === 1}
className="h-6 px-1.5 text-slate-400 hover:text-white" className="h-5 px-1 text-slate-400 hover:text-white"
> >
<ChevronsLeft className="w-3.5 h-3.5" /> <ChevronsLeft className="w-3 h-3" />
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1} disabled={currentPage === 1}
className="h-6 px-2 border-white/10 text-white text-[11px]" className="h-5 px-1.5 border-white/10 text-white text-[10px]"
> >
<ChevronLeft className="w-3 h-3 mr-0.5" /> Zurück <ChevronLeft className="w-2.5 h-2.5 mr-0.5" /> Zurück
</Button> </Button>
<span className="px-1.5 text-slate-300"> <span className="px-1 text-slate-300">
{currentPage} / {totalCustomerPages} {currentPage} / {totalCustomerPages}
</span> </span>
<Button <Button
@@ -537,18 +537,18 @@ export function StepCustomer({
size="sm" size="sm"
onClick={() => setCurrentPage((p) => Math.min(totalCustomerPages, p + 1))} onClick={() => setCurrentPage((p) => Math.min(totalCustomerPages, p + 1))}
disabled={currentPage === totalCustomerPages} disabled={currentPage === totalCustomerPages}
className="h-6 px-2 border-white/10 text-white text-[11px]" className="h-5 px-1.5 border-white/10 text-white text-[10px]"
> >
Vor <ChevronRight className="w-3 h-3 ml-0.5" /> Vor <ChevronRight className="w-2.5 h-2.5 ml-0.5" />
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => setCurrentPage(totalCustomerPages)} onClick={() => setCurrentPage(totalCustomerPages)}
disabled={currentPage === totalCustomerPages} disabled={currentPage === totalCustomerPages}
className="h-8 px-1.5 text-slate-400 hover:text-white" className="h-5 px-1 text-slate-400 hover:text-white"
> >
<ChevronsRight className="w-3.5 h-3.5" /> <ChevronsRight className="w-3 h-3" />
</Button> </Button>
</div> </div>
</div> </div>
@@ -558,17 +558,17 @@ export function StepCustomer({
</div> </div>
{/* ─── 3. FIXED BOTTOM ACTION BAR (shrink-0) ─── */} {/* ─── 3. FIXED BOTTOM ACTION BAR (shrink-0) ─── */}
<div className="shrink-0 border-t border-white/10 bg-slate-950/90 backdrop-blur-md px-6 py-3.5 flex flex-col sm:flex-row items-center justify-between gap-3 z-10"> <div className="shrink-0 border-t border-white/10 bg-slate-950/90 backdrop-blur-md px-4 py-2.5 flex flex-col sm:flex-row items-center justify-between gap-2 z-10">
{/* Selected Customer Summary Chip */} {/* Selected Customer Summary Chip */}
<div className="flex items-center gap-2.5 min-w-0"> <div className="flex items-center gap-2 min-w-0">
{activeCustomer ? ( {activeCustomer ? (
<div className="flex items-center gap-2 px-3 py-1.5 rounded-xl bg-primary/10 border border-primary/30 text-white min-w-0"> <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-primary/10 border border-primary/30 text-white min-w-0">
<CheckCircle2 className="w-4 h-4 text-primary shrink-0" /> <CheckCircle2 className="w-3.5 h-3.5 text-primary shrink-0" />
<div className="text-xs truncate"> <div className="text-xs truncate">
<span className="text-slate-400">Ausgewählt:</span>{' '} <span className="text-slate-400">Ausgewählt:</span>{' '}
<strong className="text-white font-semibold">{activeCustomer.company_name}</strong> <strong className="text-white font-semibold">{activeCustomer.company_name}</strong>
{(activeCustomer.city || activeCustomer.zip) && ( {(activeCustomer.city || activeCustomer.zip) && (
<span className="text-slate-400 ml-1.5"> <span className="text-slate-400 ml-1">
({[activeCustomer.zip, activeCustomer.city].filter(Boolean).join(' ')}) ({[activeCustomer.zip, activeCustomer.city].filter(Boolean).join(' ')})
</span> </span>
)} )}
@@ -576,7 +576,7 @@ export function StepCustomer({
</div> </div>
) : ( ) : (
<div className="text-xs text-slate-400 flex items-center gap-1.5"> <div className="text-xs text-slate-400 flex items-center gap-1.5">
<User className="w-4 h-4 text-slate-500" /> <User className="w-3.5 h-3.5 text-slate-500" />
<span>Bitte wählen Sie einen Kunden aus, um fortzufahren.</span> <span>Bitte wählen Sie einen Kunden aus, um fortzufahren.</span>
</div> </div>
)} )}
@@ -586,9 +586,9 @@ export function StepCustomer({
<Button <Button
onClick={nextStep} onClick={nextStep}
disabled={!selectedEndCustomerId} disabled={!selectedEndCustomerId}
className="w-full sm:w-auto bg-primary hover:bg-primary/90 text-white font-semibold px-6 shadow-lg shadow-primary/20 h-9 text-xs gap-1.5" className="w-full sm:w-auto bg-primary hover:bg-primary/90 text-white font-semibold px-5 shadow-md shadow-primary/20 h-8 text-xs gap-1.5"
> >
Weiter zu Schritt 2 <ChevronRight className="w-4 h-4" /> Weiter zu Schritt 2 <ChevronRight className="w-3.5 h-3.5" />
</Button> </Button>
</div> </div>
</Card> </Card>