From d1e4c0d3068d041d08edb44d05b419ca0b0c4c38 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 1 May 2026 23:17:22 +0200 Subject: [PATCH] fix(portal): show search-aware empty state when category search yields no results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Entire-Checkpoint: 2e1a36e2f0f6 --- apps/web/src/assets/i18n/en.json | 2 +- .../category-content-view.component.html | 1 + .../category-content-view.component.spec.ts | 1 + .../category-content-view.component.ts | 7 ++- .../grid-list/grid-list.component.ts | 43 +++++++++++++------ .../playlist-error-view.component.html | 2 +- .../playlist-error-view.component.scss | 7 +-- ...orkspace-context-error-view.component.html | 2 +- 8 files changed, 46 insertions(+), 19 deletions(-) diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index 804235c7e..5c0e1006b 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -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", diff --git a/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.html b/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.html index bc50ea75a..9315abad2 100644 --- a/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.html +++ b/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.html @@ -90,6 +90,7 @@ [limit]="limit()" [pageSizeOptions]="pageSizeOptions" [showPaginator]="false" + [searchTerm]="searchTerm()" (pageChange)="onPageChange($event)" /> diff --git a/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.spec.ts b/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.spec.ts index 46311d18b..3f3e3125e 100644 --- a/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.spec.ts +++ b/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.spec.ts @@ -30,6 +30,7 @@ class MockGridListComponent { readonly limit = input(); readonly pageSizeOptions = input(); readonly showPaginator = input(true); + readonly searchTerm = input(''); readonly itemClicked = output(); readonly pageChange = output(); } diff --git a/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.ts b/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.ts index 53ecde112..fb6acd7bb 100644 --- a/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.ts +++ b/libs/portal/catalog/feature/src/lib/category-content-view/category-content-view.component.ts @@ -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 ); diff --git a/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts b/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts index 44b0fff05..0b3642f2d 100644 --- a/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts +++ b/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts @@ -125,18 +125,33 @@ export function resolveGridRating( } @empty {
- + @if (hasActiveSearch()) { + + } @else { + + }
} } @@ -167,6 +182,7 @@ export class GridListComponent { readonly items = input(); readonly isLoading = input(); readonly showPaginator = input(true); + readonly searchTerm = input(''); readonly itemClicked = output(); readonly pageChange = output(); @@ -175,6 +191,9 @@ export class GridListComponent { readonly limit = input(); readonly pageSizeOptions = input(); protected readonly resolveRating = resolveGridRating; + protected readonly hasActiveSearch = computed( + () => (this.searchTerm() ?? '').trim().length > 0 + ); readonly skeletonRows = computed(() => { const preferredCount = this.limit() ?? 12; diff --git a/libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.html b/libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.html index a5eb6b02f..d1395075c 100644 --- a/libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.html +++ b/libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.html @@ -16,7 +16,7 @@ src="./assets/images/empty-category.svg" /> } @else if (viewType() === 'NO_SEARCH_RESULTS') { - search + search_off } }
diff --git a/libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.scss b/libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.scss index 339477beb..28c2798d5 100644 --- a/libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.scss +++ b/libs/portal/shared/ui/src/lib/components/playlist-error-view/playlist-error-view.component.scss @@ -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 { diff --git a/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-error-view.component.html b/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-error-view.component.html index 2e108acbb..8c1f892da 100644 --- a/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-error-view.component.html +++ b/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-error-view.component.html @@ -16,7 +16,7 @@ src="./assets/images/empty-category.svg" /> } @else if (viewType() === 'NO_SEARCH_RESULTS') { - search + search_off } }