Simplify order and cart cards
This commit is contained in:
@@ -31,31 +31,16 @@ const emit = defineEmits<{
|
|||||||
remove: [itemId: string];
|
remove: [itemId: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const coverPresets = [
|
const coverPresets = ['#edf3ef', '#f1f4ee', '#edf2f4'];
|
||||||
['#d9f5e6', '#9ce8c1', '#6fd09d'],
|
|
||||||
['#eaf9ef', '#b3e8cb', '#76c89f'],
|
|
||||||
['#e8f5ec', '#b2e0c6', '#7dd0a9'],
|
|
||||||
];
|
|
||||||
|
|
||||||
function createProductCover(name: string, seedKey: string) {
|
function createProductCover(name: string, seedKey: string) {
|
||||||
const seed = `${name}${seedKey}`.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
const seed = `${name}${seedKey}`.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
const [start, middle, finish] = coverPresets[seed % coverPresets.length];
|
const background = coverPresets[seed % coverPresets.length];
|
||||||
const firstLetter = name.trim().charAt(0).toUpperCase() || 'P';
|
const firstLetter = name.trim().charAt(0).toUpperCase() || 'P';
|
||||||
|
|
||||||
const svg = `
|
const svg = `
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
|
||||||
<defs>
|
<rect width="120" height="120" fill="${background}" rx="28" />
|
||||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
||||||
<stop offset="0%" stop-color="${start}" />
|
|
||||||
<stop offset="55%" stop-color="${middle}" />
|
|
||||||
<stop offset="100%" stop-color="${finish}" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<rect width="120" height="120" fill="url(#g)" rx="28" />
|
|
||||||
<g opacity="0.15">
|
|
||||||
<circle cx="96" cy="22" r="28" fill="#0f7a49" />
|
|
||||||
<circle cx="18" cy="106" r="30" fill="#0f7a49" />
|
|
||||||
</g>
|
|
||||||
<text x="50%" y="57%" text-anchor="middle" fill="#11412c" font-family="Manrope, sans-serif" font-size="44" font-weight="700">${firstLetter}</text>
|
<text x="50%" y="57%" text-anchor="middle" fill="#11412c" font-family="Manrope, sans-serif" font-size="44" font-weight="700">${firstLetter}</text>
|
||||||
</svg>
|
</svg>
|
||||||
`.trim();
|
`.trim();
|
||||||
@@ -168,7 +153,7 @@ function itemParameters(item: OrderItemView) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="isFramed ? 'surface-card rounded-3xl p-5' : ''">
|
<div :class="isFramed ? 'surface-card rounded-3xl p-5' : ''">
|
||||||
<div class="hidden border-b border-[#deebe4] pb-3 md:grid md:grid-cols-[minmax(0,1.8fr)_140px_160px_140px] md:gap-4">
|
<div class="hidden pb-1 md:grid md:grid-cols-[minmax(0,1.8fr)_140px_160px_140px] md:gap-4">
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Позиция</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Позиция</p>
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Цена</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Цена</p>
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Количество</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Количество</p>
|
||||||
@@ -179,13 +164,13 @@ function itemParameters(item: OrderItemView) {
|
|||||||
<li
|
<li
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="rounded-[28px] border border-[#dceae2] bg-[linear-gradient(180deg,#ffffff_0%,#f8fcf9_100%)] p-4 md:grid md:grid-cols-[minmax(0,1.8fr)_140px_160px_140px] md:gap-4 md:p-5"
|
class="surface-card rounded-[28px] p-4 md:grid md:grid-cols-[minmax(0,1.8fr)_140px_160px_140px] md:gap-4 md:p-5"
|
||||||
>
|
>
|
||||||
<div class="flex min-w-0 gap-4">
|
<div class="flex min-w-0 gap-4">
|
||||||
<img
|
<img
|
||||||
:src="createProductCover(item.productName, item.id)"
|
:src="createProductCover(item.productName, item.id)"
|
||||||
:alt="item.productName"
|
:alt="item.productName"
|
||||||
class="h-20 w-20 shrink-0 rounded-[24px] object-cover"
|
class="h-20 w-20 shrink-0 rounded-[24px] bg-[#edf3ef] object-cover"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="min-w-0 space-y-2">
|
<div class="min-w-0 space-y-2">
|
||||||
@@ -198,7 +183,7 @@ function itemParameters(item: OrderItemView) {
|
|||||||
<span
|
<span
|
||||||
v-for="parameter in itemParameters(item)"
|
v-for="parameter in itemParameters(item)"
|
||||||
:key="parameter.label"
|
:key="parameter.label"
|
||||||
class="rounded-full bg-[#edf7f1] px-3 py-1 text-xs font-semibold text-[#355947]"
|
class="rounded-full bg-[#f3f5f4] px-3 py-1 text-xs font-semibold text-[#355947]"
|
||||||
>
|
>
|
||||||
{{ parameter.label }}: {{ parameter.value }}
|
{{ parameter.label }}: {{ parameter.value }}
|
||||||
</span>
|
</span>
|
||||||
@@ -215,7 +200,7 @@ function itemParameters(item: OrderItemView) {
|
|||||||
min="0"
|
min="0"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
placeholder="Например, 125.50"
|
placeholder="Например, 125.50"
|
||||||
class="w-full rounded-2xl border border-[#d7e9de] bg-white px-4 py-3 text-sm text-[#123824] outline-none transition focus:border-[#139957] focus:shadow-[0_0_0_3px_rgba(19,153,87,0.12)] disabled:cursor-not-allowed disabled:bg-[#f4f8f5]"
|
class="w-full rounded-2xl bg-[#f3f5f4] px-4 py-3 text-sm text-[#123824] outline-none transition focus:shadow-[0_0_0_3px_rgba(19,153,87,0.12)] disabled:cursor-not-allowed disabled:bg-[#f4f8f5]"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@input="updateUnitPrice(item.id, $event)"
|
@input="updateUnitPrice(item.id, $event)"
|
||||||
>
|
>
|
||||||
@@ -229,7 +214,7 @@ function itemParameters(item: OrderItemView) {
|
|||||||
<div v-if="isCartMode" class="flex flex-wrap items-center gap-2">
|
<div v-if="isCartMode" class="flex flex-wrap items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex h-9 w-9 items-center justify-center rounded-full border border-[#d7e9de] bg-white text-lg font-semibold text-[#123824] transition hover:border-[#139957] hover:text-[#139957]"
|
class="flex h-9 w-9 items-center justify-center rounded-full bg-[#f3f5f4] text-lg font-semibold text-[#123824] transition hover:bg-[#e8efea] hover:text-[#139957]"
|
||||||
@click="decrementItem(item.id)"
|
@click="decrementItem(item.id)"
|
||||||
>
|
>
|
||||||
-
|
-
|
||||||
@@ -237,7 +222,7 @@ function itemParameters(item: OrderItemView) {
|
|||||||
<span class="min-w-8 text-center text-sm font-semibold text-[#123824]">{{ item.quantity }}</span>
|
<span class="min-w-8 text-center text-sm font-semibold text-[#123824]">{{ item.quantity }}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex h-9 w-9 items-center justify-center rounded-full border border-[#d7e9de] bg-white text-lg font-semibold text-[#123824] transition hover:border-[#139957] hover:text-[#139957]"
|
class="flex h-9 w-9 items-center justify-center rounded-full bg-[#f3f5f4] text-lg font-semibold text-[#123824] transition hover:bg-[#e8efea] hover:text-[#139957]"
|
||||||
@click="incrementItem(item.id)"
|
@click="incrementItem(item.id)"
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ const presentation = computed(() => getOrderStatusPresentation(props.status, pro
|
|||||||
|
|
||||||
function itemClass(state: 'done' | 'current' | 'upcoming') {
|
function itemClass(state: 'done' | 'current' | 'upcoming') {
|
||||||
if (state === 'current') {
|
if (state === 'current') {
|
||||||
return 'border-[#139957] bg-[#eef9f2]';
|
return 'bg-white shadow-[0_14px_28px_rgba(19,153,87,0.08)]';
|
||||||
}
|
}
|
||||||
if (state === 'done') {
|
if (state === 'done') {
|
||||||
return 'border-[#d8eadf] bg-white';
|
return 'bg-white';
|
||||||
}
|
}
|
||||||
return 'border-[#e4ede8] bg-[#f7faf8]';
|
return 'bg-white';
|
||||||
}
|
}
|
||||||
|
|
||||||
function markerClass(state: 'done' | 'current' | 'upcoming') {
|
function markerClass(state: 'done' | 'current' | 'upcoming') {
|
||||||
@@ -52,7 +52,7 @@ function markerClass(state: 'done' | 'current' | 'upcoming') {
|
|||||||
<div class="flex items-center gap-3 pt-1">
|
<div class="flex items-center gap-3 pt-1">
|
||||||
<OrderStatusBadge :status="status" />
|
<OrderStatusBadge :status="status" />
|
||||||
<span
|
<span
|
||||||
class="flex h-10 w-10 items-center justify-center rounded-full bg-[#eef5f0] text-[#123824] transition-transform"
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-[#f2f5f3] text-[#123824] transition-transform"
|
||||||
:class="{ 'rotate-180': isExpanded }"
|
:class="{ 'rotate-180': isExpanded }"
|
||||||
>
|
>
|
||||||
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="none">
|
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="none">
|
||||||
@@ -62,11 +62,11 @@ function markerClass(state: 'done' | 'current' | 'upcoming') {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div v-if="isExpanded" class="mt-5 space-y-3 border-t border-[#e1ebe5] pt-5">
|
<div v-if="isExpanded" class="mt-5 space-y-3 pt-1">
|
||||||
<div
|
<div
|
||||||
v-for="stage in presentation.stages"
|
v-for="stage in presentation.stages"
|
||||||
:key="stage.code"
|
:key="stage.code"
|
||||||
class="rounded-3xl border p-4"
|
class="rounded-3xl p-4"
|
||||||
:class="itemClass(stage.state)"
|
:class="itemClass(stage.state)"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
|
|||||||
@@ -25,11 +25,7 @@ const DATE_FORMATTER = new Intl.DateTimeFormat('ru-RU', {
|
|||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
});
|
});
|
||||||
|
|
||||||
const coverPresets = [
|
const coverPresets = ['#edf3ef', '#f1f4ee', '#edf2f4'];
|
||||||
['#d9f5e6', '#9ce8c1', '#6fd09d'],
|
|
||||||
['#eaf9ef', '#b3e8cb', '#76c89f'],
|
|
||||||
['#e8f5ec', '#b2e0c6', '#7dd0a9'],
|
|
||||||
];
|
|
||||||
|
|
||||||
function formatCreatedAt(value: string | Date) {
|
function formatCreatedAt(value: string | Date) {
|
||||||
return DATE_FORMATTER.format(new Date(value));
|
return DATE_FORMATTER.format(new Date(value));
|
||||||
@@ -37,23 +33,12 @@ function formatCreatedAt(value: string | Date) {
|
|||||||
|
|
||||||
function createProductCover(name: string, seedKey: string) {
|
function createProductCover(name: string, seedKey: string) {
|
||||||
const seed = `${name}${seedKey}`.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
const seed = `${name}${seedKey}`.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
const [start, middle, finish] = coverPresets[seed % coverPresets.length];
|
const background = coverPresets[seed % coverPresets.length];
|
||||||
const firstLetter = name.trim().charAt(0).toUpperCase() || 'P';
|
const firstLetter = name.trim().charAt(0).toUpperCase() || 'P';
|
||||||
|
|
||||||
const svg = `
|
const svg = `
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88 88">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88 88">
|
||||||
<defs>
|
<rect width="88" height="88" fill="${background}" rx="24" />
|
||||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
||||||
<stop offset="0%" stop-color="${start}" />
|
|
||||||
<stop offset="55%" stop-color="${middle}" />
|
|
||||||
<stop offset="100%" stop-color="${finish}" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<rect width="88" height="88" fill="url(#g)" rx="24" />
|
|
||||||
<g opacity="0.16">
|
|
||||||
<circle cx="72" cy="18" r="20" fill="#0f7a49" />
|
|
||||||
<circle cx="14" cy="80" r="22" fill="#0f7a49" />
|
|
||||||
</g>
|
|
||||||
<text x="50%" y="56%" text-anchor="middle" fill="#11412c" font-family="Manrope, sans-serif" font-size="34" font-weight="700">${firstLetter}</text>
|
<text x="50%" y="56%" text-anchor="middle" fill="#11412c" font-family="Manrope, sans-serif" font-size="34" font-weight="700">${firstLetter}</text>
|
||||||
</svg>
|
</svg>
|
||||||
`.trim();
|
`.trim();
|
||||||
@@ -84,7 +69,7 @@ const codeLabel = computed(() => formatOrderCode(props.code));
|
|||||||
<div
|
<div
|
||||||
v-for="item in visibleItems"
|
v-for="item in visibleItems"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="h-11 w-11 overflow-hidden rounded-[16px] border-2 border-white bg-[#edf8f1] shadow-[0_6px_16px_rgba(18,56,36,0.08)]"
|
class="h-11 w-11 overflow-hidden rounded-[16px] bg-[#edf3ef]"
|
||||||
:title="`${item.productName} × ${item.quantity}`"
|
:title="`${item.productName} × ${item.quantity}`"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -96,7 +81,7 @@ const codeLabel = computed(() => formatOrderCode(props.code));
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="hiddenCount > 0"
|
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)]"
|
class="flex h-11 w-11 items-center justify-center rounded-[16px] bg-[#edf3ef] text-xs font-bold text-[#123824]"
|
||||||
>
|
>
|
||||||
+{{ hiddenCount }}
|
+{{ hiddenCount }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ async function submitCart() {
|
|||||||
<label
|
<label
|
||||||
v-for="address in deliveryAddresses"
|
v-for="address in deliveryAddresses"
|
||||||
:key="address.id"
|
:key="address.id"
|
||||||
class="flex cursor-pointer items-start gap-3 rounded-2xl bg-white p-3 transition hover:shadow-md"
|
class="surface-card surface-card-interactive flex items-start gap-3 rounded-2xl p-3"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
@@ -191,6 +191,7 @@ async function submitCart() {
|
|||||||
<OrdersOrderItemsTable
|
<OrdersOrderItemsTable
|
||||||
v-else
|
v-else
|
||||||
mode="cart"
|
mode="cart"
|
||||||
|
:framed="false"
|
||||||
:items="cartItems.map((item) => ({
|
:items="cartItems.map((item) => ({
|
||||||
id: item.productId,
|
id: item.productId,
|
||||||
productName: item.productName,
|
productName: item.productName,
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ watch(
|
|||||||
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Адрес</p>
|
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Адрес</p>
|
||||||
<p class="text-base font-semibold">{{ currentOrder.deliveryAddress || 'Адрес пока не указан' }}</p>
|
<p class="text-base font-semibold">{{ currentOrder.deliveryAddress || 'Адрес пока не указан' }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-4 border-t border-[#deebe4] pt-4 md:grid-cols-2">
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Комментарий по доставке</p>
|
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Комментарий по доставке</p>
|
||||||
<p>{{ orderDeliveryStateText(draftDeliveryTerms) }}</p>
|
<p>{{ orderDeliveryStateText(draftDeliveryTerms) }}</p>
|
||||||
@@ -275,7 +275,7 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 flex flex-wrap items-center justify-between gap-3 border-t border-[#d6ebde] pt-4">
|
<div class="mt-4 flex flex-wrap items-center justify-between gap-3">
|
||||||
<p class="text-sm text-[#5c7b69]">
|
<p class="text-sm text-[#5c7b69]">
|
||||||
{{
|
{{
|
||||||
offerTotal == null
|
offerTotal == null
|
||||||
|
|||||||
@@ -297,10 +297,8 @@ const calendarOptions = computed(() => ({
|
|||||||
gap: 0.45rem;
|
gap: 0.45rem;
|
||||||
min-height: 74px;
|
min-height: 74px;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
border: 1px solid #dcebe3;
|
background: #ffffff;
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #f4faf6 100%);
|
|
||||||
padding: 0.62rem 0.72rem;
|
padding: 0.62rem 0.72rem;
|
||||||
box-shadow: 0 10px 24px rgba(18, 56, 36, 0.08);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.manager-calendar-order-card__header {
|
.manager-calendar-order-card__header {
|
||||||
@@ -319,7 +317,7 @@ const calendarOptions = computed(() => ({
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: linear-gradient(135deg, #e4f6eb 0%, #c6e7d5 100%);
|
background: #edf3ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.manager-calendar-order-card__avatar {
|
.manager-calendar-order-card__avatar {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ const currentOrderCode = computed(() => formatOrderCode(currentOrder.value?.code
|
|||||||
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Адрес</p>
|
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Адрес</p>
|
||||||
<p class="text-base font-semibold">{{ currentOrder.deliveryAddress || 'Адрес пока не указан' }}</p>
|
<p class="text-base font-semibold">{{ currentOrder.deliveryAddress || 'Адрес пока не указан' }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-4 border-t border-[#deebe4] pt-4 md:grid-cols-2">
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Комментарий по доставке</p>
|
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Комментарий по доставке</p>
|
||||||
<p>{{ orderDeliveryStateText(currentOrder.deliveryTerms) }}</p>
|
<p>{{ orderDeliveryStateText(currentOrder.deliveryTerms) }}</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user