This commit is contained in:
Nino
2025-03-07 18:09:02 +01:00
parent b85538f98a
commit e5f42c9de2
18 changed files with 224 additions and 38 deletions

View File

@@ -0,0 +1,7 @@
import type { Preview } from '@storybook/angular';
const preview: Preview = {
tags: ['autodocs'],
};
export default preview;

View File

@@ -1 +1,4 @@
export * from "./lib/return-results-list/";
export * from './lib/return-results-list/';
export * from './lib/return-results-item-list/';
export * from './lib/return-order-by-list/';
export * from './lib/return-results-filter-list/';

View File

@@ -0,0 +1 @@
export * from './return-order-by-list.component';

View File

@@ -0,0 +1,8 @@
<ui-toolbar>
<span class="text-isa-neutral-600 isa-text-body-1-regular">Sortieren</span>
<div class="flex-grow"></div>
<button uiTextButton>Belegdatum</button>
<button uiTextButton>Email</button>
<button uiTextButton>Name</button>
<button uiTextButton>PLZ</button>
</ui-toolbar>

View File

@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ToolbarComponent } from '@isa/ui/toolbar';
@Component({
selector: 'lib-return-order-by-list',
templateUrl: './return-order-by-list.component.html',
styleUrls: ['./return-order-by-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [ToolbarComponent],
})
export class ReturnOrderByListComponent {}

View File

@@ -0,0 +1 @@
export * from './return-results-filter-list.component';

View File

@@ -0,0 +1,3 @@
<ui-icon-button (click)="onFilter()">
<ng-icon name="isaActionFilter"></ng-icon>
</ui-icon-button>

View File

@@ -0,0 +1,19 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { isaActionFilter } from '@isa/icons';
import { IconButtonComponent } from '@isa/ui/buttons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
@Component({
selector: 'lib-return-results-filter-list',
templateUrl: './return-results-filter-list.component.html',
styleUrls: ['./return-results-filter-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [NgIconComponent, IconButtonComponent],
providers: [provideIcons({ isaActionFilter })],
})
export class ReturnResultsFilterListComponent {
onFilter() {
console.log('Filter click - open overlay');
}
}

View File

@@ -0,0 +1 @@
export * from './return-results-item-list.component';

View File

@@ -4,4 +4,7 @@
via Rechnungsnummer, E-Mail-Adresse oder Kundennamen
</p>
<filter-search-bar-input inputKey="qs" (search)="onSearch()"></filter-search-bar-input>
<filter-search-bar-input
inputKey="qs"
(search)="onSearch()"
></filter-search-bar-input>

View File

@@ -1,15 +1,30 @@
<div>Header Area</div>
<div class="w-full flex flex-row justify-between items-start">
<div class="flex flex-col gap-5 justify-start">
<filter-search-bar-input
class="flex flex-row gap-4 h-12"
[scanButton]="true"
inputKey="qs"
(search)="onSearch()"
></filter-search-bar-input>
<span class="isa-text-body-2-regular"> {{ hits() }} Einträge </span>
</div>
<div class="flex flex-row gap-4 items-center">
<lib-return-results-filter-list></lib-return-results-filter-list>
<lib-return-order-by-list></lib-return-order-by-list>
</div>
</div>
<lib-return-results-list>
@for (item of items(); track item.id) {
@defer (on viewport) {
<a [routerLink]="['../', 'details', item.id]" [routerLink] class="w-full">
<a [routerLink]="['../', 'details', item.id]" class="w-full">
<lib-return-results-item-list
[item]="item"
></lib-return-results-item-list>
</a>
} @placeholder {
<p>loading...</p>
<ui-icon-button [pending]="true" [color]="'tertiary'"></ui-icon-button>
}
}
</lib-return-results-list>

View File

@@ -2,35 +2,109 @@ import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
} from "@angular/core";
import { ReturnResultsListComponent } from "@feature/return/containers";
import { injectActivatedProcessId } from "@isa/core/process";
import { ReturnResultsItemListComponent } from "libs/feature/return/containers/src/lib/return-results-item-list/return-results-item-list.component";
import { ReturnSearchStore } from "@feature/return/services";
import { RouterLink } from "@angular/router";
untracked,
} from '@angular/core';
import {
ReturnResultsListComponent,
ReturnResultsItemListComponent,
ReturnOrderByListComponent,
ReturnResultsFilterListComponent,
} from '@feature/return/containers';
import { injectActivatedProcessId } from '@isa/core/process';
import { ReturnSearchStore } from '@feature/return/services';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { FilterService, SearchBarInputComponent } from '@isa/shared/filter';
import { IconButtonComponent } from '@isa/ui/buttons';
import { isEmpty } from 'lodash';
@Component({
selector: "lib-return-results-page",
templateUrl: "./results-page.component.html",
styleUrls: ["./results-page.component.css"],
selector: 'lib-return-results-page',
templateUrl: './results-page.component.html',
styleUrls: ['./results-page.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
RouterLink,
ReturnResultsListComponent,
ReturnResultsItemListComponent,
ReturnOrderByListComponent,
ReturnResultsFilterListComponent,
IconButtonComponent,
SearchBarInputComponent,
],
})
export class ResultsPageComponent {
#route = inject(ActivatedRoute);
#router = inject(Router);
private _processId = injectActivatedProcessId();
private _returnSearchStore = inject(ReturnSearchStore);
private _filterService = inject(FilterService);
items = computed(() => {
private _resultEntity = computed(() => {
const processId = this._processId();
if (processId) {
return this._returnSearchStore.getEntity(processId)?.items;
return this._returnSearchStore.getEntity(processId);
}
return [];
return undefined;
});
items = computed(() => {
return this._resultEntity()?.items ?? [];
});
hits = computed(() => {
return this._resultEntity()?.hits ?? 0;
});
constructor() {
this._initListSearchEffect();
}
// Triggers search after page reload (via F5 f.e.)
// TODO: Fix reload bug if click on navbar "Retoure"
private _initListSearchEffect() {
effect(() => {
const processId = this._processId();
if (processId) {
const entity = this._returnSearchStore.getEntity(processId);
const queryParams = this._filterService.toParams();
if (entity || isEmpty(queryParams)) {
return;
}
untracked(async () => {
// TODO: Navigieren und Params in der URL setzen auslagern
await this.#router.navigate([], {
queryParams,
queryParamsHandling: 'merge',
relativeTo: this.#route,
});
this._returnSearchStore.search({ processId, params: queryParams });
});
}
});
}
async onSearch() {
const processId = this._processId();
if (processId) {
// TODO: Logik für Suche&Navigation in Main Page + Results Page auslagern da selbe Logik
await this.#router.navigate([], {
queryParams: this._filterService.toParams(),
queryParamsHandling: 'merge',
relativeTo: this.#route,
});
this._returnSearchStore.search({
processId,
params: this._filterService.toParams(),
});
}
}
}

View File

@@ -1,22 +1,22 @@
import { patchState, signalStore, type, withMethods } from "@ngrx/signals";
import { rxMethod } from "@ngrx/signals/rxjs-interop";
import { patchState, signalStore, type, withMethods } from '@ngrx/signals';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import {
addEntity,
entityConfig,
updateEntity,
withEntities,
} from "@ngrx/signals/entities";
import { debounceTime, distinctUntilChanged, pipe, switchMap, tap } from "rxjs";
import { ReturnSearchService } from "./return-search.service";
import { tapResponse } from "@ngrx/operators";
import { inject } from "@angular/core";
import { ReceiptListItem, QueryTokenSchema, ListResponseArgs } from "./schemas";
} from '@ngrx/signals/entities';
import { debounceTime, distinctUntilChanged, pipe, switchMap, tap } from 'rxjs';
import { ReturnSearchService } from './return-search.service';
import { tapResponse } from '@ngrx/operators';
import { inject } from '@angular/core';
import { ReceiptListItem, QueryTokenSchema, ListResponseArgs } from './schemas';
enum ReturnSearchStatus {
Idle = "idle",
Pending = "pending",
Success = "success",
Error = "error",
Idle = 'idle',
Pending = 'pending',
Success = 'success',
Error = 'error',
}
type ReturnSearchEntity = {
@@ -34,7 +34,7 @@ const config = entityConfig({
});
export const ReturnSearchStore = signalStore(
{ providedIn: "root" },
{ providedIn: 'root' },
withEntities<ReturnSearchEntity>(config),
withMethods((store) => ({
getEntity(processId: number): ReturnSearchEntity | undefined {
@@ -48,7 +48,14 @@ export const ReturnSearchStore = signalStore(
patchState(
store,
updateEntity(
{ id: processId, changes: { status: ReturnSearchStatus.Pending } },
{
id: processId,
changes: {
status: ReturnSearchStatus.Pending,
items: [],
hits: 0,
},
},
config,
),
);

View File

@@ -10,11 +10,23 @@
<button
type="submit"
uiIconButton
color="brand"
[color]="buttonColor()"
[disabled]="control.invalid"
(click)="onSearch()"
>
<ng-icon name="isaActionSearch"></ng-icon>
</button>
</ui-search-bar>
@if (scanButton()) {
<button
type="submit"
uiIconButton
[color]="scanButtonColor()"
[disabled]="control.invalid"
(click)="onSearch()"
>
<ng-icon name="isaActionScanner"></ng-icon>
</button>
}
}

View File

@@ -1,9 +1,17 @@
import { ChangeDetectionStrategy, Component, computed, inject, input, output } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
input,
output,
ViewEncapsulation,
} from '@angular/core';
import { UiSearchBarComponent } from '@isa/ui/search-bar';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { IconButtonComponent } from '@isa/ui/buttons';
import { IconButtonColor, IconButtonComponent } from '@isa/ui/buttons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaActionSearch } from '@isa/icons';
import { isaActionSearch, isaActionScanner } from '@isa/icons';
import { FilterService, TextFilterInput } from '../../core';
import { InputType } from '../../types';
@@ -12,9 +20,16 @@ import { InputType } from '../../types';
templateUrl: './search-bar-input.component.html',
styleUrls: ['./search-bar-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [UiSearchBarComponent, IconButtonComponent, NgIconComponent, ReactiveFormsModule],
providers: [provideIcons({ isaActionSearch })],
imports: [
UiSearchBarComponent,
IconButtonComponent,
NgIconComponent,
ReactiveFormsModule,
IconButtonComponent,
],
providers: [provideIcons({ isaActionSearch, isaActionScanner })],
})
export class SearchBarInputComponent {
readonly filterService = inject(FilterService);
@@ -22,6 +37,10 @@ export class SearchBarInputComponent {
control = new FormControl();
inputKey = input.required<string>();
buttonColor = input<IconButtonColor>('brand');
scanButton = input<boolean>(false);
scanButtonColor = input<IconButtonColor>('primary');
input = computed<TextFilterInput>(() => {
const inputs = this.filterService.inputs();

View File

@@ -14,7 +14,8 @@
"fix:files:swagger": "node ./tools/fix-files.js generated/swagger",
"prettier": "prettier --write .",
"pretty-quick": "pretty-quick --staged",
"prepare": "husky"
"prepare": "husky",
"storybook": "npx nx run isa-app:storybook"
},
"private": true,
"dependencies": {