From ef994b89e335578d515065daa7fd72c20da9c407 Mon Sep 17 00:00:00 2001 From: "Tizian.Breuch" Date: Fri, 10 Oct 2025 15:46:03 +0200 Subject: [PATCH] changes --- src/app/app.routes.ts | 7 + .../components/analytics/analytics.routes.ts | 11 + .../analytics/analytics.component.css | 0 .../analytics/analytics.component.html | 70 +++++ .../analytics/analytics.component.ts | 35 +++ .../components/orders/orders.routes.ts | 2 +- .../product-list/product-list.component.html | 265 ++++++++++++------ .../product-list/product-list.component.ts | 14 + .../settings/settings/settings.component.html | 50 ++-- .../settings/settings/settings.component.ts | 60 +++- .../layout/sidebar/sidebar.component.html | 18 ++ 11 files changed, 408 insertions(+), 124 deletions(-) create mode 100644 src/app/features/components/analytics/analytics.routes.ts create mode 100644 src/app/features/components/analytics/analytics/analytics.component.css create mode 100644 src/app/features/components/analytics/analytics/analytics.component.html create mode 100644 src/app/features/components/analytics/analytics/analytics.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 124b22e..ea413a5 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -109,6 +109,13 @@ export const routes: Routes = [ (r) => r.USERS_ROUTES ), }, + { + path: 'analytics', + loadChildren: () => + import('./features/components/analytics/analytics.routes').then( + (r) => r.ANALYTICS_ROUTES + ), + }, ], }, { diff --git a/src/app/features/components/analytics/analytics.routes.ts b/src/app/features/components/analytics/analytics.routes.ts new file mode 100644 index 0000000..afa8fd2 --- /dev/null +++ b/src/app/features/components/analytics/analytics.routes.ts @@ -0,0 +1,11 @@ +import { Routes } from '@angular/router'; +import { AnalyticsComponent } from './analytics/analytics.component'; + + +export const ANALYTICS_ROUTES: Routes = [ + { + path: '', + component: AnalyticsComponent, + title: '', + }, +]; diff --git a/src/app/features/components/analytics/analytics/analytics.component.css b/src/app/features/components/analytics/analytics/analytics.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/features/components/analytics/analytics/analytics.component.html b/src/app/features/components/analytics/analytics/analytics.component.html new file mode 100644 index 0000000..4cf1f17 --- /dev/null +++ b/src/app/features/components/analytics/analytics/analytics.component.html @@ -0,0 +1,70 @@ + + +
+

Analytics Dashboard

+ +
+ + + +
+ +
+ +
+ +
+ KPI Übersicht +
+
Umsatz: {{ data.kpiSummary.totalRevenue | currency:'EUR' }}
+
Bestellungen: {{ data.kpiSummary.totalOrders }}
+
Ø Bestellwert: {{ data.kpiSummary.averageOrderValue | currency:'EUR' }}
+
Gesamtkunden: {{ data.kpiSummary.totalCustomers }}
+
Neukunden (im Zeitraum): {{ data.kpiSummary.newCustomersThisPeriod }}
+
+
+ +
+ Top Produkte + + + + + + + + + + + + + + + + + + + + +
ProduktSKUVerkaufte EinheitenUmsatz
{{ product.name }}{{ product.sku }}{{ product.unitsSold }}{{ product.totalRevenue | currency:'EUR' }}
Keine Produktdaten in diesem Zeitraum.
+
+ +
+ Lagerstatus +
+
Produkte mit niedrigem Lagerbestand: {{ data.inventoryStatus.productsWithLowStock }}
+
Lagerverfügbarkeit (gesamt): {{ data.inventoryStatus.overallStockAvailabilityPercentage | number:'1.1-2' }}%
+
+
+ +
+ Verkaufsverlauf (Rohdaten) +
{{ data.salesOverTime | json }}
+
+ +
+ + +

Lade Analysedaten...

+
+
\ No newline at end of file diff --git a/src/app/features/components/analytics/analytics/analytics.component.ts b/src/app/features/components/analytics/analytics/analytics.component.ts new file mode 100644 index 0000000..8bbbca8 --- /dev/null +++ b/src/app/features/components/analytics/analytics/analytics.component.ts @@ -0,0 +1,35 @@ +// /src/app/features/admin/components/analytics/analytics.component.ts + +import { Component, OnInit, inject } from '@angular/core'; +import { CommonModule, CurrencyPipe, DecimalPipe } from '@angular/common'; +import { Observable } from 'rxjs'; + +// Models & Enums +import { Analytics } from '../../../../core/models/analytics.model'; +import { AnalyticsPeriod } from '../../../../core/enums/shared.enum'; + +// Services +import { AnalyticsService } from '../../../services/analytics.service'; + +@Component({ + selector: 'app-analytics', // Dein gewünschter Selector + standalone: true, + imports: [CommonModule, CurrencyPipe, DecimalPipe], + templateUrl: './analytics.component.html', + styleUrl: './analytics.component.css' +}) +export class AnalyticsComponent implements OnInit { // Dein gewünschter Klassenname + private analyticsService = inject(AnalyticsService); + + analytics$!: Observable; + selectedPeriod: AnalyticsPeriod = 'Last30Days'; + + ngOnInit(): void { + this.loadAnalytics(this.selectedPeriod); + } + + loadAnalytics(period: AnalyticsPeriod): void { + this.selectedPeriod = period; + this.analytics$ = this.analyticsService.get(period); + } +} \ No newline at end of file diff --git a/src/app/features/components/orders/orders.routes.ts b/src/app/features/components/orders/orders.routes.ts index 6a58393..d71b89c 100644 --- a/src/app/features/components/orders/orders.routes.ts +++ b/src/app/features/components/orders/orders.routes.ts @@ -3,7 +3,7 @@ import { OrderListComponent } from './order-list/order-list.component'; export const ORDERS_ROUTES: Routes = [ { - path: '1', + path: '', component: OrderListComponent, title: '', }, diff --git a/src/app/features/components/products/product-list/product-list.component.html b/src/app/features/components/products/product-list/product-list.component.html index 2ebdc52..82085c7 100644 --- a/src/app/features/components/products/product-list/product-list.component.html +++ b/src/app/features/components/products/product-list/product-list.component.html @@ -1,132 +1,227 @@

Produkte verwalten

- +
-

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

- - + +

+ {{ selectedProductId ? "Produkt bearbeiten" : "Neues Produkt erstellen" }} +

Basis-Informationen

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

Preis & Lager

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

Zuweisungen

- - +
-
-
+
- + (change)="onCategoryChange($event)" + />{{ category.name }}
- -
- - +

Produktbilder

Bestehende Bilder:

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

Status & Sichtbarkeit

-
-
-
- -

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

+ + -
- -

Bestehende Produkte

-
    -
  • - {{ product.name }} (SKU: {{ product.sku }}) - Preis: {{ product.price | currency:'EUR' }} - - -
  • -
-
\ No newline at end of file +
+

Bestehende Produkte

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BildNameSKUPreisLagerbestandAktivAktionen
Keine Produkte gefunden.
+ + + + {{ product.name }}
+ Slug: {{ product.slug }} +
{{ product.sku }}{{ product.price | currency:'EUR' }}{{ product.stockQuantity }} + {{ product.isActive ? 'Ja' : 'Nein' }} + + + +
Lade Produkte...
\ No newline at end of file diff --git a/src/app/features/components/products/product-list/product-list.component.ts b/src/app/features/components/products/product-list/product-list.component.ts index 5095adb..6454fc3 100644 --- a/src/app/features/components/products/product-list/product-list.component.ts +++ b/src/app/features/components/products/product-list/product-list.component.ts @@ -265,4 +265,18 @@ export class ProductListComponent implements OnInit, OnDestroy { private capitalizeFirstLetter(string: string): string { return string.charAt(0).toUpperCase() + string.slice(1); } + + /** + * Sucht das Hauptbild aus der Bilderliste eines Produkts und gibt dessen URL zurück. + * Gibt eine Platzhalter-URL zurück, wenn kein Hauptbild gefunden wird. + * @param images Die Liste der Produktbilder. + * @returns Die URL des Hauptbildes oder eine Platzhalter-URL. + */ + getMainImageUrl(images?: ProductImage[]): string { + if (!images || images.length === 0) { + return 'https://via.placeholder.com/50'; // Platzhalter, wenn gar keine Bilder vorhanden sind + } + const mainImage = images.find(img => img.isMainImage); + return mainImage?.url || images[0].url || 'https://via.placeholder.com/50'; // Fallback auf das erste Bild, wenn kein Hauptbild markiert ist + } } diff --git a/src/app/features/components/settings/settings/settings.component.html b/src/app/features/components/settings/settings/settings.component.html index 9055778..974996a 100644 --- a/src/app/features/components/settings/settings/settings.component.html +++ b/src/app/features/components/settings/settings/settings.component.html @@ -1,29 +1,33 @@

Einstellungen verwalten

+
-
-
-

{{ group.groupName }}

-
- - -
- - - -
-
+
+
+ {{ groupControl.get('groupName')?.value }} + + +
+
+ + + + + + + +
-
+ +
- + +
+ -
+
\ No newline at end of file diff --git a/src/app/features/components/settings/settings/settings.component.ts b/src/app/features/components/settings/settings/settings.component.ts index 4e6df98..1210022 100644 --- a/src/app/features/components/settings/settings/settings.component.ts +++ b/src/app/features/components/settings/settings/settings.component.ts @@ -14,41 +14,71 @@ export class SettingsComponent implements OnInit { private settingService = inject(SettingService); private fb = inject(FormBuilder); - settingsForm: FormGroup; - settingGroups: { groupName: string, settings: Setting[] }[] = []; + settingsForm!: FormGroup; constructor() { this.settingsForm = this.fb.group({ - settings: this.fb.array([]) + groups: this.fb.array([]) }); } - get settingsArray(): FormArray { - return this.settingsForm.get('settings') as FormArray; + get groups(): FormArray { + return this.settingsForm.get('groups') as FormArray; } + // +++ NEUE HILFSFUNKTION +++ + /** + * Gibt das 'settings'-FormArray für eine bestimmte Gruppe zurück. + * Ermöglicht einen sauberen Zugriff im Template. + * @param groupIndex Der Index der Gruppe im 'groups'-FormArray. + * @returns Das verschachtelte 'settings'-FormArray. + */ + getSettingsFormArray(groupIndex: number): FormArray { + return (this.groups.at(groupIndex) as FormGroup).get('settings') as FormArray; + } + // +++ ENDE NEU +++ + ngOnInit(): void { this.settingService.getAllGrouped().subscribe(groupedSettings => { - this.settingsArray.clear(); - this.settingGroups = []; + this.groups.clear(); + Object.keys(groupedSettings).forEach(groupName => { const settings = groupedSettings[groupName]; - this.settingGroups.push({ groupName, settings }); - settings.forEach(setting => { - this.settingsArray.push(this.fb.group({ + + const settingsControls = settings.map(setting => + this.fb.group({ key: [setting.key], value: [setting.value], - isActive: [setting.isActive] - })); - }); + isActive: [setting.isActive], + description: [setting.description] + }) + ); + + this.groups.push(this.fb.group({ + groupName: [groupName], + settings: this.fb.array(settingsControls) + })); }); }); } onSubmit(): void { if (this.settingsForm.invalid) return; - this.settingService.update(this.settingsForm.value.settings).subscribe(() => { - alert('Einstellungen gespeichert!'); + + const allSettings: Setting[] = this.groups.value + .flatMap((group: any) => group.settings) + .map((setting: any) => ({ + key: setting.key, + value: setting.value, + isActive: setting.isActive + })); + + this.settingService.update(allSettings).subscribe({ + next: () => alert('Einstellungen erfolgreich gespeichert!'), + error: (err) => { + alert('Fehler beim Speichern der Einstellungen.'); + console.error('Failed to update settings', err); + } }); } } \ No newline at end of file diff --git a/src/app/shared/components/layout/sidebar/sidebar.component.html b/src/app/shared/components/layout/sidebar/sidebar.component.html index 96e263c..0f21053 100644 --- a/src/app/shared/components/layout/sidebar/sidebar.component.html +++ b/src/app/shared/components/layout/sidebar/sidebar.component.html @@ -71,6 +71,15 @@ reviews
+ +