Make catalog settings editable
This commit is contained in:
@@ -94,11 +94,17 @@ export type CatalogProductTypeSetting = {
|
|||||||
allowCustomLabel: Scalars['Boolean']['output'];
|
allowCustomLabel: Scalars['Boolean']['output'];
|
||||||
allowCustomLength: Scalars['Boolean']['output'];
|
allowCustomLength: Scalars['Boolean']['output'];
|
||||||
allowCustomSleeveBrand: Scalars['Boolean']['output'];
|
allowCustomSleeveBrand: Scalars['Boolean']['output'];
|
||||||
|
colorOptions: Array<Scalars['String']['output']>;
|
||||||
customLengthMaxM?: Maybe<Scalars['Int']['output']>;
|
customLengthMaxM?: Maybe<Scalars['Int']['output']>;
|
||||||
customLengthMinM?: Maybe<Scalars['Int']['output']>;
|
customLengthMinM?: Maybe<Scalars['Int']['output']>;
|
||||||
customLengthStepM?: Maybe<Scalars['Int']['output']>;
|
customLengthStepM?: Maybe<Scalars['Int']['output']>;
|
||||||
|
labelOptions: Array<Scalars['String']['output']>;
|
||||||
|
lengthOptionsM: Array<Scalars['Int']['output']>;
|
||||||
productType: Scalars['String']['output'];
|
productType: Scalars['String']['output'];
|
||||||
showQuantityPerBox: Scalars['Boolean']['output'];
|
showQuantityPerBox: Scalars['Boolean']['output'];
|
||||||
|
sleeveOptions: Array<Scalars['String']['output']>;
|
||||||
|
thicknessOptionsMicron: Array<Scalars['Int']['output']>;
|
||||||
|
widthOptionsMm: Array<Scalars['Int']['output']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Company = {
|
export type Company = {
|
||||||
@@ -786,11 +792,17 @@ export type UpsertCatalogProductTypeSettingInput = {
|
|||||||
allowCustomLabel: Scalars['Boolean']['input'];
|
allowCustomLabel: Scalars['Boolean']['input'];
|
||||||
allowCustomLength: Scalars['Boolean']['input'];
|
allowCustomLength: Scalars['Boolean']['input'];
|
||||||
allowCustomSleeveBrand: Scalars['Boolean']['input'];
|
allowCustomSleeveBrand: Scalars['Boolean']['input'];
|
||||||
|
colorOptions: Array<Scalars['String']['input']>;
|
||||||
customLengthMaxM?: InputMaybe<Scalars['Int']['input']>;
|
customLengthMaxM?: InputMaybe<Scalars['Int']['input']>;
|
||||||
customLengthMinM?: InputMaybe<Scalars['Int']['input']>;
|
customLengthMinM?: InputMaybe<Scalars['Int']['input']>;
|
||||||
customLengthStepM?: InputMaybe<Scalars['Int']['input']>;
|
customLengthStepM?: InputMaybe<Scalars['Int']['input']>;
|
||||||
|
labelOptions: Array<Scalars['String']['input']>;
|
||||||
|
lengthOptionsM: Array<Scalars['Int']['input']>;
|
||||||
productType: Scalars['String']['input'];
|
productType: Scalars['String']['input'];
|
||||||
showQuantityPerBox: Scalars['Boolean']['input'];
|
showQuantityPerBox: Scalars['Boolean']['input'];
|
||||||
|
sleeveOptions: Array<Scalars['String']['input']>;
|
||||||
|
thicknessOptionsMicron: Array<Scalars['Int']['input']>;
|
||||||
|
widthOptionsMm: Array<Scalars['Int']['input']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpsertMyCounterpartyProfileInput = {
|
export type UpsertMyCounterpartyProfileInput = {
|
||||||
@@ -1158,7 +1170,7 @@ export type UpsertMyCounterpartyProfileMutation = { __typename?: 'Mutation', ups
|
|||||||
export type CatalogProductTypeSettingsQueryVariables = Exact<{ [key: string]: never; }>;
|
export type CatalogProductTypeSettingsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type CatalogProductTypeSettingsQuery = { __typename?: 'Query', catalogProductTypeSettings: Array<{ __typename?: 'CatalogProductTypeSetting', productType: string, showQuantityPerBox: boolean, allowCustomLength: boolean, customLengthMinM?: number | null, customLengthMaxM?: number | null, customLengthStepM?: number | null, allowCustomSleeveBrand: boolean, allowCustomLabel: boolean }> };
|
export type CatalogProductTypeSettingsQuery = { __typename?: 'Query', catalogProductTypeSettings: Array<{ __typename?: 'CatalogProductTypeSetting', productType: string, showQuantityPerBox: boolean, allowCustomLength: boolean, customLengthMinM?: number | null, customLengthMaxM?: number | null, customLengthStepM?: number | null, allowCustomSleeveBrand: boolean, allowCustomLabel: boolean, widthOptionsMm: Array<number>, lengthOptionsM: Array<number>, thicknessOptionsMicron: Array<number>, sleeveOptions: Array<string>, colorOptions: Array<string>, labelOptions: Array<string> }> };
|
||||||
|
|
||||||
export type IntegrationSyncDashboardQueryVariables = Exact<{ [key: string]: never; }>;
|
export type IntegrationSyncDashboardQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
@@ -1170,7 +1182,7 @@ export type UpsertCatalogProductTypeSettingMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type UpsertCatalogProductTypeSettingMutation = { __typename?: 'Mutation', upsertCatalogProductTypeSetting: { __typename?: 'CatalogProductTypeSetting', productType: string, showQuantityPerBox: boolean, allowCustomLength: boolean, customLengthMinM?: number | null, customLengthMaxM?: number | null, customLengthStepM?: number | null, allowCustomSleeveBrand: boolean, allowCustomLabel: boolean } };
|
export type UpsertCatalogProductTypeSettingMutation = { __typename?: 'Mutation', upsertCatalogProductTypeSetting: { __typename?: 'CatalogProductTypeSetting', productType: string, showQuantityPerBox: boolean, allowCustomLength: boolean, customLengthMinM?: number | null, customLengthMaxM?: number | null, customLengthStepM?: number | null, allowCustomSleeveBrand: boolean, allowCustomLabel: boolean, widthOptionsMm: Array<number>, lengthOptionsM: Array<number>, thicknessOptionsMicron: Array<number>, sleeveOptions: Array<string>, colorOptions: Array<string>, labelOptions: Array<string> } };
|
||||||
|
|
||||||
|
|
||||||
export const ConsumeLoginTokenDocument = gql`
|
export const ConsumeLoginTokenDocument = gql`
|
||||||
@@ -2980,6 +2992,12 @@ export const CatalogProductTypeSettingsDocument = gql`
|
|||||||
customLengthStepM
|
customLengthStepM
|
||||||
allowCustomSleeveBrand
|
allowCustomSleeveBrand
|
||||||
allowCustomLabel
|
allowCustomLabel
|
||||||
|
widthOptionsMm
|
||||||
|
lengthOptionsM
|
||||||
|
thicknessOptionsMicron
|
||||||
|
sleeveOptions
|
||||||
|
colorOptions
|
||||||
|
labelOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -3055,6 +3073,12 @@ export const UpsertCatalogProductTypeSettingDocument = gql`
|
|||||||
customLengthStepM
|
customLengthStepM
|
||||||
allowCustomSleeveBrand
|
allowCustomSleeveBrand
|
||||||
allowCustomLabel
|
allowCustomLabel
|
||||||
|
widthOptionsMm
|
||||||
|
lengthOptionsM
|
||||||
|
thicknessOptionsMicron
|
||||||
|
sleeveOptions
|
||||||
|
colorOptions
|
||||||
|
labelOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
import { useMutation, useQuery } from '@vue/apollo-composable';
|
import { useMutation, useQuery } from '@vue/apollo-composable';
|
||||||
import {
|
import {
|
||||||
CatalogProductTypeSettingsDocument,
|
CatalogProductTypeSettingsDocument,
|
||||||
ClientProductsDocument,
|
|
||||||
UpsertCatalogProductTypeSettingDocument,
|
UpsertCatalogProductTypeSettingDocument,
|
||||||
type CatalogProductTypeSettingsQuery,
|
type CatalogProductTypeSettingsQuery,
|
||||||
type ClientProductsQuery,
|
|
||||||
} from '~/composables/graphql/generated';
|
} from '~/composables/graphql/generated';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@@ -14,7 +12,14 @@ definePageMeta({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type CatalogSettingItem = CatalogProductTypeSettingsQuery['catalogProductTypeSettings'][number];
|
type CatalogSettingItem = CatalogProductTypeSettingsQuery['catalogProductTypeSettings'][number];
|
||||||
type ProductNode = ClientProductsQuery['clientProducts'][number];
|
type OptionKey =
|
||||||
|
| 'widthOptionsMm'
|
||||||
|
| 'lengthOptionsM'
|
||||||
|
| 'thicknessOptionsMicron'
|
||||||
|
| 'sleeveOptions'
|
||||||
|
| 'colorOptions'
|
||||||
|
| 'labelOptions';
|
||||||
|
type OptionKind = 'number' | 'text';
|
||||||
type CatalogSettingForm = {
|
type CatalogSettingForm = {
|
||||||
productType: string;
|
productType: string;
|
||||||
allowCustomLength: boolean;
|
allowCustomLength: boolean;
|
||||||
@@ -23,17 +28,32 @@ type CatalogSettingForm = {
|
|||||||
customLengthStepM: string;
|
customLengthStepM: string;
|
||||||
allowCustomSleeveBrand: boolean;
|
allowCustomSleeveBrand: boolean;
|
||||||
allowCustomLabel: boolean;
|
allowCustomLabel: boolean;
|
||||||
|
widthOptionsMm: string[];
|
||||||
|
lengthOptionsM: string[];
|
||||||
|
thicknessOptionsMicron: string[];
|
||||||
|
sleeveOptions: string[];
|
||||||
|
colorOptions: string[];
|
||||||
|
labelOptions: string[];
|
||||||
|
drafts: Record<OptionKey, string>;
|
||||||
};
|
};
|
||||||
type StandardOptionGroup = {
|
type OptionGroupDefinition = {
|
||||||
|
key: OptionKey;
|
||||||
label: string;
|
label: string;
|
||||||
values: string[];
|
placeholder: string;
|
||||||
|
kind: OptionKind;
|
||||||
|
suffix?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLOR_TAGS = ['прозрачный', 'коричневый', 'белый', 'черный', 'желтый', 'зеленый', 'красный', 'синий', 'оранжевый', 'красно-белый'];
|
const OPTION_GROUPS: OptionGroupDefinition[] = [
|
||||||
const LABEL_TAGS = ['хрупкое', 'подарок', 'акция'];
|
{ key: 'widthOptionsMm', label: 'Ширина', placeholder: 'Добавить ширину', kind: 'number', suffix: 'мм' },
|
||||||
|
{ key: 'lengthOptionsM', label: 'Длина', placeholder: 'Добавить длину', kind: 'number', suffix: 'м' },
|
||||||
|
{ key: 'thicknessOptionsMicron', label: 'Толщина', placeholder: 'Добавить толщину', kind: 'number', suffix: 'мкм' },
|
||||||
|
{ key: 'sleeveOptions', label: 'Втулка', placeholder: 'Добавить втулку', kind: 'text' },
|
||||||
|
{ key: 'colorOptions', label: 'Цвет', placeholder: 'Добавить цвет', kind: 'text' },
|
||||||
|
{ key: 'labelOptions', label: 'Надпись', placeholder: 'Добавить надпись', kind: 'text' },
|
||||||
|
];
|
||||||
|
|
||||||
const settingsQuery = useQuery(CatalogProductTypeSettingsDocument);
|
const settingsQuery = useQuery(CatalogProductTypeSettingsDocument);
|
||||||
const catalogProductsQuery = useQuery(ClientProductsDocument);
|
|
||||||
const saveSettingMutation = useMutation(UpsertCatalogProductTypeSettingDocument, { throws: 'never' });
|
const saveSettingMutation = useMutation(UpsertCatalogProductTypeSettingDocument, { throws: 'never' });
|
||||||
|
|
||||||
const forms = reactive<Record<string, CatalogSettingForm>>({});
|
const forms = reactive<Record<string, CatalogSettingForm>>({});
|
||||||
@@ -42,64 +62,64 @@ const saveSuccess = ref('');
|
|||||||
const saveError = ref('');
|
const saveError = ref('');
|
||||||
|
|
||||||
const settings = computed<CatalogSettingItem[]>(() => settingsQuery.result.value?.catalogProductTypeSettings ?? []);
|
const settings = computed<CatalogSettingItem[]>(() => settingsQuery.result.value?.catalogProductTypeSettings ?? []);
|
||||||
const isLoading = computed(() => settingsQuery.loading.value || catalogProductsQuery.loading.value);
|
const isLoading = computed(() => settingsQuery.loading.value);
|
||||||
|
|
||||||
function normalizeText(value: string | null | undefined) {
|
function normalizeText(value: string | null | undefined) {
|
||||||
return String(value ?? '').replaceAll(/\s+/g, ' ').trim();
|
return String(value ?? '').replaceAll(/\s+/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatNumberOptions(values: Array<number | null | undefined>, suffix: string) {
|
|
||||||
return [...new Set(values.filter((value): value is number => typeof value === 'number'))]
|
|
||||||
.sort((a, b) => a - b)
|
|
||||||
.map((value) => `${value} ${suffix}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTextOptions(values: Array<string | null | undefined>) {
|
|
||||||
return [...new Set(values.map((value) => normalizeText(value)).filter(Boolean))]
|
|
||||||
.sort((a, b) => a.localeCompare(b, 'ru'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const standardOptionsByType = computed<Record<string, StandardOptionGroup[]>>(() => {
|
|
||||||
const products = catalogProductsQuery.result.value?.clientProducts ?? [];
|
|
||||||
const grouped = new Map<string, ProductNode[]>();
|
|
||||||
|
|
||||||
for (const product of products) {
|
|
||||||
const typeLabel = normalizeText(product.productType) || 'Без типа';
|
|
||||||
const existing = grouped.get(typeLabel);
|
|
||||||
if (existing) {
|
|
||||||
existing.push(product);
|
|
||||||
} else {
|
|
||||||
grouped.set(typeLabel, [product]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.fromEntries(
|
|
||||||
[...grouped.entries()].map(([typeLabel, items]) => {
|
|
||||||
const colorValues = formatTextOptions(
|
|
||||||
items.flatMap((product) => product.tags.filter((tag) => COLOR_TAGS.includes(normalizeText(tag)))),
|
|
||||||
);
|
|
||||||
const labelValues = formatTextOptions(
|
|
||||||
items.flatMap((product) => product.tags.filter((tag) => LABEL_TAGS.includes(normalizeText(tag)))),
|
|
||||||
);
|
|
||||||
|
|
||||||
const optionGroups: StandardOptionGroup[] = [
|
|
||||||
{ label: 'Ширина', values: formatNumberOptions(items.map((product) => product.widthMm), 'мм') },
|
|
||||||
{ label: 'Длина', values: formatNumberOptions(items.map((product) => product.lengthM), 'м') },
|
|
||||||
{ label: 'Толщина', values: formatNumberOptions(items.map((product) => product.thicknessMicron), 'мкм') },
|
|
||||||
{ label: 'Втулка', values: formatTextOptions(items.map((product) => product.sleeveBrand)) },
|
|
||||||
{ label: 'Цвет', values: colorValues },
|
|
||||||
{ label: 'Надпись', values: labelValues },
|
|
||||||
].filter((group) => group.values.length > 0);
|
|
||||||
|
|
||||||
return [typeLabel, optionGroups];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function toInputValue(value: number | null | undefined) {
|
function toInputValue(value: number | null | undefined) {
|
||||||
return value == null ? '' : String(value);
|
return value == null ? '' : String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createDrafts(): Record<OptionKey, string> {
|
||||||
|
return {
|
||||||
|
widthOptionsMm: '',
|
||||||
|
lengthOptionsM: '',
|
||||||
|
thicknessOptionsMicron: '',
|
||||||
|
sleeveOptions: '',
|
||||||
|
colorOptions: '',
|
||||||
|
labelOptions: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptionalInteger(value: string) {
|
||||||
|
const normalized = value.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = Number(normalized);
|
||||||
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeOptionEntry(value: string, kind: OptionKind) {
|
||||||
|
if (kind === 'number') {
|
||||||
|
const parsed = parseOptionalInteger(value);
|
||||||
|
return parsed == null ? '' : String(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeText(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeOptionList(values: Array<string | number | null | undefined>, kind: OptionKind) {
|
||||||
|
const normalizedValues = values
|
||||||
|
.map((value) => normalizeOptionEntry(String(value ?? ''), kind))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return [...new Set(normalizedValues)].sort((left, right) => {
|
||||||
|
if (kind === 'number') {
|
||||||
|
return Number(left) - Number(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.localeCompare(right, 'ru');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function createForm(item: CatalogSettingItem): CatalogSettingForm {
|
function createForm(item: CatalogSettingItem): CatalogSettingForm {
|
||||||
return {
|
return {
|
||||||
productType: item.productType,
|
productType: item.productType,
|
||||||
@@ -109,25 +129,45 @@ function createForm(item: CatalogSettingItem): CatalogSettingForm {
|
|||||||
customLengthStepM: toInputValue(item.customLengthStepM),
|
customLengthStepM: toInputValue(item.customLengthStepM),
|
||||||
allowCustomSleeveBrand: item.allowCustomSleeveBrand,
|
allowCustomSleeveBrand: item.allowCustomSleeveBrand,
|
||||||
allowCustomLabel: item.allowCustomLabel,
|
allowCustomLabel: item.allowCustomLabel,
|
||||||
|
widthOptionsMm: normalizeOptionList(item.widthOptionsMm, 'number'),
|
||||||
|
lengthOptionsM: normalizeOptionList(item.lengthOptionsM, 'number'),
|
||||||
|
thicknessOptionsMicron: normalizeOptionList(item.thicknessOptionsMicron, 'number'),
|
||||||
|
sleeveOptions: normalizeOptionList(item.sleeveOptions, 'text'),
|
||||||
|
colorOptions: normalizeOptionList(item.colorOptions, 'text'),
|
||||||
|
labelOptions: normalizeOptionList(item.labelOptions, 'text'),
|
||||||
|
drafts: createDrafts(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseOptionalInteger(value: string) {
|
|
||||||
const normalized = value.trim();
|
|
||||||
if (!normalized) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Number(normalized);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formFor(item: CatalogSettingItem) {
|
function formFor(item: CatalogSettingItem) {
|
||||||
forms[item.productType] ??= createForm(item);
|
forms[item.productType] ??= createForm(item);
|
||||||
return forms[item.productType];
|
return forms[item.productType];
|
||||||
}
|
}
|
||||||
|
|
||||||
function standardOptionsFor(item: CatalogSettingItem) {
|
function addOption(form: CatalogSettingForm, group: OptionGroupDefinition) {
|
||||||
return standardOptionsByType.value[item.productType] ?? [];
|
const value = normalizeOptionEntry(form.drafts[group.key], group.kind);
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
form[group.key] = normalizeOptionList([...form[group.key], value], group.kind);
|
||||||
|
form.drafts[group.key] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeOption(form: CatalogSettingForm, groupKey: OptionKey, value: string) {
|
||||||
|
form[groupKey] = form[groupKey].filter((item) => item !== value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionChipLabel(value: string, group: OptionGroupDefinition) {
|
||||||
|
return group.suffix ? `${value} ${group.suffix}` : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIntegerOptionList(values: string[]) {
|
||||||
|
return normalizeOptionList(values, 'number').map((value) => Number(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTextOptionList(values: string[]) {
|
||||||
|
return normalizeOptionList(values, 'text');
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -165,6 +205,12 @@ async function saveAllSettings() {
|
|||||||
customLengthStepM: form.allowCustomLength ? parseOptionalInteger(form.customLengthStepM) : null,
|
customLengthStepM: form.allowCustomLength ? parseOptionalInteger(form.customLengthStepM) : null,
|
||||||
allowCustomSleeveBrand: form.allowCustomSleeveBrand,
|
allowCustomSleeveBrand: form.allowCustomSleeveBrand,
|
||||||
allowCustomLabel: form.allowCustomLabel,
|
allowCustomLabel: form.allowCustomLabel,
|
||||||
|
widthOptionsMm: parseIntegerOptionList(form.widthOptionsMm),
|
||||||
|
lengthOptionsM: parseIntegerOptionList(form.lengthOptionsM),
|
||||||
|
thicknessOptionsMicron: parseIntegerOptionList(form.thicknessOptionsMicron),
|
||||||
|
sleeveOptions: parseTextOptionList(form.sleeveOptions),
|
||||||
|
colorOptions: parseTextOptionList(form.colorOptions),
|
||||||
|
labelOptions: parseTextOptionList(form.labelOptions),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -194,13 +240,13 @@ async function saveAllSettings() {
|
|||||||
Типы товаров пока не появились в каталоге.
|
Типы товаров пока не появились в каталоге.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="grid gap-4 xl:grid-cols-2">
|
<div v-else class="space-y-4">
|
||||||
<article
|
<article
|
||||||
v-for="item in settings"
|
v-for="item in settings"
|
||||||
:key="item.productType"
|
:key="item.productType"
|
||||||
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="space-y-5">
|
||||||
<h2 class="text-xl font-bold text-[#123824]">{{ item.productType }}</h2>
|
<h2 class="text-xl font-bold text-[#123824]">{{ item.productType }}</h2>
|
||||||
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
@@ -258,24 +304,57 @@ async function saveAllSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="standardOptionsFor(item).length" class="rounded-[24px] bg-[#f7fbf8] p-4">
|
<div class="rounded-[24px] bg-[#f7fbf8] p-4">
|
||||||
<div class="mb-4 text-sm font-bold uppercase tracking-[0.12em] text-[#355947]">Стандартные параметры</div>
|
<div class="mb-4 text-sm font-bold uppercase tracking-[0.12em] text-[#355947]">Стандартные параметры</div>
|
||||||
<div class="space-y-3">
|
|
||||||
|
<div class="space-y-4">
|
||||||
<div
|
<div
|
||||||
v-for="group in standardOptionsFor(item)"
|
v-for="group in OPTION_GROUPS"
|
||||||
:key="`${item.productType}-${group.label}`"
|
:key="`${item.productType}-${group.key}`"
|
||||||
class="space-y-2"
|
class="rounded-[20px] bg-white p-4"
|
||||||
>
|
>
|
||||||
|
<div class="space-y-3">
|
||||||
<div class="text-sm font-semibold text-[#123824]">{{ group.label }}</div>
|
<div class="text-sm font-semibold text-[#123824]">{{ group.label }}</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span
|
<button
|
||||||
v-for="value in group.values"
|
v-for="value in formFor(item)[group.key]"
|
||||||
:key="`${item.productType}-${group.label}-${value}`"
|
:key="`${item.productType}-${group.key}-${value}`"
|
||||||
class="rounded-full bg-white px-3 py-1 text-xs font-semibold text-[#355947]"
|
type="button"
|
||||||
|
class="inline-flex items-center gap-2 rounded-full bg-[#eef7f1] px-3 py-1 text-xs font-semibold text-[#1d5a3c]"
|
||||||
|
@click="removeOption(formFor(item), group.key, value)"
|
||||||
>
|
>
|
||||||
{{ value }}
|
<span>{{ optionChipLabel(value, group) }}</span>
|
||||||
|
<span class="text-[11px] leading-none text-[#6a8a78]">×</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="formFor(item)[group.key].length === 0"
|
||||||
|
class="rounded-full border border-dashed border-[#d6e7dc] px-3 py-1 text-xs font-medium text-[#7b8f84]"
|
||||||
|
>
|
||||||
|
Пока пусто
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2 md:flex-row">
|
||||||
|
<input
|
||||||
|
v-model="formFor(item).drafts[group.key]"
|
||||||
|
:type="group.kind === 'number' ? 'number' : 'text'"
|
||||||
|
:min="group.kind === 'number' ? '1' : undefined"
|
||||||
|
:step="group.kind === 'number' ? '1' : undefined"
|
||||||
|
class="input manager-field w-full"
|
||||||
|
:placeholder="group.placeholder"
|
||||||
|
@keydown.enter.prevent="addOption(formFor(item), group)"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn h-11 rounded-full border-0 bg-[#dff2e7] px-5 text-sm font-semibold text-[#155c3a] hover:bg-[#caead8]"
|
||||||
|
@click="addOption(formFor(item), group)"
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,5 +8,11 @@ query CatalogProductTypeSettings {
|
|||||||
customLengthStepM
|
customLengthStepM
|
||||||
allowCustomSleeveBrand
|
allowCustomSleeveBrand
|
||||||
allowCustomLabel
|
allowCustomLabel
|
||||||
|
widthOptionsMm
|
||||||
|
lengthOptionsM
|
||||||
|
thicknessOptionsMicron
|
||||||
|
sleeveOptions
|
||||||
|
colorOptions
|
||||||
|
labelOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,11 @@ mutation UpsertCatalogProductTypeSetting($input: UpsertCatalogProductTypeSetting
|
|||||||
customLengthStepM
|
customLengthStepM
|
||||||
allowCustomSleeveBrand
|
allowCustomSleeveBrand
|
||||||
allowCustomLabel
|
allowCustomLabel
|
||||||
|
widthOptionsMm
|
||||||
|
lengthOptionsM
|
||||||
|
thicknessOptionsMicron
|
||||||
|
sleeveOptions
|
||||||
|
colorOptions
|
||||||
|
labelOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,6 +257,12 @@ type CatalogProductTypeSetting {
|
|||||||
customLengthStepM: Int
|
customLengthStepM: Int
|
||||||
allowCustomSleeveBrand: Boolean!
|
allowCustomSleeveBrand: Boolean!
|
||||||
allowCustomLabel: Boolean!
|
allowCustomLabel: Boolean!
|
||||||
|
widthOptionsMm: [Int!]!
|
||||||
|
lengthOptionsM: [Int!]!
|
||||||
|
thicknessOptionsMicron: [Int!]!
|
||||||
|
sleeveOptions: [String!]!
|
||||||
|
colorOptions: [String!]!
|
||||||
|
labelOptions: [String!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type CartItem {
|
type CartItem {
|
||||||
@@ -512,6 +518,12 @@ input UpsertCatalogProductTypeSettingInput {
|
|||||||
customLengthStepM: Int
|
customLengthStepM: Int
|
||||||
allowCustomSleeveBrand: Boolean!
|
allowCustomSleeveBrand: Boolean!
|
||||||
allowCustomLabel: Boolean!
|
allowCustomLabel: Boolean!
|
||||||
|
widthOptionsMm: [Int!]!
|
||||||
|
lengthOptionsM: [Int!]!
|
||||||
|
thicknessOptionsMicron: [Int!]!
|
||||||
|
sleeveOptions: [String!]!
|
||||||
|
colorOptions: [String!]!
|
||||||
|
labelOptions: [String!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
input ReadyOrderItemInput {
|
input ReadyOrderItemInput {
|
||||||
|
|||||||
Reference in New Issue
Block a user