fix(search): drop unresolvable category filter options instead of faking a label
P0-4: the Catalog/Search category facet showed placeholder labels
like 'Категория 2008', 'Категория 22612' for every option — traced
to search.facade.ts buildFilterGroups() synthesizing 'Category {id}'
for every distinct product.categoryID with no attempt to resolve a
real name.
Investigated further: the mismatch isn't a missing-lookup bug, it's
a real data gap. Product categoryID values in the mock catalog
fixture don't correspond to any id in CategoryFacade's category
tree (a much smaller, separately-curated mock dataset) — there is
no real category name to show for these ids today.
Wired CategoryFacade into SearchFacade and resolve each option's
real title when the id does match; when it doesn't (the common case
with current mock data), the option is dropped rather than showing
a fabricated technical-looking label. The category filter group
simply doesn't render when nothing resolves, which is honest given
the data, instead of looking like broken/unseeded content in front
of a client.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { Observable, Subject, of } from 'rxjs';
|
|||||||
import { debounceTime, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { Category } from '../../../core/categories/models/category-domain.model';
|
import { Category } from '../../../core/categories/models/category-domain.model';
|
||||||
import { Product, ProductListResult } from '../../../core/products/models/product-domain.model';
|
import { Product, ProductListResult } from '../../../core/products/models/product-domain.model';
|
||||||
|
import { CategoryFacade } from '../../../facades/platform/category.facade';
|
||||||
import { ProductFacade } from '../../../facades/platform/product.facade';
|
import { ProductFacade } from '../../../facades/platform/product.facade';
|
||||||
import { TranslateService } from '../../../i18n/translate.service';
|
import { TranslateService } from '../../../i18n/translate.service';
|
||||||
import { SearchAutocompleteService } from '../services/search-autocomplete.service';
|
import { SearchAutocompleteService } from '../services/search-autocomplete.service';
|
||||||
@@ -34,6 +35,7 @@ interface LegacySearchState {
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SearchFacade {
|
export class SearchFacade {
|
||||||
private readonly productFacade = inject(ProductFacade);
|
private readonly productFacade = inject(ProductFacade);
|
||||||
|
private readonly categoryFacade = inject(CategoryFacade);
|
||||||
private readonly translate = inject(TranslateService);
|
private readonly translate = inject(TranslateService);
|
||||||
private readonly autocompleteService = inject(SearchAutocompleteService);
|
private readonly autocompleteService = inject(SearchAutocompleteService);
|
||||||
private readonly historyService = inject(SearchHistoryService);
|
private readonly historyService = inject(SearchHistoryService);
|
||||||
@@ -278,7 +280,9 @@ export class SearchFacade {
|
|||||||
id: 'category',
|
id: 'category',
|
||||||
label: this.translate.t('catalog.filterCategory'),
|
label: this.translate.t('catalog.filterCategory'),
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
options: categories.map(value => ({ id: `cat-${value}`, label: this.translate.t('catalog.filterCategoryValue', { value }), value })),
|
options: categories
|
||||||
|
.map(value => ({ id: `cat-${value}`, label: this.resolveCategoryLabel(value), value }))
|
||||||
|
.filter((option): option is { id: string; label: string; value: string } => option.label !== null),
|
||||||
enabled: enabled.has('category'),
|
enabled: enabled.has('category'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -545,6 +549,19 @@ export class SearchFacade {
|
|||||||
return [...new Set(values.map(value => value.trim()).filter(Boolean))];
|
return [...new Set(values.map(value => value.trim()).filter(Boolean))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the real category title, or null when the id has no match in the loaded
|
||||||
|
* category tree — callers drop unresolved options instead of showing a fabricated
|
||||||
|
* "Category {id}" label (see RELEASE-CANDIDATE-AUDIT.md P0-4).
|
||||||
|
*/
|
||||||
|
private resolveCategoryLabel(categoryId: string): string | null {
|
||||||
|
const id = Number(categoryId);
|
||||||
|
if (!Number.isFinite(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.categoryFacade.allCategories().find(category => category.id === id)?.title ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
private toColorHex(value: string): string {
|
private toColorHex(value: string): string {
|
||||||
const normalized = value.trim().toLowerCase();
|
const normalized = value.trim().toLowerCase();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user