Refine order code and calendar cards

This commit is contained in:
Ruslan Bakiev
2026-04-04 15:27:00 +07:00
parent 309d0e78db
commit e20565b4ae
6 changed files with 167 additions and 9 deletions

View File

@@ -11,6 +11,7 @@ import {
orderLogisticsStateText,
orderDeliveryStateText,
} from '~/composables/useOrderDetailPresentation';
import { formatOrderCode } from '~/composables/useOrderCodePresentation';
definePageMeta({
middleware: ['manager-only'],
@@ -38,6 +39,8 @@ const currentOrder = computed<ManagerOrderItem | null>(() =>
orderQuery.result.value?.order ?? null,
);
const currentOrderCode = computed(() => formatOrderCode(currentOrder.value?.code));
watch(
currentOrder,
(order) => {
@@ -209,7 +212,7 @@ watch(
<template v-else>
<div class="manager-hero">
<p class="manager-eyebrow">Заказ</p>
<h1 class="manager-title">{{ currentOrder.code }}</h1>
<h1 class="manager-title">{{ currentOrderCode }}</h1>
</div>
<div class="space-y-4">

View File

@@ -10,6 +10,8 @@ import {
type ManagerUsersQuery,
} from '~/composables/graphql/generated';
import { messengerConnectionAvatarSrc } from '~/composables/useMessengerConnectionPresentation';
import { formatOrderCode } from '~/composables/useOrderCodePresentation';
import { formatPrice } from '~/composables/useOrderDetailPresentation';
definePageMeta({
middleware: ['manager-only'],
@@ -52,6 +54,7 @@ function customerCardMeta(customerId: string) {
return {
name: customerId,
avatarSrc: '',
fallbackAvatarSrc: '/favicon.ico',
initials: customerId.slice(0, 2).toUpperCase(),
};
}
@@ -59,6 +62,7 @@ function customerCardMeta(customerId: string) {
return {
name: customer.fullName,
avatarSrc: messengerConnectionAvatarSrc(customer.telegramConnection),
fallbackAvatarSrc: '/favicon.ico',
initials: customer.fullName
.split(/\s+/)
.filter(Boolean)
@@ -95,6 +99,7 @@ const searchedOrders = computed<ManagerOrderItem[]>(() => {
return orders.filter((order) => {
const text = [
order.code,
formatOrderCode(order.code),
order.customerId,
customerCardMeta(order.customerId).name,
order.deliveryAddress || '',
@@ -109,6 +114,15 @@ const searchedOrders = computed<ManagerOrderItem[]>(() => {
const filteredOrders = computed<ManagerOrderItem[]>(() => searchedOrders.value.filter((order) => matchesFilter(order)));
function escapeHtml(value: string) {
return String(value)
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
const statusTabs = computed<Array<{ id: StatusFilter; label: string; count: number }>>(() => [
{
id: 'ALL',
@@ -152,12 +166,50 @@ const calendarOptions = computed(() => ({
buttonText: {
today: 'Сегодня',
},
events: filteredOrders.value.map((order: ManagerOrderItem) => ({
id: order.id,
title: `${order.code}${order.customerId}`,
start: new Date(order.createdAt).toISOString(),
allDay: true,
})),
events: filteredOrders.value.map((order: ManagerOrderItem) => {
const customer = customerCardMeta(order.customerId);
return {
id: order.id,
title: formatOrderCode(order.code),
start: new Date(order.createdAt).toISOString(),
allDay: true,
extendedProps: {
customerName: customer.name,
avatarSrc: customer.avatarSrc,
fallbackAvatarSrc: customer.fallbackAvatarSrc,
initials: customer.initials,
orderCode: formatOrderCode(order.code),
totalPriceLabel: formatPrice(order.totalPrice) ?? 'Цена уточняется',
},
};
}),
eventContent: ({ event }: { event: { extendedProps: Record<string, string> } }) => {
const customerName = escapeHtml(event.extendedProps.customerName || '');
const avatarSrc = event.extendedProps.avatarSrc || event.extendedProps.fallbackAvatarSrc;
const orderCode = escapeHtml(event.extendedProps.orderCode || '');
const totalPriceLabel = escapeHtml(event.extendedProps.totalPriceLabel || '');
const initials = escapeHtml(event.extendedProps.initials || '');
return {
html: `
<div class="manager-calendar-order-card">
<div class="manager-calendar-order-card__header">
<div class="manager-calendar-order-card__avatar-shell">
${avatarSrc
? `<img src="${escapeHtml(avatarSrc)}" alt="${customerName}" class="manager-calendar-order-card__avatar">`
: `<span class="manager-calendar-order-card__initials">${initials}</span>`}
</div>
<div class="manager-calendar-order-card__text">
<div class="manager-calendar-order-card__code">${orderCode}</div>
<div class="manager-calendar-order-card__name">${customerName}</div>
</div>
</div>
<div class="manager-calendar-order-card__price">${totalPriceLabel}</div>
</div>
`,
};
},
eventClick: ({ event }: { event: { id: string } }) => {
void router.push(`/client-orders/${event.id}`);
},
@@ -238,3 +290,82 @@ const calendarOptions = computed(() => ({
</div>
</section>
</template>
<style>
.manager-calendar-order-card {
display: grid;
gap: 0.45rem;
min-height: 74px;
border-radius: 18px;
border: 1px solid #dcebe3;
background: linear-gradient(180deg, #ffffff 0%, #f4faf6 100%);
padding: 0.62rem 0.72rem;
box-shadow: 0 10px 24px rgba(18, 56, 36, 0.08);
}
.manager-calendar-order-card__header {
display: flex;
align-items: center;
gap: 0.55rem;
min-width: 0;
}
.manager-calendar-order-card__avatar-shell {
width: 34px;
height: 34px;
flex: 0 0 auto;
overflow: hidden;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #e4f6eb 0%, #c6e7d5 100%);
}
.manager-calendar-order-card__avatar {
width: 100%;
height: 100%;
object-fit: cover;
}
.manager-calendar-order-card__initials {
font-size: 0.78rem;
font-weight: 800;
color: #123824;
}
.manager-calendar-order-card__text {
min-width: 0;
}
.manager-calendar-order-card__code {
font-size: 0.88rem;
line-height: 1.1;
font-weight: 800;
color: #123824;
}
.manager-calendar-order-card__name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.72rem;
color: #5a7968;
}
.manager-calendar-order-card__price {
font-size: 0.78rem;
font-weight: 700;
color: #0d854a;
}
.fc .fc-daygrid-event {
border: 0;
background: transparent;
}
.fc .fc-daygrid-dot-event:hover,
.fc .fc-daygrid-event:hover {
background: transparent;
}
</style>