komponenten
This commit is contained in:
@@ -12,9 +12,15 @@ export const routes: Routes = [
|
|||||||
redirectTo: 'auth',
|
redirectTo: 'auth',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'auth',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./features/components/auth/auth.routes').then(
|
||||||
|
(r) => r.AUTH_ROUTES
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'shop',
|
||||||
component: DefaultLayoutComponent,
|
component: DefaultLayoutComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [authGuard],
|
||||||
data: { requiredRole: 'Admin' },
|
data: { requiredRole: 'Admin' },
|
||||||
@@ -26,19 +32,8 @@ export const routes: Routes = [
|
|||||||
(r) => r.DASHBOARD_ROUTES
|
(r) => r.DASHBOARD_ROUTES
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'auth',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./features/components/auth/auth.routes').then(
|
|
||||||
(r) => r.AUTH_ROUTES
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'categories',
|
path: 'categories',
|
||||||
canActivate: [authGuard],
|
|
||||||
data: { requiredRole: 'Admin' },
|
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./features/components/categories/categories.routes').then(
|
import('./features/components/categories/categories.routes').then(
|
||||||
(r) => r.CATEGORIES_ROUTES
|
(r) => r.CATEGORIES_ROUTES
|
||||||
@@ -46,8 +41,6 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'discounts',
|
path: 'discounts',
|
||||||
canActivate: [authGuard],
|
|
||||||
data: { requiredRole: 'Admin' },
|
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./features/components/discounts/discounts.routes').then(
|
import('./features/components/discounts/discounts.routes').then(
|
||||||
(r) => r.DISCOUNTS_ROUTES
|
(r) => r.DISCOUNTS_ROUTES
|
||||||
@@ -55,8 +48,6 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'orders',
|
path: 'orders',
|
||||||
canActivate: [authGuard],
|
|
||||||
data: { requiredRole: 'Admin' },
|
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./features/components/orders/orders.routes').then(
|
import('./features/components/orders/orders.routes').then(
|
||||||
(r) => r.ORDERS_ROUTES
|
(r) => r.ORDERS_ROUTES
|
||||||
@@ -64,8 +55,6 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'payment-methods',
|
path: 'payment-methods',
|
||||||
canActivate: [authGuard],
|
|
||||||
data: { requiredRole: 'Admin' },
|
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import(
|
import(
|
||||||
'./features/components/payment-methods/payment-methods.routes'
|
'./features/components/payment-methods/payment-methods.routes'
|
||||||
@@ -73,13 +62,34 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'products',
|
path: 'products',
|
||||||
canActivate: [authGuard],
|
|
||||||
data: { requiredRole: 'Admin' },
|
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./features/components/products/products.routes').then(
|
import('./features/components/products/products.routes').then(
|
||||||
(r) => r.PRODUCTS_ROUTES
|
(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: 'access-denied',
|
path: 'access-denied',
|
||||||
component: AccessDeniedComponent,
|
component: AccessDeniedComponent,
|
||||||
@@ -90,7 +100,6 @@ export const routes: Routes = [
|
|||||||
component: NotFoundComponent,
|
component: NotFoundComponent,
|
||||||
title: '404 - Seite nicht gefunden',
|
title: '404 - Seite nicht gefunden',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
redirectTo: '404',
|
redirectTo: '404',
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export class LoginComponent {
|
|||||||
if (response && response.isAuthSuccessful) {
|
if (response && response.isAuthSuccessful) {
|
||||||
this.logger.info('Admin login successful', { email: credentials.email });
|
this.logger.info('Admin login successful', { email: credentials.email });
|
||||||
// Erfolgreich eingeloggt -> Weiterleiten zum Admin-Dashboard
|
// 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 {
|
} else {
|
||||||
// Login fehlgeschlagen (falsches Passwort etc.), vom Backend kontrolliert
|
// Login fehlgeschlagen (falsches Passwort etc.), vom Backend kontrolliert
|
||||||
this.errorMessage = 'E-Mail oder Passwort ist ungültig.';
|
this.errorMessage = 'E-Mail oder Passwort ist ungültig.';
|
||||||
|
|||||||
@@ -1,40 +1,120 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1>Produkte verwalten</h1>
|
<h1>Produkte verwalten</h1>
|
||||||
|
|
||||||
|
<!-- Formular -->
|
||||||
<form [formGroup]="productForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="productForm" (ngSubmit)="onSubmit()">
|
||||||
<h3>{{ selectedProductId ? 'Produkt bearbeiten' : 'Neues Produkt' }}</h3>
|
<h3>{{ selectedProductId ? 'Produkt bearbeiten' : 'Neues Produkt erstellen' }}</h3>
|
||||||
|
|
||||||
<!-- Basis-Informationen -->
|
<!-- Basis-Informationen -->
|
||||||
<div><label>Name:</label><input type="text" formControlName="name"></div>
|
<h4>Basis-Informationen</h4>
|
||||||
<div><label>Slug (automatisch generiert):</label><input type="text" formControlName="slug"></div>
|
|
||||||
|
|
||||||
<!-- +++ SKU-FELD MIT GENERIEREN-BUTTON +++ -->
|
|
||||||
<div>
|
<div>
|
||||||
<label>SKU (Artikelnummer):</label>
|
<label for="name">Name:</label>
|
||||||
|
<input id="name" type="text" formControlName="name">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="slug">Slug (automatisch generiert):</label>
|
||||||
|
<input id="slug" type="text" formControlName="slug">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="sku">SKU (Artikelnummer):</label>
|
||||||
<div style="display: flex; align-items: center; gap: 10px;">
|
<div style="display: flex; align-items: center; gap: 10px;">
|
||||||
<input type="text" formControlName="sku" style="flex-grow: 1;">
|
<input id="sku" type="text" formControlName="sku" style="flex-grow: 1;">
|
||||||
<button type="button" (click)="generateSku()">Generieren</button>
|
<button type="button" (click)="generateSku()">Generieren</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- +++ ENDE NEU +++ -->
|
<div>
|
||||||
|
<label for="description">Beschreibung:</label>
|
||||||
|
<textarea id="description" formControlName="description" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div><label>Beschreibung:</label><textarea formControlName="description" rows="5"></textarea></div>
|
<hr>
|
||||||
|
|
||||||
|
<!-- Preis & Lager -->
|
||||||
|
<h4>Preis & Lager</h4>
|
||||||
|
<div>
|
||||||
|
<label for="price">Preis (€):</label>
|
||||||
|
<input id="price" type="number" formControlName="price">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="oldPrice">Alter Preis (€) (optional):</label>
|
||||||
|
<input id="oldPrice" type="number" formControlName="oldPrice">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="purchasePrice">Einkaufspreis (€) (optional):</label>
|
||||||
|
<input id="purchasePrice" type="number" formControlName="purchasePrice">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="stock">Lagerbestand:</label>
|
||||||
|
<input id="stock" type="number" formControlName="stockQuantity">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="weight">Gewicht (in kg) (optional):</label>
|
||||||
|
<input id="weight" type="number" formControlName="weight">
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ... (alle anderen Felder bleiben unverändert) ... -->
|
|
||||||
<hr>
|
<hr>
|
||||||
<div><label>Preis (€):</label><input type="number" formControlName="price"></div>
|
|
||||||
<div><label>Alter Preis (€) (optional):</label><input type="number" formControlName="oldPrice"></div>
|
<!-- Zuweisungen -->
|
||||||
<div><label>Einkaufspreis (€) (optional):</label><input type="number" formControlName="purchasePrice"></div>
|
<h4>Zuweisungen</h4>
|
||||||
<div><label>Lagerbestand:</label><input type="number" formControlName="stockQuantity"></div>
|
<div>
|
||||||
<div><label>Gewicht (in kg) (optional):</label><input type="number" formControlName="weight"></div>
|
<label for="supplier">Lieferant:</label>
|
||||||
|
<select id="supplier" formControlName="supplierId">
|
||||||
|
<option [ngValue]="null">-- Kein Lieferant --</option>
|
||||||
|
<option *ngFor="let supplier of allSuppliers$ | async" [value]="supplier.id">{{ supplier.name }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Kategorien:</label>
|
||||||
|
<div class="category-checkbox-group" style="height: 100px; overflow-y: auto; border: 1px solid #ccc; padding: 5px; margin-top: 5px;">
|
||||||
|
<div *ngFor="let category of allCategories$ | async">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[value]="category.id"
|
||||||
|
[checked]="isCategorySelected(category.id)"
|
||||||
|
(change)="onCategoryChange($event)">
|
||||||
|
{{ category.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div><label>Lieferant:</label><select formControlName="supplierId"><option [ngValue]="null">-- Kein Lieferant --</option><option *ngFor="let supplier of allSuppliers$ | async" [value]="supplier.id">{{ supplier.name }}</option></select></div>
|
|
||||||
<div><label>Kategorien:</label><div class="category-checkbox-group" style="height: 100px; overflow-y: auto; border: 1px solid #ccc; padding: 5px;"><div *ngFor="let category of allCategories$ | async"><label><input type="checkbox" [value]="category.id" [checked]="isCategorySelected(category.id)" (change)="onCategoryChange($event)">{{ category.name }}</label></div></div></div>
|
<!-- Bilder -->
|
||||||
|
<h4>Produktbilder</h4>
|
||||||
|
<div *ngIf="selectedProductId && existingImages.length > 0">
|
||||||
|
<p>Bestehende Bilder:</p>
|
||||||
|
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 15px;">
|
||||||
|
<div *ngFor="let img of existingImages" style="position: relative;">
|
||||||
|
<img [src]="img.url" [alt]="productForm.get('name')?.value"
|
||||||
|
style="width: 100px; height: 100px; object-fit: cover; border: 2px solid"
|
||||||
|
[style.borderColor]="img.isMainImage ? 'green' : 'gray'">
|
||||||
|
<button (click)="deleteExistingImage(img.id, $event)"
|
||||||
|
style="position: absolute; top: -5px; right: -5px; background: red; color: white; border-radius: 50%; width: 20px; height: 20px; border: none; cursor: pointer; line-height: 20px; text-align: center; padding: 0;">X</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="main-image">Hauptbild (ersetzt bestehendes Hauptbild)</label>
|
||||||
|
<input id="main-image" type="file" (change)="onMainFileChange($event)" accept="image/*">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="additional-images">Zusätzliche Bilder hinzufügen</label>
|
||||||
|
<input id="additional-images" type="file" (change)="onAdditionalFilesChange($event)" accept="image/*" multiple>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
<!-- Status & Sichtbarkeit -->
|
||||||
|
<h4>Status & Sichtbarkeit</h4>
|
||||||
<div><label><input type="checkbox" formControlName="isActive"> Aktiv (im Shop sichtbar)</label></div>
|
<div><label><input type="checkbox" formControlName="isActive"> Aktiv (im Shop sichtbar)</label></div>
|
||||||
<div><label><input type="checkbox" formControlName="isFeatured"> Hervorgehoben (z.B. auf Startseite)</label></div>
|
<div><label><input type="checkbox" formControlName="isFeatured"> Hervorgehoben (z.B. auf Startseite)</label></div>
|
||||||
<div><label>Anzeigereihenfolge (Hervorgehoben):</label><input type="number" formControlName="featuredDisplayOrder"></div>
|
<div><label>Anzeigereihenfolge (Hervorgehoben):</label><input type="number" formControlName="featuredDisplayOrder"></div>
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<button type="submit" [disabled]="productForm.invalid">{{ selectedProductId ? 'Aktualisieren' : 'Erstellen' }}</button>
|
<button type="submit" [disabled]="productForm.invalid">{{ selectedProductId ? 'Aktualisieren' : 'Erstellen' }}</button>
|
||||||
<button type="button" *ngIf="selectedProductId" (click)="clearSelection()">Abbrechen</button>
|
<button type="button" *ngIf="selectedProductId" (click)="clearSelection()">Abbrechen</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,25 +1,36 @@
|
|||||||
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||||
import { CommonModule, CurrencyPipe } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormBuilder, FormGroup, FormArray, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
import {
|
||||||
|
FormBuilder,
|
||||||
|
FormGroup,
|
||||||
|
FormArray,
|
||||||
|
FormControl,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
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 { ProductService } from '../../../services/product.service';
|
||||||
import { CategoryService } from '../../../services/category.service';
|
import { CategoryService } from '../../../services/category.service';
|
||||||
import { SupplierService } from '../../../services/supplier.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({
|
@Component({
|
||||||
selector: 'app-product-list',
|
selector: 'app-product-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ReactiveFormsModule, CurrencyPipe],
|
imports: [CommonModule, ReactiveFormsModule],
|
||||||
templateUrl: './product-list.component.html',
|
templateUrl: './product-list.component.html',
|
||||||
})
|
})
|
||||||
export class ProductListComponent implements OnInit, OnDestroy {
|
export class ProductListComponent implements OnInit, OnDestroy {
|
||||||
// ... (alle Properties und der Konstruktor bleiben gleich)
|
|
||||||
private productService = inject(ProductService);
|
private productService = inject(ProductService);
|
||||||
private categoryService = inject(CategoryService);
|
private categoryService = inject(CategoryService);
|
||||||
private supplierService = inject(SupplierService);
|
private supplierService = inject(SupplierService);
|
||||||
@@ -33,6 +44,11 @@ export class ProductListComponent implements OnInit, OnDestroy {
|
|||||||
selectedProductId: string | null = null;
|
selectedProductId: string | null = null;
|
||||||
private nameChangeSubscription?: Subscription;
|
private nameChangeSubscription?: Subscription;
|
||||||
|
|
||||||
|
// Eigenschaften für das Bild-Management
|
||||||
|
existingImages: ProductImage[] = [];
|
||||||
|
mainImageFile: File | null = null;
|
||||||
|
additionalImageFiles: File[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.productForm = this.fb.group({
|
this.productForm = this.fb.group({
|
||||||
name: ['', Validators.required],
|
name: ['', Validators.required],
|
||||||
@@ -48,37 +64,205 @@ export class ProductListComponent implements OnInit, OnDestroy {
|
|||||||
isFeatured: [false],
|
isFeatured: [false],
|
||||||
featuredDisplayOrder: [0],
|
featuredDisplayOrder: [0],
|
||||||
supplierId: [null],
|
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 {
|
generateSku(): void {
|
||||||
const name = this.productForm.get('name')?.value || 'PROD';
|
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, '');
|
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 randomPart = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||||
const sku = `${prefix}-${randomPart}`;
|
const sku = `${prefix}-${randomPart}`;
|
||||||
this.productForm.get('sku')?.setValue(sku);
|
this.productForm.get('sku')?.setValue(sku);
|
||||||
}
|
}
|
||||||
// --- ENDE NEUE METHODE ---
|
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.productForm.invalid) return;
|
||||||
|
|
||||||
get categorieIds(): FormArray {
|
const formData = new FormData();
|
||||||
return this.productForm.get('categorieIds') as FormArray;
|
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);
|
||||||
}
|
}
|
||||||
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, '-'); }
|
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,60 @@
|
|||||||
|
<!-- /src/app/features/admin/components/reviews/review-list/review-list.component.html -->
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>Bewertungen verwalten</h1>
|
<h1>Bewertungen verwalten</h1>
|
||||||
<table>
|
|
||||||
|
<table class="review-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Kunde</th>
|
<th>Datum</th>
|
||||||
<th>Produkt</th>
|
|
||||||
<th>Bewertung</th>
|
|
||||||
<th>Titel</th>
|
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
|
<th>Produkt</th>
|
||||||
|
<th>Kunde</th>
|
||||||
|
<th>Bewertung (1-5)</th>
|
||||||
|
<th>Titel / Kommentar</th>
|
||||||
<th>Aktionen</th>
|
<th>Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let review of reviews$ | async">
|
<!-- Ladeanzeige oder Leere-Liste-Nachricht -->
|
||||||
<td>{{ review.customerName }}</td>
|
<ng-container *ngIf="reviews$ | async as reviews; else loading">
|
||||||
<td>{{ review.productName }}</td>
|
<tr *ngIf="reviews.length === 0">
|
||||||
<td>{{ review.rating }}/5</td>
|
<td colspan="7">Keine Bewertungen gefunden.</td>
|
||||||
<td>{{ review.title }}</td>
|
</tr>
|
||||||
<td>{{ review.isApproved ? "Freigegeben" : "Ausstehend" }}</td>
|
|
||||||
|
<!-- Iteration über die Bewertungen -->
|
||||||
|
<tr *ngFor="let review of reviews">
|
||||||
|
<td>{{ review.reviewDate | date:'dd.MM.yyyy HH:mm' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button *ngIf="!review.isApproved" (click)="onApprove(review.id)">
|
<span [class.status-approved]="review.isApproved" [class.status-pending]="!review.isApproved">
|
||||||
|
{{ review.isApproved ? 'Freigegeben' : 'Ausstehend' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ review.productName || 'N/A' }}</td>
|
||||||
|
<td>{{ review.customerName }}</td>
|
||||||
|
<td>{{ review.rating }}</td>
|
||||||
|
<td class="comment-cell">
|
||||||
|
<strong>{{ review.title }}</strong>
|
||||||
|
<p>{{ review.comment }}</p>
|
||||||
|
</td>
|
||||||
|
<td class="actions-cell">
|
||||||
|
<button *ngIf="!review.isApproved" (click)="onApprove(review.id)" class="approve-btn">
|
||||||
Freigeben
|
Freigeben
|
||||||
</button>
|
</button>
|
||||||
<button (click)="onDelete(review.id)">Löschen</button>
|
<button (click)="onDelete(review.id)" class="delete-btn">
|
||||||
|
Löschen
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</ng-container>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<!-- Template für den Ladezustand -->
|
||||||
|
<ng-template #loading>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7">Lade Bewertungen...</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
|
// /src/app/features/admin/components/reviews/review-list/review-list.component.ts
|
||||||
|
|
||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule, DatePipe } from '@angular/common';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
// Models & Services
|
||||||
import { Review } from '../../../../core/models/review.model';
|
import { Review } from '../../../../core/models/review.model';
|
||||||
import { ReviewService } from '../../../services/review.service';
|
import { ReviewService } from '../../../services/review.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-review-list',
|
selector: 'app-review-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule],
|
imports: [CommonModule, DatePipe], // DatePipe für die Formatierung des Datums hinzufügen
|
||||||
templateUrl: './review-list.component.html',
|
templateUrl: './review-list.component.html',
|
||||||
|
styleUrl: './review-list.component.css' // Optional: CSS für besseres Tabellen-Layout
|
||||||
})
|
})
|
||||||
export class ReviewListComponent implements OnInit {
|
export class ReviewListComponent implements OnInit {
|
||||||
private reviewService = inject(ReviewService);
|
private reviewService = inject(ReviewService);
|
||||||
@@ -23,12 +28,26 @@ export class ReviewListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onApprove(id: string): void {
|
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 {
|
onDelete(id: string): void {
|
||||||
if (confirm('Bewertung wirklich löschen?')) {
|
if (confirm('Möchten Sie diese Bewertung wirklich endgültig löschen?')) {
|
||||||
this.reviewService.delete(id).subscribe(() => this.loadReviews());
|
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)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
10
src/app/features/components/reviews/reviews.routes.ts
Normal file
10
src/app/features/components/reviews/reviews.routes.ts
Normal file
@@ -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: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
10
src/app/features/components/settings/settings.routes.ts
Normal file
10
src/app/features/components/settings/settings.routes.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
|
|
||||||
|
export const SETTINGS_ROUTES: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: SettingsComponent,
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,19 +1,68 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1>Versandmethoden verwalten</h1>
|
<h1>Versandmethoden verwalten</h1>
|
||||||
|
|
||||||
<form [formGroup]="methodForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="methodForm" (ngSubmit)="onSubmit()">
|
||||||
<h3>{{ selectedMethodId ? 'Methode bearbeiten' : 'Neue Methode' }}</h3>
|
<h3>{{ selectedMethodId ? "Methode bearbeiten" : "Neue Methode" }}</h3>
|
||||||
<input type="text" formControlName="name" placeholder="Name">
|
|
||||||
<textarea formControlName="description" placeholder="Beschreibung"></textarea>
|
<div>
|
||||||
<input type="number" formControlName="cost" placeholder="Kosten">
|
<label>Name:</label>
|
||||||
<label><input type="checkbox" formControlName="isActive"> Aktiv</label>
|
<input type="text" formControlName="name" placeholder="Name" />
|
||||||
<button type="submit" [disabled]="methodForm.invalid">{{ selectedMethodId ? 'Aktualisieren' : 'Erstellen' }}</button>
|
</div>
|
||||||
<button type="button" *ngIf="selectedMethodId" (click)="clearSelection()">Abbrechen</button>
|
|
||||||
|
<div>
|
||||||
|
<label>Beschreibung:</label>
|
||||||
|
<textarea
|
||||||
|
formControlName="description"
|
||||||
|
placeholder="Beschreibung"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Kosten (€):</label>
|
||||||
|
<input type="number" formControlName="cost" placeholder="Kosten" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- +++ NEUE FELDER HINZUGEFÜGT +++ -->
|
||||||
|
<div>
|
||||||
|
<label>Minimale Liefertage:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
formControlName="minDeliveryDays"
|
||||||
|
placeholder="z.B. 1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Maximale Liefertage:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
formControlName="maxDeliveryDays"
|
||||||
|
placeholder="z.B. 3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- +++ ENDE NEU +++ -->
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label><input type="checkbox" formControlName="isActive" /> Aktiv</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<button type="submit" [disabled]="methodForm.invalid">
|
||||||
|
{{ selectedMethodId ? "Aktualisieren" : "Erstellen" }}
|
||||||
|
</button>
|
||||||
|
<button type="button" *ngIf="selectedMethodId" (click)="clearSelection()">
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<hr>
|
<hr />
|
||||||
<h2>Bestehende Methoden</h2>
|
<h2>Bestehende Methoden</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let method of methods$ | async">
|
<li *ngFor="let method of methods$ | async">
|
||||||
{{ method.name }} ({{ method.cost | currency:'EUR' }}) - Aktiv: {{ method.isActive }}
|
{{ method.name }}
|
||||||
|
({{ method.cost | currency : "EUR" }}) - Lieferzeit:
|
||||||
|
{{ method.minDeliveryDays }}-{{ method.maxDeliveryDays }} Tage - Aktiv:
|
||||||
|
{{ method.isActive }}
|
||||||
<button (click)="selectMethod(method)">Bearbeiten</button>
|
<button (click)="selectMethod(method)">Bearbeiten</button>
|
||||||
<button (click)="onDelete(method.id)">Löschen</button>
|
<button (click)="onDelete(method.id)">Löschen</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, inject } from '@angular/core';
|
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 { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { ShippingMethod } from '../../../../core/models/shipping.model';
|
import { ShippingMethod } from '../../../../core/models/shipping.model';
|
||||||
@@ -8,7 +8,7 @@ import { ShippingMethodService } from '../../../services/shipping-method.service
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-shipping-method-list',
|
selector: 'app-shipping-method-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ReactiveFormsModule],
|
imports: [CommonModule, ReactiveFormsModule, CurrencyPipe],
|
||||||
templateUrl: './shipping-method-list.component.html',
|
templateUrl: './shipping-method-list.component.html',
|
||||||
})
|
})
|
||||||
export class ShippingMethodListComponent implements OnInit {
|
export class ShippingMethodListComponent implements OnInit {
|
||||||
@@ -24,7 +24,9 @@ export class ShippingMethodListComponent implements OnInit {
|
|||||||
name: ['', Validators.required],
|
name: ['', Validators.required],
|
||||||
description: [''],
|
description: [''],
|
||||||
cost: [0, [Validators.required, Validators.min(0)]],
|
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 {
|
clearSelection(): void {
|
||||||
this.selectedMethodId = null;
|
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 {
|
onSubmit(): void {
|
||||||
if (this.methodForm.invalid) return;
|
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) {
|
if (this.selectedMethodId) {
|
||||||
this.shippingMethodService.update(this.selectedMethodId, dataToSend).subscribe(() => this.reset());
|
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());
|
this.shippingMethodService.create(dataToSend).subscribe(() => this.reset());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// --- ENDE KORREKTUR ---
|
||||||
|
|
||||||
onDelete(id: string): void {
|
onDelete(id: string): void {
|
||||||
if (confirm('Versandmethode wirklich löschen?')) {
|
if (confirm('Versandmethode wirklich löschen?')) {
|
||||||
|
|||||||
@@ -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: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user