Simplify catalog settings layout
This commit is contained in:
@@ -27,9 +27,9 @@ const settingsQuery = useQuery(CatalogProductTypeSettingsDocument);
|
|||||||
const saveSettingMutation = useMutation(UpsertCatalogProductTypeSettingDocument, { throws: 'never' });
|
const saveSettingMutation = useMutation(UpsertCatalogProductTypeSettingDocument, { throws: 'never' });
|
||||||
|
|
||||||
const forms = reactive<Record<string, CatalogSettingForm>>({});
|
const forms = reactive<Record<string, CatalogSettingForm>>({});
|
||||||
const savingState = reactive<Record<string, boolean>>({});
|
const isSavingAll = ref(false);
|
||||||
const successMessage = reactive<Record<string, string>>({});
|
const saveSuccess = ref('');
|
||||||
const errorMessage = reactive<Record<string, string>>({});
|
const saveError = ref('');
|
||||||
|
|
||||||
const settings = computed<CatalogSettingItem[]>(() => settingsQuery.result.value?.catalogProductTypeSettings ?? []);
|
const settings = computed<CatalogSettingItem[]>(() => settingsQuery.result.value?.catalogProductTypeSettings ?? []);
|
||||||
|
|
||||||
@@ -61,9 +61,6 @@ function parseOptionalInteger(value: string) {
|
|||||||
|
|
||||||
function formFor(item: CatalogSettingItem) {
|
function formFor(item: CatalogSettingItem) {
|
||||||
forms[item.productType] ??= createForm(item);
|
forms[item.productType] ??= createForm(item);
|
||||||
savingState[item.productType] ??= false;
|
|
||||||
successMessage[item.productType] ??= '';
|
|
||||||
errorMessage[item.productType] ??= '';
|
|
||||||
return forms[item.productType];
|
return forms[item.productType];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,65 +72,53 @@ watch(
|
|||||||
for (const productType of Object.keys(forms)) {
|
for (const productType of Object.keys(forms)) {
|
||||||
if (!activeTypes.has(productType)) {
|
if (!activeTypes.has(productType)) {
|
||||||
Reflect.deleteProperty(forms, productType);
|
Reflect.deleteProperty(forms, productType);
|
||||||
Reflect.deleteProperty(savingState, productType);
|
|
||||||
Reflect.deleteProperty(successMessage, productType);
|
|
||||||
Reflect.deleteProperty(errorMessage, productType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
forms[item.productType] = createForm(item);
|
forms[item.productType] = createForm(item);
|
||||||
savingState[item.productType] ??= false;
|
|
||||||
successMessage[item.productType] ??= '';
|
|
||||||
errorMessage[item.productType] ??= '';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
async function saveProductTypeSetting(productType: string) {
|
async function saveAllSettings() {
|
||||||
const form = forms[productType];
|
saveSuccess.value = '';
|
||||||
if (!form) {
|
saveError.value = '';
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
successMessage[productType] = '';
|
isSavingAll.value = false;
|
||||||
errorMessage[productType] = '';
|
saveSuccess.value = 'Настройки сохранены.';
|
||||||
savingState[productType] = true;
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
savingState[productType] = false;
|
|
||||||
|
|
||||||
if (!result?.data?.upsertCatalogProductTypeSetting) {
|
|
||||||
errorMessage[productType] = saveSettingMutation.error.value?.message || 'Не удалось сохранить настройки.';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
forms[productType] = createForm(result.data.upsertCatalogProductTypeSetting);
|
|
||||||
successMessage[productType] = 'Настройки сохранены.';
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="space-y-6">
|
<section class="space-y-6">
|
||||||
<div class="space-y-2">
|
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Каталог</h1>
|
||||||
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Каталог</h1>
|
|
||||||
<p class="text-sm leading-6 text-[#557562]">
|
|
||||||
Правила конструктора по каждому типу товара: длина, своя втулка, своя надпись и видимость короба.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="settingsQuery.loading.value" class="manager-empty-state">
|
<div v-if="settingsQuery.loading.value" class="manager-empty-state">
|
||||||
Загружаем настройки каталога...
|
Загружаем настройки каталога...
|
||||||
@@ -150,62 +135,33 @@ async function saveProductTypeSetting(productType: string) {
|
|||||||
class="rounded-[28px] bg-white p-5 shadow-[0_18px_38px_rgba(18,56,36,0.08)]"
|
class="rounded-[28px] bg-white p-5 shadow-[0_18px_38px_rgba(18,56,36,0.08)]"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="space-y-1">
|
<h2 class="text-xl font-bold text-[#123824]">{{ item.productType }}</h2>
|
||||||
<h2 class="text-xl font-bold text-[#123824]">{{ item.productType }}</h2>
|
|
||||||
<p class="text-sm text-[#557562]">
|
|
||||||
Определяет, какие параметры клиент сможет менять на витрине для этого типа товара.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid gap-3 md:grid-cols-2">
|
<div class="grid gap-3 md:grid-cols-2">
|
||||||
<label class="surface-card flex items-start gap-3 rounded-[22px] p-4">
|
<label class="surface-card flex items-center justify-between gap-4 rounded-[22px] p-4">
|
||||||
<input v-model="formFor(item).allowCustomLength" type="checkbox" class="toggle toggle-success mt-1">
|
<span class="text-sm font-semibold text-[#123824]">Своя длина</span>
|
||||||
<span>
|
<input v-model="formFor(item).allowCustomLength" type="checkbox" class="toggle toggle-success">
|
||||||
<span class="block font-semibold text-[#123824]">Своя длина</span>
|
|
||||||
<span class="mt-1 block text-sm text-[#557562]">Разрешить длину вне стандартного списка.</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="surface-card flex items-start gap-3 rounded-[22px] p-4">
|
<label class="surface-card flex items-center justify-between gap-4 rounded-[22px] p-4">
|
||||||
<input v-model="formFor(item).allowCustomSleeveBrand" type="checkbox" class="toggle toggle-success mt-1">
|
<span class="text-sm font-semibold text-[#123824]">Своя втулка</span>
|
||||||
<span>
|
<input v-model="formFor(item).allowCustomSleeveBrand" type="checkbox" class="toggle toggle-success">
|
||||||
<span class="block font-semibold text-[#123824]">Своя втулка</span>
|
|
||||||
<span class="mt-1 block text-sm text-[#557562]">Клиент сможет запросить втулку со своим вариантом.</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="surface-card flex items-start gap-3 rounded-[22px] p-4">
|
<label class="surface-card flex items-center justify-between gap-4 rounded-[22px] p-4">
|
||||||
<input v-model="formFor(item).allowCustomLabel" type="checkbox" class="toggle toggle-success mt-1">
|
<span class="text-sm font-semibold text-[#123824]">Своя надпись</span>
|
||||||
<span>
|
<input v-model="formFor(item).allowCustomLabel" type="checkbox" class="toggle toggle-success">
|
||||||
<span class="block font-semibold text-[#123824]">Своя надпись</span>
|
|
||||||
<span class="mt-1 block text-sm text-[#557562]">Разрешить индивидуальную надпись или маркировку.</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="surface-card flex items-start gap-3 rounded-[22px] p-4">
|
<label class="surface-card flex items-center justify-between gap-4 rounded-[22px] p-4">
|
||||||
<input v-model="formFor(item).showQuantityPerBox" type="checkbox" class="toggle toggle-success mt-1">
|
<span class="text-sm font-semibold text-[#123824]">Показывать короб</span>
|
||||||
<span>
|
<input v-model="formFor(item).showQuantityPerBox" type="checkbox" class="toggle toggle-success">
|
||||||
<span class="block font-semibold text-[#123824]">Показывать короб</span>
|
|
||||||
<span class="mt-1 block text-sm text-[#557562]">Если выключено, параметр короба скрывается из клиентского каталога.</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-[24px] bg-[#f7fbf8] p-4">
|
<div v-if="formFor(item).allowCustomLength" class="rounded-[24px] bg-[#f7fbf8] p-4">
|
||||||
<div class="flex items-center justify-between gap-3">
|
<div class="mb-4 text-sm font-bold uppercase tracking-[0.12em] text-[#355947]">Диапазон длины</div>
|
||||||
<div>
|
<div class="grid gap-3 md:grid-cols-3">
|
||||||
<h3 class="text-sm font-bold uppercase tracking-[0.12em] text-[#355947]">Диапазон длины</h3>
|
|
||||||
<p class="mt-1 text-sm text-[#557562]">Минимум, максимум и шаг для пользовательской длины.</p>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="rounded-full px-3 py-1 text-xs font-bold uppercase tracking-[0.12em]"
|
|
||||||
:class="formFor(item).allowCustomLength ? 'bg-[#e8f5ec] text-[#1c6b45]' : 'bg-[#eef2ef] text-[#6b7f71]'"
|
|
||||||
>
|
|
||||||
{{ formFor(item).allowCustomLength ? 'Включено' : 'Выключено' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4 grid gap-3 md:grid-cols-3">
|
|
||||||
<label class="space-y-2">
|
<label class="space-y-2">
|
||||||
<span class="text-sm font-semibold text-[#123824]">Мин. длина, м</span>
|
<span class="text-sm font-semibold text-[#123824]">Мин. длина, м</span>
|
||||||
<input
|
<input
|
||||||
@@ -214,7 +170,6 @@ async function saveProductTypeSetting(productType: string) {
|
|||||||
min="1"
|
min="1"
|
||||||
step="1"
|
step="1"
|
||||||
class="input manager-field w-full"
|
class="input manager-field w-full"
|
||||||
:disabled="!formFor(item).allowCustomLength"
|
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -226,7 +181,6 @@ async function saveProductTypeSetting(productType: string) {
|
|||||||
min="1"
|
min="1"
|
||||||
step="1"
|
step="1"
|
||||||
class="input manager-field w-full"
|
class="input manager-field w-full"
|
||||||
:disabled="!formFor(item).allowCustomLength"
|
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -238,28 +192,27 @@ async function saveProductTypeSetting(productType: string) {
|
|||||||
min="1"
|
min="1"
|
||||||
step="1"
|
step="1"
|
||||||
class="input manager-field w-full"
|
class="input manager-field w-full"
|
||||||
:disabled="!formFor(item).allowCustomLength"
|
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
||||||
<div class="space-y-1 text-sm">
|
|
||||||
<p v-if="successMessage[item.productType]" class="font-semibold text-[#1c6b45]">{{ successMessage[item.productType] }}</p>
|
|
||||||
<p v-if="errorMessage[item.productType]" class="font-semibold text-[#c4472d]">{{ errorMessage[item.productType] }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="btn h-11 rounded-full border-0 bg-[#139957] px-5 text-sm font-semibold text-white hover:bg-[#0d854a]"
|
|
||||||
:disabled="savingState[item.productType]"
|
|
||||||
@click="saveProductTypeSetting(item.productType)"
|
|
||||||
>
|
|
||||||
{{ savingState[item.productType] ? 'Сохраняем…' : 'Сохранить' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</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>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user