Files
web-frontend/app/pages/catalog-settings.vue
2026-04-09 16:36:43 +07:00

219 lines
7.9 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 {
CatalogProductTypeSettingsDocument,
UpsertCatalogProductTypeSettingDocument,
type CatalogProductTypeSettingsQuery,
} from '~/composables/graphql/generated';
definePageMeta({
middleware: ['manager-only'],
path: '/admin/settings/catalog',
});
type CatalogSettingItem = CatalogProductTypeSettingsQuery['catalogProductTypeSettings'][number];
type CatalogSettingForm = {
productType: string;
showQuantityPerBox: boolean;
allowCustomLength: boolean;
customLengthMinM: string;
customLengthMaxM: string;
customLengthStepM: string;
allowCustomSleeveBrand: boolean;
allowCustomLabel: boolean;
};
const settingsQuery = useQuery(CatalogProductTypeSettingsDocument);
const saveSettingMutation = useMutation(UpsertCatalogProductTypeSettingDocument, { throws: 'never' });
const forms = reactive<Record<string, CatalogSettingForm>>({});
const isSavingAll = ref(false);
const saveSuccess = ref('');
const saveError = ref('');
const settings = computed<CatalogSettingItem[]>(() => settingsQuery.result.value?.catalogProductTypeSettings ?? []);
function toInputValue(value: number | null | undefined) {
return value == null ? '' : String(value);
}
function createForm(item: CatalogSettingItem): CatalogSettingForm {
return {
productType: item.productType,
showQuantityPerBox: item.showQuantityPerBox,
allowCustomLength: item.allowCustomLength,
customLengthMinM: toInputValue(item.customLengthMinM),
customLengthMaxM: toInputValue(item.customLengthMaxM),
customLengthStepM: toInputValue(item.customLengthStepM),
allowCustomSleeveBrand: item.allowCustomSleeveBrand,
allowCustomLabel: item.allowCustomLabel,
};
}
function parseOptionalInteger(value: string) {
const normalized = value.trim();
if (!normalized) {
return null;
}
return Number(normalized);
}
function formFor(item: CatalogSettingItem) {
forms[item.productType] ??= createForm(item);
return forms[item.productType];
}
watch(
settings,
(items) => {
const activeTypes = new Set(items.map((item) => item.productType));
for (const productType of Object.keys(forms)) {
if (!activeTypes.has(productType)) {
Reflect.deleteProperty(forms, productType);
}
}
for (const item of items) {
forms[item.productType] = createForm(item);
}
},
{ immediate: true },
);
async function saveAllSettings() {
saveSuccess.value = '';
saveError.value = '';
isSavingAll.value = true;
for (const item of settings.value) {
const form = formFor(item);
const result = await saveSettingMutation.mutate({
input: {
productType: form.productType,
showQuantityPerBox: form.showQuantityPerBox,
allowCustomLength: form.allowCustomLength,
customLengthMinM: form.allowCustomLength ? parseOptionalInteger(form.customLengthMinM) : null,
customLengthMaxM: form.allowCustomLength ? parseOptionalInteger(form.customLengthMaxM) : null,
customLengthStepM: form.allowCustomLength ? parseOptionalInteger(form.customLengthStepM) : null,
allowCustomSleeveBrand: form.allowCustomSleeveBrand,
allowCustomLabel: form.allowCustomLabel,
},
});
if (!result?.data?.upsertCatalogProductTypeSetting) {
saveError.value = saveSettingMutation.error.value?.message || `Не удалось сохранить настройки для "${form.productType}".`;
isSavingAll.value = false;
return;
}
forms[item.productType] = createForm(result.data.upsertCatalogProductTypeSetting);
}
isSavingAll.value = false;
saveSuccess.value = 'Настройки сохранены.';
}
</script>
<template>
<section class="space-y-6">
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Каталог</h1>
<div v-if="settingsQuery.loading.value" class="manager-empty-state">
Загружаем настройки каталога...
</div>
<div v-else-if="settings.length === 0" class="manager-empty-state">
Типы товаров пока не появились в каталоге.
</div>
<div v-else class="grid gap-4 xl:grid-cols-2">
<article
v-for="item in settings"
:key="item.productType"
class="rounded-[28px] bg-white p-5 shadow-[0_18px_38px_rgba(18,56,36,0.08)]"
>
<div class="flex flex-col gap-4">
<h2 class="text-xl font-bold text-[#123824]">{{ item.productType }}</h2>
<div class="grid gap-3 md:grid-cols-2">
<label class="surface-card flex items-center justify-between gap-4 rounded-[22px] p-4">
<span class="text-sm font-semibold text-[#123824]">Своя длина</span>
<input v-model="formFor(item).allowCustomLength" type="checkbox" class="toggle toggle-success">
</label>
<label class="surface-card flex items-center justify-between gap-4 rounded-[22px] p-4">
<span class="text-sm font-semibold text-[#123824]">Своя втулка</span>
<input v-model="formFor(item).allowCustomSleeveBrand" type="checkbox" class="toggle toggle-success">
</label>
<label class="surface-card flex items-center justify-between gap-4 rounded-[22px] p-4">
<span class="text-sm font-semibold text-[#123824]">Своя надпись</span>
<input v-model="formFor(item).allowCustomLabel" type="checkbox" class="toggle toggle-success">
</label>
<label class="surface-card flex items-center justify-between gap-4 rounded-[22px] p-4">
<span class="text-sm font-semibold text-[#123824]">Показывать короб</span>
<input v-model="formFor(item).showQuantityPerBox" type="checkbox" class="toggle toggle-success">
</label>
</div>
<div v-if="formFor(item).allowCustomLength" class="rounded-[24px] bg-[#f7fbf8] p-4">
<div class="mb-4 text-sm font-bold uppercase tracking-[0.12em] text-[#355947]">Диапазон длины</div>
<div class="grid gap-3 md:grid-cols-3">
<label class="space-y-2">
<span class="text-sm font-semibold text-[#123824]">Мин. длина, м</span>
<input
v-model="formFor(item).customLengthMinM"
type="number"
min="1"
step="1"
class="input manager-field w-full"
>
</label>
<label class="space-y-2">
<span class="text-sm font-semibold text-[#123824]">Макс. длина, м</span>
<input
v-model="formFor(item).customLengthMaxM"
type="number"
min="1"
step="1"
class="input manager-field w-full"
>
</label>
<label class="space-y-2">
<span class="text-sm font-semibold text-[#123824]">Шаг, м</span>
<input
v-model="formFor(item).customLengthStepM"
type="number"
min="1"
step="1"
class="input manager-field w-full"
>
</label>
</div>
</div>
</div>
</article>
</div>
<div v-if="saveSuccess || saveError" class="space-y-2 text-sm">
<p v-if="saveSuccess" class="font-semibold text-[#1c6b45]">{{ saveSuccess }}</p>
<p v-if="saveError" class="font-semibold text-[#c4472d]">{{ saveError }}</p>
</div>
<div v-if="settings.length" class="flex justify-end">
<button
class="btn h-11 rounded-full border-0 bg-[#139957] px-6 text-sm font-semibold text-white hover:bg-[#0d854a]"
:disabled="isSavingAll"
@click="saveAllSettings"
>
{{ isSavingAll ? 'Сохраняем…' : 'Сохранить' }}
</button>
</div>
</section>
</template>