|
|
@ -15,13 +15,11 @@ |
|
|
limitations under the License. |
|
|
limitations under the License. |
|
|
|
|
|
|
|
|
--> |
|
|
--> |
|
|
<div class="flex items-baseline gap-4 lt-md:-mb-6"> |
|
|
<div class="mb-10 flex items-baseline gap-4 lt-md:mb-4"> |
|
|
<ng-content></ng-content> |
|
|
<ng-content></ng-content> |
|
|
@if (!isLoading && totalElements > 0) { |
|
|
<span class="tb-search-results-count tb-search-results-count-mobile"> |
|
|
<span class="tb-search-results-count tb-search-results-count-mobile"> |
|
|
{{ 'iot-hub.results-count' | translate:{ count: totalElements } }} |
|
|
{{ 'iot-hub.results-count' | translate:{ count: totalElements } }} |
|
|
</span> |
|
|
</span> |
|
|
|
|
|
} |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<!-- Search + Sort row --> |
|
|
<!-- Search + Sort row --> |
|
|
<div class="flex items-center gap-4"> |
|
|
<div class="flex items-center gap-4"> |
|
|
@ -56,92 +54,105 @@ |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<!-- Results count + Pagination (shown when results exist) --> |
|
|
<!-- Results count + Pagination (shown when results exist) --> |
|
|
@if (!isLoading && totalElements > 0) { |
|
|
<div class="tb-search-pagination-row" [class.lt-md:!hidden]="totalElements === 0"> |
|
|
<div class="tb-search-pagination-row"> |
|
|
<span class="tb-search-results-count"> |
|
|
<span class="tb-search-results-count"> |
|
|
{{ 'iot-hub.results-count' | translate:{ count: totalElements } }} |
|
|
{{ 'iot-hub.results-count' | translate:{ count: totalElements } }} |
|
|
</span> |
|
|
</span> |
|
|
@if (totalElements > 0) { |
|
|
<div class="tb-search-pagination"> |
|
|
<div class="tb-search-pagination"> |
|
|
<div class="flex items-center gap-px"> |
|
|
<div class="flex items-center gap-px"> |
|
|
<button mat-icon-button [disabled]="pageIndex === 0" (click)="goToPage(pageIndex - 1)"> |
|
|
<button mat-icon-button [disabled]="pageIndex === 0" (click)="goToPage(pageIndex - 1)"> |
|
|
<mat-icon>chevron_left</mat-icon> |
|
|
<mat-icon>chevron_left</mat-icon> |
|
|
|
|
|
</button> |
|
|
|
|
|
@for (p of getPageNumbers(); track p) { |
|
|
|
|
|
<button class="tb-search-page-btn" [class.active]="p === pageIndex" (click)="goToPage(p)"> |
|
|
|
|
|
{{ p + 1 }} |
|
|
</button> |
|
|
</button> |
|
|
@for (p of getPageNumbers(); track p) { |
|
|
} |
|
|
<button class="tb-search-page-btn" [class.active]="p === pageIndex" (click)="goToPage(p)"> |
|
|
<button mat-icon-button [disabled]="pageIndex >= totalPages - 1" (click)="goToPage(pageIndex + 1)"> |
|
|
{{ p + 1 }} |
|
|
<mat-icon>chevron_right</mat-icon> |
|
|
</button> |
|
|
</button> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="tb-search-page-size"> |
|
|
|
|
|
<span>{{ 'iot-hub.items-per-page' | translate }}:</span> |
|
|
|
|
|
<mat-select [value]="pageSize" (selectionChange)="onPageSizeChange($event.value)" class="tb-search-page-size-select"> |
|
|
|
|
|
@for (size of pageSizeOptions; track size) { |
|
|
|
|
|
<mat-option [value]="size">{{ size }}</mat-option> |
|
|
} |
|
|
} |
|
|
<button mat-icon-button [disabled]="pageIndex >= totalPages - 1" (click)="goToPage(pageIndex + 1)"> |
|
|
</mat-select> |
|
|
<mat-icon>chevron_right</mat-icon> |
|
|
|
|
|
</button> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="tb-search-page-size"> |
|
|
|
|
|
<span>{{ 'iot-hub.items-per-page' | translate }}:</span> |
|
|
|
|
|
<mat-select [value]="pageSize" (selectionChange)="onPageSizeChange($event.value)" class="tb-search-page-size-select"> |
|
|
|
|
|
@for (size of pageSizeOptions; track size) { |
|
|
|
|
|
<mat-option [value]="size">{{ size }}</mat-option> |
|
|
|
|
|
} |
|
|
|
|
|
</mat-select> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
} |
|
|
} |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<!-- No results count when 0 --> |
|
|
<div class="tb-search-divider"></div> |
|
|
@if (!isLoading && totalElements === 0 && searchText?.trim()) { |
|
|
|
|
|
<div class="tb-search-pagination-row"> |
|
|
|
|
|
<span class="tb-search-results-count"> |
|
|
|
|
|
{{ 'iot-hub.results-count' | translate:{ count: 0 } }} |
|
|
|
|
|
</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
<!-- Loading --> |
|
|
<div class="relative min-h-0"> |
|
|
@if (isLoading) { |
|
|
|
|
|
<div class="tb-search-loading flex justify-center py-12"> |
|
|
|
|
|
<mat-spinner diameter="40"></mat-spinner> |
|
|
|
|
|
</div> |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
<!-- No results --> |
|
|
<div class="tb-results-container"> |
|
|
@if (!isLoading && totalElements === 0 && searchText?.trim()) { |
|
|
<!-- Error --> |
|
|
<div class="tb-search-empty flex flex-col items-center py-10"> |
|
|
@if (hasError) { |
|
|
<div class="tb-no-data-bg"></div> |
|
|
<div class="tb-search-empty flex flex-col items-center py-10"> |
|
|
<h3>{{ 'iot-hub.no-items-found' | translate }}</h3> |
|
|
<div class="tb-no-service-bg"></div> |
|
|
<p>{{ 'iot-hub.try-adjusting-search' | translate }}</p> |
|
|
<h3>{{ 'iot-hub.network-server-unavailable' | translate }}</h3> |
|
|
</div> |
|
|
<p>{{ 'iot-hub.network-server-unavailable-text' | translate }}</p> |
|
|
} |
|
|
<button mat-flat-button color="primary" (click)="retryLoadResults()"> |
|
|
|
|
|
{{ 'iot-hub.try-again' | translate }} |
|
|
<!-- Results grouped by type --> |
|
|
|
|
|
@if (!isLoading && resultGroups.length > 0) { |
|
|
|
|
|
<div class="tb-search-divider"></div> |
|
|
|
|
|
<div class="tb-search-categories"> |
|
|
|
|
|
@for (group of resultGroups; track group.type) { |
|
|
|
|
|
<div class="tb-search-section"> |
|
|
|
|
|
<!-- Section header --> |
|
|
|
|
|
<button mat-button class="tb-search-section-header" (click)="navigateToType(group.type)"> |
|
|
|
|
|
{{ getTypeLabel(group.type) }} |
|
|
|
|
|
<mat-icon>chevron_right</mat-icon> |
|
|
|
|
|
</button> |
|
|
</button> |
|
|
<!-- Card grid --> |
|
|
</div> |
|
|
<div class="tb-search-card-grid" [class.tb-search-card-grid-compact]="isCompactType(group.type)"> |
|
|
} |
|
|
@for (item of group.items; track item.id) { |
|
|
|
|
|
<tb-iot-hub-item-card |
|
|
<!-- No results --> |
|
|
[item]="item" |
|
|
@if (!hasError && totalElements === 0 && searchText?.trim()) { |
|
|
[installedItem]="getInstalledItem(item)" |
|
|
<div class="tb-search-empty flex flex-col items-center py-10"> |
|
|
[installedItemsCount]="getInstalledItemsCount(item)" |
|
|
<div class="tb-no-data-bg"></div> |
|
|
[showTypeChip]="false" |
|
|
<h3>{{ 'iot-hub.no-items-found' | translate }}</h3> |
|
|
[showSubtype]="false" |
|
|
<p>{{ 'iot-hub.try-adjusting-search' | translate }}</p> |
|
|
[showCreator]="showCreator" |
|
|
</div> |
|
|
(cardClick)="openItemDetail($event)" |
|
|
} |
|
|
(creatorClick)="navigateToCreator($event)" |
|
|
|
|
|
(installClick)="installItem($event)" |
|
|
<!-- Results grouped by type --> |
|
|
(updateClick)="updateItem($event)" |
|
|
@if (resultGroups.length > 0) { |
|
|
(deleteClick)="deleteInstalledItem($event)"> |
|
|
<div class="tb-search-categories"> |
|
|
</tb-iot-hub-item-card> |
|
|
@for (group of resultGroups; track group.type) { |
|
|
} |
|
|
<div class="tb-search-section"> |
|
|
</div> |
|
|
<!-- Section header --> |
|
|
|
|
|
<button mat-button class="tb-search-section-header" (click)="navigateToType(group.type)"> |
|
|
|
|
|
{{ getTypeLabel(group.type) }} |
|
|
|
|
|
<mat-icon>chevron_right</mat-icon> |
|
|
|
|
|
</button> |
|
|
|
|
|
<!-- Card grid --> |
|
|
|
|
|
<div class="tb-search-card-grid" [class.tb-search-card-grid-compact]="isCompactType(group.type)"> |
|
|
|
|
|
@for (item of group.items; track item.id) { |
|
|
|
|
|
<tb-iot-hub-item-card |
|
|
|
|
|
[item]="item" |
|
|
|
|
|
[installedItem]="getInstalledItem(item)" |
|
|
|
|
|
[installedItemsCount]="getInstalledItemsCount(item)" |
|
|
|
|
|
[showTypeChip]="false" |
|
|
|
|
|
[showSubtype]="false" |
|
|
|
|
|
[showCreator]="showCreator" |
|
|
|
|
|
(cardClick)="openItemDetail($event)" |
|
|
|
|
|
(creatorClick)="navigateToCreator($event)" |
|
|
|
|
|
(installClick)="installItem($event)" |
|
|
|
|
|
(updateClick)="updateItem($event)" |
|
|
|
|
|
(deleteClick)="deleteInstalledItem($event)"> |
|
|
|
|
|
</tb-iot-hub-item-card> |
|
|
|
|
|
} |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
} |
|
|
</div> |
|
|
</div> |
|
|
} |
|
|
} |
|
|
</div> |
|
|
</div> |
|
|
} |
|
|
|
|
|
|
|
|
<!-- Loading --> |
|
|
|
|
|
@if (isLoading) { |
|
|
|
|
|
<div class="absolute inset-0 z-10 backdrop-grayscale" style="background: rgba(255, 255, 255, 0.72)"> |
|
|
|
|
|
<div class="flex justify-center py-32"> |
|
|
|
|
|
<mat-spinner diameter="64"></mat-spinner> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|