273 lines
12 KiB
TypeScript
273 lines
12 KiB
TypeScript
import { createClient } from '@/lib/supabase/server'
|
|
import { redirect } from 'next/navigation'
|
|
import Link from 'next/link'
|
|
import { CheckCircle2, Download, ExternalLink, ShoppingBag, ArrowRight, Calendar } from 'lucide-react'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Separator } from '@/components/ui/separator'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import type { Order } from '@/lib/types'
|
|
|
|
export default async function OrderSuccessPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ id?: string }>
|
|
}) {
|
|
const { id } = await searchParams
|
|
if (!id) redirect('/')
|
|
|
|
const supabase = await createClient()
|
|
|
|
// Authentifizierung prüfen
|
|
const { data: { user } } = await supabase.auth.getUser()
|
|
if (!user) redirect('/auth/login')
|
|
|
|
// 1. Hole Benutzerrolle und Company
|
|
const { data: dbUser } = await supabase
|
|
.from('users')
|
|
.select('role, company_id')
|
|
.eq('id', user.id)
|
|
.single()
|
|
|
|
// 2. Hole Bestellung
|
|
const { data: order, error } = await supabase
|
|
.from('orders')
|
|
.select('*')
|
|
.eq('id', id)
|
|
.single()
|
|
|
|
if (error || !order) redirect('/')
|
|
|
|
// Berechtigung prüfen
|
|
const isAdmin = dbUser?.role === 'admin'
|
|
const isOwner = order.user_id === user.id
|
|
const isCompanyMember = dbUser?.company_id && order.company_id === dbUser.company_id
|
|
|
|
if (!isAdmin && !isOwner && !isCompanyMember) {
|
|
redirect('/')
|
|
}
|
|
|
|
const o = order as Order
|
|
const items = o.order_data?.items ?? []
|
|
const groupedItems: Record<string, typeof items> = {}
|
|
items.forEach(item => {
|
|
const devName = item.device_name || 'Kasse 1'
|
|
if (!groupedItems[devName]) {
|
|
groupedItems[devName] = []
|
|
}
|
|
groupedItems[devName].push(item)
|
|
})
|
|
|
|
const taxRate = o.order_data?.tax_rate ?? 19
|
|
const oneTimeItems = items.filter((item) => item.billing_interval === 'one_time')
|
|
const monthlyItems = items.filter((item) => item.billing_interval === 'monthly')
|
|
|
|
const oneTimeNet = oneTimeItems.reduce((sum, item) => sum + (item.item_total || 0), 0)
|
|
const oneTimeTax = Math.round(oneTimeNet * (taxRate / 100) * 100) / 100
|
|
const oneTimeGross = Math.round((oneTimeNet + oneTimeTax) * 100) / 100
|
|
|
|
const monthlyNet = monthlyItems.reduce((sum, item) => sum + (item.item_total || 0), 0)
|
|
const monthlyTax = Math.round(monthlyNet * (taxRate / 100) * 100) / 100
|
|
const monthlyGross = Math.round((monthlyNet + monthlyTax) * 100) / 100
|
|
|
|
const statusLabel: Record<string, string> = {
|
|
pending: 'Eingegangen',
|
|
active: 'In Bearbeitung',
|
|
cancelled: 'Storniert',
|
|
rejected: 'Abgelehnt',
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#020617] text-white flex items-center justify-center px-4 py-16">
|
|
<div className="w-full max-w-2xl space-y-6">
|
|
{/* Erfolgsmeldung */}
|
|
<div className="text-center space-y-4">
|
|
<div className="w-24 h-24 bg-green-500/10 rounded-full flex items-center justify-center mx-auto border border-green-500/30 animate-pulse">
|
|
<CheckCircle2 className="w-12 h-12 text-green-400" />
|
|
</div>
|
|
<h1 className="text-4xl font-extrabold tracking-tight text-white">
|
|
Anfrage eingegangen!
|
|
</h1>
|
|
<p className="text-slate-400 text-lg">
|
|
Ihre Anfrage wurde erfolgreich gespeichert.
|
|
</p>
|
|
<div className="inline-flex items-center gap-2 bg-white/5 border border-white/10 rounded-full px-5 py-2 font-mono text-lg font-bold text-primary">
|
|
#{o.order_number}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Anfrage-Details */}
|
|
<Card className="glass-dark border-white/10">
|
|
<CardHeader className="flex flex-row items-center justify-between">
|
|
<CardTitle className="text-white">Anfrageübersicht</CardTitle>
|
|
<Badge
|
|
className={
|
|
o.status === 'active'
|
|
? 'bg-green-500/20 text-green-400 border-green-500/30'
|
|
: o.status === 'cancelled'
|
|
? 'bg-red-500/20 text-red-400 border-red-500/30'
|
|
: 'bg-amber-500/20 text-amber-400 border-amber-500/30'
|
|
}
|
|
>
|
|
{statusLabel[o.status] ?? o.status}
|
|
</Badge>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Produkte gruppiert nach Kasse */}
|
|
<div className="space-y-4">
|
|
{Object.entries(groupedItems).map(([deviceName, devItems]) => (
|
|
<div key={deviceName} className="space-y-2 border-b border-white/5 pb-3 last:border-0 last:pb-0">
|
|
<div className="bg-white/5 px-2 py-1 rounded text-xs text-primary font-bold">
|
|
Kasse: {deviceName}
|
|
</div>
|
|
{devItems.map((item) => (
|
|
<div key={item.product_id} className="space-y-1 pl-2">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="font-semibold text-white">{item.product_name}</span>
|
|
<span className="text-white">
|
|
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(item.base_price)}
|
|
<span className="text-slate-400 text-xs ml-1">
|
|
{item.billing_interval === 'one_time' ? 'einmalig' : '/ Monat'}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
{item.selected_modules.map((mod) => (
|
|
<div key={mod.module_id} className="flex justify-between text-xs pl-4 text-slate-400">
|
|
<span>+ {mod.module_name}</span>
|
|
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(mod.price)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<Separator className="bg-white/10" />
|
|
|
|
{/* Gesamtbetrag */}
|
|
{oneTimeNet > 0 && (
|
|
<div className="space-y-1">
|
|
{monthlyNet > 0 && <p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Einmaliger Betrag</p>}
|
|
<div className="flex justify-between text-sm text-slate-400">
|
|
<span>Netto:</span>
|
|
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeNet)}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm text-slate-400">
|
|
<span>zzgl. {taxRate}% MwSt.:</span>
|
|
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeTax)}</span>
|
|
</div>
|
|
<div className="flex justify-between text-base font-bold text-white">
|
|
<span>Brutto Gesamtbetrag:</span>
|
|
<span className={monthlyNet > 0 ? "text-white" : "text-gradient"}>
|
|
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(oneTimeGross)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{oneTimeNet > 0 && monthlyNet > 0 && (
|
|
<Separator className="bg-white/5 my-2" />
|
|
)}
|
|
|
|
{monthlyNet > 0 && (
|
|
<div className="space-y-1">
|
|
{oneTimeNet > 0 && <p className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Monatlicher Betrag</p>}
|
|
<div className="flex justify-between text-sm text-slate-400">
|
|
<span>Netto:</span>
|
|
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyNet)} / Monat</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm text-slate-400">
|
|
<span>zzgl. {taxRate}% MwSt.:</span>
|
|
<span>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyTax)} / Monat</span>
|
|
</div>
|
|
<div className="flex justify-between text-base font-bold text-white">
|
|
<span>Brutto Gesamtbetrag:</span>
|
|
<span className="text-gradient">
|
|
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(monthlyGross)} / Monat
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{o.order_data?.price_multiplier !== null && o.order_data?.price_multiplier !== undefined && (
|
|
<div className="text-sm text-green-400 font-semibold bg-green-500/10 p-3 rounded-lg border border-green-500/20 space-y-1">
|
|
<p>Update-Rabatt angewendet:</p>
|
|
<p className="text-xs font-normal text-slate-300">
|
|
{o.order_data.price_multiplier === 0
|
|
? "Update (0-12 Monate): 100% Rabatt (inklusive)"
|
|
: o.order_data.price_multiplier === 0.15
|
|
? "Update (13-14 Monate): 15% vom Listenpreis"
|
|
: o.order_data.price_multiplier === 0.30
|
|
? "Update (15-25 Monate): 30% vom Listenpreis"
|
|
: o.order_data.price_multiplier === 0.45
|
|
? "Update (26-37 Monate): 45% vom Listenpreis"
|
|
: o.order_data.price_multiplier === 0.60
|
|
? "Update (38-49 Monate): 60% vom Listenpreis"
|
|
: o.order_data.price_multiplier === 0.90
|
|
? "Neukauf (>= 50 Monate): 10% Rabatt"
|
|
: `Gebühr: ${o.order_data.price_multiplier * 100}%`}
|
|
</p>
|
|
{o.order_data.last_license_date && (
|
|
<p className="text-xs font-normal text-slate-400 flex items-center gap-1">
|
|
<Calendar className="w-3.5 h-3.5 text-primary" />
|
|
Letzte Lizenz vom: {new Date(o.order_data.last_license_date).toLocaleDateString('de-DE')}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<Separator className="bg-white/10" />
|
|
|
|
{/* Kundendaten */}
|
|
<div className="text-sm text-slate-400 space-y-1">
|
|
<p className="font-semibold text-white">{o.customer_data?.company_name}</p>
|
|
<p>{o.customer_data?.first_name} {o.customer_data?.last_name}</p>
|
|
<p>{o.customer_data?.address}, {o.customer_data?.zip_code} {o.customer_data?.city}</p>
|
|
{o.customer_data?.bank_iban && (
|
|
<div className="text-xs text-slate-500 pt-2 border-t border-white/5 mt-2 space-y-0.5">
|
|
<p><span className="font-semibold text-slate-400">Kontoinhaber:</span> {o.customer_data.bank_owner}</p>
|
|
<p><span className="font-semibold text-slate-400">IBAN:</span> {o.customer_data.bank_iban}</p>
|
|
{o.customer_data.bank_bic && <p><span className="font-semibold text-slate-400">BIC:</span> {o.customer_data.bank_bic}</p>}
|
|
{o.customer_data.bank_name && <p><span className="font-semibold text-slate-400">Bank:</span> {o.customer_data.bank_name}</p>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* PDF-Download */}
|
|
{o.pdf_url && (
|
|
<div className="flex gap-2 pt-2">
|
|
<Button variant="outline" size="sm" asChild className="border-white/10 hover:bg-white/10">
|
|
<a href={`/api/orders/${o.id}/download?inline=true`} target="_blank" rel="noopener noreferrer">
|
|
<ExternalLink className="w-4 h-4 mr-2" /> Anfragebestätigung ansehen
|
|
</a>
|
|
</Button>
|
|
<Button variant="secondary" size="sm" asChild>
|
|
<a href={`/api/orders/${o.id}/download`} download>
|
|
<Download className="w-4 h-4 mr-2" /> Herunterladen
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Aktions-Buttons */}
|
|
<div className="flex gap-3">
|
|
<Link href="/my-orders" className="flex-1">
|
|
<Button variant="outline" className="w-full border-white/10 hover:bg-white/5">
|
|
<ShoppingBag className="w-4 h-4 mr-2" />
|
|
Meine Bestellungen
|
|
</Button>
|
|
</Link>
|
|
<Link href="/" className="flex-1">
|
|
<Button className="w-full">
|
|
Zurück zur Startseite <ArrowRight className="w-4 h-4 ml-2" />
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|