187 lines
5.6 KiB
Vue
187 lines
5.6 KiB
Vue
<template>
|
||
<el-card class="order-summary-panel" shadow="never">
|
||
<template #header>
|
||
<span>Summary</span>
|
||
</template>
|
||
|
||
<el-descriptions :column="1" direction="vertical" class="order-summary-panel__details">
|
||
<el-descriptions-item label="Order ID">
|
||
<span class="mono text-break">{{ order.id }}</span>
|
||
</el-descriptions-item>
|
||
|
||
<el-descriptions-item label="Access token">
|
||
<el-input
|
||
class="mono access-token-input"
|
||
:model-value="order.accessToken"
|
||
type="password"
|
||
show-password
|
||
readonly
|
||
/>
|
||
</el-descriptions-item>
|
||
|
||
<el-descriptions-item label="Created">{{ formatDate(order.createdAt) }}</el-descriptions-item>
|
||
|
||
<el-descriptions-item v-if="order.failureReason" label="Failure reason">
|
||
<span class="failure-reason">{{ formatOrderFailureReason(order.failureReason) }}</span>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<div class="order-summary-panel__totals order-totals pt-24">
|
||
<div class="flex justify-between gap-12 mb-12">
|
||
<span>Subtotal</span>
|
||
<span>{{ subtotalFormatted }}</span>
|
||
</div>
|
||
|
||
<p v-if="discounts.length > 0" class="order-totals__section m-0 mb-8">Discounts</p>
|
||
|
||
<div
|
||
v-for="discount in discounts"
|
||
:key="discount.id"
|
||
class="flex justify-between gap-12 mb-4 order-totals__discount"
|
||
>
|
||
<span>{{ discount.code }}</span>
|
||
<span>−{{ formatFiatPrice(discount.amountFiat, fiatCurrency) }}</span>
|
||
</div>
|
||
|
||
<div class="flex justify-between gap-12 mt-12 mb-12">
|
||
<span>Total discounts</span>
|
||
<span>{{ discountTotalFormatted }}</span>
|
||
</div>
|
||
|
||
<template v-if="hasManualLines">
|
||
<div class="flex justify-between gap-12 mt-12 mb-12 order-totals__order-total">
|
||
<span>Total</span>
|
||
<span>{{ totalFormatted }}</span>
|
||
</div>
|
||
|
||
<div class="flex justify-between gap-12 mb-12">
|
||
<span>Shipping</span>
|
||
<span>{{ shippingFormatted }}</span>
|
||
</div>
|
||
|
||
<div class="flex justify-between gap-12 order-totals__total pt-8">
|
||
<span>Grand total</span>
|
||
<span>{{ grandTotalFormatted }}</span>
|
||
</div>
|
||
</template>
|
||
|
||
<div v-else class="flex justify-between gap-12 order-totals__total pt-8 mt-12">
|
||
<span>Total</span>
|
||
<span>{{ totalFormatted }}</span>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, type PropType } from 'vue';
|
||
import type { OrderExtended } from '@/types/order/OrderExtended';
|
||
import { DeliveryMode } from '@/types/product/DeliveryMode';
|
||
import { formatDate } from '@/utils/formatDate';
|
||
import { formatFiatPrice } from '@/utils/formatFiatPrice';
|
||
import { formatOrderFailureReason } from '@/utils/order/formatOrderFailureReason';
|
||
|
||
const props = defineProps({
|
||
order: {
|
||
type: Object as PropType<OrderExtended>,
|
||
required: true
|
||
}
|
||
});
|
||
|
||
const fiatCurrency = computed(() => props.order.fiatCurrency);
|
||
|
||
const discounts = computed(() => props.order.discounts ?? []);
|
||
|
||
const hasManualLines = computed(() =>
|
||
(props.order.lines ?? []).some(line => line.deliveryMode === DeliveryMode.Manual)
|
||
);
|
||
|
||
const subtotalFormatted = computed(() => formatFiatPrice(props.order.totals.subtotalFiat, fiatCurrency.value));
|
||
|
||
const discountTotalFormatted = computed(() =>
|
||
formatOrderDiscountTotal(props.order.totals.discountTotalFiat, fiatCurrency.value)
|
||
);
|
||
|
||
const totalFormatted = computed(() => formatFiatPrice(props.order.totals.totalFiat, fiatCurrency.value));
|
||
|
||
const shippingFormatted = computed(() => {
|
||
const shippingAmount = props.order.totals.shippingCostFiat;
|
||
|
||
if (shippingAmount === null) {
|
||
return '—';
|
||
}
|
||
|
||
return formatFiatPrice(shippingAmount, fiatCurrency.value);
|
||
});
|
||
|
||
const grandTotalFormatted = computed(() => {
|
||
const grandTotal = props.order.totals.grandTotalFiat ?? props.order.totals.totalFiat;
|
||
|
||
return formatFiatPrice(grandTotal, fiatCurrency.value);
|
||
});
|
||
|
||
const formatOrderDiscountTotal = (discountTotalFiat: number, currency: string): string => {
|
||
if (discountTotalFiat <= 0) {
|
||
return formatFiatPrice(0, currency);
|
||
}
|
||
|
||
return `−${formatFiatPrice(discountTotalFiat, currency)}`;
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.order-summary-panel {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
:deep(.el-card__body) {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
}
|
||
}
|
||
|
||
.order-summary-panel__details {
|
||
flex-shrink: 0;
|
||
|
||
:deep(.el-descriptions__content) {
|
||
min-width: 0;
|
||
}
|
||
}
|
||
|
||
.order-summary-panel__totals {
|
||
flex-shrink: 0;
|
||
margin-top: auto;
|
||
}
|
||
|
||
.access-token-input {
|
||
width: 100%;
|
||
max-width: min(350px, 100%);
|
||
}
|
||
|
||
.failure-reason {
|
||
color: var(--el-color-danger);
|
||
}
|
||
|
||
.order-totals__section {
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
|
||
.order-totals__discount {
|
||
color: var(--el-text-color-secondary);
|
||
font-size: var(--el-font-size-small);
|
||
}
|
||
|
||
.order-totals__order-total {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.order-totals__total {
|
||
font-size: var(--el-font-size-large);
|
||
font-weight: 600;
|
||
border-top: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
</style>
|