aufräumen

This commit is contained in:
Tizian.Breuch
2025-09-26 11:30:14 +02:00
parent c066476cc3
commit 7d10ecf1cc
47 changed files with 503 additions and 253 deletions

View File

@@ -3,17 +3,17 @@
<app-page-header></app-page-header> <app-page-header></app-page-header>
</div> </div>
<app-kpi-card <!-- <app-kpi-card
*ngFor="let kpi of kpiData" *ngFor="let kpi of kpiData"
[value]="kpi.value" [value]="kpi.value"
[label]="kpi.label" [label]="kpi.label"
[color]="kpi.color" [color]="kpi.color"
[iconName]="kpi.iconName" [iconName]="kpi.iconName"
> >
</app-kpi-card> </app-kpi-card> -->
<div class="grid-col-span-4"> <div class="grid-col-span-4">
<app-form-group title="Data-display Components"> <!-- <app-form-group title="Data-display Components">
<app-orders-table <app-orders-table
[data]="ordersData" [data]="ordersData"
[itemsPerPage]="5" [itemsPerPage]="5"
@@ -22,7 +22,7 @@
(delete)="handleDeleteOrder($event)" (delete)="handleDeleteOrder($event)"
> >
</app-orders-table> </app-orders-table>
</app-form-group> </app-form-group> -->
</div> </div>
<div class="grid-col-span-2"> <div class="grid-col-span-2">

View File

@@ -9,7 +9,7 @@ import { DOCUMENT } from '@angular/common';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CardComponent } from '../../../../shared/components/ui/card/card.component'; import { CardComponent } from '../../../../shared/components/ui/card/card.component';
// Wir müssen KpiColor hier importieren, um es als Typ verwenden zu können // Wir müssen KpiColor hier importieren, um es als Typ verwenden zu können
import { KpiCardComponent } from '../../../dashboard/components/kpi-card/kpi-card.component'; import { KpiCardComponent } from '../../../components/dashboard/kpi-card/kpi-card.component';
import { KpiColor } from '../../../../core/types/dashboard'; import { KpiColor } from '../../../../core/types/dashboard';
@@ -86,134 +86,134 @@ export class Demo2Component {
demoForm: FormGroup; demoForm: FormGroup;
kpiData: Kpi[] = [ // kpiData: Kpi[] = [
{ // {
value: '€ 14.750', // value: '€ 14.750',
label: 'Umsatz', // label: 'Umsatz',
color: 'green', // color: 'green',
iconName: 'placeholder', // iconName: 'placeholder',
}, // },
{ // {
value: '1.284', // value: '1.284',
label: 'Neue Nutzer', // label: 'Neue Nutzer',
color: 'blue', // color: 'blue',
iconName: 'placeholder', // iconName: 'placeholder',
}, // },
{ // {
value: '312', // value: '312',
label: 'Bestellungen', // label: 'Bestellungen',
color: 'orange', // color: 'orange',
iconName: 'placeholder', // iconName: 'placeholder',
}, // },
{ // {
value: '99.8%', // value: '99.8%',
label: 'Verfügbarkeit', // label: 'Verfügbarkeit',
color: 'purple', // color: 'purple',
iconName: 'placeholder', // iconName: 'placeholder',
}, // },
]; // ];
ordersData: Order[] = [ // ordersData: Order[] = [
{ // {
id: '10543', // id: '10543',
user: { // user: {
name: 'Max Mustermann', // name: 'Max Mustermann',
email: 'max.m@example.com', // email: 'max.m@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=max', // avatarUrl: 'https://i.pravatar.cc/40?u=max',
}, // },
amount: '€ 129,99', // amount: '€ 129,99',
status: 'success', // status: 'success',
statusText: 'Abgeschlossen', // statusText: 'Abgeschlossen',
}, // },
{ // {
id: '10542', // id: '10542',
user: { // user: {
name: 'Erika Mustermann', // name: 'Erika Mustermann',
email: 'erika.m@example.com', // email: 'erika.m@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=erika', // avatarUrl: 'https://i.pravatar.cc/40?u=erika',
}, // },
amount: '€ 49,50', // amount: '€ 49,50',
status: 'warning', // status: 'warning',
statusText: 'In Bearbeitung', // statusText: 'In Bearbeitung',
}, // },
{ // {
id: '10541', // id: '10541',
user: { // user: {
name: 'Peter Pan', // name: 'Peter Pan',
email: 'peter.p@example.com', // email: 'peter.p@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=peter', // avatarUrl: 'https://i.pravatar.cc/40?u=peter',
}, // },
amount: '€ 87,00', // amount: '€ 87,00',
status: 'danger', // status: 'danger',
statusText: 'Storniert', // statusText: 'Storniert',
}, // },
{ // {
id: '10543', // id: '10543',
user: { // user: {
name: 'Max Mustermann', // name: 'Max Mustermann',
email: 'max.m@example.com', // email: 'max.m@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=max', // avatarUrl: 'https://i.pravatar.cc/40?u=max',
}, // },
amount: '€ 129,99', // amount: '€ 129,99',
status: 'success', // status: 'success',
statusText: 'Abgeschlossen', // statusText: 'Abgeschlossen',
}, // },
{ // {
id: '10542', // id: '10542',
user: { // user: {
name: 'Erika Mustermann', // name: 'Erika Mustermann',
email: 'erika.m@example.com', // email: 'erika.m@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=erika', // avatarUrl: 'https://i.pravatar.cc/40?u=erika',
}, // },
amount: '€ 49,50', // amount: '€ 49,50',
status: 'warning', // status: 'warning',
statusText: 'In Bearbeitung', // statusText: 'In Bearbeitung',
}, // },
{ // {
id: '10541', // id: '10541',
user: { // user: {
name: 'Peter Pan', // name: 'Peter Pan',
email: 'peter.p@example.com', // email: 'peter.p@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=peter', // avatarUrl: 'https://i.pravatar.cc/40?u=peter',
}, // },
amount: '€ 87,00', // amount: '€ 87,00',
status: 'danger', // status: 'danger',
statusText: 'Storniert', // statusText: 'Storniert',
}, // },
{ // {
id: '10543', // id: '10543',
user: { // user: {
name: 'Max Mustermann', // name: 'Max Mustermann',
email: 'max.m@example.com', // email: 'max.m@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=max', // avatarUrl: 'https://i.pravatar.cc/40?u=max',
}, // },
amount: '€ 129,99', // amount: '€ 129,99',
status: 'success', // status: 'success',
statusText: 'Abgeschlossen', // statusText: 'Abgeschlossen',
}, // },
{ // {
id: '10542', // id: '10542',
user: { // user: {
name: 'Erika Mustermann', // name: 'Erika Mustermann',
email: 'erika.m@example.com', // email: 'erika.m@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=erika', // avatarUrl: 'https://i.pravatar.cc/40?u=erika',
}, // },
amount: '€ 49,50', // amount: '€ 49,50',
status: 'warning', // status: 'warning',
statusText: 'In Bearbeitung', // statusText: 'In Bearbeitung',
}, // },
{ // {
id: '10541', // id: '10541',
user: { // user: {
name: 'Peter Pan', // name: 'Peter Pan',
email: 'peter.p@example.com', // email: 'peter.p@example.com',
avatarUrl: 'https://i.pravatar.cc/40?u=peter', // avatarUrl: 'https://i.pravatar.cc/40?u=peter',
}, // },
amount: '€ 87,00', // amount: '€ 87,00',
status: 'danger', // status: 'danger',
statusText: 'Storniert', // statusText: 'Storniert',
}, // },
]; // ];
aktuellesPasswort: any = ''; aktuellesPasswort: any = '';
neuesPasswort: any = ''; neuesPasswort: any = '';

View File

@@ -1,7 +1,7 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { Demo1Component } from './components/demo1/demo1.component'; import { Demo1Component } from './components/demo1/demo1.component';
// import { Demo2Component } from './components/demo2/demo2.component'; import { Demo2Component } from './components/demo2/demo2.component';
export const DEMO_ROUTES: Routes = [ export const DEMO_ROUTES: Routes = [
{ {
@@ -17,12 +17,12 @@ export const DEMO_ROUTES: Routes = [
component: Demo1Component, component: Demo1Component,
title: 'Demo1', title: 'Demo1',
}, },
// { {
// // Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal. // Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal.
// path: '2', path: '2',
// component: Demo2Component, component: Demo2Component,
// title: 'Demo2', title: 'Demo2',
// }, },
// Hier könntest du weitere Routen wie '2', '3' etc. hinzufügen, // Hier könntest du weitere Routen wie '2', '3' etc. hinzufügen,
// die andere Komponenten laden. // die andere Komponenten laden.
// { path: '2', component: AnotherDemoComponent }, // { path: '2', component: AnotherDemoComponent },

View File

@@ -5,7 +5,8 @@ import { NotFoundComponent } from './core/components/not-found/not-found.compone
import { DefaultLayoutComponent } from './core/components/default-layout/default-layout.component'; import { DefaultLayoutComponent } from './core/components/default-layout/default-layout.component';
import { DashboardPageComponent } from './features/dashboard/components/dashboard-page/dashboard-page.component'; import { DashboardPageComponent } from './features/components/dashboard/dashboard-page/dashboard-page.component';
import { authGuard } from './core/guards/auth.guard';
export const routes: Routes = [ export const routes: Routes = [
{ {
@@ -17,11 +18,13 @@ export const routes: Routes = [
{ {
path: 'dashboard', path: 'dashboard',
component: DefaultLayoutComponent, component: DefaultLayoutComponent,
// canActivate: [authGuard],
// data: { requiredRole: 'Admin' },
children: [ children: [
{ {
path: '', path: '',
loadChildren: () => loadChildren: () =>
import('./features/dashboard/dashboard.routes').then( import('./features/components/dashboard/dashboard.routes').then(
(r) => r.DASHBOARD_ROUTES (r) => r.DASHBOARD_ROUTES
), ),
}, },
@@ -30,12 +33,7 @@ export const routes: Routes = [
{ {
path: 'auth', path: 'auth',
loadChildren: () => loadChildren: () =>
import('./features/auth/auth.routes').then((r) => r.AUTH_ROUTES), import('./features/components/auth/auth.routes').then((r) => r.AUTH_ROUTES),
},
{
path: 'demo',
loadChildren: () =>
import('./features/demo/demo.routes').then((r) => r.DEMO_ROUTES),
}, },
{ {
path: 'access-denied', path: 'access-denied',

View File

@@ -1,5 +1,48 @@
import { CanActivateFn } from '@angular/router'; import { inject } from '@angular/core';
import { CanActivateFn, Router, ActivatedRouteSnapshot } from '@angular/router';
import { AuthService } from '../services/auth.service';
export const authGuard: CanActivateFn = (route, state) => { /**
* Ein funktionaler Guard, der Routen schützt.
*
* Verwendung in der Routing-Konfiguration:
* {
* path: 'admin',
* component: AdminDashboardComponent,
* canActivate: [authGuard],
* data: { requiredRole: 'Admin' } // Die erforderliche Rolle hier übergeben
* }
* {
* path: 'profile',
* component: UserProfileComponent,
* canActivate: [authGuard] // Schützt nur vor nicht angemeldeten Benutzern
* }
*/
export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
const authService = inject(AuthService);
const router = inject(Router);
// 1. Prüfen, ob der Benutzer überhaupt angemeldet ist
if (!authService.getToken()) {
// Nicht angemeldet -> zum Login umleiten
router.navigate(['/login']);
return false;
}
// 2. Prüfen, ob die Route eine bestimmte Rolle erfordert
const requiredRole = route.data['requiredRole'] as string;
if (requiredRole) {
// Rolle ist erforderlich -> prüfen, ob der Benutzer sie hat
if (authService.hasRole(requiredRole)) {
// Benutzer hat die Rolle -> Zugriff erlaubt
return true;
} else {
// Benutzer hat die Rolle nicht -> zu einer "Zugriff verweigert"-Seite umleiten
router.navigate(['/access-denied']);
return false;
}
}
// 3. Wenn die Route nur eine Anmeldung, aber keine spezielle Rolle erfordert
return true; return true;
}; };

View File

@@ -0,0 +1,15 @@
// src/app/core/models/auth.models.ts
export interface LoginRequestDto {
email: string;
password: string;
}
export interface AuthResponseDto {
isAuthSuccessful: boolean;
errorMessage?: string;
token?: string;
userId?: string;
email?: string;
roles?: string[];
}

View File

@@ -0,0 +1,121 @@
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { isPlatformBrowser } from '@angular/common';
import { Router } from '@angular/router';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { LoginRequestDto } from '../models/auth.models';
import { AuthResponseDto } from '../models/auth.models';
@Injectable({
providedIn: 'root'
})
export class AuthService {
// Basis-URL Ihrer API
private apiUrl = '/api/v1/Auth';
// Keys für die Speicherung im localStorage
private readonly tokenKey = 'auth-token';
private readonly userRolesKey = 'auth-user-roles';
// Ein BehaviorSubject, um den Login-Status reaktiv in der App zu teilen
private loggedInStatus = new BehaviorSubject<boolean>(this.hasToken());
public isLoggedIn$ = this.loggedInStatus.asObservable();
constructor(
private http: HttpClient,
private router: Router,
@Inject(PLATFORM_ID) private platformId: Object
) {}
/**
* Meldet einen Admin-Benutzer an.
*/
loginAdmin(credentials: LoginRequestDto): Observable<AuthResponseDto> {
return this.http.post<AuthResponseDto>(`${this.apiUrl}/login/admin`, credentials).pipe(
tap(response => {
if (response.isAuthSuccessful && response.token) {
this.setSession(response);
}
}),
catchError(error => {
// Fehlerbehandlung, z.B. das Token löschen, falls eines vorhanden war
this.logout();
throw error;
})
);
}
/**
* Meldet einen normalen Kunden an.
*/
loginCustomer(credentials: LoginRequestDto): Observable<AuthResponseDto> {
return this.http.post<AuthResponseDto>(`${this.apiUrl}/login/customer`, credentials).pipe(
tap(response => {
if (response.isAuthSuccessful && response.token) {
this.setSession(response);
}
})
);
}
/**
* Meldet den Benutzer ab, entfernt die Daten aus dem Speicher und leitet um.
*/
logout(): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.removeItem(this.tokenKey);
localStorage.removeItem(this.userRolesKey);
}
this.loggedInStatus.next(false);
this.router.navigate(['/login']); // oder wohin auch immer Sie umleiten möchten
}
/**
* Gibt das gespeicherte JWT-Token zurück.
*/
getToken(): string | null {
if (isPlatformBrowser(this.platformId)) {
return localStorage.getItem(this.tokenKey);
}
return null;
}
/**
* Gibt die Rollen des angemeldeten Benutzers zurück.
*/
getUserRoles(): string[] {
if (isPlatformBrowser(this.platformId)) {
const roles = localStorage.getItem(this.userRolesKey);
return roles ? JSON.parse(roles) : [];
}
return [];
}
/**
* Überprüft, ob der Benutzer eine bestimmte Rolle hat.
*/
hasRole(requiredRole: string): boolean {
const userRoles = this.getUserRoles();
return userRoles.includes(requiredRole);
}
/**
* Private Methode zum Speichern der Sitzungsdaten.
*/
private setSession(authResponse: AuthResponseDto): void {
if (isPlatformBrowser(this.platformId) && authResponse.token && authResponse.roles) {
localStorage.setItem(this.tokenKey, authResponse.token);
localStorage.setItem(this.userRolesKey, JSON.stringify(authResponse.roles));
this.loggedInStatus.next(true);
}
}
/**
* Private Methode, die prüft, ob ein Token vorhanden ist (nützlich beim App-Start).
*/
private hasToken(): boolean {
return !!this.getToken();
}
}

View File

@@ -0,0 +1,5 @@
// /src/app/core/tokens/api-url.token.ts
import { InjectionToken } from '@angular/core';
export const API_URL = new InjectionToken<string>('api.url');

5
src/app/environment.ts Normal file
View File

@@ -0,0 +1,5 @@
// /src/environments/environment.ts
export const environment = {
production: false,
apiUrl: 'https://shopsolution-backend.tzbre.dev/api/v1',
};

View File

@@ -1,10 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthLayoutComponent } from './components/auth-layout/auth-layout.component';
import { LoginComponent } from './components/login/login.component';
@NgModule({
declarations: [],
imports: [CommonModule, AuthLayoutComponent, LoginComponent],
})
export class AuthModule {}

View File

@@ -1,13 +1,13 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
// Importiere dein spezielles Layout für Auth-Seiten und alle Komponenten // Importiere dein spezielles Layout für Auth-Seiten und alle Komponenten
import { AuthLayoutComponent } from './components/auth-layout/auth-layout.component'; import { AuthLayoutComponent } from './auth-layout/auth-layout.component';
import { LoginComponent } from './components/login/login.component'; import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './components/register/register.component'; import { RegisterComponent } from './register/register.component';
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component'; import { ForgotPasswordComponent } from './forgot-password/forgot-password.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; import { ResetPasswordComponent } from './reset-password/reset-password.component';
import { VerifyEmailComponent } from './components/verify-email/verify-email.component'; import { VerifyEmailComponent } from './verify-email/verify-email.component';
import { NotFoundComponent } from '../../core/components/not-found/not-found.component'; import { NotFoundComponent } from '../../../core/components/not-found/not-found.component';
export const AUTH_ROUTES: Routes = [ export const AUTH_ROUTES: Routes = [
{ {

View File

@@ -0,0 +1,157 @@
<main>
<div class="grid">
<app-kpi-card
*ngFor="let kpi of kpiData"
[value]="kpi.value"
[label]="kpi.label"
[color]="kpi.color"
[iconName]="kpi.iconName"
>
</app-kpi-card>
</div>
<div>
<app-orders-table
[data]="mockOrders"
[itemsPerPage]="5"
(view)="handleViewDetails($event)"
(edit)="handleEditOrder($event)"
(delete)="handleDeleteOrder($event)"
>
</app-orders-table>
</div>
</main>
<body>
<div class="dashboard-container">
<h1>Übersicht Admin-Dashboard</h1>
<div class="category">
<h2>Kategorie 1: Handlungsbedarf "Was muss ich jetzt tun?"</h2>
<div class="section">
<h3>Offene Bestellungen</h3>
<p>Anzahl und Liste der letzten 3-5 Bestellungen mit Status "Pending" oder "Processing".</p>
<p>Jede Zeile sollte direkt zur Detailseite der Bestellung verlinken.</p>
<ul>
<li class="sub-item">Bestellung #12345 - Kunde: Max Mustermann - Status: Pending <a href="#order-detail/12345" class="link-style">[Details]</a></li>
<li class="sub-item">Bestellung #12346 - Kunde: Anna Schmidt - Status: Processing <a href="#order-detail/12346" class="link-style">[Details]</a></li>
<li class="sub-item sub-item-last">Bestellung #12347 - Kunde: Peter Müller - Status: Pending <a href="#order-detail/12347" class="link-style">[Details]</a></li>
</ul>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminOrders <em>(clientseitig filtern)</em>
</div>
</div>
<div class="section">
<h3>Niedriger Lagerbestand</h3>
<p class="call-to-action">Warnung und Liste der Produkte mit stockQuantity unter einem kritischen Schwellenwert.</p>
<ul>
<li class="sub-item">Produkt A - Aktueller Bestand: 5</li>
<li class="sub-item">Produkt B - Aktueller Bestand: 2</li>
<li class="sub-item sub-item-last">Produkt C - Aktueller Bestand: 1</li>
</ul>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminAnalytics -&gt; inventoryStatus.productsWithLowStock
</div>
</div>
<div class="section">
<h3>Zu genehmigende Bewertungen</h3>
<p>Anzahl der Kundenbewertungen, die auf Freigabe warten.</p>
<p><strong>Anzahl:</strong> 7</p>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminReviews <em>(clientseitig nach isApproved === false filtern)</em>
</div>
</div>
</div>
<div class="category">
<h2>Kategorie 2: Key Performance Indicators (KPIs) "Wie läuft mein Geschäft?"</h2>
<div class="section">
<h3>Umsatz (letzte 30 Tage)</h3>
<p><strong>Wichtigste Kennzahl.</strong></p>
<p><strong>Betrag:</strong> 15.450,00 €</p>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminAnalytics -&gt; kpiSummary.totalRevenue
</div>
</div>
<div class="section">
<h3>Anzahl Bestellungen (letzte 30 Tage)</h3>
<p>Zeigt die allgemeine Aktivität.</p>
<p><strong>Anzahl:</strong> 320</p>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminAnalytics -&gt; kpiSummary.totalOrders
</div>
</div>
<div class="section">
<h3>Durchschnittlicher Bestellwert</h3>
<p>Gibt Aufschluss über das Kaufverhalten.</p>
<p><strong>Wert:</strong> 48,28 €</p>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminAnalytics -&gt; kpiSummary.averageOrderValue
</div>
</div>
<div class="section">
<h3>Neukunden (letzte 30 Tage)</h3>
<p>Wichtige Metrik für das Wachstum.</p>
<p><strong>Anzahl:</strong> 55</p>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminAnalytics -&gt; kpiSummary.newCustomersThisPeriod
</div>
</div>
</div>
<div class="category">
<h2>Kategorie 3: Jüngste Aktivitäten & Trends "Was passiert gerade?"</h2>
<div class="section">
<h3>Umsatzverlauf (Diagramm)</h3>
<p>Einfaches Linien- oder Balkendiagramm, das den Umsatz der letzten 7 oder 30 Tage anzeigt.</p>
<!-- Hier wäre normalerweise ein <canvas> oder <div> für ein Diagramm-Bibliothek -->
<div style="width: 100%; height: 200px; background-color: #eee; text-align: center; line-height: 200px; border-radius: 5px;">
[Platzhalter für Umsatzdiagramm]
</div>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminAnalytics -&gt; salesOverTime
</div>
</div>
<div class="section">
<h3>Letzte Bestellungen (Feed)</h3>
<p>Kurze, scrollbare Liste der letzten 10 Bestellungen (unabhängig vom Status) mit Kundenname und Bestellwert.</p>
<ul>
<li class="sub-item">Bestellung #12348 - Kunde: Lisa Meier - Wert: 75,50 €</li>
<li class="sub-item">Bestellung #12349 - Kunde: Tom Weber - Wert: 120,00 €</li>
<li class="sub-item">Bestellung #12350 - Kunde: Sarah Bauer - Wert: 35,20 €</li>
<li class="sub-item sub-item-last">Bestellung #12351 - Kunde: Tim Schulz - Wert: 90,00 €</li>
<!-- ... weitere Bestellungen ... -->
</ul>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminOrders
</div>
</div>
</div>
<div class="category">
<h2>Kategorie 4: Strategische Einblicke "Was funktioniert gut?"</h2>
<div class="section">
<h3>Top 5 Produkte</h3>
<p>Einfache Liste der meistverkauften Produkte im aktuellen Zeitraum (nach Umsatz oder verkauften Einheiten).</p>
<ol class="sub-item-list">
<li class="sub-item">Produkt X (Umsatz: 5000 €)</li>
<li class="sub-item">Produkt Y (Umsatz: 4500 €)</li>
<li class="sub-item">Produkt Z (Umsatz: 4000 €)</li>
<li class="sub-item">Produkt A (Umsatz: 3800 €)</li>
<li class="sub-item sub-item-last">Produkt B (Umsatz: 3500 €)</li>
</ol>
<div class="datasource">
<strong>Datenquelle:</strong> GET /api/v1/admin/AdminAnalytics -&gt; topPerformingProducts
</div>
</div>
</div>
</div>
</body>

View File

@@ -1,5 +1,5 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { DashboardPageComponent } from './components/dashboard-page/dashboard-page.component'; import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
// Importiere dein spezielles Layout für Auth-Seiten und alle Komponenten // Importiere dein spezielles Layout für Auth-Seiten und alle Komponenten
export const DASHBOARD_ROUTES: Routes = [ export const DASHBOARD_ROUTES: Routes = [

View File

@@ -1,75 +0,0 @@
<main>
<div class="grid">
<app-kpi-card
*ngFor="let kpi of kpiData"
[value]="kpi.value"
[label]="kpi.label"
[color]="kpi.color"
[iconName]="kpi.iconName"
>
</app-kpi-card>
</div>
<div>
<app-orders-table
[data]="mockOrders"
[itemsPerPage]="5"
(view)="handleViewDetails($event)"
(edit)="handleEditOrder($event)"
(delete)="handleDeleteOrder($event)"
>
</app-orders-table>
</div>
</main>
Exzellente Frage! Ein gutes Dashboard ist der Schlüssel zu einem effizienten
Admin-Panel. Es sollte auf einen Blick die wichtigsten Fragen beantworten: "Wie
läuft mein Geschäft?" und "Was muss ich jetzt tun?". Basierend auf den
Endpunkten und Datenmodellen, die in Ihrer Swagger-Datei verfügbar sind
(insbesondere AdminAnalytics, AdminOrders etc.), hier eine Gliederung der
Kerninformationen, von der höchsten zur niedrigsten Priorität: Kategorie 1:
Handlungsbedarf "Was muss ich jetzt tun?" Diese Sektion ist die wichtigste,
denn sie treibt die tägliche Arbeit an. Sie sollte prominent platziert sein.
Offene Bestellungen: Eine Zählung und eine Liste der letzten 3-5 Bestellungen
mit dem Status Pending oder Processing. Jede Zeile sollte direkt zur Detailseite
der Bestellung verlinken. Warum? Dies ist die primäre Aufgabe des
Shop-Betreibers: Bestellungen bearbeiten und versenden. Datenquelle: GET
/api/v1/admin/AdminOrders (clientseitig filtern). Niedriger Lagerbestand: Eine
Warnung und eine Liste der Produkte, deren stockQuantity unter einen kritischen
Schwellenwert gefallen ist. Warum? Verhindert "Out of Stock"-Situationen und
Umsatzverluste. Ermöglicht rechtzeitige Nachbestellungen. Datenquelle: GET
/api/v1/admin/AdminAnalytics -> inventoryStatus.productsWithLowStock. Zu
genehmigende Bewertungen: Eine Zählung der Kundenbewertungen, die auf Freigabe
warten. Warum? Fördert die Interaktion mit Kunden und stellt die Qualität der
Inhalte sicher. Datenquelle: GET /api/v1/admin/AdminReviews (clientseitig nach
isApproved === false filtern). Kategorie 2: Key Performance Indicators (KPIs)
"Wie läuft mein Geschäft?" Dies ist der "Auf einen Blick"-Überblick über die
Gesundheit des Shops. Ideal als große, klare Zahlen am oberen Rand des
Dashboards. Umsatz (letzte 30 Tage): Die wichtigste Kennzahl. Datenquelle: GET
/api/v1/admin/AdminAnalytics -> kpiSummary.totalRevenue. Anzahl Bestellungen
(letzte 30 Tage): Zeigt die allgemeine Aktivität. Datenquelle: GET
/api/v1/admin/AdminAnalytics -> kpiSummary.totalOrders. Durchschnittlicher
Bestellwert: Gibt Aufschluss über das Kaufverhalten. Datenquelle: GET
/api/v1/admin/AdminAnalytics -> kpiSummary.averageOrderValue. Neukunden (letzte
30 Tage): Eine wichtige Metrik für das Wachstum. Datenquelle: GET
/api/v1/admin/AdminAnalytics -> kpiSummary.newCustomersThisPeriod. Kategorie 3:
Jüngste Aktivitäten & Trends "Was passiert gerade?" Diese Sektion gibt ein
Gefühl für die aktuelle Dynamik und hilft, Muster zu erkennen. Umsatzverlauf
(Diagramm): Ein einfaches Linien- oder Balkendiagramm, das den Umsatz der
letzten 7 oder 30 Tage anzeigt. Warum? Visualisiert Trends, Hochs und Tiefs
(z.B. an Wochenenden). Datenquelle: GET /api/v1/admin/AdminAnalytics ->
salesOverTime. Letzte Bestellungen (Feed): Eine kurze, scrollbare Liste der
letzten 10 Bestellungen (unabhängig vom Status) mit Kundenname und Bestellwert.
Warum? Gibt dem Dashboard ein "lebendiges" Gefühl. Datenquelle: GET
/api/v1/admin/AdminOrders. Kategorie 4: Strategische Einblicke "Was
funktioniert gut?" Diese Informationen helfen bei Marketing- und
Bestandsentscheidungen. Top 5 Produkte: Eine einfache Liste der meistverkauften
Produkte im aktuellen Zeitraum (nach Umsatz oder verkauften Einheiten). Warum?
Zeigt, welche Produkte beworben werden sollten und wo der Lagerbestand immer
hoch sein muss. Datenquelle: GET /api/v1/admin/AdminAnalytics ->
topPerformingProducts. Vorschlag für ein Dashboard-Layout: Ein bewährtes Layout
könnte so aussehen: Obere Reihe: Die 4 großen KPI-Karten (Umsatz, Bestellungen,
Ø-Wert, Neukunden). Linke (breitere) Spalte: Ganz oben das
Umsatzverlauf-Diagramm. Darunter die Liste der "Letzten Bestellungen". Rechte
(schmalere) Spalte: Ganz oben die "Handlungsbedarf"-Box mit den offenen
Bestellungen und dem niedrigen Lagerbestand. Darunter die "Top 5
Produkte"-Liste. Mit dieser Auswahl an Informationen hat der Admin alles
Wichtige im Blick und kann direkt die notwendigen Aktionen ausführen.

View File

@@ -1,9 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [CommonModule],
})
export class DashboardModule {}

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
export const MATERIAL_MODULES = [ export const MODULES = [
CommonModule, CommonModule,
]; ];