komponenten
This commit is contained in:
@@ -62,6 +62,24 @@ export const routes: Routes = [
|
||||
(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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
|
||||
// Importiere dein spezielles Layout für Auth-Seiten und alle Komponenten
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
|
||||
export const DASHBOARD_ROUTES: Routes = [
|
||||
{ path: '', redirectTo: 'home', pathMatch: 'full' },
|
||||
@@ -9,4 +9,9 @@ export const DASHBOARD_ROUTES: Routes = [
|
||||
component: DashboardPageComponent,
|
||||
title: 'Dashboard Übersicht',
|
||||
},
|
||||
{
|
||||
path: 'kpi',
|
||||
component: DashboardComponent,
|
||||
title: 'kpi Übersicht',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AnalyticsPeriod } from '../../../../core/enums/shared.enum';
|
||||
imports: [CommonModule],
|
||||
templateUrl: './dashboard.component.html',
|
||||
})
|
||||
export class AdminDashboardComponent implements OnInit {
|
||||
export class DashboardComponent implements OnInit {
|
||||
private analyticsService = inject(AnalyticsService);
|
||||
analytics$!: Observable<Analytics>;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { OrderListComponent } from './order-list/order-list.component';
|
||||
|
||||
export const ORDERS_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
path: '1',
|
||||
component: OrderListComponent,
|
||||
title: '',
|
||||
},
|
||||
|
||||
@@ -2,15 +2,55 @@
|
||||
<h1>Zahlungsmethoden verwalten</h1>
|
||||
<form [formGroup]="methodForm" (ngSubmit)="onSubmit()">
|
||||
<h3>{{ selectedMethodId ? 'Methode bearbeiten' : 'Neue Methode' }}</h3>
|
||||
<input type="text" formControlName="name" placeholder="Name">
|
||||
<textarea formControlName="description" placeholder="Beschreibung"></textarea>
|
||||
<select formControlName="paymentGatewayType">
|
||||
|
||||
<div><label for="name">Name:</label>
|
||||
<input id="name" type="text" formControlName="name" placeholder="Name"></div>
|
||||
|
||||
<div><label for="desc">Beschreibung:</label>
|
||||
<textarea id="desc" formControlName="description" placeholder="Beschreibung"></textarea></div>
|
||||
|
||||
<div><label for="fee">Bearbeitungsgebühr (%):</label>
|
||||
<input id="fee" type="number" formControlName="processingFee" placeholder="z.B. 1.5"></div>
|
||||
|
||||
<div><label for="type">Gateway Typ:</label>
|
||||
<select id="type" formControlName="paymentGatewayType">
|
||||
<option *ngFor="let type of gatewayTypes" [value]="type">{{ type }}</option>
|
||||
</select>
|
||||
<label><input type="checkbox" formControlName="isActive"> Aktiv</label>
|
||||
</select></div>
|
||||
|
||||
<div><label><input type="checkbox" formControlName="isActive"> Aktiv</label></div>
|
||||
|
||||
<!-- +++ DYNAMISCHER KONFIGURATIONS-BLOCK +++ -->
|
||||
<div formGroupName="configuration">
|
||||
<h4>Konfiguration</h4>
|
||||
|
||||
<div *ngIf="methodForm.get('paymentGatewayType')?.value === 'BankTransfer'">
|
||||
<div><label for="iban">IBAN:</label><input id="iban" formControlName="IBAN"></div>
|
||||
<div><label for="bic">BIC:</label><input id="bic" formControlName="BIC"></div>
|
||||
<div><label for="bankName">Bankname:</label><input id="bankName" formControlName="BankName"></div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="methodForm.get('paymentGatewayType')?.value === 'Stripe'">
|
||||
<div><label for="stripePk">Public Key:</label><input id="stripePk" formControlName="PublicKey"></div>
|
||||
<div><label for="stripeSk">Secret Key:</label><input id="stripeSk" formControlName="SecretKey"></div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="methodForm.get('paymentGatewayType')?.value === 'PayPal'">
|
||||
<div><label for="paypalCid">Client ID:</label><input id="paypalCid" formControlName="ClientId"></div>
|
||||
<div><label for="paypalCs">Client Secret:</label><input id="paypalCs" formControlName="ClientSecret"></div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="methodForm.get('paymentGatewayType')?.value === 'Invoice'">
|
||||
<div><label for="invoiceTerms">Zahlungsbedingungen:</label><input id="invoiceTerms" formControlName="PaymentTerms"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- +++ ENDE DYNAMISCH +++ -->
|
||||
|
||||
<br>
|
||||
<button type="submit" [disabled]="methodForm.invalid">{{ selectedMethodId ? 'Aktualisieren' : 'Erstellen' }}</button>
|
||||
<button type="button" *ngIf="selectedMethodId" (click)="clearSelection()">Abbrechen</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
<h2>Bestehende Methoden</h2>
|
||||
<ul>
|
||||
|
||||
@@ -19,33 +19,64 @@ export class PaymentMethodListComponent implements OnInit {
|
||||
methods$!: Observable<AdminPaymentMethod[]>;
|
||||
methodForm: FormGroup;
|
||||
selectedMethodId: string | null = null;
|
||||
gatewayTypes: PaymentGatewayType[] = ['BankTransfer', 'Invoice', 'PayPal', 'Stripe', 'CashOnDelivery'];
|
||||
gatewayTypes: PaymentGatewayType[] = ['BankTransfer', 'PayPal', 'Stripe', 'Invoice', 'CashOnDelivery'];
|
||||
|
||||
constructor() {
|
||||
this.methodForm = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
description: [''],
|
||||
isActive: [true],
|
||||
paymentGatewayType: ['Invoice', Validators.required]
|
||||
paymentGatewayType: ['Invoice', Validators.required],
|
||||
processingFee: [0, Validators.min(0)],
|
||||
// Verschachteltes FormGroup für die dynamische Konfiguration
|
||||
configuration: this.fb.group({})
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void { this.loadMethods(); }
|
||||
loadMethods(): void { this.methods$ = this.paymentMethodService.getAll(); }
|
||||
ngOnInit(): void {
|
||||
this.loadMethods();
|
||||
|
||||
// Beobachte Änderungen am Gateway-Typ, um das Formular dynamisch anzupassen
|
||||
this.methodForm.get('paymentGatewayType')?.valueChanges.subscribe(type => {
|
||||
this.updateConfigurationControls(type);
|
||||
});
|
||||
|
||||
// Initialen Zustand für das Formular setzen
|
||||
this.updateConfigurationControls(this.methodForm.value.paymentGatewayType);
|
||||
}
|
||||
|
||||
// Getter für einfachen Zugriff im Template
|
||||
get configurationGroup(): FormGroup {
|
||||
return this.methodForm.get('configuration') as FormGroup;
|
||||
}
|
||||
|
||||
loadMethods(): void {
|
||||
this.methods$ = this.paymentMethodService.getAll();
|
||||
}
|
||||
|
||||
selectMethod(method: AdminPaymentMethod): void {
|
||||
this.selectedMethodId = method.id;
|
||||
// Zuerst die korrekten Controls für den Typ erstellen
|
||||
this.updateConfigurationControls(method.paymentGatewayType);
|
||||
// Dann die Werte setzen (patchValue füllt auch verschachtelte Gruppen)
|
||||
this.methodForm.patchValue(method);
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
this.selectedMethodId = null;
|
||||
this.methodForm.reset({ isActive: true, paymentGatewayType: 'Invoice' });
|
||||
this.methodForm.reset({
|
||||
name: '',
|
||||
description: '',
|
||||
isActive: true,
|
||||
paymentGatewayType: 'Invoice',
|
||||
processingFee: 0
|
||||
});
|
||||
this.updateConfigurationControls('Invoice');
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.methodForm.invalid) return;
|
||||
const dataToSend = { ...this.methodForm.value, id: this.selectedMethodId || '0' };
|
||||
const dataToSend = { ...this.methodForm.value, id: this.selectedMethodId || undefined };
|
||||
|
||||
if (this.selectedMethodId) {
|
||||
this.paymentMethodService.update(this.selectedMethodId, dataToSend).subscribe(() => this.reset());
|
||||
@@ -64,4 +95,33 @@ export class PaymentMethodListComponent implements OnInit {
|
||||
this.loadMethods();
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
private updateConfigurationControls(type: PaymentGatewayType): void {
|
||||
const configGroup = this.configurationGroup;
|
||||
|
||||
// Alle alten Controls entfernen
|
||||
Object.keys(configGroup.controls).forEach(key => {
|
||||
configGroup.removeControl(key);
|
||||
});
|
||||
|
||||
// Neue Controls basierend auf dem Typ hinzufügen
|
||||
switch (type) {
|
||||
case 'BankTransfer':
|
||||
configGroup.addControl('IBAN', this.fb.control('', Validators.required));
|
||||
configGroup.addControl('BIC', this.fb.control('', Validators.required));
|
||||
configGroup.addControl('BankName', this.fb.control('', Validators.required));
|
||||
break;
|
||||
case 'Stripe':
|
||||
configGroup.addControl('PublicKey', this.fb.control('', Validators.required));
|
||||
configGroup.addControl('SecretKey', this.fb.control('', Validators.required));
|
||||
break;
|
||||
case 'PayPal':
|
||||
configGroup.addControl('ClientId', this.fb.control('', Validators.required));
|
||||
configGroup.addControl('ClientSecret', this.fb.control('', Validators.required));
|
||||
break;
|
||||
case 'Invoice':
|
||||
configGroup.addControl('PaymentTerms', this.fb.control('', Validators.required));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { PaymentMethodListComponent } from './payment-method-list/payment-method-list.component';
|
||||
|
||||
export const PAYMENT_METHODS_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: PaymentMethodListComponent,
|
||||
title: '',
|
||||
},
|
||||
];
|
||||
@@ -1,27 +1,50 @@
|
||||
<div>
|
||||
<h1>Produkte verwalten</h1>
|
||||
|
||||
<!-- Formular -->
|
||||
<form [formGroup]="productForm" (ngSubmit)="onSubmit()">
|
||||
<h3>{{ selectedProductId ? 'Produkt bearbeiten' : 'Neues Produkt' }}</h3>
|
||||
<input type="text" formControlName="name" placeholder="Produktname">
|
||||
<input type="text" formControlName="slug" placeholder="Slug">
|
||||
<input type="text" formControlName="sku" placeholder="SKU">
|
||||
<input type="number" formControlName="price" placeholder="Preis">
|
||||
<input type="number" formControlName="stockQuantity" placeholder="Lagerbestand">
|
||||
<label><input type="checkbox" formControlName="isActive"> Aktiv</label>
|
||||
<label><input type="checkbox" formControlName="isFeatured"> Hervorgehoben</label>
|
||||
|
||||
<!-- Basis-Informationen -->
|
||||
<div><label>Name:</label><input type="text" formControlName="name"></div>
|
||||
<div><label>Slug (automatisch generiert):</label><input type="text" formControlName="slug"></div>
|
||||
|
||||
<!-- +++ SKU-FELD MIT GENERIEREN-BUTTON +++ -->
|
||||
<div>
|
||||
<label>SKU (Artikelnummer):</label>
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<input type="text" formControlName="sku" style="flex-grow: 1;">
|
||||
<button type="button" (click)="generateSku()">Generieren</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- +++ ENDE NEU +++ -->
|
||||
|
||||
<div><label>Beschreibung:</label><textarea formControlName="description" rows="5"></textarea></div>
|
||||
|
||||
<!-- ... (alle anderen Felder bleiben unverändert) ... -->
|
||||
<hr>
|
||||
<div><label>Preis (€):</label><input type="number" formControlName="price"></div>
|
||||
<div><label>Alter Preis (€) (optional):</label><input type="number" formControlName="oldPrice"></div>
|
||||
<div><label>Einkaufspreis (€) (optional):</label><input type="number" formControlName="purchasePrice"></div>
|
||||
<div><label>Lagerbestand:</label><input type="number" formControlName="stockQuantity"></div>
|
||||
<div><label>Gewicht (in kg) (optional):</label><input type="number" formControlName="weight"></div>
|
||||
<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>
|
||||
<hr>
|
||||
<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>Anzeigereihenfolge (Hervorgehoben):</label><input type="number" formControlName="featuredDisplayOrder"></div>
|
||||
<br><br>
|
||||
<button type="submit" [disabled]="productForm.invalid">{{ selectedProductId ? 'Aktualisieren' : 'Erstellen' }}</button>
|
||||
<button type="button" *ngIf="selectedProductId" (click)="clearSelection()">Abbrechen</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Liste -->
|
||||
<h2>Bestehende Produkte</h2>
|
||||
<ul>
|
||||
<li *ngFor="let product of products$ | async">
|
||||
{{ product.name }} (SKU: {{ product.sku }}) - Preis: {{ product.price | number:'1.2-2' }} €
|
||||
{{ product.name }} (SKU: {{ product.sku }}) - Preis: {{ product.price | currency:'EUR' }}
|
||||
<button (click)="selectProduct(product)">Bearbeiten</button>
|
||||
<button (click)="onDelete(product.id)">Löschen</button>
|
||||
</li>
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AdminProduct } from '../../../../core/models/product.model';
|
||||
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 { Observable, Subscription } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
// ... (alle anderen Imports bleiben gleich)
|
||||
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],
|
||||
imports: [CommonModule, ReactiveFormsModule, CurrencyPipe],
|
||||
templateUrl: './product-list.component.html',
|
||||
})
|
||||
export class ProductListComponent implements OnInit {
|
||||
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);
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
products$!: Observable<AdminProduct[]>;
|
||||
allCategories$!: Observable<Category[]>;
|
||||
allSuppliers$!: Observable<Supplier[]>;
|
||||
|
||||
productForm: FormGroup;
|
||||
selectedProductId: string | null = null;
|
||||
|
||||
private nameChangeSubscription?: Subscription;
|
||||
|
||||
constructor() {
|
||||
this.productForm = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
@@ -26,49 +40,45 @@ export class ProductListComponent implements OnInit {
|
||||
sku: ['', Validators.required],
|
||||
description: [''],
|
||||
price: [0, [Validators.required, Validators.min(0)]],
|
||||
oldPrice: [null, [Validators.min(0)]],
|
||||
purchasePrice: [null, [Validators.min(0)]],
|
||||
stockQuantity: [0, [Validators.required, Validators.min(0)]],
|
||||
weight: [null, [Validators.min(0)]],
|
||||
isActive: [true],
|
||||
isFeatured: [false]
|
||||
isFeatured: [false],
|
||||
featuredDisplayOrder: [0],
|
||||
supplierId: [null],
|
||||
categorieIds: this.fb.array([])
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void { this.loadProducts(); }
|
||||
loadProducts(): void { this.products$ = this.productService.getAll(); }
|
||||
|
||||
selectProduct(product: AdminProduct): void {
|
||||
this.selectedProductId = product.id;
|
||||
this.productForm.patchValue(product);
|
||||
// --- NEUE METHODE ZUM GENERIEREN DER SKU ---
|
||||
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 ---
|
||||
|
||||
clearSelection(): void {
|
||||
this.selectedProductId = null;
|
||||
this.productForm.reset({ isActive: true, isFeatured: false });
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.productForm.invalid) return;
|
||||
|
||||
const formData = new FormData();
|
||||
Object.keys(this.productForm.value).forEach(key => {
|
||||
formData.append(key, this.productForm.value[key]);
|
||||
});
|
||||
|
||||
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.loadProducts());
|
||||
}
|
||||
}
|
||||
|
||||
private reset(): void {
|
||||
this.loadProducts();
|
||||
this.clearSelection();
|
||||
get categorieIds(): FormArray {
|
||||
return this.productForm.get('categorieIds') 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))); }
|
||||
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, '-'); }
|
||||
|
||||
}
|
||||
10
src/app/features/components/products/products.routes.ts
Normal file
10
src/app/features/components/products/products.routes.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { ProductListComponent } from './product-list/product-list.component';
|
||||
|
||||
export const PRODUCTS_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ProductListComponent,
|
||||
title: '',
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user