Files
web-frontend/app/pages/cart.vue

240 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { useMutation, useQuery } from '@vue/apollo-composable';
import {
MyDeliveryAddressesDocument,
SubmitCalculationOrderDocument,
type MyDeliveryAddressesQuery,
} from '~/composables/graphql/generated';
import { useClientCart } from '~/composables/useClientCart';
import { useCounterpartyProfile } from '~/composables/useCounterpartyProfile';
type DeliveryAddressItem = MyDeliveryAddressesQuery['myDeliveryAddresses'][number];
const { isComplete: isCounterpartyComplete, loading: counterpartyLoading } = useCounterpartyProfile();
const submitMutation = useMutation(SubmitCalculationOrderDocument, { throws: 'never' });
const deliveryAddressesQuery = useQuery(MyDeliveryAddressesDocument);
const {
items: cartItems,
totalPositions,
totalItems,
totalVolume,
incrementQuantity,
decrementQuantity,
removeProduct,
clearCart,
} = useClientCart();
const selectedDeliveryAddressId = ref('');
const sending = ref(false);
const success = ref('');
const errorMessage = ref('');
const deliveryAddresses = computed<DeliveryAddressItem[]>(() => deliveryAddressesQuery.result.value?.myDeliveryAddresses ?? []);
const hasDeliveryAddresses = computed(() => deliveryAddresses.value.length > 0);
watch(
deliveryAddresses,
(addresses) => {
if (addresses.length < 1) {
selectedDeliveryAddressId.value = '';
return;
}
const hasCurrentSelection = addresses.some((address) => address.id === selectedDeliveryAddressId.value);
if (hasCurrentSelection) {
return;
}
const defaultAddress = addresses.find((address) => address.isDefault);
selectedDeliveryAddressId.value = defaultAddress?.id || addresses[0]?.id || '';
},
{ immediate: true },
);
function lineVolume(productId: string) {
const item = cartItems.value.find((entry) => entry.productId === productId);
if (!item) {
return 0;
}
return Number(item.quantity) * Number(item.parameters.width) * Number(item.parameters.thickness);
}
function increment(productId: string) {
success.value = '';
errorMessage.value = '';
incrementQuantity(productId);
}
function decrement(productId: string) {
success.value = '';
errorMessage.value = '';
decrementQuantity(productId);
}
function removeFromCart(productId: string) {
success.value = '';
errorMessage.value = '';
removeProduct(productId);
}
async function submitCart() {
success.value = '';
errorMessage.value = '';
if (!isCounterpartyComplete.value) {
errorMessage.value = 'Сначала заполните карточку контрагента в профиле.';
return;
}
if (!selectedDeliveryAddressId.value) {
errorMessage.value = 'Выберите адрес доставки в профиле.';
return;
}
if (cartItems.value.length < 1) {
errorMessage.value = 'Добавьте хотя бы одну позицию в корзину.';
return;
}
sending.value = true;
const createdCodes: string[] = [];
for (const item of cartItems.value) {
const result = await submitMutation.mutate({
input: {
productName: item.productName,
quantity: Number(item.quantity),
parameters: {
width: Number(item.parameters.width),
thickness: Number(item.parameters.thickness),
color: item.parameters.color,
},
deliveryAddressId: selectedDeliveryAddressId.value,
},
});
const code = result?.data?.submitCalculationOrder?.code;
if (!code) {
errorMessage.value = submitMutation.error.value?.message || 'Не удалось отправить одну из позиций.';
sending.value = false;
return;
}
createdCodes.push(code);
}
sending.value = false;
clearCart();
success.value = `Отправлено заявок: ${createdCodes.length}. Статус: уточнение цены.`;
}
</script>
<template>
<section class="space-y-6">
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Корзина</h1>
<div v-if="counterpartyLoading" class="alert surface-card border-0">
Проверяем карточку контрагента...
</div>
<div v-else-if="!isCounterpartyComplete" class="alert alert-warning">
Для оформления заявки заполните карточку контрагента в
<NuxtLink to="/profile/counterparty" class="link link-hover font-semibold">профиле</NuxtLink>.
</div>
<div class="surface-card rounded-3xl p-4 md:p-5">
<h2 class="text-lg font-bold text-[#123824]">Адрес доставки</h2>
<div v-if="deliveryAddressesQuery.loading" class="alert mt-3 surface-card border-0">
Загружаем адреса...
</div>
<div v-else-if="!hasDeliveryAddresses" class="alert alert-warning mt-3">
Адреса не добавлены.
<NuxtLink to="/profile/addresses" class="link link-hover font-semibold">Добавить адрес в профиле</NuxtLink>
</div>
<div v-else class="mt-3 space-y-2">
<label
v-for="address in deliveryAddresses"
:key="address.id"
class="flex cursor-pointer items-start gap-3 rounded-2xl border border-[#d6ebde] bg-white/75 p-3"
>
<input
v-model="selectedDeliveryAddressId"
type="radio"
name="delivery-address"
class="radio radio-success mt-1"
:value="address.id"
>
<span>
<span class="block font-semibold text-[#123824]">
{{ address.label || 'Адрес доставки' }}
<span v-if="address.isDefault" class="badge badge-success ml-2">Основной</span>
</span>
<span class="block text-sm text-[#355947]">{{ address.unrestrictedValue || address.address }}</span>
</span>
</label>
</div>
</div>
<h2 class="text-xl font-bold text-[#123824]">Позиции</h2>
<div v-if="cartItems.length === 0" class="alert surface-card border-0">
Корзина пока пустая. Добавьте товар из каталога.
</div>
<ul v-else class="space-y-3">
<li
v-for="item in cartItems"
:key="item.productId"
class="surface-card flex flex-col gap-3 rounded-3xl px-4 py-4 md:flex-row md:items-center md:justify-between md:px-5 md:py-5"
>
<div>
<p class="font-semibold text-[#123824]">{{ item.productName }}</p>
<p class="text-xs opacity-70">SKU: {{ item.sku }}</p>
<p class="text-sm opacity-80">
Объем: {{ lineVolume(item.productId) }}
</p>
</div>
<div class="flex items-center gap-2">
<button class="btn btn-square btn-sm" @click="decrement(item.productId)">-</button>
<span class="min-w-8 text-center font-semibold">{{ item.quantity }}</span>
<button class="btn btn-square btn-sm" @click="increment(item.productId)">+</button>
<button class="btn btn-ghost btn-sm text-error" @click="removeFromCart(item.productId)">
Удалить
</button>
</div>
</li>
</ul>
<div class="divider my-1" />
<div class="space-y-2 text-sm text-[#214735]">
<div class="flex items-center justify-between">
<span>Позиций</span>
<span class="font-semibold">{{ totalPositions }}</span>
</div>
<div class="flex items-center justify-between">
<span>Количество, шт.</span>
<span class="font-semibold">{{ totalItems }}</span>
</div>
<div class="flex items-center justify-between">
<span>Суммарный объем</span>
<span class="font-semibold">{{ totalVolume }}</span>
</div>
</div>
<button
class="btn w-full border-0 bg-[#139957] text-white hover:bg-[#0d854a]"
:disabled="sending || counterpartyLoading || !isCounterpartyComplete || !selectedDeliveryAddressId || cartItems.length === 0"
@click="submitCart"
>
{{ sending ? 'Отправляем…' : 'Оформить заявку' }}
</button>
<div v-if="success" class="alert alert-success">{{ success }}</div>
<div v-if="errorMessage" class="alert alert-error">{{ errorMessage }}</div>
</section>
</template>