endpoint anpassen
This commit is contained in:
@@ -31,7 +31,36 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'auth',
|
path: 'auth',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./features/components/auth/auth.routes').then((r) => r.AUTH_ROUTES),
|
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: 'access-denied',
|
path: 'access-denied',
|
||||||
|
|||||||
10
src/app/features/components/categories/categories.routes.ts
Normal file
10
src/app/features/components/categories/categories.routes.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { CategoryListComponent } from './category-list/category-list.component';
|
||||||
|
|
||||||
|
export const CATEGORIES_ROUTES: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CategoryListComponent,
|
||||||
|
title: 'Category Übersicht',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,26 +1,66 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1>Kategorien verwalten</h1>
|
<h1>Kategorien verwalten</h1>
|
||||||
|
|
||||||
<!-- Formular für Erstellen/Bearbeiten -->
|
|
||||||
<form [formGroup]="categoryForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="categoryForm" (ngSubmit)="onSubmit()">
|
||||||
<h3>{{ selectedCategoryId ? 'Kategorie bearbeiten' : 'Neue Kategorie erstellen' }}</h3>
|
<h3>{{ selectedCategoryId ? 'Kategorie bearbeiten' : 'Neue Kategorie erstellen' }}</h3>
|
||||||
<input type="text" formControlName="name" placeholder="Name">
|
|
||||||
<input type="text" formControlName="slug" placeholder="Slug">
|
<div>
|
||||||
<textarea formControlName="description" placeholder="Beschreibung"></textarea>
|
<label for="name">Name</label>
|
||||||
<label>
|
<input id="name" type="text" formControlName="name" placeholder="Name">
|
||||||
<input type="checkbox" formControlName="isActive"> Aktiv
|
</div>
|
||||||
</label>
|
|
||||||
<input type="file" (change)="onFileChange($event)">
|
<div>
|
||||||
|
<label for="slug">Slug</label>
|
||||||
|
<input id="slug" type="text" formControlName="slug" placeholder="Slug">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="parent">Übergeordnete Kategorie</label>
|
||||||
|
<select id="parent" formControlName="parentcategorieId">
|
||||||
|
<option [ngValue]="null">-- Keine (Hauptkategorie) --</option>
|
||||||
|
<option *ngFor="let parent of parentCategories$ | async" [value]="parent.id">
|
||||||
|
{{ parent.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="description">Beschreibung</label>
|
||||||
|
<textarea id="description" formControlName="description" placeholder="Beschreibung"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" formControlName="isActive"> Aktiv
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- +++ NEUER BILD-MANAGEMENT-BLOCK +++ -->
|
||||||
|
<div>
|
||||||
|
<label for="file">Bild</label>
|
||||||
|
<!-- Bild-Vorschau -->
|
||||||
|
<div *ngIf="existingImageUrl">
|
||||||
|
<img [src]="existingImageUrl" alt="Kategorie-Vorschau" style="max-width: 100px; max-height: 100px; display: block; margin-bottom: 10px;">
|
||||||
|
<button type="button" (click)="removeImage()">Bild entfernen</button>
|
||||||
|
</div>
|
||||||
|
<!-- Datei-Input -->
|
||||||
|
<input id="file" type="file" (change)="onFileChange($event)">
|
||||||
|
</div>
|
||||||
|
<!-- +++ ENDE NEU +++ -->
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
<button type="submit" [disabled]="categoryForm.invalid">{{ selectedCategoryId ? 'Aktualisieren' : 'Erstellen' }}</button>
|
<button type="submit" [disabled]="categoryForm.invalid">{{ selectedCategoryId ? 'Aktualisieren' : 'Erstellen' }}</button>
|
||||||
<button type="button" *ngIf="selectedCategoryId" (click)="clearSelection()">Abbrechen</button>
|
<button type="button" *ngIf="selectedCategoryId" (click)="clearSelection()">Abbrechen</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<!-- Liste der Kategorien -->
|
|
||||||
<h2>Bestehende Kategorien</h2>
|
<h2>Bestehende Kategorien</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let category of categories$ | async">
|
<li *ngFor="let category of categories$ | async">
|
||||||
|
<!-- Bild-Vorschau in der Liste -->
|
||||||
|
<img *ngIf="category.imageUrl" [src]="category.imageUrl" alt="{{ category.name }}" style="width: 30px; height: 30px; vertical-align: middle; margin-right: 10px;">
|
||||||
{{ category.name }} (Slug: {{ category.slug }}) - Aktiv: {{ category.isActive }}
|
{{ category.name }} (Slug: {{ category.slug }}) - Aktiv: {{ category.isActive }}
|
||||||
<button (click)="selectCategory(category)">Bearbeiten</button>
|
<button (click)="selectCategory(category)">Bearbeiten</button>
|
||||||
<button (click)="onDelete(category.id)">Löschen</button>
|
<button (click)="onDelete(category.id)">Löschen</button>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } 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, map } from 'rxjs';
|
||||||
import { Category } from '../../../../core/models/category.model';
|
import { Category } from '../../../../core/models/category.model';
|
||||||
import { CategoryService } from '../../../services/category.service';
|
import { CategoryService } from '../../../services/category.service';
|
||||||
|
|
||||||
@@ -16,9 +16,14 @@ export class CategoryListComponent implements OnInit {
|
|||||||
private fb = inject(FormBuilder);
|
private fb = inject(FormBuilder);
|
||||||
|
|
||||||
categories$!: Observable<Category[]>;
|
categories$!: Observable<Category[]>;
|
||||||
|
parentCategories$!: Observable<Category[]>;
|
||||||
categoryForm: FormGroup;
|
categoryForm: FormGroup;
|
||||||
selectedCategoryId: string | null = null;
|
selectedCategoryId: string | null = null;
|
||||||
|
|
||||||
|
// --- NEUE EIGENSCHAFTEN FÜR BILD-MANAGEMENT ---
|
||||||
selectedFile: File | null = null;
|
selectedFile: File | null = null;
|
||||||
|
existingImageUrl: string | null = null;
|
||||||
|
// --- ENDE NEU ---
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.categoryForm = this.fb.group({
|
this.categoryForm = this.fb.group({
|
||||||
@@ -26,6 +31,7 @@ export class CategoryListComponent implements OnInit {
|
|||||||
slug: ['', Validators.required],
|
slug: ['', Validators.required],
|
||||||
description: [''],
|
description: [''],
|
||||||
isActive: [true],
|
isActive: [true],
|
||||||
|
parentcategorieId: [null]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,45 +41,74 @@ export class CategoryListComponent implements OnInit {
|
|||||||
|
|
||||||
loadCategories(): void {
|
loadCategories(): void {
|
||||||
this.categories$ = this.categoryService.getAll();
|
this.categories$ = this.categoryService.getAll();
|
||||||
|
this.updateParentCategoryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileChange(event: Event): void {
|
onFileChange(event: Event): void {
|
||||||
const element = event.currentTarget as HTMLInputElement;
|
const element = event.currentTarget as HTMLInputElement;
|
||||||
let fileList: FileList | null = element.files;
|
const fileList: FileList | null = element.files;
|
||||||
if (fileList) {
|
if (fileList && fileList.length > 0) {
|
||||||
this.selectedFile = fileList[0];
|
this.selectedFile = fileList[0];
|
||||||
|
// Zeige eine Vorschau des neu ausgewählten Bildes an
|
||||||
|
this.existingImageUrl = URL.createObjectURL(this.selectedFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectCategory(category: Category): void {
|
selectCategory(category: Category): void {
|
||||||
this.selectedCategoryId = category.id;
|
this.selectedCategoryId = category.id;
|
||||||
this.categoryForm.patchValue(category);
|
this.categoryForm.patchValue(category);
|
||||||
|
|
||||||
|
// --- NEU: Bild-URL speichern ---
|
||||||
|
this.existingImageUrl = category.imageUrl || null;
|
||||||
|
|
||||||
|
this.updateParentCategoryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection(): void {
|
clearSelection(): void {
|
||||||
this.selectedCategoryId = null;
|
this.selectedCategoryId = null;
|
||||||
this.categoryForm.reset({ isActive: true });
|
this.categoryForm.reset({ isActive: true, parentcategorieId: null });
|
||||||
|
|
||||||
|
// --- NEU: Bild-Referenzen zurücksetzen ---
|
||||||
this.selectedFile = null;
|
this.selectedFile = null;
|
||||||
|
this.existingImageUrl = null;
|
||||||
|
|
||||||
|
this.updateParentCategoryList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- NEU: Methode zum Entfernen des Bildes ---
|
||||||
|
removeImage(): void {
|
||||||
|
this.selectedFile = null;
|
||||||
|
this.existingImageUrl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
if (this.categoryForm.invalid) return;
|
if (this.categoryForm.invalid) return;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
Object.keys(this.categoryForm.value).forEach(key => {
|
const formValue = this.categoryForm.value;
|
||||||
formData.append(key, this.categoryForm.value[key]);
|
|
||||||
|
Object.keys(formValue).forEach(key => {
|
||||||
|
const value = formValue[key] === null || formValue[key] === undefined ? '' : formValue[key];
|
||||||
|
formData.append(key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- ÜBERARBEITETE BILD-LOGIK ---
|
||||||
if (this.selectedFile) {
|
if (this.selectedFile) {
|
||||||
|
// Nur wenn eine NEUE Datei ausgewählt wurde, wird sie gesendet.
|
||||||
formData.append('ImageFile', this.selectedFile, this.selectedFile.name);
|
formData.append('ImageFile', this.selectedFile, this.selectedFile.name);
|
||||||
|
} else if (this.existingImageUrl) {
|
||||||
|
// Wenn keine neue Datei da ist, aber ein altes Bild existiert,
|
||||||
|
// senden wir die URL, damit das Backend weiß, dass es erhalten bleiben soll.
|
||||||
|
formData.append('ImageUrl', this.existingImageUrl);
|
||||||
}
|
}
|
||||||
|
// Wenn beides null ist (z.B. nach Klick auf "Bild entfernen"),
|
||||||
|
// wird kein Bild-Parameter gesendet und das Backend sollte das Bild löschen.
|
||||||
|
// --- ENDE ÜBERARBEITUNG ---
|
||||||
|
|
||||||
if (this.selectedCategoryId) {
|
if (this.selectedCategoryId) {
|
||||||
// Update
|
|
||||||
formData.append('Id', this.selectedCategoryId);
|
formData.append('Id', this.selectedCategoryId);
|
||||||
this.categoryService.update(this.selectedCategoryId, formData).subscribe(() => this.reset());
|
this.categoryService.update(this.selectedCategoryId, formData).subscribe(() => this.reset());
|
||||||
} else {
|
} else {
|
||||||
// Create
|
|
||||||
this.categoryService.create(formData).subscribe(() => this.reset());
|
this.categoryService.create(formData).subscribe(() => this.reset());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,4 +123,10 @@ export class CategoryListComponent implements OnInit {
|
|||||||
this.loadCategories();
|
this.loadCategories();
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateParentCategoryList(): void {
|
||||||
|
this.parentCategories$ = this.categories$.pipe(
|
||||||
|
map(categories => categories.filter(c => c.id !== this.selectedCategoryId))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,28 +3,83 @@
|
|||||||
|
|
||||||
<form [formGroup]="discountForm" (ngSubmit)="onSubmit()">
|
<form [formGroup]="discountForm" (ngSubmit)="onSubmit()">
|
||||||
<h3>{{ selectedDiscountId ? "Rabatt bearbeiten" : "Neuer Rabatt" }}</h3>
|
<h3>{{ selectedDiscountId ? "Rabatt bearbeiten" : "Neuer Rabatt" }}</h3>
|
||||||
<input type="text" formControlName="name" placeholder="Name des Rabatts" />
|
|
||||||
<select formControlName="discountType">
|
<div><label for="name">Name:</label>
|
||||||
<option *ngFor="let type of discountTypes" [value]="type">
|
<input id="name" type="text" formControlName="name" placeholder="Name des Rabatts" /></div>
|
||||||
{{ type }}
|
|
||||||
</option>
|
<div><label for="desc">Beschreibung:</label>
|
||||||
</select>
|
<textarea id="desc" formControlName="description" placeholder="Beschreibung"></textarea></div>
|
||||||
<input
|
|
||||||
type="number"
|
<div><label for="type">Typ:</label>
|
||||||
formControlName="discountValue"
|
<select id="type" formControlName="discountType">
|
||||||
placeholder="Wert (z.B. 10 für 10% oder 10€)"
|
<option *ngFor="let type of discountTypes" [value]="type">{{ type }}</option>
|
||||||
/>
|
</select></div>
|
||||||
<input
|
|
||||||
type="text"
|
<div><label for="value">Wert:</label>
|
||||||
formControlName="couponCode"
|
<input id="value" type="number" formControlName="discountValue" placeholder="z.B. 10 für 10% oder 10€" /></div>
|
||||||
placeholder="Gutscheincode (optional)"
|
|
||||||
/>
|
<hr>
|
||||||
<label
|
|
||||||
><input type="checkbox" formControlName="requiresCouponCode" /> Code
|
<div><label for="couponCode">Gutscheincode:</label>
|
||||||
erforderlich</label
|
<input id="couponCode" type="text" formControlName="couponCode" placeholder="z.B. SOMMER2024" /></div>
|
||||||
>
|
|
||||||
<label><input type="checkbox" formControlName="isActive" /> Aktiv</label>
|
<div><label><input type="checkbox" formControlName="requiresCouponCode" /> Code erforderlich</label></div>
|
||||||
<input type="date" formControlName="startDate" />
|
<div><label><input type="checkbox" formControlName="isActive" /> Aktiv</label></div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div><label for="startDate">Startdatum:</label>
|
||||||
|
<input id="startDate" type="date" formControlName="startDate" /></div>
|
||||||
|
|
||||||
|
<div><label for="endDate">Enddatum (optional):</label>
|
||||||
|
<input id="endDate" type="date" formControlName="endDate" /></div>
|
||||||
|
|
||||||
|
<div><label for="minOrder">Mindestbestellwert (optional):</label>
|
||||||
|
<input id="minOrder" type="number" formControlName="minimumOrderAmount" placeholder="z.B. 50" /></div>
|
||||||
|
|
||||||
|
<div><label for="maxUsage">Maximale Nutzungen (optional):</label>
|
||||||
|
<input id="maxUsage" type="number" formControlName="maximumUsageCount" placeholder="z.B. 100" /></div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h4>Produkten zuweisen</h4>
|
||||||
|
<div>
|
||||||
|
<select #productSelect>
|
||||||
|
<option [ngValue]="null">-- Produkt auswählen --</option>
|
||||||
|
<option *ngFor="let product of allProducts$ | async" [value]="product.id">
|
||||||
|
{{ product.name }} ({{ product.sku }})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" (click)="addProduct(productSelect.value)">+</button>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let control of assignedProductIds.controls; let i = index">
|
||||||
|
{{ getProductName(control.value, (allProducts$ | async)) }}
|
||||||
|
<button type="button" (click)="removeProduct(i)">x</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h4>Kategorien zuweisen</h4>
|
||||||
|
<div>
|
||||||
|
<select #categorySelect>
|
||||||
|
<option [ngValue]="null">-- Kategorie auswählen --</option>
|
||||||
|
<option *ngFor="let category of allCategories$ | async" [value]="category.id">
|
||||||
|
{{ category.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" (click)="addCategory(categorySelect.value)">+</button>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let control of assignedCategoryIds.controls; let i = index">
|
||||||
|
{{ getCategoryName(control.value, (allCategories$ | async)) }}
|
||||||
|
<button type="button" (click)="removeCategory(i)">x</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
<button type="submit" [disabled]="discountForm.invalid">
|
<button type="submit" [disabled]="discountForm.invalid">
|
||||||
{{ selectedDiscountId ? "Aktualisieren" : "Erstellen" }}
|
{{ selectedDiscountId ? "Aktualisieren" : "Erstellen" }}
|
||||||
</button>
|
</button>
|
||||||
@@ -38,8 +93,8 @@
|
|||||||
<h2>Bestehende Rabatte</h2>
|
<h2>Bestehende Rabatte</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let discount of discounts$ | async">
|
<li *ngFor="let discount of discounts$ | async">
|
||||||
{{ discount.name }} ({{ discount.discountValue
|
{{ discount.name }} ({{ discount.discountValue }}{{ discount.discountType === "Percentage" ? "%" : "€" }})
|
||||||
}}{{ discount.discountType === "Percentage" ? "%" : "€" }})
|
- Code: {{ discount.couponCode || 'Kein Code' }}
|
||||||
<button (click)="selectDiscount(discount)">Bearbeiten</button>
|
<button (click)="selectDiscount(discount)">Bearbeiten</button>
|
||||||
<button (click)="onDelete(discount.id)">Löschen</button>
|
<button (click)="onDelete(discount.id)">Löschen</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, FormArray, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
// Models
|
||||||
import { Discount } from '../../../../core/models/discount.model';
|
import { Discount } from '../../../../core/models/discount.model';
|
||||||
import { DiscountService } from '../../../services/discount.service';
|
import { AdminProduct } from '../../../../core/models/product.model';
|
||||||
|
import { Category } from '../../../../core/models/category.model';
|
||||||
|
|
||||||
import { DiscountType } from '../../../../core/enums/shared.enum';
|
import { DiscountType } from '../../../../core/enums/shared.enum';
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { DiscountService } from '../../../services/discount.service';
|
||||||
|
import { ProductService } from '../../../services/product.service';
|
||||||
|
import { CategoryService } from '../../../services/category.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-discount-list',
|
selector: 'app-discount-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -13,10 +22,18 @@ import { DiscountType } from '../../../../core/enums/shared.enum';
|
|||||||
templateUrl: './discount-list.component.html',
|
templateUrl: './discount-list.component.html',
|
||||||
})
|
})
|
||||||
export class DiscountListComponent implements OnInit {
|
export class DiscountListComponent implements OnInit {
|
||||||
|
// --- Service Injection ---
|
||||||
private discountService = inject(DiscountService);
|
private discountService = inject(DiscountService);
|
||||||
|
private productService = inject(ProductService);
|
||||||
|
private categoryService = inject(CategoryService);
|
||||||
private fb = inject(FormBuilder);
|
private fb = inject(FormBuilder);
|
||||||
|
|
||||||
|
// --- Data Observables ---
|
||||||
discounts$!: Observable<Discount[]>;
|
discounts$!: Observable<Discount[]>;
|
||||||
|
allProducts$!: Observable<AdminProduct[]>;
|
||||||
|
allCategories$!: Observable<Category[]>;
|
||||||
|
|
||||||
|
// --- Form Properties ---
|
||||||
discountForm: FormGroup;
|
discountForm: FormGroup;
|
||||||
selectedDiscountId: string | null = null;
|
selectedDiscountId: string | null = null;
|
||||||
discountTypes: DiscountType[] = ['Percentage', 'FixedAmount'];
|
discountTypes: DiscountType[] = ['Percentage', 'FixedAmount'];
|
||||||
@@ -29,31 +46,87 @@ export class DiscountListComponent implements OnInit {
|
|||||||
couponCode: [''],
|
couponCode: [''],
|
||||||
requiresCouponCode: [false],
|
requiresCouponCode: [false],
|
||||||
isActive: [true],
|
isActive: [true],
|
||||||
startDate: [new Date().toISOString().split('T')[0], Validators.required]
|
startDate: [new Date().toISOString().split('T')[0], Validators.required],
|
||||||
|
// +++ HIER SIND DIE FEHLENDEN FELDER +++
|
||||||
|
endDate: [null],
|
||||||
|
minimumOrderAmount: [null, [Validators.min(0)]],
|
||||||
|
maximumUsageCount: [null, [Validators.min(0)]],
|
||||||
|
description: [''],
|
||||||
|
assignedProductIds: this.fb.array([]),
|
||||||
|
assignedCategoryIds: this.fb.array([])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void { this.loadDiscounts(); }
|
get assignedProductIds(): FormArray {
|
||||||
loadDiscounts(): void { this.discounts$ = this.discountService.getAll(); }
|
return this.discountForm.get('assignedProductIds') as FormArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
get assignedCategoryIds(): FormArray {
|
||||||
|
return this.discountForm.get('assignedCategoryIds') as FormArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadInitialData();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadInitialData(): void {
|
||||||
|
this.discounts$ = this.discountService.getAll();
|
||||||
|
this.allProducts$ = this.productService.getAll();
|
||||||
|
this.allCategories$ = this.categoryService.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
selectDiscount(discount: Discount): void {
|
selectDiscount(discount: Discount): void {
|
||||||
this.selectedDiscountId = discount.id;
|
this.selectedDiscountId = discount.id;
|
||||||
// Format date for the input[type=date]
|
this.discountForm.patchValue({
|
||||||
const formattedDiscount = {
|
|
||||||
...discount,
|
...discount,
|
||||||
startDate: new Date(discount.startDate).toISOString().split('T')[0]
|
startDate: new Date(discount.startDate).toISOString().split('T')[0],
|
||||||
};
|
endDate: discount.endDate ? new Date(discount.endDate).toISOString().split('T')[0] : null,
|
||||||
this.discountForm.patchValue(formattedDiscount);
|
});
|
||||||
|
|
||||||
|
this.assignedProductIds.clear();
|
||||||
|
discount.assignedProductIds?.forEach(id => this.assignedProductIds.push(this.fb.control(id)));
|
||||||
|
|
||||||
|
this.assignedCategoryIds.clear();
|
||||||
|
discount.assignedCategoryIds?.forEach(id => this.assignedCategoryIds.push(this.fb.control(id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection(): void {
|
clearSelection(): void {
|
||||||
this.selectedDiscountId = null;
|
this.selectedDiscountId = null;
|
||||||
this.discountForm.reset({
|
this.discountForm.reset({
|
||||||
|
name: '',
|
||||||
discountType: 'Percentage',
|
discountType: 'Percentage',
|
||||||
isActive: true,
|
discountValue: 0,
|
||||||
|
couponCode: '',
|
||||||
requiresCouponCode: false,
|
requiresCouponCode: false,
|
||||||
startDate: new Date().toISOString().split('T')[0]
|
isActive: true,
|
||||||
|
startDate: new Date().toISOString().split('T')[0],
|
||||||
|
endDate: null,
|
||||||
|
minimumOrderAmount: null,
|
||||||
|
maximumUsageCount: null,
|
||||||
|
description: '',
|
||||||
});
|
});
|
||||||
|
this.assignedProductIds.clear();
|
||||||
|
this.assignedCategoryIds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
addProduct(productId: string | null): void {
|
||||||
|
if (productId && !this.assignedProductIds.value.includes(productId)) {
|
||||||
|
this.assignedProductIds.push(this.fb.control(productId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeProduct(index: number): void {
|
||||||
|
this.assignedProductIds.removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
addCategory(categoryId: string | null): void {
|
||||||
|
if (categoryId && !this.assignedCategoryIds.value.includes(categoryId)) {
|
||||||
|
this.assignedCategoryIds.push(this.fb.control(categoryId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCategory(index: number): void {
|
||||||
|
this.assignedCategoryIds.removeAt(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
@@ -62,8 +135,12 @@ export class DiscountListComponent implements OnInit {
|
|||||||
const formValue = this.discountForm.value;
|
const formValue = this.discountForm.value;
|
||||||
const dataToSend: Discount = {
|
const dataToSend: Discount = {
|
||||||
...formValue,
|
...formValue,
|
||||||
id: this.selectedDiscountId || '00000000-0000-0000-0000-000000000000', // Dummy ID for create
|
id: this.selectedDiscountId || undefined,
|
||||||
startDate: new Date(formValue.startDate).toISOString()
|
startDate: new Date(formValue.startDate).toISOString(),
|
||||||
|
endDate: formValue.endDate ? new Date(formValue.endDate).toISOString() : null,
|
||||||
|
// Die Werte aus den FormArrays sind bereits korrekte Arrays von Strings
|
||||||
|
assignedProductIds: this.assignedProductIds.value,
|
||||||
|
assignedCategoryIds: this.assignedCategoryIds.value
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.selectedDiscountId) {
|
if (this.selectedDiscountId) {
|
||||||
@@ -75,12 +152,20 @@ export class DiscountListComponent implements OnInit {
|
|||||||
|
|
||||||
onDelete(id: string): void {
|
onDelete(id: string): void {
|
||||||
if (confirm('Rabatt wirklich löschen?')) {
|
if (confirm('Rabatt wirklich löschen?')) {
|
||||||
this.discountService.delete(id).subscribe(() => this.loadDiscounts());
|
this.discountService.delete(id).subscribe(() => this.loadInitialData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private reset(): void {
|
private reset(): void {
|
||||||
this.loadDiscounts();
|
this.loadInitialData();
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getProductName(productId: string, products: AdminProduct[] | null): string {
|
||||||
|
return products?.find(p => p.id === productId)?.name || 'Unbekanntes Produkt';
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategoryName(categoryId: string, categories: Category[] | null): string {
|
||||||
|
return categories?.find(c => c.id === categoryId)?.name || 'Unbekannte Kategorie';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
10
src/app/features/components/discounts/discounts.routes.ts
Normal file
10
src/app/features/components/discounts/discounts.routes.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { DiscountListComponent } from './discount-list/discount-list.component';
|
||||||
|
|
||||||
|
export const DISCOUNTS_ROUTES: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: DiscountListComponent,
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
10
src/app/features/components/orders/orders.routes.ts
Normal file
10
src/app/features/components/orders/orders.routes.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { OrderListComponent } from './order-list/order-list.component';
|
||||||
|
|
||||||
|
export const ORDERS_ROUTES: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: OrderListComponent,
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -9,7 +9,7 @@ import { AnalyticsPeriod } from '../../core/enums/shared.enum';
|
|||||||
export class AnalyticsService {
|
export class AnalyticsService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminAnalytics';
|
private readonly endpoint = '/admin/AdminAnalytics';
|
||||||
|
|
||||||
get(period: AnalyticsPeriod): Observable<Analytics> {
|
get(period: AnalyticsPeriod): Observable<Analytics> {
|
||||||
const params = new HttpParams().set('period', period);
|
const params = new HttpParams().set('period', period);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Category } from '../../core/models/category.model';
|
|||||||
export class CategoryService {
|
export class CategoryService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminCategories';
|
private readonly endpoint = '/admin/AdminCategories';
|
||||||
|
|
||||||
getAll(): Observable<Category[]> {
|
getAll(): Observable<Category[]> {
|
||||||
return this.http.get<Category[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<Category[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Discount } from '../../core/models/discount.model';
|
|||||||
export class DiscountService {
|
export class DiscountService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminDiscounts';
|
private readonly endpoint = '/admin/AdminDiscounts';
|
||||||
|
|
||||||
getAll(): Observable<Discount[]> {
|
getAll(): Observable<Discount[]> {
|
||||||
return this.http.get<Discount[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<Discount[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { OrderDetail, OrderSummary, UpdateOrderStatusRequest } from '../../core/
|
|||||||
export class OrderService {
|
export class OrderService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminOrders';
|
private readonly endpoint = '/admin/AdminOrders';
|
||||||
|
|
||||||
getAll(): Observable<OrderSummary[]> {
|
getAll(): Observable<OrderSummary[]> {
|
||||||
return this.http.get<OrderSummary[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<OrderSummary[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { AdminPaymentMethod } from '../../core/models/payment.model';
|
|||||||
export class PaymentMethodService {
|
export class PaymentMethodService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminPaymentMethods';
|
private readonly endpoint = '/admin/AdminPaymentMethods';
|
||||||
|
|
||||||
getAll(): Observable<AdminPaymentMethod[]> {
|
getAll(): Observable<AdminPaymentMethod[]> {
|
||||||
return this.http.get<AdminPaymentMethod[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<AdminPaymentMethod[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { AdminProduct } from '../../core/models/product.model';
|
|||||||
export class ProductService {
|
export class ProductService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminProducts';
|
private readonly endpoint = '/admin/AdminProducts';
|
||||||
|
|
||||||
getAll(): Observable<AdminProduct[]> {
|
getAll(): Observable<AdminProduct[]> {
|
||||||
return this.http.get<AdminProduct[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<AdminProduct[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Review } from '../../core/models/review.model';
|
|||||||
export class ReviewService {
|
export class ReviewService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminReviews';
|
private readonly endpoint = '/admin/AdminReviews';
|
||||||
|
|
||||||
getAll(): Observable<Review[]> {
|
getAll(): Observable<Review[]> {
|
||||||
return this.http.get<Review[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<Review[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Setting } from '../../core/models/setting.model';
|
|||||||
export class SettingService {
|
export class SettingService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminSettings';
|
private readonly endpoint = '/admin/AdminSettings';
|
||||||
|
|
||||||
getAllGrouped(): Observable<{ [group: string]: Setting[] }> {
|
getAllGrouped(): Observable<{ [group: string]: Setting[] }> {
|
||||||
return this.http.get<{ [group: string]: Setting[] }>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<{ [group: string]: Setting[] }>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { ShippingMethod } from '../../core/models/shipping.model';
|
|||||||
export class ShippingMethodService {
|
export class ShippingMethodService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminShippingMethods';
|
private readonly endpoint = '/admin/AdminShippingMethods';
|
||||||
|
|
||||||
getAll(): Observable<ShippingMethod[]> {
|
getAll(): Observable<ShippingMethod[]> {
|
||||||
return this.http.get<ShippingMethod[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<ShippingMethod[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { AdminShopInfo } from '../../core/models/shop.model';
|
|||||||
export class ShopInfoService {
|
export class ShopInfoService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminShopInfo';
|
private readonly endpoint = '/admin/AdminShopInfo';
|
||||||
|
|
||||||
get(): Observable<AdminShopInfo> {
|
get(): Observable<AdminShopInfo> {
|
||||||
return this.http.get<AdminShopInfo>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<AdminShopInfo>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Supplier } from '../../core/models/supplier.model';
|
|||||||
export class SupplierService {
|
export class SupplierService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminSuppliers';
|
private readonly endpoint = '/admin/AdminSuppliers';
|
||||||
|
|
||||||
getAll(): Observable<Supplier[]> {
|
getAll(): Observable<Supplier[]> {
|
||||||
return this.http.get<Supplier[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<Supplier[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { User, UpdateUserRolesRequest } from '../../core/models/user.model';
|
|||||||
export class UserService {
|
export class UserService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private apiUrl = inject(API_URL);
|
private apiUrl = inject(API_URL);
|
||||||
private readonly endpoint = '/AdminUsers';
|
private readonly endpoint = '/admin/AdminUsers';
|
||||||
|
|
||||||
getAll(): Observable<User[]> {
|
getAll(): Observable<User[]> {
|
||||||
return this.http.get<User[]>(`${this.apiUrl}${this.endpoint}`);
|
return this.http.get<User[]>(`${this.apiUrl}${this.endpoint}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user