mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Result List Empty State - Initial Loader - Pending Loader
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
inputKey="qs"
|
||||
(search)="onSearch()"
|
||||
></filter-search-bar-input>
|
||||
<span class="isa-text-body-2-regular"> {{ hits() }} Einträge </span>
|
||||
<span class="isa-text-body-2-regular"> {{ entityHits() }} Einträge </span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
@@ -15,17 +15,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 w-full items-center justify-center">
|
||||
@for (item of items(); track item.id) {
|
||||
@defer (on viewport) {
|
||||
<a [routerLink]="['../', 'details', item.id]" class="w-full">
|
||||
<lib-return-results-item-list
|
||||
#listElement
|
||||
[item]="item"
|
||||
></lib-return-results-item-list>
|
||||
</a>
|
||||
} @placeholder {
|
||||
<ui-icon-button [pending]="true" [color]="'tertiary'"></ui-icon-button>
|
||||
@let items = entityItems();
|
||||
@if (items.length > 0) {
|
||||
<div class="flex flex-col gap-4 w-full items-center justify-center">
|
||||
@for (item of items; track item.id) {
|
||||
@defer (on viewport) {
|
||||
<a [routerLink]="['../', 'details', item.id]" class="w-full">
|
||||
<lib-return-results-item-list
|
||||
#listElement
|
||||
[item]="item"
|
||||
></lib-return-results-item-list>
|
||||
</a>
|
||||
} @placeholder {
|
||||
<!-- TODO: Den Spinner durch Skeleton Loader Kacheln ersetzen -->
|
||||
<div class="h-[7.75rem] w-full flex items-center justify-center">
|
||||
<ui-icon-button
|
||||
[pending]="true"
|
||||
[color]="'tertiary'"
|
||||
></ui-icon-button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (entityStatus() === ReturnSearchStatus.Pending) {
|
||||
<div class="h-[7.75rem] w-full flex items-center justify-center">
|
||||
<ui-icon-button [pending]="true" [color]="'tertiary'"></ui-icon-button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else if (
|
||||
items.length === 0 && entityStatus() === ReturnSearchStatus.Pending
|
||||
) {
|
||||
<div class="h-[7.75rem] w-full flex items-center justify-center">
|
||||
<ui-icon-button [pending]="true" [color]="'tertiary'"></ui-icon-button>
|
||||
</div>
|
||||
} @else if (entityStatus() !== ReturnSearchStatus.Idle) {
|
||||
<ui-empty-state
|
||||
[title]="emptyState().title"
|
||||
[description]="emptyState().description"
|
||||
>
|
||||
</ui-empty-state>
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { injectActivatedProcessId } from '@isa/core/process';
|
||||
import {
|
||||
ReturnSearchEntity,
|
||||
ReturnSearchStatus,
|
||||
ReturnSearchStore,
|
||||
} from '@feature/return/services';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
@@ -25,6 +26,12 @@ import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
import { ViewportScroller } from '@angular/common';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { debounceTime, fromEvent, map } from 'rxjs';
|
||||
import { EmptyStateComponent } from '@isa/ui/empty-state';
|
||||
|
||||
type EmptyState = {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'lib-return-results-page',
|
||||
@@ -39,6 +46,7 @@ import { debounceTime, fromEvent, map } from 'rxjs';
|
||||
ReturnResultsFilterListComponent,
|
||||
IconButtonComponent,
|
||||
SearchBarInputComponent,
|
||||
EmptyStateComponent,
|
||||
],
|
||||
})
|
||||
export class ResultsPageComponent {
|
||||
@@ -50,6 +58,8 @@ export class ResultsPageComponent {
|
||||
private _returnSearchStore = inject(ReturnSearchStore);
|
||||
private _filterService = inject(FilterService);
|
||||
|
||||
ReturnSearchStatus = ReturnSearchStatus;
|
||||
|
||||
private _entity = computed(() => {
|
||||
const processId = this._processId();
|
||||
if (processId) {
|
||||
@@ -58,14 +68,25 @@ export class ResultsPageComponent {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
items = computed(() => {
|
||||
entityItems = computed(() => {
|
||||
return this._entity()?.items ?? [];
|
||||
});
|
||||
|
||||
hits = computed(() => {
|
||||
entityHits = computed(() => {
|
||||
return this._entity()?.hits ?? 0;
|
||||
});
|
||||
|
||||
entityStatus = computed(() => {
|
||||
return this._entity()?.status ?? ReturnSearchStatus.Idle;
|
||||
});
|
||||
|
||||
emptyState = computed<EmptyState>(() => {
|
||||
return {
|
||||
title: 'Keine Suchergebnisse',
|
||||
description: 'Suchen Sie nach einer Rechnungsnummer oder Kundennamen.',
|
||||
};
|
||||
});
|
||||
|
||||
listElements =
|
||||
viewChildren<QueryList<ReturnResultsItemListComponent>>('listElement');
|
||||
|
||||
@@ -86,7 +107,8 @@ export class ResultsPageComponent {
|
||||
if (processId) {
|
||||
const entity = this._entity();
|
||||
if (entity) {
|
||||
const isPending = entity.status === 'pending';
|
||||
const isPending =
|
||||
this.entityStatus() === ReturnSearchStatus.Pending;
|
||||
// Trigger reload search if no search request is already pending and
|
||||
// 1. List scrolled to bottom
|
||||
// 2. After Process change AND no items in entity
|
||||
|
||||
@@ -12,7 +12,7 @@ import { tapResponse } from '@ngrx/operators';
|
||||
import { inject } from '@angular/core';
|
||||
import { ReceiptListItem, QueryTokenSchema, ListResponseArgs } from './schemas';
|
||||
|
||||
enum ReturnSearchStatus {
|
||||
export enum ReturnSearchStatus {
|
||||
Idle = 'idle',
|
||||
Pending = 'pending',
|
||||
Success = 'success',
|
||||
|
||||
Reference in New Issue
Block a user