OrderBy Toolbar mobile, ScrollPos Update, Schema Update

This commit is contained in:
Nino
2025-03-13 18:15:36 +01:00
parent 1a4d0a38da
commit 91668e53fa
8 changed files with 104 additions and 70 deletions

View File

@@ -1,11 +1,22 @@
import { argsToTemplate, type Meta, type StoryObj, moduleMetadata } from '@storybook/angular';
import { UiSearchBarClearComponent, UiSearchBarComponent } from '@isa/ui/search-bar';
import {
argsToTemplate,
type Meta,
type StoryObj,
moduleMetadata,
} from '@storybook/angular';
import {
UiSearchBarClearComponent,
UiSearchBarComponent,
} from '@isa/ui/search-bar';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaActionSearch } from '@isa/icons';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IconButtonComponent } from '@isa/ui/buttons';
type Appearance = 'main' | 'results';
export interface UiSearchBarComponentInputs {
appearance: Appearance;
placeholder: string;
value: string;
resetValue: string;
@@ -36,20 +47,37 @@ const meta: Meta<UiSearchBarComponentInputs> = {
resetValue: {
control: 'text',
},
appearance: {
control: {
type: 'select',
options: ['main', 'results'] as Appearance[],
},
},
},
args: {
placeholder: 'Rechnungsnummer, E-Mail, Kundenkarte, Name...',
appearance: 'main',
},
render: (args) => {
let button = '';
if (args.appearance === 'main') {
button =
'<button type="submit" uiIconButton color="brand"><ng-icon name="isaActionSearch"></ng-icon></button>';
} else if (args.appearance === 'results') {
button =
'<button type="submit" uiIconButton prefix color="neutral"><ng-icon name="isaActionSearch"></ng-icon></button>';
}
return {
props: { ...args, control: new FormControl(args.value) },
template: `<ui-search-bar class="flex items-center shrink-0 h-[3.75rem] pr-2 pl-6" ${argsToTemplate(args, { exclude: ['placeholder', 'value', 'resetValue', 'appearance'] })}>
<input [formControl]="control" type="text" placeholder="${args.placeholder}" />
<ui-search-bar-clear [class.pr-4]="${args.appearance === 'results'}" value="${args.resetValue}"></ui-search-bar-clear>
${button}
</ui-search-bar>`,
};
},
render: (args) => ({
props: { ...args, control: new FormControl(args.value) },
template: `<ui-search-bar ${argsToTemplate(args, { exclude: ['placeholder', 'value', 'resetValue'] })}>
<input [formControl]="control" type="text" placeholder="${args.placeholder}" />
<ui-search-bar-clear value="${args.resetValue}"></ui-search-bar-clear>
<button type="submit" uiIconButton color="brand">
<ng-icon name="isaActionSearch"></ng-icon>
</button>
</ui-search-bar>`,
}),
};
export default meta;

View File

@@ -1,14 +1,8 @@
@if (isMobileDevice) {
<ui-icon-button (click)="onOpenOrderByDialog()">
<ng-icon name="isaActionSort"></ng-icon>
</ui-icon-button>
} @else {
<ui-toolbar>
<span class="text-isa-neutral-600 isa-text-body-2-regular">Sortieren</span>
<div class="flex-grow"></div>
<button (click)="onClick('Belegdatum')" uiTextButton>Belegdatum</button>
<button (click)="onClick('Email')" uiTextButton>Email</button>
<button (click)="onClick('Name')" uiTextButton>Name</button>
<button (click)="onClick('PLZ')" uiTextButton>PLZ</button>
</ui-toolbar>
}
<ui-toolbar>
<span class="text-isa-neutral-600 isa-text-body-2-regular">Sortieren</span>
<div class="flex-grow"></div>
<button (click)="onClick('Belegdatum')" uiTextButton>Belegdatum</button>
<button (click)="onClick('Email')" uiTextButton>Email</button>
<button (click)="onClick('Name')" uiTextButton>Name</button>
<button (click)="onClick('PLZ')" uiTextButton>PLZ</button>
</ui-toolbar>

View File

@@ -1,9 +1,7 @@
import { Platform } from '@angular/cdk/platform';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { isaActionSort } from '@isa/icons';
import { IconButtonComponent, TextButtonComponent } from '@isa/ui/buttons';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { TextButtonComponent } from '@isa/ui/buttons';
import { ToolbarComponent } from '@isa/ui/toolbar';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
@Component({
selector: 'lib-return-order-by-list',
@@ -11,21 +9,10 @@ import { NgIconComponent, provideIcons } from '@ng-icons/core';
styleUrls: ['./return-order-by-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
ToolbarComponent,
TextButtonComponent,
NgIconComponent,
IconButtonComponent,
],
providers: [provideIcons({ isaActionSort })],
imports: [ToolbarComponent, TextButtonComponent],
})
export class ReturnOrderByListComponent {
#platform = inject(Platform);
isMobileDevice = this.#platform.ANDROID || this.#platform.IOS;
onClick(label: string) {
console.log('Order By click -', label);
}
onOpenOrderByDialog() {
console.log('Open Order By dialog');
}
}

View File

@@ -1,22 +1,33 @@
<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"
[appearance]="'results'"
inputKey="qs"
(search)="onSearch()"
></filter-search-bar-input>
<span class="text-isa-neutral-900 isa-text-body-2-regular">
{{ entityHits() }} Einträge
</span>
</div>
<filter-search-bar-input
class="flex flex-row gap-4 h-12"
[appearance]="'results'"
inputKey="qs"
(search)="onSearch()"
></filter-search-bar-input>
<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>
@if (isMobileDevice()) {
<ui-icon-button
(click)="toggleOrderByToolbar.set(!toggleOrderByToolbar())"
>
<ng-icon name="isaActionSort"></ng-icon>
</ui-icon-button>
} @else {
<lib-return-order-by-list></lib-return-order-by-list>
}
</div>
</div>
@if (isMobileDevice() && toggleOrderByToolbar()) {
<lib-return-order-by-list class="w-full"></lib-return-order-by-list>
}
<span class="text-isa-neutral-900 isa-text-body-2-regular self-start">
{{ entityHits() }} Einträge
</span>
@let items = entityItems();
@if (items.length > 0) {
<div class="flex flex-col gap-4 w-full items-center justify-center">

View File

@@ -6,6 +6,7 @@ import {
effect,
inject,
QueryList,
signal,
untracked,
viewChildren,
} from '@angular/core';
@@ -27,6 +28,9 @@ 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';
import { Platform } from '@angular/cdk/platform';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { isaActionSort } from '@isa/icons';
type EmptyState = {
title: string;
@@ -47,12 +51,15 @@ type EmptyState = {
IconButtonComponent,
SearchBarInputComponent,
EmptyStateComponent,
NgIconComponent,
],
providers: [provideIcons({ isaActionSort })],
})
export class ResultsPageComponent {
#viewportScroller = inject(ViewportScroller);
#route = inject(ActivatedRoute);
#router = inject(Router);
#platform = inject(Platform);
private _processId = injectActivatedProcessId();
private _returnSearchStore = inject(ReturnSearchStore);
@@ -98,6 +105,9 @@ export class ResultsPageComponent {
{ initialValue: this.#viewportScroller.getScrollPosition()[1] },
);
isMobileDevice = signal(this.#platform.ANDROID || this.#platform.IOS);
toggleOrderByToolbar = signal(false);
searchEffectFn = () =>
effect(() => {
const processId = this._processId();
@@ -128,19 +138,28 @@ export class ResultsPageComponent {
const scrollPos = this.scrollPosY();
untracked(() => {
const processId = this._processId();
if (processId) {
console.log('scrollPos', scrollPos);
this._returnSearchStore.setScrollPos(processId, scrollPos);
const entity = this._entity();
if (processId && entity) {
const scrollPosChanged = entity.scrollPos !== scrollPos;
if (scrollPosChanged) {
console.log('set scrollPos in entity', scrollPos);
this._returnSearchStore.setScrollPos(processId, scrollPos);
}
}
});
});
applyScrollPosFn = () =>
afterNextRender(() => {
// TODO: Überprüfen ob Lösung nach F5/Refresh funktioniert, wenn Entities gecached werden
const entity = this._entity();
console.log('after next render scroll pos');
if (entity && entity.scrollPos) {
this.#viewportScroller.scrollToPosition([0, entity.scrollPos]);
if (entity) {
console.log(
'after next render trigger - scroll to position',
entity?.scrollPos ?? 0,
);
this.#viewportScroller.scrollToPosition([0, entity?.scrollPos ?? 0]);
}
});

View File

@@ -9,7 +9,7 @@ import {
import { debounceTime, distinctUntilChanged, pipe, switchMap, tap } from 'rxjs';
import { ReturnSearchService } from './return-search.service';
import { tapResponse } from '@ngrx/operators';
import { inject, input } from '@angular/core';
import { inject } from '@angular/core';
import { ReceiptListItem, QueryTokenSchema, ListResponseArgs } from './schemas';
export enum ReturnSearchStatus {

View File

@@ -1,11 +1,5 @@
import { z } from 'zod';
export const FilterSchema = z.record(z.any());
export const InputSchema = z.object({
qs: z.string().optional(),
});
export const OrderBySchema = z.object({
by: z.string().optional(),
label: z.string().optional(),
@@ -14,8 +8,8 @@ export const OrderBySchema = z.object({
});
export const QueryTokenSchema = z.object({
filter: FilterSchema.default({}),
input: InputSchema.default({}),
filter: z.record(z.any()).default({}),
input: z.record(z.any()).default({}),
orderBy: z.array(OrderBySchema).default([]),
skip: z.number().default(0),
take: z.number().default(25),