feat: split pricing into one-time and monthly totals, fix invoice pdf rendering
Some checks failed
Staging Build / build (push) Has been cancelled
Some checks failed
Staging Build / build (push) Has been cancelled
This commit is contained in:
@@ -118,20 +118,29 @@ export function OrderWizard({
|
||||
)
|
||||
|
||||
// Total price across all selections
|
||||
const totalPrice = useMemo(() => {
|
||||
let total = 0
|
||||
// Total price calculations (split by billing interval)
|
||||
const { monthlyTotal, oneTimeTotal } = useMemo(() => {
|
||||
let monthly = 0
|
||||
let oneTime = 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)
|
||||
const base = Number(prod.base_price)
|
||||
let modulesSum = 0
|
||||
sel.moduleIds.forEach(mId => {
|
||||
const mod = prod.modules?.find(m => m.id === mId)
|
||||
if (mod) total += Number(mod.price)
|
||||
if (mod) modulesSum += Number(mod.price)
|
||||
})
|
||||
const totalItem = base + modulesSum
|
||||
if (prod.billing_interval === 'one_time') {
|
||||
oneTime += totalItem
|
||||
} else {
|
||||
monthly += totalItem
|
||||
}
|
||||
}
|
||||
return total
|
||||
return { monthlyTotal: monthly, oneTimeTotal: oneTime }
|
||||
}, [selections, products, categories])
|
||||
|
||||
// Helper: update product selection for a category (resets modules)
|
||||
@@ -425,12 +434,38 @@ export function OrderWizard({
|
||||
)
|
||||
})}
|
||||
<Separator className="bg-white/10" />
|
||||
<div className="flex justify-between text-xl font-bold text-gradient">
|
||||
<span>Gesamt:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(totalPrice)}
|
||||
</span>
|
||||
</div>
|
||||
{oneTimeTotal > 0 && monthlyTotal > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-base font-semibold text-white">
|
||||
<span className="text-slate-400">Einmalig gesamt:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeTotal)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-base font-semibold text-white">
|
||||
<span className="text-slate-400">Monatlich gesamt:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyTotal)}
|
||||
{' '}<span className="text-xs font-normal text-slate-400">/ Monat</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : oneTimeTotal > 0 ? (
|
||||
<div className="flex justify-between text-xl font-bold text-gradient">
|
||||
<span>Gesamt (einmalig):</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeTotal)}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between text-xl font-bold text-gradient">
|
||||
<span>Gesamt:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyTotal)}
|
||||
{' '}<span className="text-sm font-normal text-slate-400">/ Monat</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!allCategoriesFilled && (
|
||||
<p className="text-xs text-destructive flex items-center gap-1">
|
||||
<AlertCircle className="w-3 h-3" />
|
||||
@@ -669,12 +704,38 @@ export function OrderWizard({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between text-xl font-bold text-gradient">
|
||||
<span>Gesamtbetrag:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(totalPrice)} / Monat
|
||||
</span>
|
||||
</div>
|
||||
{oneTimeTotal > 0 && monthlyTotal > 0 ? (
|
||||
<div className="space-y-2 border-t border-white/10 pt-4">
|
||||
<div className="flex justify-between text-lg font-bold text-white">
|
||||
<span className="text-slate-400">Einmaliger Gesamtbetrag:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeTotal)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-lg font-bold text-white">
|
||||
<span className="text-slate-400">Monatlicher Gesamtbetrag:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyTotal)}
|
||||
{' '}<span className="text-sm font-normal text-slate-400">/ Monat</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : oneTimeTotal > 0 ? (
|
||||
<div className="flex justify-between text-xl font-bold text-gradient border-t border-white/10 pt-4">
|
||||
<span>Gesamtbetrag:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeTotal)}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between text-xl font-bold text-gradient border-t border-white/10 pt-4">
|
||||
<span>Gesamtbetrag:</span>
|
||||
<span>
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyTotal)}
|
||||
{' '}<span className="text-sm font-normal text-slate-400">/ Monat</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-slate-500 italic">
|
||||
Mit dem Klick auf „Kostenpflichtig bestellen" akzeptieren Sie unsere AGB und die
|
||||
Datenschutzerklärung.
|
||||
|
||||
Reference in New Issue
Block a user