Files
web-frontend/app/pages/cart.vue
2026-04-02 17:17:16 +07:00

225 lines
8.1 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 } from '@vue/apollo-composable';
import { SubmitCalculationOrderDocument } from '~/composables/graphql/generated';
import { useCounterpartyProfile } from '~/composables/useCounterpartyProfile';
type CartLine = {
id: number;
productName: string;
quantity: number;
width: number;
thickness: number;
color: string;
};
const { isComplete: isCounterpartyComplete, loading: counterpartyLoading } = useCounterpartyProfile();
const submitMutation = useMutation(SubmitCalculationOrderDocument, { throws: 'never' });
const draft = reactive({
productName: '',
quantity: 1,
width: 100,
thickness: 50,
color: 'прозрачный',
});
const cartItems = ref<CartLine[]>([]);
const nextLineId = ref(1);
const sending = ref(false);
const success = ref('');
const errorMessage = ref('');
function lineVolume(item: CartLine) {
return Number(item.quantity) * Number(item.width) * Number(item.thickness);
}
const totalItems = computed(() => cartItems.value.reduce((acc, item) => acc + Number(item.quantity), 0));
const totalVolume = computed(() => cartItems.value.reduce((acc, item) => acc + lineVolume(item), 0));
function addToCart() {
success.value = '';
errorMessage.value = '';
if (!draft.productName.trim()) {
errorMessage.value = 'Укажите название позиции.';
return;
}
if (Number(draft.quantity) <= 0) {
errorMessage.value = 'Количество должно быть больше нуля.';
return;
}
cartItems.value.push({
id: nextLineId.value,
productName: draft.productName.trim(),
quantity: Number(draft.quantity),
width: Number(draft.width),
thickness: Number(draft.thickness),
color: draft.color.trim() || 'прозрачный',
});
nextLineId.value += 1;
draft.productName = '';
}
function removeFromCart(id: number) {
cartItems.value = cartItems.value.filter((item) => item.id !== id);
}
async function submitCart() {
success.value = '';
errorMessage.value = '';
if (!isCounterpartyComplete.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.width),
thickness: Number(item.thickness),
color: item.color,
},
},
});
const code = result?.data?.submitCalculationOrder?.code;
if (!code) {
errorMessage.value = submitMutation.error.value?.message || 'Не удалось отправить одну из позиций.';
sending.value = false;
return;
}
createdCodes.push(code);
}
sending.value = false;
cartItems.value = [];
success.value = `Отправлено заявок: ${createdCodes.length}.`;
}
</script>
<template>
<section class="space-y-6">
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Корзина</h1>
<div class="surface-card rounded-3xl p-5 md:p-6">
<div class="grid gap-6 xl:grid-cols-[1.6fr_1fr]">
<div class="space-y-4">
<div v-if="counterpartyLoading.value" class="alert">
Проверяем карточку контрагента...
</div>
<div v-else-if="!isCounterpartyComplete" class="alert alert-warning">
Для оформления заявки заполните карточку контрагента в
<NuxtLink to="/profile" class="link link-hover font-semibold">профиле</NuxtLink>.
</div>
<div class="rounded-2xl border border-base-300 bg-base-100 p-4">
<h2 class="text-lg font-bold text-[#123824]">Добавить позицию</h2>
<div class="mt-3 grid gap-3 md:grid-cols-2 xl:grid-cols-3">
<label class="form-control xl:col-span-3">
<span class="label-text">Название позиции</span>
<input v-model="draft.productName" type="text" class="input input-bordered w-full" placeholder="Лист ПЭТ" >
</label>
<label class="form-control">
<span class="label-text">Количество</span>
<input v-model="draft.quantity" type="number" min="1" class="input input-bordered w-full" >
</label>
<label class="form-control">
<span class="label-text">Ширина</span>
<input v-model="draft.width" type="number" min="1" class="input input-bordered w-full" >
</label>
<label class="form-control">
<span class="label-text">Толщина</span>
<input v-model="draft.thickness" type="number" min="1" class="input input-bordered w-full" >
</label>
<label class="form-control md:col-span-2 xl:col-span-3">
<span class="label-text">Цвет</span>
<input v-model="draft.color" type="text" class="input input-bordered w-full" >
</label>
</div>
<button class="btn btn-primary mt-4" @click="addToCart">Добавить в корзину</button>
</div>
<div class="rounded-2xl border border-base-300 bg-base-100 p-4">
<h2 class="text-lg font-bold text-[#123824]">Список позиций</h2>
<div v-if="cartItems.length === 0" class="alert mt-3">
Корзина пока пустая.
</div>
<ul v-else class="mt-3 space-y-2">
<li
v-for="item in cartItems"
:key="item.id"
class="flex flex-col gap-2 rounded-xl border border-[#d6ebde] bg-white/75 px-3 py-3 md:flex-row md:items-center md:justify-between"
>
<div>
<p class="font-semibold text-[#123824]">{{ item.productName }}</p>
<p class="text-sm opacity-80">
{{ item.quantity }} шт. {{ item.width }} × {{ item.thickness }} {{ item.color }}
</p>
</div>
<div class="flex items-center gap-3">
<span class="text-sm font-semibold text-[#123824]">Объем: {{ lineVolume(item) }}</span>
<button class="btn btn-ghost btn-sm text-error" @click="removeFromCart(item.id)">Удалить</button>
</div>
</li>
</ul>
</div>
</div>
<aside class="rounded-2xl border border-base-300 bg-base-100 p-4">
<h2 class="text-lg font-bold text-[#123824]">Итого</h2>
<ul class="mt-3 space-y-2 text-sm text-[#214735]">
<li class="flex items-center justify-between">
<span>Позиций</span>
<span class="font-semibold">{{ cartItems.length }}</span>
</li>
<li class="flex items-center justify-between">
<span>Количество, шт.</span>
<span class="font-semibold">{{ totalItems }}</span>
</li>
<li class="flex items-center justify-between">
<span>Суммарный объем</span>
<span class="font-semibold">{{ totalVolume }}</span>
</li>
</ul>
<button
class="btn mt-4 w-full border-0 bg-[#139957] text-white hover:bg-[#0d854a]"
:disabled="sending || counterpartyLoading.value || !isCounterpartyComplete || cartItems.length === 0"
@click="submitCart"
>
{{ sending ? 'Отправляем…' : 'Оформить заявку' }}
</button>
</aside>
</div>
</div>
<div v-if="success" class="alert alert-success">{{ success }}</div>
<div v-if="errorMessage" class="alert alert-error">{{ errorMessage }}</div>
</section>
</template>