From 0d8d9b5987a0e58271e1dc48016f176923aa604d Mon Sep 17 00:00:00 2001 From: "Tizian.Breuch" Date: Tue, 30 Sep 2025 19:57:05 +0200 Subject: [PATCH] komponenten --- src/app/app.routes.ts | 119 +++++---- .../components/auth/login/login.component.ts | 2 +- .../product-list/product-list.component.html | 116 +++++++-- .../product-list/product-list.component.ts | 242 +++++++++++++++--- .../review-list/review-list.component.html | 68 +++-- .../review-list/review-list.component.ts | 29 ++- .../components/reviews/reviews.routes.ts | 10 + .../components/settings/settings.routes.ts | 10 + .../settings/settings/settings.component.ts | 2 +- .../shipping-method-list.component.html | 69 ++++- .../shipping-method-list.component.ts | 26 +- .../shipping-methods.routes.ts | 10 + 12 files changed, 560 insertions(+), 143 deletions(-) create mode 100644 src/app/features/components/reviews/reviews.routes.ts create mode 100644 src/app/features/components/settings/settings.routes.ts create mode 100644 src/app/features/components/shipping-methods/shipping-methods.routes.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 16c1407..c978050 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -12,9 +12,15 @@ export const routes: Routes = [ redirectTo: 'auth', pathMatch: 'full', }, - { - path: 'dashboard', + path: 'auth', + loadChildren: () => + import('./features/components/auth/auth.routes').then( + (r) => r.AUTH_ROUTES + ), + }, + { + path: 'shop', component: DefaultLayoutComponent, canActivate: [authGuard], data: { requiredRole: 'Admin' }, @@ -26,60 +32,64 @@ export const routes: Routes = [ (r) => r.DASHBOARD_ROUTES ), }, + { + path: 'categories', + loadChildren: () => + import('./features/components/categories/categories.routes').then( + (r) => r.CATEGORIES_ROUTES + ), + }, + { + path: 'discounts', + loadChildren: () => + import('./features/components/discounts/discounts.routes').then( + (r) => r.DISCOUNTS_ROUTES + ), + }, + { + path: 'orders', + loadChildren: () => + import('./features/components/orders/orders.routes').then( + (r) => r.ORDERS_ROUTES + ), + }, + { + path: 'payment-methods', + loadChildren: () => + import( + './features/components/payment-methods/payment-methods.routes' + ).then((r) => r.PAYMENT_METHODS_ROUTES), + }, + { + path: 'products', + loadChildren: () => + import('./features/components/products/products.routes').then( + (r) => r.PRODUCTS_ROUTES + ), + }, + { + path: 'reviews', + loadChildren: () => + import('./features/components/reviews/reviews.routes').then( + (r) => r.REVIEWS_ROUTES + ), + }, + { + path: 'settings', + loadChildren: () => + import('./features/components/settings/settings.routes').then( + (r) => r.SETTINGS_ROUTES + ), + }, + { + path: 'shipping-methods', + loadChildren: () => + import( + './features/components/shipping-methods/shipping-methods.routes' + ).then((r) => r.SHIPPING_METHODS_ROUTES), + }, ], }, - { - path: 'auth', - loadChildren: () => - import('./features/components/auth/auth.routes').then( - (r) => r.AUTH_ROUTES - ), - }, - { - path: 'categories', - canActivate: [authGuard], - data: { requiredRole: 'Admin' }, - loadChildren: () => - import('./features/components/categories/categories.routes').then( - (r) => r.CATEGORIES_ROUTES - ), - }, - { - path: 'discounts', - canActivate: [authGuard], - data: { requiredRole: 'Admin' }, - loadChildren: () => - import('./features/components/discounts/discounts.routes').then( - (r) => r.DISCOUNTS_ROUTES - ), - }, - { - path: 'orders', - canActivate: [authGuard], - data: { requiredRole: 'Admin' }, - loadChildren: () => - import('./features/components/orders/orders.routes').then( - (r) => r.ORDERS_ROUTES - ), - }, - { - path: 'payment-methods', - canActivate: [authGuard], - data: { requiredRole: 'Admin' }, - loadChildren: () => - import( - './features/components/payment-methods/payment-methods.routes' - ).then((r) => r.PAYMENT_METHODS_ROUTES), - }, - { - path: 'products', - canActivate: [authGuard], - data: { requiredRole: 'Admin' }, - loadChildren: () => - import('./features/components/products/products.routes').then( - (r) => r.PRODUCTS_ROUTES - ), - }, { path: 'access-denied', component: AccessDeniedComponent, @@ -90,7 +100,6 @@ export const routes: Routes = [ component: NotFoundComponent, title: '404 - Seite nicht gefunden', }, - { path: '**', redirectTo: '404', diff --git a/src/app/features/components/auth/login/login.component.ts b/src/app/features/components/auth/login/login.component.ts index c58d130..db09024 100644 --- a/src/app/features/components/auth/login/login.component.ts +++ b/src/app/features/components/auth/login/login.component.ts @@ -67,7 +67,7 @@ export class LoginComponent { if (response && response.isAuthSuccessful) { this.logger.info('Admin login successful', { email: credentials.email }); // Erfolgreich eingeloggt -> Weiterleiten zum Admin-Dashboard - this.router.navigate(['/dashboard']); // Passe die Route ggf. an + this.router.navigate(['/shop']); // Passe die Route ggf. an } else { // Login fehlgeschlagen (falsches Passwort etc.), vom Backend kontrolliert this.errorMessage = 'E-Mail oder Passwort ist ungültig.'; diff --git a/src/app/features/components/products/product-list/product-list.component.html b/src/app/features/components/products/product-list/product-list.component.html index 1f9bba8..2ebdc52 100644 --- a/src/app/features/components/products/product-list/product-list.component.html +++ b/src/app/features/components/products/product-list/product-list.component.html @@ -1,40 +1,120 @@

Produkte verwalten

+
-

{{ selectedProductId ? 'Produkt bearbeiten' : 'Neues Produkt' }}

+

{{ selectedProductId ? 'Produkt bearbeiten' : 'Neues Produkt erstellen' }}

-
-
- - +

Basis-Informationen

- + + +
+
+ + +
+
+
- +
- +
+ + +
-
+
+ + +

Preis & Lager

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +

Zuweisungen

+
+ + +
+ +
+ +
+
+ +
+
+
-
-
-
-
-
-
-
-
-
+ + +

Produktbilder

+
+

Bestehende Bilder:

+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +

Status & Sichtbarkeit

+

+
diff --git a/src/app/features/components/products/product-list/product-list.component.ts b/src/app/features/components/products/product-list/product-list.component.ts index b653525..5095adb 100644 --- a/src/app/features/components/products/product-list/product-list.component.ts +++ b/src/app/features/components/products/product-list/product-list.component.ts @@ -1,25 +1,36 @@ import { Component, OnInit, OnDestroy, inject } from '@angular/core'; -import { CommonModule, CurrencyPipe } from '@angular/common'; -import { FormBuilder, FormGroup, FormArray, FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { + FormBuilder, + FormGroup, + FormArray, + FormControl, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; import { Observable, Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; -// ... (alle anderen Imports bleiben gleich) +// Models +import { + AdminProduct, + ProductImage, +} from '../../../../core/models/product.model'; +import { Category } from '../../../../core/models/category.model'; +import { Supplier } from '../../../../core/models/supplier.model'; + +// Services import { ProductService } from '../../../services/product.service'; import { CategoryService } from '../../../services/category.service'; import { SupplierService } from '../../../services/supplier.service'; -import { AdminProduct } from '../../../../core/models/product.model'; -import { Category } from '../../../../core/models/category.model'; -import { Supplier } from '../../../../core/models/supplier.model'; @Component({ selector: 'app-product-list', standalone: true, - imports: [CommonModule, ReactiveFormsModule, CurrencyPipe], + imports: [CommonModule, ReactiveFormsModule], templateUrl: './product-list.component.html', }) export class ProductListComponent implements OnInit, OnDestroy { - // ... (alle Properties und der Konstruktor bleiben gleich) private productService = inject(ProductService); private categoryService = inject(CategoryService); private supplierService = inject(SupplierService); @@ -33,6 +44,11 @@ export class ProductListComponent implements OnInit, OnDestroy { selectedProductId: string | null = null; private nameChangeSubscription?: Subscription; + // Eigenschaften für das Bild-Management + existingImages: ProductImage[] = []; + mainImageFile: File | null = null; + additionalImageFiles: File[] = []; + constructor() { this.productForm = this.fb.group({ name: ['', Validators.required], @@ -48,37 +64,205 @@ export class ProductListComponent implements OnInit, OnDestroy { isFeatured: [false], featuredDisplayOrder: [0], supplierId: [null], - categorieIds: this.fb.array([]) + categorieIds: this.fb.array([]), + imagesToDelete: this.fb.array([]), // FormArray für die IDs der zu löschenden Bilder }); } - // --- NEUE METHODE ZUM GENERIEREN DER SKU --- + // Getter für einfachen Zugriff auf FormArrays + get categorieIds(): FormArray { + return this.productForm.get('categorieIds') as FormArray; + } + + get imagesToDelete(): FormArray { + return this.productForm.get('imagesToDelete') as FormArray; + } + + ngOnInit(): void { + this.loadInitialData(); + this.subscribeToNameChanges(); + } + + ngOnDestroy(): void { + this.nameChangeSubscription?.unsubscribe(); + } + + loadInitialData(): void { + this.products$ = this.productService.getAll(); + this.allCategories$ = this.categoryService.getAll(); + this.allSuppliers$ = this.supplierService.getAll(); + } + + selectProduct(product: AdminProduct): void { + this.selectedProductId = product.id; + this.productForm.patchValue(product); + + this.categorieIds.clear(); + product.categorieIds?.forEach((id) => + this.categorieIds.push(this.fb.control(id)) + ); + + this.existingImages = product.images || []; + } + + clearSelection(): void { + this.selectedProductId = null; + this.productForm.reset({ + name: '', + slug: '', + sku: '', + description: '', + price: 0, + oldPrice: null, + purchasePrice: null, + stockQuantity: 0, + weight: null, + isActive: true, + isFeatured: false, + featuredDisplayOrder: 0, + supplierId: null, + }); + this.categorieIds.clear(); + this.imagesToDelete.clear(); + this.existingImages = []; + this.mainImageFile = null; + this.additionalImageFiles = []; + } + + onMainFileChange(event: Event): void { + const file = (event.target as HTMLInputElement).files?.[0]; + if (file) this.mainImageFile = file; + } + + onAdditionalFilesChange(event: Event): void { + const files = (event.target as HTMLInputElement).files; + if (files) this.additionalImageFiles = Array.from(files); + } + + deleteExistingImage(imageId: string, event: Event): void { + event.preventDefault(); + this.imagesToDelete.push(this.fb.control(imageId)); + this.existingImages = this.existingImages.filter( + (img) => img.id !== imageId + ); + } + + onCategoryChange(event: Event): void { + const checkbox = event.target as HTMLInputElement; + const categoryId = checkbox.value; + if (checkbox.checked) { + if (!this.categorieIds.value.includes(categoryId)) { + this.categorieIds.push(new FormControl(categoryId)); + } + } else { + const index = this.categorieIds.controls.findIndex( + (x) => x.value === categoryId + ); + if (index !== -1) { + this.categorieIds.removeAt(index); + } + } + } + + isCategorySelected(categoryId: string): boolean { + return this.categorieIds.value.includes(categoryId); + } + generateSku(): void { const name = this.productForm.get('name')?.value || 'PROD'; - // Nimmt die ersten 4 Buchstaben des Namens (oder weniger, falls kürzer) const prefix = name.substring(0, 4).toUpperCase().replace(/\s+/g, ''); - // Hängt einen zufälligen, 6-stelligen alphanumerischen String an const randomPart = Math.random().toString(36).substring(2, 8).toUpperCase(); const sku = `${prefix}-${randomPart}`; this.productForm.get('sku')?.setValue(sku); } - // --- ENDE NEUE METHODE --- + onSubmit(): void { + if (this.productForm.invalid) return; - get categorieIds(): FormArray { - return this.productForm.get('categorieIds') as FormArray; + const formData = new FormData(); + const formValue = this.productForm.value; + + Object.keys(formValue).forEach((key) => { + const value = formValue[key]; + if (key === 'categorieIds' || key === 'imagesToDelete') { + // FormArrays müssen speziell behandelt werden + (value as string[]).forEach((id) => + formData.append(this.capitalizeFirstLetter(key), id) + ); + } else if (value !== null && value !== undefined && value !== '') { + // Leere Strings für optionale number-Felder nicht mitsenden + if ( + ['oldPrice', 'purchasePrice', 'weight'].includes(key) && + value === '' + ) + return; + formData.append(this.capitalizeFirstLetter(key), value); + } + }); + + if (this.mainImageFile) { + formData.append('MainImageFile', this.mainImageFile); + } + this.additionalImageFiles.forEach((file) => { + formData.append('AdditionalImageFiles', file); + }); + + if (this.selectedProductId) { + formData.append('Id', this.selectedProductId); + this.productService + .update(this.selectedProductId, formData) + .subscribe(() => this.reset()); + } else { + this.productService.create(formData).subscribe(() => this.reset()); + } } - ngOnInit(): void { this.loadInitialData(); this.subscribeToNameChanges(); } - ngOnDestroy(): void { this.nameChangeSubscription?.unsubscribe(); } - loadInitialData(): void { this.products$ = this.productService.getAll(); this.allCategories$ = this.categoryService.getAll(); this.allSuppliers$ = this.supplierService.getAll(); } - selectProduct(product: AdminProduct): void { this.selectedProductId = product.id; this.productForm.patchValue(product); this.categorieIds.clear(); product.categorieIds?.forEach(id => this.categorieIds.push(this.fb.control(id))); } - clearSelection(): void { this.selectedProductId = null; this.productForm.reset({ price: 0, stockQuantity: 0, isActive: true, isFeatured: false, featuredDisplayOrder: 0 }); this.categorieIds.clear(); } - onCategoryChange(event: Event): void { const checkbox = event.target as HTMLInputElement; const categoryId = checkbox.value; if (checkbox.checked) { if (!this.categorieIds.value.includes(categoryId)) { this.categorieIds.push(new FormControl(categoryId)); } } else { const index = this.categorieIds.controls.findIndex(x => x.value === categoryId); if (index !== -1) { this.categorieIds.removeAt(index); } } } - isCategorySelected(categoryId: string): boolean { return this.categorieIds.value.includes(categoryId); } - onSubmit(): void { if (this.productForm.invalid) return; const formData = new FormData(); const formValue = this.productForm.value; Object.keys(formValue).forEach(key => { const value = formValue[key]; if (key === 'categorieIds') { (value as string[]).forEach(id => formData.append('CategorieIds', id)); } else if (value !== null && value !== undefined && value !== '') { if (['oldPrice', 'purchasePrice', 'weight'].includes(key) && value === '') return; formData.append(key, value); } }); if (this.selectedProductId) { formData.append('Id', this.selectedProductId); this.productService.update(this.selectedProductId, formData).subscribe(() => this.reset()); } else { this.productService.create(formData).subscribe(() => this.reset()); } } - onDelete(id: string): void { if (confirm('Produkt wirklich löschen?')) { this.productService.delete(id).subscribe(() => this.loadInitialData()); } } - private reset(): void { this.loadInitialData(); this.clearSelection(); } - private subscribeToNameChanges(): void { this.nameChangeSubscription = this.productForm.get('name')?.valueChanges.pipe(debounceTime(300), distinctUntilChanged()).subscribe(name => { if (name && !this.productForm.get('slug')?.dirty) { const slug = this.generateSlug(name); this.productForm.get('slug')?.setValue(slug); } }); } - private generateSlug(name: string): string { return name.toLowerCase().replace(/\s+/g, '-').replace(/[äöüß]/g, (char) => { switch (char) { case 'ä': return 'ae'; case 'ö': return 'oe'; case 'ü': return 'ue'; case 'ß': return 'ss'; default: return ''; } }).replace(/[^a-z0-9-]/g, '').replace(/-+/g, '-'); } - -} \ No newline at end of file + + onDelete(id: string): void { + if (confirm('Produkt wirklich löschen?')) { + this.productService.delete(id).subscribe(() => this.loadInitialData()); + } + } + + private reset(): void { + this.loadInitialData(); + this.clearSelection(); + } + + private subscribeToNameChanges(): void { + this.nameChangeSubscription = this.productForm + .get('name') + ?.valueChanges.pipe(debounceTime(300), distinctUntilChanged()) + .subscribe((name) => { + if (name && !this.productForm.get('slug')?.dirty) { + const slug = this.generateSlug(name); + this.productForm.get('slug')?.setValue(slug); + } + }); + } + + private generateSlug(name: string): string { + return name + .toLowerCase() + .replace(/\s+/g, '-') + .replace(/[äöüß]/g, (char) => { + switch (char) { + case 'ä': + return 'ae'; + case 'ö': + return 'oe'; + case 'ü': + return 'ue'; + case 'ß': + return 'ss'; + default: + return ''; + } + }) + .replace(/[^a-z0-9-]/g, '') + .replace(/-+/g, '-'); + } + + private capitalizeFirstLetter(string: string): string { + return string.charAt(0).toUpperCase() + string.slice(1); + } +} diff --git a/src/app/features/components/reviews/review-list/review-list.component.html b/src/app/features/components/reviews/review-list/review-list.component.html index 8c12577..0724a43 100644 --- a/src/app/features/components/reviews/review-list/review-list.component.html +++ b/src/app/features/components/reviews/review-list/review-list.component.html @@ -1,30 +1,60 @@ + +

Bewertungen verwalten

- + +
- - - - + + + + + - - - - - - - - + + + + + + + + + + + + + + + + +
KundeProduktBewertungTitelDatum StatusProduktKundeBewertung (1-5)Titel / Kommentar Aktionen
{{ review.customerName }}{{ review.productName }}{{ review.rating }}/5{{ review.title }}{{ review.isApproved ? "Freigegeben" : "Ausstehend" }} - - -
Keine Bewertungen gefunden.
{{ review.reviewDate | date:'dd.MM.yyyy HH:mm' }} + + {{ review.isApproved ? 'Freigegeben' : 'Ausstehend' }} + + {{ review.productName || 'N/A' }}{{ review.customerName }}{{ review.rating }} + {{ review.title }} +

{{ review.comment }}

+
+ + +
-
+ + + + + Lade Bewertungen... + + + +
\ No newline at end of file diff --git a/src/app/features/components/reviews/review-list/review-list.component.ts b/src/app/features/components/reviews/review-list/review-list.component.ts index 1707846..a481dc0 100644 --- a/src/app/features/components/reviews/review-list/review-list.component.ts +++ b/src/app/features/components/reviews/review-list/review-list.component.ts @@ -1,14 +1,19 @@ +// /src/app/features/admin/components/reviews/review-list/review-list.component.ts + import { Component, OnInit, inject } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { CommonModule, DatePipe } from '@angular/common'; import { Observable } from 'rxjs'; + +// Models & Services import { Review } from '../../../../core/models/review.model'; import { ReviewService } from '../../../services/review.service'; @Component({ selector: 'app-review-list', standalone: true, - imports: [CommonModule], + imports: [CommonModule, DatePipe], // DatePipe für die Formatierung des Datums hinzufügen templateUrl: './review-list.component.html', + styleUrl: './review-list.component.css' // Optional: CSS für besseres Tabellen-Layout }) export class ReviewListComponent implements OnInit { private reviewService = inject(ReviewService); @@ -23,12 +28,26 @@ export class ReviewListComponent implements OnInit { } onApprove(id: string): void { - this.reviewService.approve(id).subscribe(() => this.loadReviews()); + if (confirm('Möchten Sie diese Bewertung wirklich freigeben?')) { + this.reviewService.approve(id).subscribe({ + next: () => { + console.log(`Review ${id} approved successfully.`); + this.loadReviews(); // Liste neu laden, um den geänderten Status anzuzeigen + }, + error: (err) => console.error(`Failed to approve review ${id}`, err) + }); + } } onDelete(id: string): void { - if (confirm('Bewertung wirklich löschen?')) { - this.reviewService.delete(id).subscribe(() => this.loadReviews()); + if (confirm('Möchten Sie diese Bewertung wirklich endgültig löschen?')) { + this.reviewService.delete(id).subscribe({ + next: () => { + console.log(`Review ${id} deleted successfully.`); + this.loadReviews(); // Liste neu laden + }, + error: (err) => console.error(`Failed to delete review ${id}`, err) + }); } } } \ No newline at end of file diff --git a/src/app/features/components/reviews/reviews.routes.ts b/src/app/features/components/reviews/reviews.routes.ts new file mode 100644 index 0000000..f0efff7 --- /dev/null +++ b/src/app/features/components/reviews/reviews.routes.ts @@ -0,0 +1,10 @@ +import { Routes } from '@angular/router'; +import { ReviewListComponent } from './review-list/review-list.component'; + +export const REVIEWS_ROUTES: Routes = [ + { + path: '', + component: ReviewListComponent, + title: '', + }, +]; diff --git a/src/app/features/components/settings/settings.routes.ts b/src/app/features/components/settings/settings.routes.ts new file mode 100644 index 0000000..cf09401 --- /dev/null +++ b/src/app/features/components/settings/settings.routes.ts @@ -0,0 +1,10 @@ +import { Routes } from '@angular/router'; +import { SettingsComponent } from './settings/settings.component'; + +export const SETTINGS_ROUTES: Routes = [ + { + path: '', + component: SettingsComponent, + title: '', + }, +]; diff --git a/src/app/features/components/settings/settings/settings.component.ts b/src/app/features/components/settings/settings/settings.component.ts index 4fcc5ef..4e6df98 100644 --- a/src/app/features/components/settings/settings/settings.component.ts +++ b/src/app/features/components/settings/settings/settings.component.ts @@ -39,7 +39,7 @@ export class SettingsComponent implements OnInit { key: [setting.key], value: [setting.value], isActive: [setting.isActive] - })); + })); }); }); }); diff --git a/src/app/features/components/shipping-methods/shipping-method-list/shipping-method-list.component.html b/src/app/features/components/shipping-methods/shipping-method-list/shipping-method-list.component.html index e28253b..7a810f8 100644 --- a/src/app/features/components/shipping-methods/shipping-method-list/shipping-method-list.component.html +++ b/src/app/features/components/shipping-methods/shipping-method-list/shipping-method-list.component.html @@ -1,21 +1,70 @@

Versandmethoden verwalten

+
-

{{ selectedMethodId ? 'Methode bearbeiten' : 'Neue Methode' }}

- - - - - - +

{{ selectedMethodId ? "Methode bearbeiten" : "Neue Methode" }}

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+ + +
+ +
+ +
+ + +
-
+

Bestehende Methoden

-
\ No newline at end of file + diff --git a/src/app/features/components/shipping-methods/shipping-method-list/shipping-method-list.component.ts b/src/app/features/components/shipping-methods/shipping-method-list/shipping-method-list.component.ts index 6e70faa..4b4d847 100644 --- a/src/app/features/components/shipping-methods/shipping-method-list/shipping-method-list.component.ts +++ b/src/app/features/components/shipping-methods/shipping-method-list/shipping-method-list.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, inject } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { CommonModule, CurrencyPipe } from '@angular/common'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { Observable } from 'rxjs'; import { ShippingMethod } from '../../../../core/models/shipping.model'; @@ -8,7 +8,7 @@ import { ShippingMethodService } from '../../../services/shipping-method.service @Component({ selector: 'app-shipping-method-list', standalone: true, - imports: [CommonModule, ReactiveFormsModule], + imports: [CommonModule, ReactiveFormsModule, CurrencyPipe], templateUrl: './shipping-method-list.component.html', }) export class ShippingMethodListComponent implements OnInit { @@ -24,7 +24,9 @@ export class ShippingMethodListComponent implements OnInit { name: ['', Validators.required], description: [''], cost: [0, [Validators.required, Validators.min(0)]], - isActive: [true] + isActive: [true], + minDeliveryDays: [1, [Validators.required, Validators.min(0)]], + maxDeliveryDays: [3, [Validators.required, Validators.min(0)]] }); } @@ -38,12 +40,25 @@ export class ShippingMethodListComponent implements OnInit { clearSelection(): void { this.selectedMethodId = null; - this.methodForm.reset({ isActive: true }); + this.methodForm.reset({ + name: '', + description: '', + cost: 0, + isActive: true, + minDeliveryDays: 1, + maxDeliveryDays: 3 + }); } + // --- KORREKTUR: onSubmit sendet jetzt direkt das Formularwert-Objekt als JSON --- onSubmit(): void { if (this.methodForm.invalid) return; - const dataToSend = { ...this.methodForm.value, id: this.selectedMethodId || '0' }; + + // Das Formular-Objekt hat bereits die richtige Struktur, die das Backend erwartet. + const dataToSend: ShippingMethod = { + id: this.selectedMethodId || '00000000-0000-0000-0000-000000000000', + ...this.methodForm.value + }; if (this.selectedMethodId) { this.shippingMethodService.update(this.selectedMethodId, dataToSend).subscribe(() => this.reset()); @@ -51,6 +66,7 @@ export class ShippingMethodListComponent implements OnInit { this.shippingMethodService.create(dataToSend).subscribe(() => this.reset()); } } + // --- ENDE KORREKTUR --- onDelete(id: string): void { if (confirm('Versandmethode wirklich löschen?')) { diff --git a/src/app/features/components/shipping-methods/shipping-methods.routes.ts b/src/app/features/components/shipping-methods/shipping-methods.routes.ts new file mode 100644 index 0000000..05d4d19 --- /dev/null +++ b/src/app/features/components/shipping-methods/shipping-methods.routes.ts @@ -0,0 +1,10 @@ +import { Routes } from '@angular/router'; +import { ShippingMethodListComponent } from './shipping-method-list/shipping-method-list.component'; + +export const SHIPPING_METHODS_ROUTES: Routes = [ + { + path: '', + component: ShippingMethodListComponent, + title: '', + }, +];