Refine order list layout and client card

This commit is contained in:
Ruslan Bakiev
2026-04-04 14:30:50 +07:00
parent 3bee6c370a
commit 254fa45ced
6 changed files with 66 additions and 115 deletions

View File

@@ -14,14 +14,17 @@ const statusLabel = computed(() => {
});
const className = computed(() => {
if (props.status === 'COMPLETED') return 'badge badge-success';
if (props.status === 'CLIENT_REJECTED' || props.status === 'MANAGER_REJECTED') return 'badge badge-error';
if (props.status === 'MANAGER_BLOCKED') return 'badge badge-warning';
if (props.status === 'NEW' || props.status === 'MANAGER_PROCESSING') return 'badge badge-warning';
return 'badge badge-info';
if (props.status === 'COMPLETED') return 'bg-[#139957]';
if (props.status === 'CLIENT_REJECTED' || props.status === 'MANAGER_REJECTED') return 'bg-[#d94b55]';
if (props.status === 'MANAGER_BLOCKED') return 'bg-[#f1a43a]';
if (props.status === 'NEW' || props.status === 'MANAGER_PROCESSING') return 'bg-[#f1a43a]';
return 'bg-[#2e8de4]';
});
</script>
<template>
<span :class="className">{{ statusLabel }}</span>
<span class="inline-flex items-center gap-2 text-sm font-semibold text-[#123824]">
<span class="h-2.5 w-2.5 rounded-full" :class="className" />
<span>{{ statusLabel }}</span>
</span>
</template>

View File

@@ -8,12 +8,6 @@ type OrderCardItem = {
quantity: number;
};
type CustomerCardMeta = {
name?: string | null;
avatarSrc?: string | null;
initials?: string | null;
};
const props = defineProps<{
to: string;
code: string;
@@ -21,7 +15,6 @@ const props = defineProps<{
createdAt: string | Date;
totalPrice?: number | null;
items: OrderCardItem[];
customer?: CustomerCardMeta | null;
}>();
const DATE_FORMATTER = new Intl.DateTimeFormat('ru-RU', {
@@ -41,33 +34,6 @@ function formatCreatedAt(value: string | Date) {
return DATE_FORMATTER.format(new Date(value));
}
function buildInitials(value?: string | null) {
const parts = String(value ?? '')
.trim()
.split(/\s+/)
.filter(Boolean)
.slice(0, 2);
if (!parts.length) {
return 'FR';
}
return parts.map((part) => part.charAt(0).toUpperCase()).join('');
}
function itemCountLabel(count: number) {
const mod10 = count % 10;
const mod100 = count % 100;
if (mod10 === 1 && mod100 !== 11) {
return `${count} позиция`;
}
if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) {
return `${count} позиции`;
}
return `${count} позиций`;
}
function createProductCover(name: string, seedKey: string) {
const seed = `${name}${seedKey}`.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
const [start, middle, finish] = coverPresets[seed % coverPresets.length];
@@ -96,11 +62,7 @@ function createProductCover(name: string, seedKey: string) {
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 customerName = computed(() => String(props.customer?.name ?? '').trim());
const customerInitials = computed(() => (
String(props.customer?.initials ?? '').trim() || buildInitials(customerName.value)
));
const totalPriceLabel = computed(() => formatPrice(props.totalPrice) ?? '');
</script>
<template>
@@ -108,44 +70,14 @@ const customerInitials = computed(() => (
:to="to"
class="surface-card group block rounded-[30px] border border-[#dbece2] bg-white px-4 py-4 transition hover:-translate-y-0.5 hover:shadow-[0_18px_40px_rgba(18,56,36,0.08)] md:px-5"
>
<div class="flex items-center gap-4">
<div v-if="customer" class="shrink-0">
<img
v-if="customer?.avatarSrc"
:src="customer.avatarSrc"
:alt="customerName || code"
class="h-14 w-14 rounded-[20px] object-cover"
>
<div
v-else
class="flex h-14 w-14 items-center justify-center rounded-[20px] bg-[linear-gradient(135deg,#dff7e9_0%,#c2ead3_100%)] text-base font-black text-[#123824]"
>
{{ customerInitials }}
</div>
<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]">{{ code }}</h2>
<p class="mt-1 text-sm text-[#688676]">{{ formatCreatedAt(createdAt) }}</p>
</div>
<div class="min-w-0 flex-1">
<div class="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<h2 class="text-lg font-bold text-[#123824]">{{ code }}</h2>
<p class="text-sm text-[#688676]">{{ formatCreatedAt(createdAt) }}</p>
</div>
<p v-if="customerName" class="mt-1 truncate text-sm font-semibold text-[#355947]">
{{ customerName }}
</p>
</div>
<div class="flex flex-wrap items-center gap-3 lg:justify-end">
<div class="text-left lg:text-right">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Сумма</p>
<p class="text-sm font-bold text-[#123824]">{{ totalPriceLabel }}</p>
</div>
<OrderStatusBadge :status="status" />
</div>
</div>
<div class="mt-3 flex flex-wrap items-center gap-3">
<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"
@@ -159,19 +91,23 @@ const customerInitials = computed(() => (
class="h-full w-full object-cover"
>
</div>
<div
v-if="hiddenCount > 0"
class="flex h-11 w-11 items-center justify-center rounded-[16px] border-2 border-white bg-[#123824] text-xs font-bold text-white shadow-[0_6px_16px_rgba(18,56,36,0.14)]"
>
+{{ hiddenCount }}
</div>
</div>
<p class="text-sm text-[#5c7b69]">
{{ itemCountLabel(items.length) }}
</p>
<div
v-if="hiddenCount > 0"
class="flex h-11 w-11 items-center justify-center rounded-[16px] bg-[#123824] text-xs font-bold text-white shadow-[0_6px_16px_rgba(18,56,36,0.14)]"
>
+{{ 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>