aufräumen
This commit is contained in:
@@ -3,17 +3,17 @@
|
||||
<app-page-header></app-page-header>
|
||||
</div>
|
||||
|
||||
<app-kpi-card
|
||||
<!-- <app-kpi-card
|
||||
*ngFor="let kpi of kpiData"
|
||||
[value]="kpi.value"
|
||||
[label]="kpi.label"
|
||||
[color]="kpi.color"
|
||||
[iconName]="kpi.iconName"
|
||||
>
|
||||
</app-kpi-card>
|
||||
</app-kpi-card> -->
|
||||
|
||||
<div class="grid-col-span-4">
|
||||
<app-form-group title="Data-display Components">
|
||||
<!-- <app-form-group title="Data-display Components">
|
||||
<app-orders-table
|
||||
[data]="ordersData"
|
||||
[itemsPerPage]="5"
|
||||
@@ -22,7 +22,7 @@
|
||||
(delete)="handleDeleteOrder($event)"
|
||||
>
|
||||
</app-orders-table>
|
||||
</app-form-group>
|
||||
</app-form-group> -->
|
||||
</div>
|
||||
|
||||
<div class="grid-col-span-2">
|
||||
@@ -9,7 +9,7 @@ import { DOCUMENT } from '@angular/common';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
|
||||
// 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';
|
||||
|
||||
@@ -86,134 +86,134 @@ export class Demo2Component {
|
||||
demoForm: FormGroup;
|
||||
|
||||
|
||||
kpiData: Kpi[] = [
|
||||
{
|
||||
value: '€ 14.750',
|
||||
label: 'Umsatz',
|
||||
color: 'green',
|
||||
iconName: 'placeholder',
|
||||
},
|
||||
{
|
||||
value: '1.284',
|
||||
label: 'Neue Nutzer',
|
||||
color: 'blue',
|
||||
iconName: 'placeholder',
|
||||
},
|
||||
{
|
||||
value: '312',
|
||||
label: 'Bestellungen',
|
||||
color: 'orange',
|
||||
iconName: 'placeholder',
|
||||
},
|
||||
{
|
||||
value: '99.8%',
|
||||
label: 'Verfügbarkeit',
|
||||
color: 'purple',
|
||||
iconName: 'placeholder',
|
||||
},
|
||||
];
|
||||
// kpiData: Kpi[] = [
|
||||
// {
|
||||
// value: '€ 14.750',
|
||||
// label: 'Umsatz',
|
||||
// color: 'green',
|
||||
// iconName: 'placeholder',
|
||||
// },
|
||||
// {
|
||||
// value: '1.284',
|
||||
// label: 'Neue Nutzer',
|
||||
// color: 'blue',
|
||||
// iconName: 'placeholder',
|
||||
// },
|
||||
// {
|
||||
// value: '312',
|
||||
// label: 'Bestellungen',
|
||||
// color: 'orange',
|
||||
// iconName: 'placeholder',
|
||||
// },
|
||||
// {
|
||||
// value: '99.8%',
|
||||
// label: 'Verfügbarkeit',
|
||||
// color: 'purple',
|
||||
// iconName: 'placeholder',
|
||||
// },
|
||||
// ];
|
||||
|
||||
ordersData: Order[] = [
|
||||
{
|
||||
id: '10543',
|
||||
user: {
|
||||
name: 'Max Mustermann',
|
||||
email: 'max.m@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=max',
|
||||
},
|
||||
amount: '€ 129,99',
|
||||
status: 'success',
|
||||
statusText: 'Abgeschlossen',
|
||||
},
|
||||
{
|
||||
id: '10542',
|
||||
user: {
|
||||
name: 'Erika Mustermann',
|
||||
email: 'erika.m@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=erika',
|
||||
},
|
||||
amount: '€ 49,50',
|
||||
status: 'warning',
|
||||
statusText: 'In Bearbeitung',
|
||||
},
|
||||
{
|
||||
id: '10541',
|
||||
user: {
|
||||
name: 'Peter Pan',
|
||||
email: 'peter.p@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=peter',
|
||||
},
|
||||
amount: '€ 87,00',
|
||||
status: 'danger',
|
||||
statusText: 'Storniert',
|
||||
},
|
||||
{
|
||||
id: '10543',
|
||||
user: {
|
||||
name: 'Max Mustermann',
|
||||
email: 'max.m@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=max',
|
||||
},
|
||||
amount: '€ 129,99',
|
||||
status: 'success',
|
||||
statusText: 'Abgeschlossen',
|
||||
},
|
||||
{
|
||||
id: '10542',
|
||||
user: {
|
||||
name: 'Erika Mustermann',
|
||||
email: 'erika.m@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=erika',
|
||||
},
|
||||
amount: '€ 49,50',
|
||||
status: 'warning',
|
||||
statusText: 'In Bearbeitung',
|
||||
},
|
||||
{
|
||||
id: '10541',
|
||||
user: {
|
||||
name: 'Peter Pan',
|
||||
email: 'peter.p@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=peter',
|
||||
},
|
||||
amount: '€ 87,00',
|
||||
status: 'danger',
|
||||
statusText: 'Storniert',
|
||||
},
|
||||
{
|
||||
id: '10543',
|
||||
user: {
|
||||
name: 'Max Mustermann',
|
||||
email: 'max.m@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=max',
|
||||
},
|
||||
amount: '€ 129,99',
|
||||
status: 'success',
|
||||
statusText: 'Abgeschlossen',
|
||||
},
|
||||
{
|
||||
id: '10542',
|
||||
user: {
|
||||
name: 'Erika Mustermann',
|
||||
email: 'erika.m@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=erika',
|
||||
},
|
||||
amount: '€ 49,50',
|
||||
status: 'warning',
|
||||
statusText: 'In Bearbeitung',
|
||||
},
|
||||
{
|
||||
id: '10541',
|
||||
user: {
|
||||
name: 'Peter Pan',
|
||||
email: 'peter.p@example.com',
|
||||
avatarUrl: 'https://i.pravatar.cc/40?u=peter',
|
||||
},
|
||||
amount: '€ 87,00',
|
||||
status: 'danger',
|
||||
statusText: 'Storniert',
|
||||
},
|
||||
];
|
||||
// ordersData: Order[] = [
|
||||
// {
|
||||
// id: '10543',
|
||||
// user: {
|
||||
// name: 'Max Mustermann',
|
||||
// email: 'max.m@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=max',
|
||||
// },
|
||||
// amount: '€ 129,99',
|
||||
// status: 'success',
|
||||
// statusText: 'Abgeschlossen',
|
||||
// },
|
||||
// {
|
||||
// id: '10542',
|
||||
// user: {
|
||||
// name: 'Erika Mustermann',
|
||||
// email: 'erika.m@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=erika',
|
||||
// },
|
||||
// amount: '€ 49,50',
|
||||
// status: 'warning',
|
||||
// statusText: 'In Bearbeitung',
|
||||
// },
|
||||
// {
|
||||
// id: '10541',
|
||||
// user: {
|
||||
// name: 'Peter Pan',
|
||||
// email: 'peter.p@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=peter',
|
||||
// },
|
||||
// amount: '€ 87,00',
|
||||
// status: 'danger',
|
||||
// statusText: 'Storniert',
|
||||
// },
|
||||
// {
|
||||
// id: '10543',
|
||||
// user: {
|
||||
// name: 'Max Mustermann',
|
||||
// email: 'max.m@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=max',
|
||||
// },
|
||||
// amount: '€ 129,99',
|
||||
// status: 'success',
|
||||
// statusText: 'Abgeschlossen',
|
||||
// },
|
||||
// {
|
||||
// id: '10542',
|
||||
// user: {
|
||||
// name: 'Erika Mustermann',
|
||||
// email: 'erika.m@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=erika',
|
||||
// },
|
||||
// amount: '€ 49,50',
|
||||
// status: 'warning',
|
||||
// statusText: 'In Bearbeitung',
|
||||
// },
|
||||
// {
|
||||
// id: '10541',
|
||||
// user: {
|
||||
// name: 'Peter Pan',
|
||||
// email: 'peter.p@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=peter',
|
||||
// },
|
||||
// amount: '€ 87,00',
|
||||
// status: 'danger',
|
||||
// statusText: 'Storniert',
|
||||
// },
|
||||
// {
|
||||
// id: '10543',
|
||||
// user: {
|
||||
// name: 'Max Mustermann',
|
||||
// email: 'max.m@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=max',
|
||||
// },
|
||||
// amount: '€ 129,99',
|
||||
// status: 'success',
|
||||
// statusText: 'Abgeschlossen',
|
||||
// },
|
||||
// {
|
||||
// id: '10542',
|
||||
// user: {
|
||||
// name: 'Erika Mustermann',
|
||||
// email: 'erika.m@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=erika',
|
||||
// },
|
||||
// amount: '€ 49,50',
|
||||
// status: 'warning',
|
||||
// statusText: 'In Bearbeitung',
|
||||
// },
|
||||
// {
|
||||
// id: '10541',
|
||||
// user: {
|
||||
// name: 'Peter Pan',
|
||||
// email: 'peter.p@example.com',
|
||||
// avatarUrl: 'https://i.pravatar.cc/40?u=peter',
|
||||
// },
|
||||
// amount: '€ 87,00',
|
||||
// status: 'danger',
|
||||
// statusText: 'Storniert',
|
||||
// },
|
||||
// ];
|
||||
|
||||
aktuellesPasswort: any = '';
|
||||
neuesPasswort: any = '';
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
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 = [
|
||||
{
|
||||
@@ -17,12 +17,12 @@ export const DEMO_ROUTES: Routes = [
|
||||
component: Demo1Component,
|
||||
title: 'Demo1',
|
||||
},
|
||||
// {
|
||||
// // Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal.
|
||||
// path: '2',
|
||||
// component: Demo2Component,
|
||||
// title: 'Demo2',
|
||||
// },
|
||||
{
|
||||
// Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal.
|
||||
path: '2',
|
||||
component: Demo2Component,
|
||||
title: 'Demo2',
|
||||
},
|
||||
// Hier könntest du weitere Routen wie '2', '3' etc. hinzufügen,
|
||||
// die andere Komponenten laden.
|
||||
// { path: '2', component: AnotherDemoComponent },
|
||||
@@ -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 { 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 = [
|
||||
{
|
||||
@@ -17,11 +18,13 @@ export const routes: Routes = [
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: DefaultLayoutComponent,
|
||||
// canActivate: [authGuard],
|
||||
// data: { requiredRole: 'Admin' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () =>
|
||||
import('./features/dashboard/dashboard.routes').then(
|
||||
import('./features/components/dashboard/dashboard.routes').then(
|
||||
(r) => r.DASHBOARD_ROUTES
|
||||
),
|
||||
},
|
||||
@@ -30,12 +33,7 @@ export const routes: Routes = [
|
||||
{
|
||||
path: 'auth',
|
||||
loadChildren: () =>
|
||||
import('./features/auth/auth.routes').then((r) => r.AUTH_ROUTES),
|
||||
},
|
||||
{
|
||||
path: 'demo',
|
||||
loadChildren: () =>
|
||||
import('./features/demo/demo.routes').then((r) => r.DEMO_ROUTES),
|
||||
import('./features/components/auth/auth.routes').then((r) => r.AUTH_ROUTES),
|
||||
},
|
||||
{
|
||||
path: 'access-denied',
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
15
src/app/core/models/auth.models.ts
Normal file
15
src/app/core/models/auth.models.ts
Normal 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[];
|
||||
}
|
||||
121
src/app/core/services/auth.service.ts
Normal file
121
src/app/core/services/auth.service.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
5
src/app/core/tokens/api-url.token.ts
Normal file
5
src/app/core/tokens/api-url.token.ts
Normal 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
5
src/app/environment.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// /src/environments/environment.ts
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: 'https://shopsolution-backend.tzbre.dev/api/v1',
|
||||
};
|
||||
@@ -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 {}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
// Importiere dein spezielles Layout für Auth-Seiten und alle Komponenten
|
||||
import { AuthLayoutComponent } from './components/auth-layout/auth-layout.component';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { RegisterComponent } from './components/register/register.component';
|
||||
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
|
||||
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
|
||||
import { VerifyEmailComponent } from './components/verify-email/verify-email.component';
|
||||
import { NotFoundComponent } from '../../core/components/not-found/not-found.component';
|
||||
import { AuthLayoutComponent } from './auth-layout/auth-layout.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { ForgotPasswordComponent } from './forgot-password/forgot-password.component';
|
||||
import { ResetPasswordComponent } from './reset-password/reset-password.component';
|
||||
import { VerifyEmailComponent } from './verify-email/verify-email.component';
|
||||
import { NotFoundComponent } from '../../../core/components/not-found/not-found.component';
|
||||
|
||||
export const AUTH_ROUTES: Routes = [
|
||||
{
|
||||
@@ -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 -> 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 -> 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 -> 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 -> 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 -> 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 -> 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 -> topPerformingProducts
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@@ -1,5 +1,5 @@
|
||||
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
|
||||
|
||||
export const DASHBOARD_ROUTES: Routes = [
|
||||
@@ -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.
|
||||
@@ -1,9 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [CommonModule],
|
||||
})
|
||||
export class DashboardModule {}
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
|
||||
|
||||
export const MATERIAL_MODULES = [
|
||||
export const MODULES = [
|
||||
CommonModule,
|
||||
|
||||
];
|
||||
Reference in New Issue
Block a user