From 57f9acfcdbe6acda923723cc42434b25b98c2643 Mon Sep 17 00:00:00 2001 From: josiadmin Date: Tue, 28 Oct 2025 22:37:36 +0100 Subject: [PATCH] add edit delete and input forms --- angular.json | 2 +- src/app/app.routes.ts | 6 +- .../components/create/create.component.css | 98 +++++++++++ .../components/create/create.component.html | 42 +++++ src/app/components/create/create.component.ts | 51 ++++++ .../components/header/header.component.css | 13 -- .../components/header/header.component.html | 8 - src/app/components/header/header.component.ts | 40 ----- src/app/components/list/list.component.css | 164 ++++++++++++++++++ src/app/components/list/list.component.html | 66 +++++++ src/app/components/list/list.component.ts | 91 ++++++++++ src/app/components/login/login.component.ts | 2 +- src/app/models/kantin.model.ts | 19 ++ 13 files changed, 538 insertions(+), 64 deletions(-) create mode 100644 src/app/components/create/create.component.css create mode 100644 src/app/components/create/create.component.html create mode 100644 src/app/components/create/create.component.ts delete mode 100644 src/app/components/header/header.component.css delete mode 100644 src/app/components/header/header.component.html delete mode 100644 src/app/components/header/header.component.ts create mode 100644 src/app/components/list/list.component.css create mode 100644 src/app/components/list/list.component.html create mode 100644 src/app/components/list/list.component.ts create mode 100644 src/app/models/kantin.model.ts diff --git a/angular.json b/angular.json index bb4725c..4c5a4a8 100644 --- a/angular.json +++ b/angular.json @@ -1,5 +1,5 @@ { - "$schema": "@angular/cli/lib/config/schema.json", + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 8b3c172..cde7b19 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,10 +1,14 @@ import { Routes } from '@angular/router'; import { LoginComponent } from './components/login/login.component'; import { RegisterComponent } from './components/register/register.component'; +import { ListComponent } from './components/list/list.component'; +import { CreateComponent } from './components/create/create.component'; export const routes: Routes = [ { path: '', redirectTo: '/login', pathMatch: 'full' }, { path: 'login', component: LoginComponent }, - { path: 'register', component: RegisterComponent}, + { path: 'register', component: RegisterComponent }, + { path: 'list', component: ListComponent }, + { path: 'create', component: CreateComponent }, { path: '**', redirectTo: '/login' }, ]; diff --git a/src/app/components/create/create.component.css b/src/app/components/create/create.component.css new file mode 100644 index 0000000..7bb465a --- /dev/null +++ b/src/app/components/create/create.component.css @@ -0,0 +1,98 @@ +.create-card { + width: min(100% - 2rem, 700px); +} + +.glass-card { + margin: 2rem auto; + padding: 1.25rem; + border-radius: 1rem; + background: rgba(255, 255, 255, 0.18); + box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.18); + color: #111; +} + +.status { + padding: 0.75rem 1rem; + border-radius: 0.75rem; + background: rgba(255, 255, 255, 0.55); + margin-bottom: 0.75rem; +} + +.status.error { + color: #8b0000; + background: rgba(255, 230, 230, 0.8); +} + +.title { + margin: 0 0 1rem 0; + font-size: clamp(1.25rem, 1.5vw + 1rem, 2rem); + color: #000000b0; + font-weight: 700; +} + +.form { + display: grid; + gap: 0.75rem; +} + +.form-row { + display: grid; + gap: 0.25rem; +} + +.form-row input { + padding: 0.6rem 0.75rem; + border: 1px solid #ddd; + border-radius: 0.5rem; + font-size: 1rem; + transition: + background-color 120ms ease, + color 120ms ease, + border-color 120ms ease, + box-shadow 120ms ease; +} + +/* Bessere Lesbarkeit beim Fokussieren */ +.form-row input:focus, +.form-row input:focus-visible { + background: #ffffff; + color: #000000; + outline: none; + border-color: #000000; + box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.08); + caret-color: #000000; +} + +.form-row input::placeholder { + color: #666; +} + +.form-row input:focus::placeholder { + color: #888; +} + +.actions { + margin-top: 0.75rem; + display: flex; + gap: 0.5rem; + justify-content: flex-end; +} + +.btn { + padding: 0.6rem 1rem; + border-radius: 0.5rem; + border: none; + cursor: pointer; +} + +.btn.primary { + background: linear-gradient(135deg, #ff0048, #e03164); + color: #fff; +} + +.btn.secondary { + background: rgba(255, 255, 255, 0.6); +} diff --git a/src/app/components/create/create.component.html b/src/app/components/create/create.component.html new file mode 100644 index 0000000..75e8a4b --- /dev/null +++ b/src/app/components/create/create.component.html @@ -0,0 +1,42 @@ +
+
+

Neuer Kantinen-Eintrag

+ +
{{ errorMessage }}
+ +
+ + + + +
+ + +
+
+
+
diff --git a/src/app/components/create/create.component.ts b/src/app/components/create/create.component.ts new file mode 100644 index 0000000..6ded983 --- /dev/null +++ b/src/app/components/create/create.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { AppwriteService } from '../../services/appwrite.service'; + +@Component({ + selector: 'app-create', + standalone: true, + imports: [CommonModule, FormsModule], + templateUrl: './create.component.html', + styleUrl: './create.component.css', +}) +export class CreateComponent { + constructor( + private appwrite: AppwriteService, + private router: Router, + ) {} + + byeDate = ''; + betrag: number | null = null; + belegName = ''; + loading = false; + errorMessage: string | null = null; + + async save(): Promise { + if (!this.byeDate || this.betrag === null || Number.isNaN(this.betrag)) { + this.errorMessage = 'Bitte Datum und Betrag korrekt ausfüllen.'; + return; + } + this.loading = true; + this.errorMessage = null; + try { + await this.appwrite.createDocument({ + ByeDate: this.byeDate, + Betrag: this.betrag, + BelegName: this.belegName, + }); + this.router.navigate(['/list']); + } catch (e: any) { + console.error(e); + this.errorMessage = e?.message ?? 'Fehler beim Speichern.'; + } finally { + this.loading = false; + } + } + + goToList(): void { + this.router.navigate(['/list']); + } +} diff --git a/src/app/components/header/header.component.css b/src/app/components/header/header.component.css deleted file mode 100644 index e3986a8..0000000 --- a/src/app/components/header/header.component.css +++ /dev/null @@ -1,13 +0,0 @@ - -.headerText { - text-align: center; - color: rgb(42, 42, 88); - font-weight: bold; - font-size: 1.75rem; - font-family: "Libre Baskerville", serif; -} - -i{ - color: rgb(42, 42, 88); - cursor: pointer; -} diff --git a/src/app/components/header/header.component.html b/src/app/components/header/header.component.html deleted file mode 100644 index 4ed9abe..0000000 --- a/src/app/components/header/header.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
-

Kegel Spielplan

-
- - - -
-
diff --git a/src/app/components/header/header.component.ts b/src/app/components/header/header.component.ts deleted file mode 100644 index c3a3418..0000000 --- a/src/app/components/header/header.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Component } from '@angular/core'; -import { AppwriteService } from '../../services/appwrite.service'; -import { Router } from '@angular/router'; - -@Component({ - selector: 'app-header', - standalone: true, - imports: [], - templateUrl: './header.component.html', - styleUrl: './header.component.css', -}) -export class HeaderComponentComponent { - - constructor( - private appwriteService: AppwriteService, - private routes: Router, - ) {} - - goToSummery() { - // this.routes.navigate(['/list']); - } - - goToInput() { - // this.routes.navigate(['/input']); - } - - async logout() { - try { - const accountService = await this.appwriteService.accountService; - await this.appwriteService.accountService.deleteSession( - (await accountService.getSession('current')).$id, - ); - // Bei erfolgreicher Abmeldung - //this.toast.success('Logout erfolgreich!', 'Auf Wiedersehen!'); - this.routes.navigate(['/login']); - } catch (err: any) { - console.error('Fehler beim Abmelden:', err.message); - } - } -} diff --git a/src/app/components/list/list.component.css b/src/app/components/list/list.component.css new file mode 100644 index 0000000..621b3cb --- /dev/null +++ b/src/app/components/list/list.component.css @@ -0,0 +1,164 @@ +/* Glas/Glassmorphism Card auf demselben Hintergrund wie Login (login-container liefert BG) */ +.glass-card { + width: min(100% - 2rem, 1000px); + margin: 2rem auto; + padding: 1.25rem; + border-radius: 1rem; + background: rgba(255, 255, 255, 0.18); + box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.18); + color: #111; +} + +.glass-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; +} + +.glass-header h2 { + margin: 0; + font-size: clamp(1.25rem, 1.5vw + 1rem, 2rem); + color: #000000b0; + font-weight: 700; +} + +/* Scrollbarer Bereich, der sich dynamisch anpasst */ +.glass-scroll { + max-height: min(70vh, 800px); + overflow: auto; +} + +/* Tabelle */ +.list-table { + width: 100%; + border-collapse: collapse; +} + +.list-table th, +.list-table td { + text-align: left; + padding: 0.75rem 0.5rem; +} + +.list-table thead th { + position: sticky; + top: 0; + background: rgba(255, 255, 255, 0.4); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +.list-table tbody tr:nth-child(even) { + background: rgba(255, 255, 255, 0.25); +} + +.actions-col { + width: 1%; + white-space: nowrap; +} + +.row-actions { + display: inline-flex; + gap: 0.35rem; +} + +.icon-btn { + appearance: none; + border: none; + width: 2rem; + height: 2rem; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 0.5rem; + cursor: pointer; + background: rgba(255, 255, 255, 0.8); + color: #000; + transition: + transform 120ms ease, + background-color 120ms ease, + filter 120ms ease; +} + +.icon-btn:hover { + transform: translateY(-1px); + filter: brightness(1.05); +} + +.icon-btn.edit { + background: rgba(255, 255, 255, 0.85); +} + +.icon-btn.delete { + background: #ff0048; + color: #fff; +} + +.icon-btn.delete:hover { + background: #e03164; +} + +.status { + padding: 0.75rem 1rem; + border-radius: 0.75rem; + background: rgba(255, 255, 255, 0.55); +} + +.status.error { + color: #8b0000; + background: rgba(255, 230, 230, 0.8); +} + +.empty { + padding: 1rem; + color: #333; +} + +@media (max-width: 600px) { + .glass-card { + margin: 1rem auto; + padding: 1rem; + } + .list-table th, + .list-table td { + padding: 0.5rem; + } +} + +/* Floating Action Button */ +.fab { + position: fixed; + right: 1.25rem; + bottom: 1.25rem; + width: clamp(3rem, 4vw + 2rem, 4rem); + height: clamp(3rem, 4vw + 2rem, 4rem); + border-radius: 9999px; + border: none; + display: inline-flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #ff0048, #e03164); + color: #fff; + font-size: clamp(1.5rem, 2vw + 1rem, 2rem); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.25); + cursor: pointer; + transition: + transform 0.15s ease, + box-shadow 0.15s ease, + filter 0.15s ease; +} + +.fab:hover { + transform: translateY(-2px); + box-shadow: 0 14px 28px rgba(0, 0, 0, 0.3); + filter: brightness(1.05); +} + +.fab:active { + transform: translateY(0); +} diff --git a/src/app/components/list/list.component.html b/src/app/components/list/list.component.html new file mode 100644 index 0000000..5b564d6 --- /dev/null +++ b/src/app/components/list/list.component.html @@ -0,0 +1,66 @@ +
+
+
+

Kantinenliste

+
+ +
Lade Daten…
+
+ {{ errorMessage }} +
+ +
+ + + + + + + + + + + + + + + + + +
DatumBetragBelegAktionen
{{ k.szByeDate }}{{ k.mnBetrag | number: "1.2-2" }} €{{ k.szBelegName }} +
+ + +
+
+ +
Keine Einträge vorhanden.
+
+
+
+ + +
diff --git a/src/app/components/list/list.component.ts b/src/app/components/list/list.component.ts new file mode 100644 index 0000000..5e4adf6 --- /dev/null +++ b/src/app/components/list/list.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AppwriteService } from '../../services/appwrite.service'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { Kantin } from '../../models/kantin.model'; +import { firstValueFrom } from 'rxjs'; +import { Query } from 'appwrite'; + +@Component({ + selector: 'app-list', + imports: [CommonModule, FormsModule], + templateUrl: './list.component.html', + styleUrl: './list.component.css', +}) +export class ListComponent implements OnInit { + constructor( + private router: Router, + private appwriteService: AppwriteService, + ) {} + + public kantinList: Kantin[] = []; + public loading = false; + public errorMessage: string | null = null; + + ngOnInit(): void { + // Async Logik in eigene Methode ausgelagert + void this.loadRecords(); + } + + private async loadRecords(): Promise { + this.loading = true; + this.errorMessage = null; + try { + // getRecords() liefert ein Observable -> mit firstValueFrom einmalig auflösen + // Serverseitiges Sortieren: neueste Einträge zuerst nach ByeDate + const documents = await firstValueFrom( + this.appwriteService.getRecords([Query.orderDesc('ByeDate')]), + ); + this.kantinList = (documents ?? []).map( + (doc: any) => + new Kantin( + doc?.$id ?? '', + doc?.ByeDate ?? '', + Number(doc?.Betrag ?? 0), + doc?.BelegName ?? '', + ), + ); + } catch (error: any) { + console.error('Fehler beim Laden der Kantin-Einträge:', error); + this.errorMessage = error?.message ?? 'Unbekannter Fehler beim Laden.'; + } finally { + this.loading = false; + } + } + + // trackBy für *ngFor Performance + trackById(index: number, item: Kantin): string { + return item.szDocumentId ?? index.toString(); + } + + // Navigation zu einer (noch zu erstellenden) Create-Ansicht + goToCreate(): void { + this.router.navigate(['/create']); + } + + onEdit(item: Kantin): void { + // TODO: Edit-Ansicht bauen. Bis dahin könnte Create mit Prefill genutzt werden. + this.router.navigate(['/create'], { + queryParams: { + id: item.szDocumentId, + date: item.szByeDate, + amount: item.mnBetrag, + name: item.szBelegName, + }, + }); + } + + async onDelete(item: Kantin): Promise { + const confirmed = window.confirm('Diesen Eintrag wirklich löschen?'); + if (!confirmed) return; + + try { + await this.appwriteService.deleteDocument(item.szDocumentId); + await this.loadRecords(); + } catch (e) { + console.error('Löschen fehlgeschlagen', e); + this.errorMessage = 'Löschen fehlgeschlagen.'; + } + } +} diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index 2b62765..b20a3e2 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -52,7 +52,7 @@ detailHeight: number = 0; this.sessionId = response.$id; this.email = ''; this.password = ''; - //this.router.navigate(['/input']); + this.router.navigate(['/list']); }).catch((error) => { console.error('Fehler beim Anmelden: ' + error.message); alert('Fehler beim Anmelden: ' + error.message); diff --git a/src/app/models/kantin.model.ts b/src/app/models/kantin.model.ts new file mode 100644 index 0000000..9d6441e --- /dev/null +++ b/src/app/models/kantin.model.ts @@ -0,0 +1,19 @@ +export class Kantin { + public szDocumentId: string; + public szByeDate: string; + public mnBetrag: number; + public szBelegName: string; + + constructor( + documentId: string, + byeDate: string, + betrag: number, + belegName: string + ) { + this.szDocumentId = documentId; + this.szByeDate = byeDate; + this.mnBetrag = betrag; + this.szBelegName = belegName; + } + +}