101 lines
3.3 KiB
Vue
101 lines
3.3 KiB
Vue
<script setup lang="ts">
|
||
import OrderStatusBadge from '~/components/orders/OrderStatusBadge.vue';
|
||
import { formatOrderCode } from '~/composables/useOrderCodePresentation';
|
||
import { formatPrice } from '~/composables/useOrderDetailPresentation';
|
||
|
||
type OrderCardItem = {
|
||
id: string;
|
||
productName: string;
|
||
quantity: number;
|
||
};
|
||
|
||
const props = defineProps<{
|
||
to: string;
|
||
code: string;
|
||
status: string;
|
||
createdAt: string | Date;
|
||
totalPrice?: number | null;
|
||
items: OrderCardItem[];
|
||
}>();
|
||
|
||
const DATE_FORMATTER = new Intl.DateTimeFormat('ru-RU', {
|
||
day: '2-digit',
|
||
month: 'short',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
});
|
||
|
||
const coverPresets = ['#edf3ef', '#f1f4ee', '#edf2f4'];
|
||
|
||
function formatCreatedAt(value: string | Date) {
|
||
return DATE_FORMATTER.format(new Date(value));
|
||
}
|
||
|
||
function createProductCover(name: string, seedKey: string) {
|
||
const seed = `${name}${seedKey}`.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||
const background = coverPresets[seed % coverPresets.length];
|
||
const firstLetter = name.trim().charAt(0).toUpperCase() || 'P';
|
||
|
||
const svg = `
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88 88">
|
||
<rect width="88" height="88" fill="${background}" rx="24" />
|
||
<text x="50%" y="56%" text-anchor="middle" fill="#11412c" font-family="Manrope, sans-serif" font-size="34" font-weight="700">${firstLetter}</text>
|
||
</svg>
|
||
`.trim();
|
||
|
||
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
||
}
|
||
|
||
const visibleItems = computed(() => props.items.slice(0, 4));
|
||
const hiddenCount = computed(() => Math.max(props.items.length - visibleItems.value.length, 0));
|
||
const totalPriceLabel = computed(() => formatPrice(props.totalPrice) ?? '—');
|
||
const codeLabel = computed(() => formatOrderCode(props.code));
|
||
</script>
|
||
|
||
<template>
|
||
<NuxtLink
|
||
:to="to"
|
||
class="surface-card surface-card-interactive group block rounded-[30px] bg-white px-4 py-4 md:px-5"
|
||
>
|
||
<div class="grid gap-4 md:grid-cols-4 md:items-center md:gap-6">
|
||
<div class="min-w-0">
|
||
<h2 class="truncate text-lg font-bold text-[#123824]">{{ codeLabel }}</h2>
|
||
<p class="mt-1 text-sm text-[#688676]">{{ formatCreatedAt(createdAt) }}</p>
|
||
</div>
|
||
|
||
<div class="min-w-0">
|
||
<div class="flex flex-wrap items-center gap-2 md:gap-1">
|
||
<div class="flex -space-x-3">
|
||
<div
|
||
v-for="item in visibleItems"
|
||
:key="item.id"
|
||
class="h-11 w-11 overflow-hidden rounded-[16px] bg-[#edf3ef]"
|
||
:title="`${item.productName} × ${item.quantity}`"
|
||
>
|
||
<img
|
||
:src="createProductCover(item.productName, item.id)"
|
||
:alt="item.productName"
|
||
class="h-full w-full object-cover"
|
||
>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-if="hiddenCount > 0"
|
||
class="flex h-11 w-11 items-center justify-center rounded-[16px] bg-[#edf3ef] text-xs font-bold text-[#123824]"
|
||
>
|
||
+{{ hiddenCount }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center md:justify-center">
|
||
<OrderStatusBadge :status="status" />
|
||
</div>
|
||
|
||
<div class="text-left md:text-right">
|
||
<p class="text-base font-bold text-[#123824]">{{ totalPriceLabel }}</p>
|
||
</div>
|
||
</div>
|
||
</NuxtLink>
|
||
</template>
|