Make catalog filters adjust to valid combinations
This commit is contained in:
@@ -239,7 +239,7 @@ watch(
|
||||
|
||||
for (const key of Object.keys(groupStates)) {
|
||||
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) {
|
||||
const keys = requiredKeys(group);
|
||||
const state = getGroupState(group);
|
||||
@@ -303,6 +298,14 @@ function formatOptionLabel(field: ParamFieldKey, value: ParamValue) {
|
||||
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) {
|
||||
const state = getGroupState(group);
|
||||
const scopedState = {
|
||||
@@ -313,25 +316,73 @@ function isOptionAvailable(group: ProductGroup, field: ParamFieldKey, option: Pa
|
||||
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);
|
||||
state[field] = value as GroupState[typeof field];
|
||||
const candidates = group.products.filter((product) => productHasOption(product, field, option));
|
||||
|
||||
const resolved = selectedProduct(group);
|
||||
if (resolved) {
|
||||
if (candidates.length === 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.widthMm = fallback.widthMm ?? null;
|
||||
state.lengthM = fallback.lengthM ?? null;
|
||||
state.thicknessMicron = fallback.thicknessMicron ?? null;
|
||||
state.sleeveBrand = fallback.sleeveBrand ?? null;
|
||||
state.quantityPerBox = fallback.quantityPerBoxOptions[0] ?? null;
|
||||
applyProductToState(state, fallback, field === 'quantityPerBox' ? value : state.quantityPerBox);
|
||||
|
||||
if (field === 'quantityPerBox') {
|
||||
state.quantityPerBox = String(value);
|
||||
return;
|
||||
}
|
||||
|
||||
state[field] = value as GroupState[typeof field];
|
||||
}
|
||||
|
||||
function articleLabel(group: ProductGroup) {
|
||||
@@ -451,14 +502,14 @@ function decrementSelected(group: ProductGroup) {
|
||||
<label
|
||||
v-for="option in getAllFieldOptions(group, field.key)"
|
||||
: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="[
|
||||
getGroupState(group)[field.key] === option
|
||||
? 'border-neutral 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)
|
||||
? 'pointer-events-none opacity-35'
|
||||
: 'cursor-pointer',
|
||||
? 'bg-neutral text-neutral-content'
|
||||
: isOptionAvailable(group, field.key, option)
|
||||
? 'bg-base-100 text-base-content hover:bg-base-200'
|
||||
: 'bg-[#eef1f4] text-[#7b8591] hover:bg-[#e3e7eb]',
|
||||
'cursor-pointer',
|
||||
]"
|
||||
>
|
||||
<input
|
||||
@@ -466,7 +517,6 @@ function decrementSelected(group: ProductGroup) {
|
||||
class="sr-only"
|
||||
:name="`${group.key}-${field.key}`"
|
||||
:checked="getGroupState(group)[field.key] === option"
|
||||
:disabled="!isOptionAvailable(group, field.key, option)"
|
||||
@change="updateField(group, field.key, option)"
|
||||
>
|
||||
<span>{{ formatOptionLabel(field.key, option) }}</span>
|
||||
@@ -487,14 +537,14 @@ function decrementSelected(group: ProductGroup) {
|
||||
<label
|
||||
v-for="option in getAllFieldOptions(group, field.key)"
|
||||
: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="[
|
||||
getGroupState(group)[field.key] === option
|
||||
? 'border-neutral 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)
|
||||
? 'pointer-events-none opacity-35'
|
||||
: 'cursor-pointer',
|
||||
? 'bg-neutral text-neutral-content'
|
||||
: isOptionAvailable(group, field.key, option)
|
||||
? 'bg-base-100 text-base-content hover:bg-base-200'
|
||||
: 'bg-[#eef1f4] text-[#7b8591] hover:bg-[#e3e7eb]',
|
||||
'cursor-pointer',
|
||||
]"
|
||||
>
|
||||
<input
|
||||
@@ -502,7 +552,6 @@ function decrementSelected(group: ProductGroup) {
|
||||
class="sr-only"
|
||||
:name="`${group.key}-${field.key}`"
|
||||
:checked="getGroupState(group)[field.key] === option"
|
||||
:disabled="!isOptionAvailable(group, field.key, option)"
|
||||
@change="updateField(group, field.key, option)"
|
||||
>
|
||||
<span>{{ formatOptionLabel(field.key, option) }}</span>
|
||||
|
||||
Reference in New Issue
Block a user