Files
flat-pilot/apps/frontend/src/app/features/items/items.page.ts
2026-07-19 13:09:04 +02:00

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());
}
}
}