generated from bastian/boilerplate
170 lines
5.0 KiB
TypeScript
170 lines
5.0 KiB
TypeScript
import { Component, inject, signal } from '@angular/core';
|
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
import { ApiClientService, type ApiErrorBody, type ItemDto } from '@boilerplate/api-client';
|
|
import { UiPaginationComponent } from '../../shared/ui';
|
|
|
|
@Component({
|
|
standalone: true,
|
|
imports: [ReactiveFormsModule, UiPaginationComponent],
|
|
template: `
|
|
<form class="ui-toolbar item-toolbar" (ngSubmit)="searchItems()">
|
|
<label class="ui-form-field">
|
|
<span class="ui-label">Suche</span>
|
|
<input class="ui-control" [formControl]="search" placeholder="Item suchen" />
|
|
</label>
|
|
<button class="ui-button ui-button--primary" type="submit">Suchen</button>
|
|
</form>
|
|
|
|
@if (error()) {
|
|
<p class="ui-notice ui-notice--error">{{ error() }}</p>
|
|
}
|
|
|
|
<section class="ui-grid item-grid">
|
|
@for (item of items(); track item.id) {
|
|
<button class="ui-card ui-card--interactive item-card" type="button" (click)="edit(item)">
|
|
<strong>{{ item.name }}</strong>
|
|
<span class="ui-help-text">{{ item.description || 'Keine Beschreibung' }}</span>
|
|
<small class="ui-meta">{{ item.status }} - Version {{ item.version }}</small>
|
|
</button>
|
|
}
|
|
</section>
|
|
|
|
@if (total() > 0) {
|
|
<ui-pagination
|
|
[page]="page()"
|
|
[pageSize]="pageSize"
|
|
[total]="total()"
|
|
(pageChange)="goToPage($event)"
|
|
/>
|
|
}
|
|
|
|
<form class="ui-card ui-form" [formGroup]="form" (ngSubmit)="save()">
|
|
<h2>{{ selected()?.id ? 'Item bearbeiten' : 'Item erstellen' }}</h2>
|
|
<label class="ui-form-field">
|
|
<span class="ui-label">Name</span>
|
|
<input class="ui-control" formControlName="name" />
|
|
</label>
|
|
<label class="ui-form-field">
|
|
<span class="ui-label">Beschreibung</span>
|
|
<textarea class="ui-control" formControlName="description"></textarea>
|
|
</label>
|
|
<label class="ui-form-field">
|
|
<span class="ui-label">Status</span>
|
|
<select class="ui-control" formControlName="status">
|
|
<option value="draft">Entwurf</option>
|
|
<option value="active">Aktiv</option>
|
|
<option value="archived">Archiviert</option>
|
|
</select>
|
|
</label>
|
|
<div class="ui-actions">
|
|
<button class="ui-button ui-button--primary" type="submit" [disabled]="form.invalid">
|
|
Speichern
|
|
</button>
|
|
@if (selected(); as item) {
|
|
<button class="ui-button ui-button--danger" type="button" (click)="delete(item)">
|
|
Loeschen
|
|
</button>
|
|
}
|
|
</div>
|
|
</form>
|
|
`,
|
|
styles: [
|
|
`
|
|
.item-toolbar,
|
|
.item-grid {
|
|
margin-bottom: var(--space-5);
|
|
}
|
|
.item-card {
|
|
text-align: left;
|
|
}
|
|
@media (min-width: 48rem) {
|
|
.item-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
`,
|
|
],
|
|
})
|
|
export class ItemsPageComponent {
|
|
private readonly api = inject(ApiClientService);
|
|
readonly items = signal<ItemDto[]>([]);
|
|
readonly selected = signal<ItemDto | null>(null);
|
|
readonly error = signal('');
|
|
readonly page = signal(1);
|
|
readonly total = signal(0);
|
|
readonly pageSize = 20;
|
|
readonly search = new FormControl('', { nonNullable: true });
|
|
readonly form = new FormGroup({
|
|
name: new FormControl('', {
|
|
nonNullable: true,
|
|
validators: [
|
|
(control) => Validators.required(control),
|
|
(control) => Validators.maxLength(160)(control),
|
|
],
|
|
}),
|
|
description: new FormControl('', { nonNullable: true }),
|
|
status: new FormControl<ItemDto['status']>('draft', { nonNullable: true }),
|
|
});
|
|
|
|
constructor() {
|
|
this.load();
|
|
}
|
|
|
|
load(): void {
|
|
this.api
|
|
.items({
|
|
search: this.search.value,
|
|
page: this.page(),
|
|
pageSize: this.pageSize,
|
|
})
|
|
.subscribe((page) => {
|
|
this.items.set(page.items);
|
|
this.page.set(page.page);
|
|
this.total.set(page.total);
|
|
});
|
|
}
|
|
|
|
searchItems(): void {
|
|
this.page.set(1);
|
|
this.load();
|
|
}
|
|
|
|
goToPage(page: number): void {
|
|
this.page.set(page);
|
|
this.load();
|
|
}
|
|
|
|
edit(item: ItemDto): void {
|
|
this.selected.set(item);
|
|
this.form.setValue({
|
|
name: item.name,
|
|
description: item.description ?? '',
|
|
status: item.status,
|
|
});
|
|
}
|
|
|
|
save(): void {
|
|
this.error.set('');
|
|
const value = this.form.getRawValue();
|
|
const selected = this.selected();
|
|
const request = selected
|
|
? this.api.updateItem(selected.id, { ...value, version: selected.version })
|
|
: this.api.createItem(value);
|
|
request.subscribe({
|
|
next: () => {
|
|
this.selected.set(null);
|
|
this.form.reset({ name: '', description: '', status: 'draft' });
|
|
this.load();
|
|
},
|
|
error: (err: { error?: ApiErrorBody }) =>
|
|
this.error.set(err.error?.message ?? 'Speichern fehlgeschlagen.'),
|
|
});
|
|
}
|
|
|
|
delete(item: ItemDto): void {
|
|
if (confirm('Item wirklich loeschen?')) {
|
|
this.api.deleteItem(item.id, item.version).subscribe(() => this.load());
|
|
}
|
|
}
|
|
}
|