Files
2026-04-06 21:44:24 +07:00

187 lines
6.0 KiB
Vue
Raw Permalink 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 { CreateMyDeliveryAddressDocument } from '~/composables/graphql/generated';
type AddressSuggestion = {
value: string;
unrestricted_value?: string;
data?: {
fias_id?: string;
};
};
const addressFeedback = ref('');
const addressLoading = ref(false);
const addressOpen = ref(false);
const addressSearch = ref('');
const addressSuggestions = ref<AddressSuggestion[]>([]);
const addressSearchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
const addressDropdownRef = ref<HTMLElement | null>(null);
const createAddressMutation = useMutation(CreateMyDeliveryAddressDocument, { throws: 'never' });
const addressForm = reactive({
label: '',
address: '',
unrestrictedValue: '',
fiasId: '',
});
function clearAddressTimer() {
if (!addressSearchTimer.value) {
return;
}
clearTimeout(addressSearchTimer.value);
addressSearchTimer.value = null;
}
async function fetchAddressSuggestions() {
const query = addressSearch.value.trim();
if (query.length < 2) {
addressSuggestions.value = [];
addressOpen.value = false;
return;
}
addressLoading.value = true;
await $fetch<{ suggestions: AddressSuggestion[] }>('/api/dadata/address', {
method: 'POST',
body: { query },
})
.then((response) => {
addressSuggestions.value = response.suggestions || [];
addressOpen.value = addressSuggestions.value.length > 0;
})
.finally(() => {
addressLoading.value = false;
});
}
function scheduleAddressSuggest() {
clearAddressTimer();
addressSearchTimer.value = setTimeout(() => {
void fetchAddressSuggestions();
}, 250);
}
function applyAddressSuggestion(item: AddressSuggestion) {
addressOpen.value = false;
addressSearch.value = item.value;
addressForm.address = item.value;
addressForm.unrestrictedValue = item.unrestricted_value || item.value;
addressForm.fiasId = item.data?.fias_id || '';
}
function closeDropdownsFromOutside(event: MouseEvent) {
const target = event.target as Node | null;
if (addressDropdownRef.value && target && !addressDropdownRef.value.contains(target)) {
addressOpen.value = false;
}
}
async function addDeliveryAddress() {
addressFeedback.value = '';
const normalizedAddress = addressForm.address.trim() || addressSearch.value.trim();
if (normalizedAddress.length < 5) {
addressFeedback.value = 'Введите адрес через подсказки DaData.';
return;
}
const result = await createAddressMutation.mutate({
input: {
label: addressForm.label.trim() ? addressForm.label.trim() : null,
address: normalizedAddress,
unrestrictedValue: addressForm.unrestrictedValue.trim() ? addressForm.unrestrictedValue.trim() : null,
fiasId: addressForm.fiasId.trim() ? addressForm.fiasId.trim() : null,
},
});
if (!result?.data?.createMyDeliveryAddress) {
addressFeedback.value = createAddressMutation.error.value?.message || 'Не удалось добавить адрес.';
return;
}
await navigateTo('/profile/addresses?created=1');
}
onMounted(() => {
document.addEventListener('click', closeDropdownsFromOutside);
});
onBeforeUnmount(() => {
document.removeEventListener('click', closeDropdownsFromOutside);
clearAddressTimer();
});
</script>
<template>
<section class="space-y-6">
<UiBackHeader
to="/profile/addresses"
back-label="Назад к адресам"
title="Новый адрес"
subtitle="Найдите адрес через DaData и сохраните его в профиль."
/>
<div class="rounded-[28px] bg-white px-5 py-5 shadow-[0_18px_38px_rgba(18,56,36,0.08)] md:px-6 md:py-6">
<fieldset class="fieldset">
<legend class="fieldset-legend">Название адреса</legend>
<input v-model="addressForm.label" type="text" class="input w-full" placeholder="Склад МСК">
</fieldset>
<div ref="addressDropdownRef" class="relative mt-4">
<fieldset class="fieldset">
<legend class="fieldset-legend">Адрес</legend>
<input
v-model="addressSearch"
type="text"
class="input w-full"
placeholder="Начните вводить адрес"
@input="scheduleAddressSuggest"
@focus="addressOpen = addressSuggestions.length > 0"
>
</fieldset>
<span v-if="addressLoading" class="loading loading-spinner loading-sm absolute right-3 top-1/2 -translate-y-1/2" />
<div
v-if="addressOpen && addressSuggestions.length > 0"
class="absolute z-30 mt-2 max-h-72 w-full overflow-auto rounded-[24px] bg-white p-2 shadow-[0_18px_38px_rgba(18,56,36,0.08)]"
>
<button
v-for="item in addressSuggestions"
:key="`${item.value}-${item.data?.fias_id || ''}`"
type="button"
class="w-full rounded-[18px] px-3 py-3 text-left transition hover:bg-[#f6fbf8]"
@click="applyAddressSuggestion(item)"
>
<span class="block text-sm font-semibold text-[#123824]">{{ item.value }}</span>
</button>
</div>
</div>
<div
v-if="addressFeedback"
class="mt-4 rounded-[20px] border border-[#f1d1c7] bg-[#fff3ef] px-4 py-3 text-sm font-medium text-[#9d4426]"
>
{{ addressFeedback }}
</div>
<div class="mt-6 flex flex-col gap-3 md:flex-row">
<button
class="btn rounded-full border-0 bg-[#123824] px-6 text-white hover:bg-[#0f2f20]"
:disabled="createAddressMutation.loading.value"
@click="addDeliveryAddress"
>
{{ createAddressMutation.loading.value ? 'Сохраняем...' : 'Сохранить адрес' }}
</button>
<NuxtLink to="/profile/addresses" class="btn rounded-full border border-[#d7e6dc] bg-white px-6 text-[#123824] hover:border-[#bed6c7] hover:bg-[#f6fbf8]">
Отмена
</NuxtLink>
</div>
</div>
</section>
</template>