aufräumen
This commit is contained in:
@@ -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">
|
||||||
@@ -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 = '';
|
||||||
@@ -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 },
|
||||||
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
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';
|
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 = [
|
||||||
{
|
{
|
||||||
@@ -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 { 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 = [
|
||||||
@@ -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,
|
CommonModule,
|
||||||
|
|
||||||
];
|
];
|
||||||
Reference in New Issue
Block a user