bug-fixes

This commit is contained in:
sdarbinyan
2026-07-09 01:40:22 +04:00
parent 92e1bdaff8
commit 55216817b2
59 changed files with 885 additions and 158 deletions

View File

@@ -1,7 +1,6 @@
<aside class="catalog-filters card">
<div class="filters-head">
<h3>Filters</h3>
<button type="button" (click)="resetFilters.emit()">Reset</button>
<h3>{{ 'catalog.filtersTitle' | translate }}</h3>
</div>
@for (filter of definitions; track filter.id) {
@@ -32,12 +31,12 @@
type="number"
[value]="state.ranges[filter.id]?.min ?? ''"
(input)="updateRange(filter.id, 'min', $any($event.target).value)"
[placeholder]="'Min ' + (filter.min ?? '')" />
[placeholder]="'catalog.minValue' | translate:{ value: filter.min ?? '' }" />
<input
type="number"
[value]="state.ranges[filter.id]?.max ?? ''"
(input)="updateRange(filter.id, 'max', $any($event.target).value)"
[placeholder]="'Max ' + (filter.max ?? '')" />
[placeholder]="'catalog.maxValue' | translate:{ value: filter.max ?? '' }" />
</div>
}
@@ -47,7 +46,7 @@
type="checkbox"
[checked]="state.toggles[filter.id]"
(change)="updateToggle(filter.id, $any($event.target).checked)" />
<span>Enabled</span>
<span>{{ 'catalog.enabled' | translate }}</span>
</label>
}
</section>

View File

@@ -8,7 +8,7 @@
.filters-head {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: flex-start;
gap: 8px;
}
@@ -17,14 +17,6 @@
color: var(--text-primary);
}
.filters-head button {
border: 0;
background: transparent;
color: var(--primary-color);
font-weight: 700;
cursor: pointer;
}
.filter-group {
display: grid;
gap: 8px;
@@ -64,3 +56,13 @@
border-radius: var(--radius-sm);
padding: 0 8px;
}
@media (max-width: 1024px) {
.catalog-filters {
gap: 12px;
}
.range-inputs {
grid-template-columns: 1fr;
}
}

View File

@@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FilterDefinition } from '../../../../../core/products/models/catalog-experience.model';
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
export interface CatalogFilterStateValue {
values: Record<string, string[]>;
@@ -11,7 +12,7 @@ export interface CatalogFilterStateValue {
@Component({
selector: 'app-catalog-filters-panel',
standalone: true,
imports: [FormsModule],
imports: [FormsModule, TranslatePipe],
templateUrl: './filters-panel.component.html',
styleUrls: ['./filters-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -21,7 +22,6 @@ export class CatalogFiltersPanelComponent {
@Input() state: CatalogFilterStateValue = { values: {}, ranges: {}, toggles: {} };
@Output() stateChange = new EventEmitter<CatalogFilterStateValue>();
@Output() resetFilters = new EventEmitter<void>();
isSelected(filterId: string, optionValue: string): boolean {
return (this.state.values[filterId] ?? []).includes(optionValue);

View File

@@ -1,11 +1,50 @@
<div class="catalog-layout-switcher card">
@for (layout of available; track layout) {
@for (layout of available; track $index) {
<button
type="button"
class="layout-btn"
[class.active]="layout === active"
[attr.aria-label]="labels[layout] | translate"
(click)="activeChange.emit(layout)">
{{ labels[layout] }}
@switch (layout) {
@case ('grid') {
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<rect x="3" y="3" width="8" height="8" rx="1"></rect>
<rect x="13" y="3" width="8" height="8" rx="1"></rect>
<rect x="3" y="13" width="8" height="8" rx="1"></rect>
<rect x="13" y="13" width="8" height="8" rx="1"></rect>
</svg>
}
@case ('large-grid') {
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<rect x="3" y="3" width="18" height="8" rx="1"></rect>
<rect x="3" y="13" width="8" height="8" rx="1"></rect>
<rect x="13" y="13" width="8" height="8" rx="1"></rect>
</svg>
}
@case ('compact-grid') {
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<rect x="3" y="3" width="5" height="5" rx="1"></rect>
<rect x="10" y="3" width="5" height="5" rx="1"></rect>
<rect x="17" y="3" width="4" height="5" rx="1"></rect>
<rect x="3" y="10" width="5" height="5" rx="1"></rect>
<rect x="10" y="10" width="5" height="5" rx="1"></rect>
<rect x="17" y="10" width="4" height="5" rx="1"></rect>
<rect x="3" y="17" width="5" height="4" rx="1"></rect>
<rect x="10" y="17" width="5" height="4" rx="1"></rect>
<rect x="17" y="17" width="4" height="4" rx="1"></rect>
</svg>
}
@default {
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<rect x="3" y="4" width="18" height="4" rx="1"></rect>
<rect x="3" y="10" width="18" height="4" rx="1"></rect>
<rect x="3" y="16" width="18" height="4" rx="1"></rect>
</svg>
}
}
<span>{{ labels[layout] | translate }}</span>
</button>
}
</div>

View File

@@ -6,17 +6,46 @@
}
.layout-btn {
min-height: 34px;
min-height: 36px;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
background: var(--bg-primary);
color: var(--text-primary);
font-weight: 700;
padding: 0 10px;
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}
.layout-btn svg {
width: 16px;
height: 16px;
fill: none;
stroke: currentColor;
stroke-width: 1.7;
}
.layout-btn.active {
border-color: var(--primary-color);
background: color-mix(in srgb, var(--primary-color) 10%, white);
}
.layout-btn:hover {
transform: translateY(-1px);
border-color: var(--primary-color);
}
@media (max-width: 640px) {
.catalog-layout-switcher {
width: 100%;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.layout-btn {
justify-content: center;
}
}

View File

@@ -1,9 +1,11 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { CatalogLayoutMode } from '../../../../../core/products/models/catalog-experience.model';
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
@Component({
selector: 'app-catalog-layout-switcher',
standalone: true,
imports: [TranslatePipe],
templateUrl: './layout-switcher.component.html',
styleUrls: ['./layout-switcher.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -15,9 +17,9 @@ export class CatalogLayoutSwitcherComponent {
@Output() activeChange = new EventEmitter<CatalogLayoutMode>();
readonly labels: Record<CatalogLayoutMode, string> = {
grid: 'Grid',
'large-grid': 'Large Grid',
'compact-grid': 'Compact Grid',
list: 'List'
grid: 'catalog.layoutGrid',
'large-grid': 'catalog.layoutLargeGrid',
'compact-grid': 'catalog.layoutCompactGrid',
list: 'catalog.layoutList'
};
}

View File

@@ -5,16 +5,16 @@
[ngModel]="query"
(ngModelChange)="queryChange.emit($event)"
name="query"
placeholder="Search products, brands, categories"
[placeholder]="'catalog.searchPlaceholder' | translate"
autocomplete="off" />
<button type="submit" [disabled]="loading">Search</button>
<button type="submit" [disabled]="loading">{{ 'catalog.searchSubmit' | translate }}</button>
</form>
@if (suggestions.length > 0) {
<div class="suggestions">
<strong>Suggestions</strong>
<strong>{{ 'catalog.suggestionsTitle' | translate }}</strong>
<div class="chip-list">
@for (item of suggestions; track item) {
@for (item of suggestions; track $index) {
<button type="button" class="chip" (click)="selectSuggestion(item)">{{ item }}</button>
}
</div>
@@ -23,9 +23,9 @@
@if (recentSearches.length > 0) {
<div class="recent">
<strong>Recent searches</strong>
<strong>{{ 'catalog.recentSearchesTitle' | translate }}</strong>
<div class="chip-list">
@for (item of recentSearches; track item) {
@for (item of recentSearches; track $index) {
<button type="button" class="chip" (click)="recentSelected.emit(item)">{{ item }}</button>
}
</div>
@@ -35,11 +35,11 @@
@if (searchHistory.length > 0) {
<div class="history">
<div class="history-head">
<strong>Search history</strong>
<button type="button" (click)="historyCleared.emit()">Clear</button>
<strong>{{ 'catalog.searchHistoryTitle' | translate }}</strong>
<button type="button" (click)="historyCleared.emit()">{{ 'catalog.clearHistory' | translate }}</button>
</div>
<div class="chip-list">
@for (item of searchHistory; track item) {
@for (item of searchHistory; track $index) {
<button type="button" class="chip" (click)="recentSelected.emit(item)">{{ item }}</button>
}
</div>
@@ -47,6 +47,6 @@
}
@if (noResults && !loading) {
<p class="no-results">No results found for this query. Try broader keywords.</p>
<p class="no-results">{{ 'catalog.searchNoResultsHint' | translate }}</p>
}
</section>

View File

@@ -16,6 +16,13 @@
border-radius: var(--radius-sm);
padding: 0 12px;
font: inherit;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.search-form input:focus-visible {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 18%, transparent);
outline: 0;
}
.search-form button {
@@ -26,6 +33,13 @@
color: #fff;
font-weight: 700;
padding: 0 14px;
cursor: pointer;
transition: transform 0.2s ease, filter 0.2s ease;
}
.search-form button:hover:not(:disabled) {
transform: translateY(-1px);
filter: brightness(0.96);
}
.chip-list {
@@ -42,6 +56,13 @@
color: var(--text-secondary);
padding: 0 10px;
cursor: pointer;
transition: border-color 0.2s ease, color 0.2s ease, background-color 0.2s ease;
}
.chip:hover {
border-color: var(--primary-color);
color: var(--text-primary);
background: color-mix(in srgb, var(--primary-color) 8%, white);
}
.history-head {
@@ -56,6 +77,7 @@
background: transparent;
color: var(--primary-color);
cursor: pointer;
font-weight: 700;
}
.no-results {
@@ -67,4 +89,8 @@
.search-form {
grid-template-columns: 1fr;
}
.search-form button {
width: 100%;
}
}

View File

@@ -1,10 +1,11 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
@Component({
selector: 'app-catalog-search-box',
standalone: true,
imports: [FormsModule],
imports: [FormsModule, TranslatePipe],
templateUrl: './search-box.component.html',
styleUrls: ['./search-box.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -1,7 +1,7 @@
<section class="catalog-search-results section">
<div class="results-head">
<strong>{{ summary }}</strong>
<span>{{ total }} items</span>
<span>{{ 'catalog.itemsCount' | translate:{ count: total } }}</span>
</div>
@if (loading) {
@@ -29,16 +29,16 @@
(shareRequested)="shareRequested.emit($event)" />
} @else {
<div class="empty-state card">
<h3>No results found</h3>
<p>Try changing filters, sorting, or search keywords.</p>
<h3>{{ 'catalog.emptyResultsTitle' | translate }}</h3>
<p>{{ 'catalog.emptyResultsDescription' | translate }}</p>
</div>
}
@if (!loading && totalPages > 1) {
<div class="results-pager">
<button type="button" (click)="previous()" [disabled]="page <= 1">Previous</button>
<span>Page {{ page }} / {{ totalPages }}</span>
<button type="button" (click)="next()" [disabled]="page >= totalPages">Next</button>
<button type="button" (click)="previous()" [disabled]="page <= 1">{{ 'catalog.previousPage' | translate }}</button>
<span>{{ 'catalog.pageOf' | translate:{ page: page, total: totalPages } }}</span>
<button type="button" (click)="next()" [disabled]="page >= totalPages">{{ 'catalog.nextPage' | translate }}</button>
</div>
}
</section>

View File

@@ -64,6 +64,25 @@
border-radius: var(--radius-sm);
background: var(--bg-primary);
padding: 0 12px;
cursor: pointer;
transition: border-color 0.2s ease, transform 0.2s ease;
}
.results-pager button:hover:not(:disabled) {
border-color: var(--primary-color);
transform: translateY(-1px);
}
@media (max-width: 640px) {
.results-pager {
display: grid;
grid-template-columns: 1fr;
justify-items: stretch;
}
.results-pager span {
text-align: center;
}
}
@keyframes shimmer {

View File

@@ -2,11 +2,12 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from
import { Product } from '../../../../../core/products/models/product-domain.model';
import { CatalogLayoutMode } from '../../../../../core/products/models/catalog-experience.model';
import { CatalogProductGridComponent } from '../product-grid/product-grid.component';
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
@Component({
selector: 'app-catalog-search-results',
standalone: true,
imports: [CatalogProductGridComponent],
imports: [CatalogProductGridComponent, TranslatePipe],
templateUrl: './search-results.component.html',
styleUrls: ['./search-results.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -1,5 +1,5 @@
<label class="catalog-sorting card">
<span>Sort by</span>
<span>{{ 'catalog.sortBy' | translate }}</span>
<select [ngModel]="selected" (ngModelChange)="selectedChange.emit($event)">
@for (option of options; track option.id) {
@if (option.enabled !== false) {

View File

@@ -20,3 +20,15 @@
padding: 0 8px;
font: inherit;
}
@media (max-width: 640px) {
.catalog-sorting {
width: 100%;
grid-template-columns: 1fr;
gap: 6px;
}
.catalog-sorting select {
width: 100%;
}
}

View File

@@ -1,11 +1,12 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SortDefinition } from '../../../../../core/products/models/catalog-experience.model';
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
@Component({
selector: 'app-catalog-sorting-control',
standalone: true,
imports: [FormsModule],
imports: [FormsModule, TranslatePipe],
templateUrl: './sorting-control.component.html',
styleUrls: ['./sorting-control.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -30,7 +30,7 @@
@for (saved of savedSearches(); track saved.id) {
<div class="catalog-saved-chip">
<button type="button" (click)="useSavedSearch(saved.id)">{{ saved.name }}</button>
<button type="button" class="catalog-saved-chip-remove" (click)="removeSavedSearch(saved.id)">×</button>
<button type="button" class="catalog-saved-chip-remove" [attr.aria-label]="'catalog.removeSavedSearch' | translate" (click)="removeSavedSearch(saved.id)">×</button>
</div>
}
</div>
@@ -39,7 +39,7 @@
@if (loading()) {
<section class="catalog-loading" [attr.aria-label]="'catalog.loading' | translate">
@for (slot of skeletonSlots; track slot) {
@for (slot of skeletonSlots; track $index) {
<div class="catalog-skeleton-card">
<div class="catalog-skeleton-image"></div>
<div class="catalog-skeleton-line catalog-skeleton-title"></div>
@@ -61,13 +61,13 @@
<section class="catalog-section">
<div class="catalog-section-heading">
<h1>{{ state().category?.title || ('catalog.allCategories' | translate) }}</h1>
<p>{{ 'catalog.categoryHint' | translate }} • {{ categories().length }} entries</p>
<p>{{ 'catalog.categoryHint' | translate }} • {{ 'catalog.entriesCount' | translate:{ count: categories().length } }}</p>
</div>
@if (catalogConfig().showCategoryBanner) {
<div class="catalog-category-banner card">
<h3>{{ state().category?.title || ('catalog.allCategories' | translate) }}</h3>
<p>Category banner placeholder for backend-driven media and description.</p>
<p>{{ 'catalog.categoryBannerPlaceholder' | translate }}</p>
</div>
}
@@ -98,16 +98,19 @@
<app-catalog-filters-panel
[definitions]="filterDefinitions()"
[state]="filterState()"
(stateChange)="onFilterStateChange($event)"
(resetFilters)="resetFilters()" />
(stateChange)="onFilterStateChange($event)" />
</aside>
<div class="catalog-results-panel">
<div class="catalog-tools-row">
<app-catalog-sorting-control
[options]="sortDefinitions()"
[selected]="state().sort"
(selectedChange)="changeSort($event)" />
<div class="catalog-sort-reset-group">
<app-catalog-sorting-control
[options]="sortDefinitions()"
[selected]="state().sort"
(selectedChange)="changeSort($event)" />
<button type="button" class="catalog-reset-btn" (click)="resetFilters()">{{ 'catalog.resetFilters' | translate }}</button>
</div>
@if (userExperienceConfig().savedSearches.enabled) {
<button type="button" class="catalog-save-search-btn" (click)="saveCurrentSearch()">{{ 'ux.saveSearch' | translate }}</button>
@@ -122,7 +125,7 @@
@if (catalogConfig().navigationMode !== 'default') {
<div class="catalog-navigation-placeholder card">
<strong>{{ catalogConfig().navigationMode }}</strong>
<p>Navigation layout placeholder prepared for backend/bootstrap-driven rendering.</p>
<p>{{ 'catalog.navigationPlaceholder' | translate }}</p>
</div>
}

View File

@@ -101,6 +101,31 @@
justify-content: space-between;
}
.catalog-sort-reset-group {
display: inline-flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.catalog-reset-btn {
min-height: 36px;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-secondary);
padding: 0 12px;
font-weight: 700;
cursor: pointer;
transition: border-color 0.2s ease, color 0.2s ease, background-color 0.2s ease;
}
.catalog-reset-btn:hover {
border-color: var(--primary-color);
color: var(--primary-color);
background: color-mix(in srgb, var(--primary-color) 7%, white);
}
.catalog-save-search-btn {
min-height: 36px;
border: 1px solid var(--border-color);
@@ -110,6 +135,13 @@
padding: 0 12px;
font-weight: 700;
cursor: pointer;
transition: transform 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}
.catalog-save-search-btn:hover {
transform: translateY(-1px);
border-color: var(--primary-color);
background: color-mix(in srgb, var(--primary-color) 8%, white);
}
.catalog-category-banner {
@@ -272,14 +304,40 @@
padding: 16px;
}
.catalog-header {
gap: 12px;
margin-bottom: 18px;
}
.catalog-products-section {
grid-template-columns: 1fr;
gap: 14px;
}
.catalog-left-panel {
position: static;
}
.catalog-tools-row {
align-items: stretch;
}
.catalog-sort-reset-group,
.catalog-save-search-btn,
.catalog-layout-switcher {
width: 100%;
}
.catalog-sort-reset-group {
display: grid;
grid-template-columns: 1fr;
}
.catalog-reset-btn,
.catalog-save-search-btn {
min-height: 40px;
}
.catalog-root-link {
font-size: 1.5rem;
}

View File

@@ -15,6 +15,7 @@ import { LanguageService } from '../../../../services/language.service';
import { PrefetchService } from '../../../../services/prefetch.service';
import { LangRoutePipe } from '../../../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../../../i18n/translate.pipe';
import { TranslateService } from '../../../../i18n/translate.service';
import { DEFAULT_CATALOG_CONFIG, DEFAULT_USER_EXPERIENCE_CONFIG } from '../../../../shared/models/config';
import { CatalogCategoryGridComponent } from '../components/category-grid/category-grid.component';
import { CatalogFiltersPanelComponent, CatalogFilterStateValue } from '../components/filters-panel/filters-panel.component';
@@ -61,6 +62,7 @@ export class CatalogContainerComponent {
private readonly uxFacade = inject(UserExperienceFacade);
private readonly shareService = inject(ProductShareService);
private readonly notifications = inject(UserNotificationService);
private readonly translate = inject(TranslateService);
readonly catalogConfig = signal(this.resolveCatalogConfig());
readonly userExperienceConfig = signal(this.resolveUserExperienceConfig());
@@ -89,13 +91,13 @@ export class CatalogContainerComponent {
readonly sortDefinitions = computed<SortDefinition[]>(() => {
const labels: Record<string, string> = {
relevance: 'Relevance',
latest: 'Newest',
price_asc: 'Price Low -> High',
price_desc: 'Price High -> Low',
rating: 'Highest Rated',
popular: 'Most Popular',
discount: 'Discount'
relevance: this.translate.t('catalog.sortRelevance'),
latest: this.translate.t('catalog.sortLatest'),
price_asc: this.translate.t('catalog.sortPriceAsc'),
price_desc: this.translate.t('catalog.sortPriceDesc'),
rating: this.translate.t('catalog.sortRating'),
popular: this.translate.t('catalog.sortPopular'),
discount: this.translate.t('catalog.sortDiscount')
};
return this.catalogConfig().availableSorts.map((id: string) => ({ id, label: labels[id] ?? id, enabled: true }));
@@ -267,24 +269,27 @@ export class CatalogContainerComponent {
onFavoriteToggled(product: Product): void {
const result = this.uxFacade.toggleWishlist(product);
this.notifications.show(result.added ? 'Added to wishlist' : 'Removed from wishlist', result.added ? 'success' : 'info');
this.notifications.show(
result.added ? this.translate.t('ux.wishlistAdded') : this.translate.t('ux.wishlistRemoved'),
result.added ? 'success' : 'info'
);
}
onCompareToggled(product: Product): void {
if (this.uxFacade.isInCompare(product.itemID)) {
this.uxFacade.removeFromCompare(product.itemID);
this.notifications.show('Removed from compare', 'info');
this.notifications.show(this.translate.t('ux.compareRemoved'), 'info');
return;
}
const maxItems = this.userExperienceConfig().compare.maxItems;
const result = this.uxFacade.addToCompare(product, maxItems);
if (result.reason === 'limit') {
this.notifications.show(`Compare limit reached (${maxItems})`, 'warning');
this.notifications.show(this.translate.t('ux.compareLimitReached', { maxItems }), 'warning');
return;
}
this.notifications.show('Added to compare', 'success');
this.notifications.show(this.translate.t('ux.compareAdded'), 'success');
}
async onShareRequested(product: Product): Promise<void> {
@@ -300,16 +305,16 @@ export class CatalogContainerComponent {
const result = await this.shareService.shareProduct(product, productUrl);
if (result === 'native') {
this.notifications.show('Product shared', 'success');
this.notifications.show(this.translate.t('ux.productShared'), 'success');
return;
}
if (result === 'copied') {
this.notifications.show('Product link copied', 'success');
this.notifications.show(this.translate.t('ux.productLinkCopied'), 'success');
return;
}
this.notifications.show('Sharing is not supported on this device', 'warning');
this.notifications.show(this.translate.t('ux.sharingUnsupported'), 'warning');
}
saveCurrentSearch(): void {
@@ -319,7 +324,7 @@ export class CatalogContainerComponent {
const query = this.state().search.trim();
if (!query.length) {
this.notifications.show('Enter a search query before saving', 'warning');
this.notifications.show(this.translate.t('ux.enterQueryBeforeSave'), 'warning');
return;
}
@@ -332,7 +337,7 @@ export class CatalogContainerComponent {
filters: this.filterState()
}, this.userExperienceConfig().savedSearches.maxItems);
this.notifications.show(`Saved search: ${saved.name}`, 'success');
this.notifications.show(this.translate.t('ux.savedSearchNamed', { name: saved.name }), 'success');
}
useSavedSearch(id: string): void {
@@ -483,7 +488,7 @@ export class CatalogContainerComponent {
const definitions: FilterDefinition[] = [
{
id: 'price',
label: 'Price',
label: this.translate.t('catalog.filterPrice'),
type: 'range',
min: priceValues.length ? Math.min(...priceValues) : 0,
max: priceValues.length ? Math.max(...priceValues) : 0,
@@ -491,72 +496,72 @@ export class CatalogContainerComponent {
},
{
id: 'availability',
label: 'Availability',
label: this.translate.t('catalog.filterAvailability'),
type: 'multi-select',
options: [
{ id: 'in-stock', label: 'In stock', value: 'in-stock' },
{ id: 'low-stock', label: 'Low stock', value: 'low-stock' },
{ id: 'out-of-stock', label: 'Out of stock', value: 'out-of-stock' }
{ id: 'in-stock', label: this.translate.t('catalog.filterInStock'), value: 'in-stock' },
{ id: 'low-stock', label: this.translate.t('catalog.filterLowStock'), value: 'low-stock' },
{ id: 'out-of-stock', label: this.translate.t('catalog.filterOutOfStock'), value: 'out-of-stock' }
],
enabled: enabled.has('availability')
},
{
id: 'rating',
label: 'Rating',
label: this.translate.t('catalog.filterRating'),
type: 'multi-select',
options: ratings.map(value => ({ id: `rating-${value}`, label: `${value} stars`, value: String(value) })),
options: ratings.map(value => ({ id: `rating-${value}`, label: this.translate.t('catalog.filterStars', { count: value }), value: String(value) })),
enabled: enabled.has('rating')
},
{
id: 'brand',
label: 'Brand',
label: this.translate.t('catalog.filterBrand'),
type: 'multi-select',
options: brands.map(value => ({ id: `brand-${value}`, label: value, value })),
enabled: enabled.has('brand')
},
{
id: 'category',
label: 'Category',
label: this.translate.t('catalog.filterCategory'),
type: 'multi-select',
options: categories.map(value => ({ id: `cat-${value}`, label: `Category ${value}`, value })),
options: categories.map(value => ({ id: `cat-${value}`, label: this.translate.t('catalog.filterCategoryValue', { value }), value })),
enabled: enabled.has('category')
},
{
id: 'subcategory',
label: 'Subcategory',
label: this.translate.t('catalog.filterSubcategory'),
type: 'multi-select',
options: subcategories.map(value => ({ id: `sub-${value}`, label: value, value })),
enabled: enabled.has('subcategory')
},
{
id: 'discount',
label: 'Discount',
label: this.translate.t('catalog.filterDiscount'),
type: 'toggle',
enabled: enabled.has('discount')
},
{
id: 'new',
label: 'New',
label: this.translate.t('catalog.filterNew'),
type: 'toggle',
enabled: enabled.has('new')
},
{
id: 'color',
label: 'Color',
label: this.translate.t('catalog.filterColor'),
type: 'multi-select',
options: colors.map(value => ({ id: `color-${value}`, label: value, value })),
enabled: enabled.has('color')
},
{
id: 'size',
label: 'Size',
label: this.translate.t('catalog.filterSize'),
type: 'multi-select',
options: sizes.map(value => ({ id: `size-${value}`, label: value, value })),
enabled: enabled.has('size')
},
{
id: 'attributes',
label: 'Attributes',
label: this.translate.t('catalog.filterAttributes'),
type: 'multi-select',
options: attributes.map(value => ({ id: `attr-${value}`, label: value, value })),
enabled: enabled.has('attributes')
@@ -579,7 +584,7 @@ export class CatalogContainerComponent {
page: 1
}
}));
this.searchSummary.set('No results');
this.searchSummary.set(this.translate.t('catalog.noResults'));
this.searchResult.set(null);
this.searchSuggestions.set([]);
return;