Files
my2/app/components/Order/OrderSummary.vue
Bastian Masanek 782bd6cdd7 Enhance CheckoutForm and Order components with user data integration
- Refactored CheckoutForm.vue to utilize an extended user type, incorporating additional address fields for improved user data handling.
- Updated OrderSummary.vue to conditionally display salutation based on user input.
- Standardized error alert styling across multiple pages, changing variant from 'destructive' to 'error' for consistency.
- Adjusted button styles in various components to align with the new 'experimenta' variant.

These changes aim to improve user experience by ensuring accurate data representation and consistent UI elements across the checkout and order processes.
2025-11-03 17:00:22 +01:00

190 lines
5.2 KiB
Vue

<script setup lang="ts">
/**
* OrderSummary Component
*
* Displays order details including:
* - Product list with quantities and prices
* - Subtotal, VAT, and total
* - Billing address
*
* Used on:
* - Order confirmation page (/bestellung/bestaetigen/[orderId])
* - Order success page (/bestellung/erfolg/[orderId])
*/
interface OrderItem {
id: string
productId: string
quantity: number
priceSnapshot: string
productSnapshot: {
name: string
description?: string
}
product?: {
name: string
imageUrl?: string | null
}
subtotal: number
}
interface BillingAddress {
salutation: 'male' | 'female' | 'other'
firstName: string
lastName: string
dateOfBirth: string
street: string
postCode: string
city: string
countryCode: string
}
interface Order {
id: string
orderNumber: string
totalAmount: string | number
status: string
billingAddress: BillingAddress
items: OrderItem[]
createdAt: Date | string
}
interface Props {
order: Order
/**
* Show billing address section
*/
showAddress?: boolean
}
const props = withDefaults(defineProps<Props>(), {
showAddress: true,
})
// Format currency in EUR
const formatCurrency = (amount: number | string) => {
const value = typeof amount === 'string' ? Number.parseFloat(amount) : amount
return new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(value)
}
// Calculate total from items (for client-side display)
const total = computed(() => {
return typeof props.order.totalAmount === 'string'
? Number.parseFloat(props.order.totalAmount)
: props.order.totalAmount
})
// Calculate VAT (7% already included in total)
const vatAmount = computed(() => {
return total.value * (0.07 / 1.07)
})
// Format salutation
const formatSalutation = (salutation: string) => {
const map: Record<string, string> = {
male: 'Herr',
female: 'Frau',
other: 'Keine Angabe',
}
return map[salutation] || salutation
}
// Format date
const formatDate = (date: Date | string) => {
const d = typeof date === 'string' ? new Date(date) : date
return new Intl.DateTimeFormat('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
}).format(d)
}
</script>
<template>
<div class="space-y-6">
<!-- Order Header -->
<div class="pb-4 border-b border-white/20">
<h2 class="text-2xl font-bold text-white">Bestellübersicht</h2>
<p class="text-sm text-white/60 mt-1">
Bestellnummer: <span class="font-mono text-white/80">{{ order.orderNumber }}</span>
</p>
<p class="text-sm text-white/60">
Erstellt am: {{ formatDate(order.createdAt) }}
</p>
</div>
<!-- Order Items -->
<div class="space-y-4">
<h3 class="text-lg font-semibold text-white">Artikel</h3>
<div class="space-y-3">
<div
v-for="item in order.items"
:key="item.id"
class="flex items-start justify-between gap-4 p-4 rounded-lg bg-white/5 border border-white/10"
>
<div class="flex-1">
<h4 class="font-medium text-white">
{{ item.productSnapshot?.name || item.product?.name }}
</h4>
<p class="text-sm text-white/60 mt-1">
{{ item.quantity }}x {{ formatCurrency(item.priceSnapshot) }}
</p>
</div>
<div class="text-right">
<p class="font-semibold text-white">
{{ formatCurrency(item.subtotal) }}
</p>
</div>
</div>
</div>
</div>
<!-- Price Breakdown -->
<div class="space-y-3 pt-4 border-t border-white/20">
<!-- Subtotal -->
<div class="flex items-center justify-between text-white/80">
<span class="text-sm">Zwischensumme</span>
<span class="font-medium">{{ formatCurrency(total) }}</span>
</div>
<!-- VAT (included) -->
<div class="flex items-center justify-between text-white/60 text-sm">
<span>inkl. MwSt. (7%)</span>
<span>{{ formatCurrency(vatAmount) }}</span>
</div>
<!-- Total -->
<div class="flex items-center justify-between pt-3 border-t border-white/20">
<span class="text-lg font-bold text-white">Gesamt</span>
<span class="text-2xl font-bold text-experimenta-accent">
{{ formatCurrency(total) }}
</span>
</div>
</div>
<!-- Billing Address -->
<div v-if="showAddress" class="space-y-3 pt-6 border-t border-white/20">
<h3 class="text-lg font-semibold text-white">Rechnungsadresse</h3>
<div class="p-4 rounded-lg bg-white/5 border border-white/10 space-y-1 text-sm">
<p class="text-white">
<template v-if="order.billingAddress.salutation !== 'other'">
{{ formatSalutation(order.billingAddress.salutation) }}
</template>
{{ order.billingAddress.firstName }}
{{ order.billingAddress.lastName }}
</p>
<p class="text-white/80">{{ order.billingAddress.street }}</p>
<p class="text-white/80">
{{ order.billingAddress.postCode }} {{ order.billingAddress.city }}
</p>
<p class="text-white/80">{{ order.billingAddress.countryCode }}</p>
</div>
</div>
</div>
</template>