fix(portal): show search-aware empty state when category search yields no results

When a search filter inside a category produces zero results, the grid
@empty fallback used to render the EMPTY_CATEGORY view ("No content in
this category" with the empty-tv illustration). That message was
misleading — the category itself wasn't empty, only the search filter
excluded everything.

grid-list now takes a searchTerm input and branches the @empty fallback:
when a search is active it renders the NO_SEARCH_RESULTS view with the
parameterised "No results found for {term}" title, otherwise it falls
back to EMPTY_CATEGORY as before. category-content-view derives the
searchTerm signal from the ?q= query param and passes it through.

Also: bump the NO_SEARCH_RESULTS icon from a 64px mat-icon to a clamp()
sized icon (120-180px) at 0.5 opacity so it visually balances with the
SVG illustrations used by the other viewTypes, swap the icon to
search_off (struck-through magnifier) so it reads as "no results"
rather than "search now", and fix the EN i18n typo
("change you search request" -> "change your search request").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 2e1a36e2f0f6
This commit is contained in:
4grayandClaude Opus 4.7 committed 2026-05-01 23:17:22 +02:00
1 parent d6a91d79e9
commit d1e4c0d306
8 files changed
+46 -19

No files matched your search

+1 -1
View File
@@ -663,7 +663,7 @@
},
"EMPTY_LIST_VIEW": {
"TITLE": "No results found",
"NO_SEARCH_RESULTS": "Nothing found, try to change you search request"
"NO_SEARCH_RESULTS": "Nothing found, try to change your search request."
},
"SEARCH_VIEW": {
"INITIAL_TITLE": "Search for content",
@@ -90,6 +90,7 @@
[limit]="limit()"
[pageSizeOptions]="pageSizeOptions"
[showPaginator]="false"
[searchTerm]="searchTerm()"
(pageChange)="onPageChange($event)"
/>
</div>
@@ -30,6 +30,7 @@ class MockGridListComponent {
readonly limit = input<number>();
readonly pageSizeOptions = input<number[]>();
readonly showPaginator = input(true);
readonly searchTerm = input<string>('');
readonly itemClicked = output<unknown>();
readonly pageChange = output<unknown>();
}
@@ -7,7 +7,8 @@ import {
inject,
OnInit,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs/operators';
import { MatIconButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
@@ -98,6 +99,10 @@ export class CategoryContentViewComponent implements OnInit {
return `${itemCount} ${itemCount === 1 ? 'item' : 'items'}`;
});
readonly canSortContent = computed(() => this.contentSortMode() !== null);
readonly searchTerm = toSignal(
this.activatedRoute.queryParamMap.pipe(map((p) => p.get('q') ?? '')),
{ initialValue: '' }
);
readonly selectedDetailComponent = computed(() =>
this.selectedItem() ? this.detailComponent : null
);
@@ -125,18 +125,33 @@ export function resolveGridRating(
</mat-card>
} @empty {
<div class="grid-empty-state">
<app-playlist-error-view
[title]="
'PORTALS.ERROR_VIEW.EMPTY_CATEGORY.TITLE'
| translate
"
[description]="
'PORTALS.ERROR_VIEW.EMPTY_CATEGORY.DESCRIPTION'
| translate
"
[showActionButtons]="false"
[viewType]="'EMPTY_CATEGORY'"
/>
@if (hasActiveSearch()) {
<app-playlist-error-view
[title]="
'PORTALS.SEARCH_VIEW.NO_RESULTS_FOR'
| translate: { term: searchTerm() }
"
[description]="
'PORTALS.EMPTY_LIST_VIEW.NO_SEARCH_RESULTS'
| translate
"
[showActionButtons]="false"
[viewType]="'NO_SEARCH_RESULTS'"
/>
} @else {
<app-playlist-error-view
[title]="
'PORTALS.ERROR_VIEW.EMPTY_CATEGORY.TITLE'
| translate
"
[description]="
'PORTALS.ERROR_VIEW.EMPTY_CATEGORY.DESCRIPTION'
| translate
"
[showActionButtons]="false"
[viewType]="'EMPTY_CATEGORY'"
/>
}
</div>
}
}
@@ -167,6 +182,7 @@ export class GridListComponent {
readonly items = input<GridListItem[]>();
readonly isLoading = input<boolean>();
readonly showPaginator = input(true);
readonly searchTerm = input<string>('');
readonly itemClicked = output<GridListItem>();
readonly pageChange = output<PageEvent>();
@@ -175,6 +191,9 @@ export class GridListComponent {
readonly limit = input<number>();
readonly pageSizeOptions = input<number[]>();
protected readonly resolveRating = resolveGridRating;
protected readonly hasActiveSearch = computed(
() => (this.searchTerm() ?? '').trim().length > 0
);
readonly skeletonRows = computed(() => {
const preferredCount = this.limit() ?? 12;
@@ -16,7 +16,7 @@
src="./assets/images/empty-category.svg"
/>
} @else if (viewType() === 'NO_SEARCH_RESULTS') {
<mat-icon class="icon">search</mat-icon>
<mat-icon class="icon">search_off</mat-icon>
}
}
<div class="error-view__copy">
@@ -44,9 +44,10 @@
}
.icon {
font-size: 64px;
height: 64px;
width: 64px;
font-size: clamp(120px, 18vw, 180px);
height: clamp(120px, 18vw, 180px);
width: clamp(120px, 18vw, 180px);
opacity: 0.5;
}
.error-view--empty-category .error-view__illustration {
@@ -16,7 +16,7 @@
src="./assets/images/empty-category.svg"
/>
} @else if (viewType() === 'NO_SEARCH_RESULTS') {
<mat-icon class="icon">search</mat-icon>
<mat-icon class="icon">search_off</mat-icon>
}
}