Make catalog filters adjust to valid combinations
This commit is contained in:
@@ -239,7 +239,7 @@ watch(
|
|||||||
|
|
||||||
for (const key of Object.keys(groupStates)) {
|
for (const key of Object.keys(groupStates)) {
|
||||||
if (!keys.has(key)) {
|
if (!keys.has(key)) {
|
||||||
delete groupStates[key];
|
Reflect.deleteProperty(groupStates, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,11 +269,6 @@ function matchesProductState(product: ParsedProduct, state: GroupState, keys: Pa
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchingProducts(group: ProductGroup) {
|
|
||||||
const state = getGroupState(group);
|
|
||||||
return group.products.filter((product) => matchesProductState(product, state, requiredKeys(group)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectedProduct(group: ProductGroup) {
|
function selectedProduct(group: ProductGroup) {
|
||||||
const keys = requiredKeys(group);
|
const keys = requiredKeys(group);
|
||||||
const state = getGroupState(group);
|
const state = getGroupState(group);
|
||||||
@@ -303,6 +298,14 @@ function formatOptionLabel(field: ParamFieldKey, value: ParamValue) {
|
|||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function productHasOption(product: ParsedProduct, field: ParamFieldKey, option: ParamValue) {
|
||||||
|
if (field === 'quantityPerBox') {
|
||||||
|
return product.quantityPerBoxOptions.includes(String(option));
|
||||||
|
}
|
||||||
|
|
||||||
|
return product[field] === option;
|
||||||
|
}
|
||||||
|
|
||||||
function isOptionAvailable(group: ProductGroup, field: ParamFieldKey, option: ParamValue) {
|
function isOptionAvailable(group: ProductGroup, field: ParamFieldKey, option: ParamValue) {
|
||||||
const state = getGroupState(group);
|
const state = getGroupState(group);
|
||||||
const scopedState = {
|
const scopedState = {
|
||||||
@@ -313,25 +316,73 @@ function isOptionAvailable(group: ProductGroup, field: ParamFieldKey, option: Pa
|
|||||||
return group.products.some((product) => matchesProductState(product, scopedState, requiredKeys(group)));
|
return group.products.some((product) => matchesProductState(product, scopedState, requiredKeys(group)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateField(group: ProductGroup, field: ParamFieldKey, value: ParamValue) {
|
function pickBestProductForOption(group: ProductGroup, field: ParamFieldKey, option: ParamValue) {
|
||||||
const state = getGroupState(group);
|
const state = getGroupState(group);
|
||||||
state[field] = value as GroupState[typeof field];
|
const candidates = group.products.filter((product) => productHasOption(product, field, option));
|
||||||
|
|
||||||
const resolved = selectedProduct(group);
|
if (candidates.length === 0) {
|
||||||
if (resolved) {
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...candidates].sort((a, b) => {
|
||||||
|
let aScore = 0;
|
||||||
|
let bScore = 0;
|
||||||
|
|
||||||
|
for (const key of PARAM_KEYS) {
|
||||||
|
if (key === field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedValue = state[key];
|
||||||
|
if (selectedValue === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productHasOption(a, key, selectedValue)) {
|
||||||
|
aScore += 1;
|
||||||
|
}
|
||||||
|
if (productHasOption(b, key, selectedValue)) {
|
||||||
|
bScore += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aScore !== bScore) {
|
||||||
|
return bScore - aScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareProducts(a, b);
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyProductToState(state: GroupState, product: ParsedProduct, preferredBoxOption: ParamValue | null = null) {
|
||||||
|
state.widthMm = product.widthMm ?? null;
|
||||||
|
state.lengthM = product.lengthM ?? null;
|
||||||
|
state.thicknessMicron = product.thicknessMicron ?? null;
|
||||||
|
state.sleeveBrand = product.sleeveBrand ?? null;
|
||||||
|
|
||||||
|
if (preferredBoxOption !== null && product.quantityPerBoxOptions.includes(String(preferredBoxOption))) {
|
||||||
|
state.quantityPerBox = String(preferredBoxOption);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallback = group.products.find((product) => product[field] === value) ?? group.products[0];
|
state.quantityPerBox = product.quantityPerBoxOptions[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateField(group: ProductGroup, field: ParamFieldKey, value: ParamValue) {
|
||||||
|
const state = getGroupState(group);
|
||||||
|
const fallback = pickBestProductForOption(group, field, value);
|
||||||
if (!fallback) {
|
if (!fallback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.widthMm = fallback.widthMm ?? null;
|
applyProductToState(state, fallback, field === 'quantityPerBox' ? value : state.quantityPerBox);
|
||||||
state.lengthM = fallback.lengthM ?? null;
|
|
||||||
state.thicknessMicron = fallback.thicknessMicron ?? null;
|
if (field === 'quantityPerBox') {
|
||||||
state.sleeveBrand = fallback.sleeveBrand ?? null;
|
state.quantityPerBox = String(value);
|
||||||
state.quantityPerBox = fallback.quantityPerBoxOptions[0] ?? null;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state[field] = value as GroupState[typeof field];
|
||||||
}
|
}
|
||||||
|
|
||||||
function articleLabel(group: ProductGroup) {
|
function articleLabel(group: ProductGroup) {
|
||||||
@@ -451,14 +502,14 @@ function decrementSelected(group: ProductGroup) {
|
|||||||
<label
|
<label
|
||||||
v-for="option in getAllFieldOptions(group, field.key)"
|
v-for="option in getAllFieldOptions(group, field.key)"
|
||||||
:key="`${group.key}-${field.key}-${option}`"
|
:key="`${group.key}-${field.key}-${option}`"
|
||||||
class="btn btn-sm rounded-full border text-sm normal-case shadow-none transition"
|
class="btn btn-sm rounded-full text-sm normal-case shadow-none transition"
|
||||||
:class="[
|
:class="[
|
||||||
getGroupState(group)[field.key] === option
|
getGroupState(group)[field.key] === option
|
||||||
? 'border-neutral bg-neutral text-neutral-content'
|
? 'bg-neutral text-neutral-content'
|
||||||
: 'border-base-300 bg-base-100 text-base-content hover:border-neutral/30 hover:bg-base-200',
|
: isOptionAvailable(group, field.key, option)
|
||||||
!isOptionAvailable(group, field.key, option)
|
? 'bg-base-100 text-base-content hover:bg-base-200'
|
||||||
? 'pointer-events-none opacity-35'
|
: 'bg-[#eef1f4] text-[#7b8591] hover:bg-[#e3e7eb]',
|
||||||
: 'cursor-pointer',
|
'cursor-pointer',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -466,7 +517,6 @@ function decrementSelected(group: ProductGroup) {
|
|||||||
class="sr-only"
|
class="sr-only"
|
||||||
:name="`${group.key}-${field.key}`"
|
:name="`${group.key}-${field.key}`"
|
||||||
:checked="getGroupState(group)[field.key] === option"
|
:checked="getGroupState(group)[field.key] === option"
|
||||||
:disabled="!isOptionAvailable(group, field.key, option)"
|
|
||||||
@change="updateField(group, field.key, option)"
|
@change="updateField(group, field.key, option)"
|
||||||
>
|
>
|
||||||
<span>{{ formatOptionLabel(field.key, option) }}</span>
|
<span>{{ formatOptionLabel(field.key, option) }}</span>
|
||||||
@@ -487,14 +537,14 @@ function decrementSelected(group: ProductGroup) {
|
|||||||
<label
|
<label
|
||||||
v-for="option in getAllFieldOptions(group, field.key)"
|
v-for="option in getAllFieldOptions(group, field.key)"
|
||||||
:key="`${group.key}-${field.key}-${option}`"
|
:key="`${group.key}-${field.key}-${option}`"
|
||||||
class="btn btn-sm rounded-full border text-sm normal-case shadow-none transition"
|
class="btn btn-sm rounded-full text-sm normal-case shadow-none transition"
|
||||||
:class="[
|
:class="[
|
||||||
getGroupState(group)[field.key] === option
|
getGroupState(group)[field.key] === option
|
||||||
? 'border-neutral bg-neutral text-neutral-content'
|
? 'bg-neutral text-neutral-content'
|
||||||
: 'border-base-300 bg-base-100 text-base-content hover:border-neutral/30 hover:bg-base-200',
|
: isOptionAvailable(group, field.key, option)
|
||||||
!isOptionAvailable(group, field.key, option)
|
? 'bg-base-100 text-base-content hover:bg-base-200'
|
||||||
? 'pointer-events-none opacity-35'
|
: 'bg-[#eef1f4] text-[#7b8591] hover:bg-[#e3e7eb]',
|
||||||
: 'cursor-pointer',
|
'cursor-pointer',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -502,7 +552,6 @@ function decrementSelected(group: ProductGroup) {
|
|||||||
class="sr-only"
|
class="sr-only"
|
||||||
:name="`${group.key}-${field.key}`"
|
:name="`${group.key}-${field.key}`"
|
||||||
:checked="getGroupState(group)[field.key] === option"
|
:checked="getGroupState(group)[field.key] === option"
|
||||||
:disabled="!isOptionAvailable(group, field.key, option)"
|
|
||||||
@change="updateField(group, field.key, option)"
|
@change="updateField(group, field.key, option)"
|
||||||
>
|
>
|
||||||
<span>{{ formatOptionLabel(field.key, option) }}</span>
|
<span>{{ formatOptionLabel(field.key, option) }}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user