schon/storefront/components/cards/order.vue
Alexandr SaVBaD Waltz 52b32bd608 Features: 1) Introduce useUserBaseData composable to fetch and manage user's wishlist, orders, and promocodes; 2) Add reusable useOrders and useOrderOverwrite composables with advanced filtering and pagination; 3) Implement order.vue component for detailed order displays with UI enhancements;
Fixes: 1) Replace deprecated context usage in `useAvatarUpload` mutation; 2) Resolve incorrect locale parsing in `useDate` utility and fix non-reactive cart state in `profile/cart.vue`; 3) Update stale imports and standardize type naming across composables;

Extra: 1) Refactor i18n strings including order status and search-related texts; 2) Replace temporary workarounds with `apollo-upload-client` configuration and add `apollo-upload-link.ts` plugin; 3) Cleanup redundant files, comments, and improve SCSS structure with new variables and placeholders.
2025-07-11 18:39:13 +03:00

246 lines
No EOL
5.3 KiB
Vue

<template>
<el-collapse-item
class="order"
>
<template #title="{ isActive }">
<div :class="['order__top', { 'is-active': isActive }]">
<div>
<p>{{ t('profile.orders.id') }}: {{ order.humanReadableId }}</p>
<p v-if="order.buyTime">{{ useDate(order.buyTime, locale) }}</p>
<el-tooltip
:content="order.status"
placement="top"
>
<p class="status" :style="[{ backgroundColor: statusColor(order.status) }]">
{{ order.status }}
<icon name="material-symbols:info-outline-rounded" size="14" />
</p>
</el-tooltip>
</div>
<icon
name="material-symbols:keyboard-arrow-down"
size="22"
class="order__top-icon"
:class="[{ active: isActive }]"
/>
</div>
<div class="order__top-bottom" :class="{ active: !isActive }">
<div>
<div>
<div>
<nuxt-img
v-for="product in order.orderProducts.edges"
:key="product.node.uuid"
:src="product.node.product.images.edges[0].node.image"
:alt="product.node.product.name"
format="webp"
densities="x1"
/>
</div>
<p>{{ order.totalPrice }}{{ CURRENCY }}</p>
</div>
</div>
</div>
</template>
<div class="order__main">
<div
class="order__product"
v-for="product in order.orderProducts.edges"
:key="product.node.uuid"
>
<div class="order__product-left">
<nuxt-img
:src="product.node.product.images.edges[0].node.image"
:alt="product.node.product.name"
/>
<p>{{ product.node.product.name }}</p>
</div>
<div class="order__product-right">
<h6>{{ t('profile.orders.price') }}: {{ product.node.product.price * product.node.quantity }}{{ CURRENCY }}</h6>
<p>{{ product.node.quantity }} X {{ product.node.product.price }}{{ CURRENCY }}</p>
</div>
</div>
<div class="order__total">
<p>{{ t('profile.orders.total') }}: {{ order.totalPrice }}{{ CURRENCY }}</p>
</div>
</div>
</el-collapse-item>
</template>
<script setup lang="ts">
import {useDate} from "~/composables/date";
import {CURRENCY, orderStatuses} from "~/config/constants";
import type {IOrder} from "~/types";
const props = defineProps<{
order: IOrder;
}>();
const {t, locale} = useI18n();
const statusColor = (status: string) => {
switch (status) {
case orderStatuses.FAILED:
return '#FF0000';
case orderStatuses.PAYMENT:
return '#FFC107';
case orderStatuses.CREATED:
return '#007BFF';
case orderStatuses.DELIVERING:
return '#00C853';
case orderStatuses.FINISHED:
return '#00C853';
case orderStatuses.MOMENTAL:
return '#00C853';
default:
return '#000';
}
};
</script>
<style scoped lang="scss">
.order {
&__top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
padding: 0 10px 5px 10px;
& div {
display: flex;
align-items: center;
gap: 25px;
& p {
display: flex;
align-items: center;
gap: 5px;
&.status {
border-radius: $default_border_radius;
padding: 3px 7px;
}
}
}
&-icon {
transition: 0.2s;
&.active {
transform: rotate(-180deg);
}
}
&-bottom {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.2s ease;
overflow: hidden;
&.active {
grid-template-rows: 1fr;
}
& > * {
min-height: 0;
}
& div {
& div {
padding-top: 10px;
border-top: 1px solid $accent;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 25px;
& div {
padding-top: 0;
border: none;
display: flex;
flex-wrap: wrap;
gap: 10px;
& img {
height: 65px;
border-radius: $default_border_radius;
}
}
& p {
font-weight: 600;
font-size: 20px;
}
}
}
}
}
&__main {
display: flex;
flex-direction: column;
gap: 20px;
}
&__product {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding-bottom: 15px;
border-bottom: 2px solid $accent;
&-left {
display: flex;
align-items: flex-start;
gap: 10px;
& img {
height: 150px;
border-radius: $default_border_radius;
}
& p {
font-size: 18px;
font-weight: 500;
}
}
&-right {
display: flex;
flex-direction: column;
align-items: flex-end;
& h6 {
font-size: 20px;
}
& p {
font-size: 14px;
}
}
}
&__total {
display: flex;
align-items: flex-end;
justify-content: flex-end;
& p {
font-size: 20px;
font-weight: 600;
}
}
}
:deep(.el-collapse-item__header) {
height: fit-content;
padding-block: 10px;
}
</style>