218 lines
7.0 KiB
Vue
218 lines
7.0 KiB
Vue
<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,
|
||
fetchCart,
|
||
selectedDeliveryAddressId,
|
||
incrementQuantity,
|
||
decrementQuantity,
|
||
removeProduct,
|
||
clearCart,
|
||
setDeliveryAddress,
|
||
} = useClientCart();
|
||
|
||
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,
|
||
async (addresses) => {
|
||
if (addresses.length < 1) {
|
||
if (selectedDeliveryAddressId.value) {
|
||
await setDeliveryAddress(null);
|
||
}
|
||
return;
|
||
}
|
||
|
||
const hasCurrentSelection = addresses.some((address) => address.id === selectedDeliveryAddressId.value);
|
||
if (hasCurrentSelection) {
|
||
return;
|
||
}
|
||
|
||
const defaultAddress = addresses.find((address) => address.isDefault);
|
||
await setDeliveryAddress(defaultAddress?.id || addresses[0]?.id || null);
|
||
},
|
||
{ immediate: true },
|
||
);
|
||
|
||
onMounted(() => {
|
||
void fetchCart(true);
|
||
});
|
||
|
||
function increment(productId: string) {
|
||
success.value = '';
|
||
errorMessage.value = '';
|
||
void incrementQuantity(productId);
|
||
}
|
||
|
||
function decrement(productId: string) {
|
||
success.value = '';
|
||
errorMessage.value = '';
|
||
void decrementQuantity(productId);
|
||
}
|
||
|
||
function removeFromCart(productId: string) {
|
||
success.value = '';
|
||
errorMessage.value = '';
|
||
void removeProduct(productId);
|
||
}
|
||
|
||
function selectDeliveryAddress(addressId: string) {
|
||
success.value = '';
|
||
errorMessage.value = '';
|
||
void setDeliveryAddress(addressId);
|
||
}
|
||
|
||
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;
|
||
await 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">
|
||
Проверяем карточку контрагента...
|
||
</div>
|
||
<div v-else-if="!isCounterpartyComplete" class="alert alert-warning">
|
||
Для оформления заявки заполните карточку контрагента в
|
||
<NuxtLink to="/profile/counterparty" class="link link-hover font-semibold">профиле</NuxtLink>.
|
||
</div>
|
||
|
||
<h2 class="text-xl font-bold text-[#123824]">Состав заказа</h2>
|
||
|
||
<div v-if="cartItems.length === 0" class="alert surface-card">
|
||
Корзина пока пустая. Добавьте товар из каталога.
|
||
</div>
|
||
|
||
<OrdersOrderItemsTable
|
||
v-else
|
||
mode="cart"
|
||
:framed="false"
|
||
:items="cartItems.map((item) => ({
|
||
id: item.productId,
|
||
productName: item.productName,
|
||
sku: item.sku,
|
||
quantity: item.quantity,
|
||
parameters: item.parameters,
|
||
unitPrice: null,
|
||
lineTotal: null,
|
||
}))"
|
||
@increment="increment"
|
||
@decrement="decrement"
|
||
@remove="removeFromCart"
|
||
/>
|
||
|
||
<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.value" class="alert mt-3 surface-card">
|
||
Загружаем адреса...
|
||
</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="surface-card surface-card-interactive flex items-start gap-3 rounded-2xl p-3"
|
||
>
|
||
<input
|
||
type="radio"
|
||
name="delivery-address"
|
||
class="radio radio-success mt-1"
|
||
:checked="selectedDeliveryAddressId === address.id"
|
||
@change="selectDeliveryAddress(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>
|
||
|
||
<button
|
||
class="btn w-full 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>
|