This commit is contained in:
Tizian.Breuch
2025-09-17 21:17:27 +02:00
parent b8b0e167af
commit c066476cc3
31 changed files with 480 additions and 179 deletions

View File

@@ -5,7 +5,7 @@ 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/pages/dashboard-page/dashboard-page.component';
import { DashboardPageComponent } from './features/dashboard/components/dashboard-page/dashboard-page.component';
export const routes: Routes = [
{

View File

@@ -34,11 +34,13 @@ app-page-header {
}
app-sidebar {
margin: 1rem;
margin-top:0;
grid-area: 2 / 1 / 6 / 2;
}
.main-content {
background-color: var(--color-body-bg);
margin: 1rem;
border-radius: var(--border-radius-md) 0 0 0;
background-color: var(--color-body-bg-lighter);
padding: 1rem;
grid-area: 2 / 2 / 6 / 6;
overflow-y: auto; /* Fügt Scrollen hinzu, falls der Inhalt den Bereich übersteigt */
}

View File

@@ -0,0 +1,9 @@
import { KpiColor } from "../types/dashboard";
export interface Kpi {
value: string;
label: string;
color: KpiColor; // <-- Hier verwenden wir den importierten Typ
iconName: string;
}

View File

@@ -0,0 +1,19 @@
import { OrderStatus } from "../types/order";
export interface OrderUser {
name: string;
email: string;
avatarUrl: string;
}
export interface Order {
id: string;
user: OrderUser;
amount: string;
status: OrderStatus;
}
export interface StatusOption {
value: OrderStatus | 'all';
label: string;
}

View File

@@ -0,0 +1 @@
export type KpiColor = 'blue' | 'green' | 'orange' | 'purple';

View File

@@ -0,0 +1 @@
export type OrderStatus = 'completed' | 'processing' | 'cancelled' | 'info';

View File

@@ -0,0 +1 @@
export type PillStatus = 'success' | 'warning' | 'danger' | 'info';

View File

@@ -0,0 +1,75 @@
<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

@@ -0,0 +1,85 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { KpiCardComponent } from '../kpi-card/kpi-card.component';
import { OrdersTableComponent } from '../../../../shared/components/data-display/orders-table/orders-table.component';
import { Kpi } from '../../../../core/models/dashboard';
import { Order } from '../../../../core/models/order';
@Component({
selector: 'app-dashboard-page',
imports: [CommonModule,KpiCardComponent,OrdersTableComponent],
templateUrl: './dashboard-page.component.html',
styleUrl: './dashboard-page.component.css'
})
export class DashboardPageComponent {
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',
},
];
mockOrders: Order[] = [
{
id: 'a2d4b',
user: { name: 'Max Mustermann', email: 'max@test.de', avatarUrl: 'https://i.pravatar.cc/150?u=max' },
amount: '€129.99',
status: 'completed', // NEU: Sprechender Status
},
{
id: 'f8e9c',
user: { name: 'Erika Musterfrau', email: 'erika@test.de', avatarUrl: 'https://i.pravatar.cc/150?u=erika' },
amount: '€49.50',
status: 'processing', // NEU: Sprechender Status
},
{
id: 'h1g3j',
user: { name: 'John Doe', email: 'john.d@test.com', avatarUrl: 'https://i.pravatar.cc/150?u=john' },
amount: '€87.00',
status: 'cancelled', // NEU: Sprechender Status
},
{
id: 'h1g3j',
user: { name: 'John Doe', email: 'john.d@test.com', avatarUrl: 'https://i.pravatar.cc/150?u=john' },
amount: '€87.00',
status: 'info', // NEU: Sprechender Status
},
];
handleDeleteOrder(orderId: string): void {
console.log('Lösche Bestellung mit ID:', orderId);
// Hier könnten Sie z.B. einen Bestätigungs-Dialog öffnen
}
handleViewDetails(orderId: string): void {
console.log('View Bestellung mit ID:', orderId);
// Hier könnten Sie z.B. einen Bestätigungs-Dialog öffnen
}
handleEditOrder(orderId: string): void {
console.log('Edit Bestellung mit ID:', orderId);
// Hier könnten Sie z.B. einen Bestätigungs-Dialog öffnen
}
}

View File

@@ -1,9 +1,9 @@
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IconComponent } from '../../ui/icon/icon.component';
import { CardComponent } from '../../ui/card/card.component';
import { IconComponent } from '../../../../shared/components/ui/icon/icon.component';
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
export type KpiColor = 'blue' | 'green' | 'orange' | 'purple';
import { KpiColor } from '../../../../core/types/dashboard';
@Component({
selector: 'app-kpi-card',

View File

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

View File

@@ -1,56 +0,0 @@
<p>dashboard-page works!</p>
<p>
Key Performance Indicators (KPIs) Das "Big Picture" Dies sind die
wichtigsten Kennzahlen, die den Geschäftserfolg auf einen Blick zeigen. Sie
sollten prominent ganz oben platziert werden. Alle diese Daten stammen direkt
aus dem KpiSummaryDto, das Sie vom /api/v1/admin/AdminAnalytics-Endpunkt
bekommen. Gesamtumsatz (totalRevenue): Die wichtigste Kennzahl. Zeigt, wie
viel Geld der Shop einnimmt. Anzahl der Bestellungen (totalOrders): Zeigt die
Aktivität im Shop. Durchschnittlicher Bestellwert (averageOrderValue): Hilft
zu beurteilen, ob Kunden tendenziell mehr oder weniger pro Einkauf ausgeben.
Anzahl der Neukunden (newCustomersThisPeriod): Wichtig für das Wachstum.
Zeigt, wie viele neue Kunden im gewählten Zeitraum registriert wurden.
Darstellung: Am besten als einzelne, große "KPI-Karten", wie wir es im
vorherigen Beispiel hatten.
</p>
<br />
<p>
Handlungsrelevante Aufgaben "Was muss ich jetzt tun?" Das ist der vielleicht
wichtigste Teil des Dashboards. Er sollte dem Admin direkt zeigen, wo seine
Aufmerksamkeit benötigt wird. Bestellungen zur Bearbeitung: Eine Zählung und
eine Liste der letzten Bestellungen mit dem Status Pending oder Processing.
Dies signalisiert "Hier muss etwas verpackt und versendet werden!".
API-Endpunkt: GET /api/v1/admin/AdminOrders Sie müssten die zurückgegebene
Liste clientseitig nach dem Status filtern. Produkte mit niedrigem
Lagerbestand: Eine Warnung oder eine Liste von Produkten, deren stockQuantity
unter einen bestimmten Schwellenwert fällt. API-Endpunkt: Das
InventoryStatusDto aus dem AdminAnalytics-Endpunkt liefert Ihnen direkt die
Anzahl (productsWithLowStock). Zu genehmigende Bewertungen (Reviews): Falls
Sie ein Freigabeverfahren für Kundenbewertungen haben, ist eine Anzeige wie "3
neue Bewertungen warten auf Freigabe" extrem hilfreich. API-Endpunkt: GET
/api/v1/admin/AdminReviews Sie müssten hier eine Zählung der noch nicht
genehmigten Reviews durchführen. Darstellung: Als auffällige
Benachrichtigungs-Boxen oder "To-Do"-Listen.
</p>
<br />
<p>
Jüngste Aktivitäten "Was passiert gerade im Shop?" Diese Sektion gibt dem
Admin ein Gefühl für die aktuelle Dynamik im Shop. Letzte Bestellungen: Eine
kurze Liste der 5-10 neuesten Bestellungen, unabhängig vom Status. Zeigt den
Namen des Kunden, den Bestellwert und das Datum. API-Endpunkt: GET
/api/v1/admin/AdminOrders. Umsatzverlauf der letzten 30 Tage: Ein einfaches
Linien- oder Balkendiagramm, das den täglichen Umsatz anzeigt. API-Endpunkt:
Die salesOverTime-Daten aus dem AdminAnalytics-Endpunkt sind perfekt dafür
gemacht.
</p>
<br />
<p>
Top-Listen "Was läuft am besten?" Diese Informationen sind strategisch
wertvoll, um zu wissen, welche Produkte populär sind. Top 5 meistverkaufte
Produkte: Eine einfache Tabelle mit den Produkten, die den meisten Umsatz
generieren oder am häufigsten verkauft wurden. API-Endpunkt: Die Liste
topPerformingProducts aus dem AdminAnalytics-Endpunkt liefert Ihnen genau
diese Daten.
</p>
<br />

View File

@@ -1,11 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-dashboard-page',
imports: [],
templateUrl: './dashboard-page.component.html',
styleUrl: './dashboard-page.component.css'
})
export class DashboardPageComponent {
}

View File

@@ -9,13 +9,14 @@ 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,
KpiColor,
} from '../../../../shared/components/data-display/kpi-card/kpi-card.component';
import { KpiCardComponent } from '../../../dashboard/components/kpi-card/kpi-card.component';
import { KpiColor } from '../../../../core/types/dashboard';
import {
OrdersTableComponent,
Order,
} from '../../../../shared/components/data-display/orders-table/orders-table.component';
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
import { FormGroupComponent } from '../../../../shared/components/form/form-group/form-group.component';

View File

@@ -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 },

View File

@@ -88,7 +88,7 @@
}
.mono {
font-family: 'Courier New', Courier, monospace;
font-family: "Courier New", Courier, monospace;
}
.actions-cell {
@@ -101,4 +101,40 @@
text-align: center;
padding: 2rem;
color: var(--color-text-light);
}
}
.table-header {
/* display: flex; */
justify-content: space-between;
align-items: center;
gap: 1.5rem;
}
.table-header app-search-bar {
flex-grow: 1; /* Lässt die Suchleiste den verfügbaren Platz einnehmen */
/* max-width: 400px; Verhindert, dass sie zu breit wird */
}
.order-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
.order-searchbar {
}
.order-filter {
margin-left: auto;
grid-area: 1 / 2 / 2 / 5;
}
@media (max-width: 1200px) {
.order-searchbar {
grid-area: 1 / 1 / 2 / 3;
}
.order-filter {
margin-left: auto;
grid-area: 1 / 3 / 2 / 5;
}
}

View File

@@ -1,3 +1,19 @@
<div class="order-grid table-header">
<app-search-bar class="order-searchbar"
placeholder="Kunde oder Bestell-ID..."
(search)="onSearchChange($event)"
></app-search-bar>
<div class="order-filter">
<app-status-pill
*ngFor="let option of statusOptions"
[class.active]="selectedStatus === 'all'"
(click)="onStatusChange(option.value)"
>
{{ option.label }}
</app-status-pill>
</div>
</div>
<div class="table-container">
<table class="modern-table">
<thead>
@@ -28,9 +44,9 @@
<span class="mono">#{{ order.id }}</span>
</td>
<td>
<app-status-pill [status]="order.status">{{
order.statusText
}}</app-status-pill>
<app-status-pill [status]="order.status">
{{ order.status }}</app-status-pill
>
</td>
<td class="amount">{{ order.amount }}</td>
<td class="actions-cell text-right">
@@ -66,7 +82,7 @@
<div>
<app-paginator
[currentPage]="currentPage"
[totalItems]="data.length"
[totalItems]="filteredData.length"
[itemsPerPage]="itemsPerPage"
(pageChange)="onPageChange($event)"
>

View File

@@ -1,23 +1,19 @@
import { Component, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
import {
Component,
Input,
Output,
EventEmitter,
SimpleChanges,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { StatusPillComponent } from '../../ui/status-pill/status-pill.component';
import { ButtonComponent } from '../../ui/button/button.component';
import { PaginatorComponent } from '../paginator/paginator.component';
// Interfaces für die Datenstruktur
export interface OrderUser {
name: string;
email: string;
avatarUrl: string;
}
export interface Order {
id: string;
user: OrderUser;
amount: string;
status: 'success' | 'warning' | 'danger';
statusText: string;
}
import { Order } from '../../../../core/models/order';
import { StatusOption } from '../../../../core/models/order';
import { OrderStatus } from '../../../../core/types/order';
import { SearchBarComponent } from '../../layout/search-bar/search-bar.component';
@Component({
selector: 'app-orders-table',
@@ -26,42 +22,100 @@ export interface Order {
CommonModule,
StatusPillComponent,
ButtonComponent,
PaginatorComponent
PaginatorComponent,
SearchBarComponent,
],
templateUrl: './orders-table.component.html',
styleUrl: './orders-table.component.css'
styleUrl: './orders-table.component.css',
})
export class OrdersTableComponent {
// Nimmt die anzuzeigenden Bestelldaten entgegen
@Input() data: Order[] = [];
@Input() itemsPerPage = 5;
currentPage = 1;
displayedOrders: Order[] = [];
// Gibt Events für die Aktionen aus, mit der ID der betroffenen Bestellung
@Output() view = new EventEmitter<string>();
@Output() edit = new EventEmitter<string>();
@Output() delete = new EventEmitter<string>();
ngOnChanges(changes: SimpleChanges): void {
// Wenn sich die Eingabedaten (allOrders) ändern, aktualisieren wir die Ansicht
public searchTerm = '';
public selectedStatus: OrderStatus | 'all' = 'all';
public statusOptions: StatusOption[] = [
{ value: 'all', label: 'Alle' },
{ value: 'info', label: 'Info' },
{ value: 'completed', label: 'Abgeschlossen' },
{ value: 'processing', label: 'In Bearbeitung' },
{ value: 'cancelled', label: 'Storniert' },
];
public filteredData: Order[] = [];
public displayedOrders: Order[] = [];
public currentPage = 1;
private statusTextMap = new Map<OrderStatus, string>([
['completed', 'Abgeschlossen'],
['processing', 'In Bearbeitung'],
['cancelled', 'Storniert'],
['info', 'Info'],
]);
ngOnInit(): void {
this.applyFiltersAndPagination();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['data']) {
this.updateDisplayedOrders();
this.applyFiltersAndPagination();
}
}
// Wird aufgerufen, wenn der Paginator die Seite wechselt
onPageChange(newPage: number): void {
this.currentPage = newPage;
this.updateDisplayedOrders();
// Called on input from the search field
onSearchChange(term: string): void {
this.searchTerm = term;
this.applyFiltersAndPagination();
}
// Diese Methode berechnet, welcher Teil der Daten angezeigt werden soll
private updateDisplayedOrders(): void {
// Called when a status pill is clicked
onStatusChange(status: OrderStatus | 'all'): void {
this.selectedStatus = status;
this.applyFiltersAndPagination();
}
onPageChange(newPage: number): void {
this.currentPage = newPage;
this.applyFiltersAndPagination();
}
private applyFiltersAndPagination(): void {
let processedData = [...this.data];
if (this.selectedStatus !== 'all') {
processedData = processedData.filter(
(order) => order.status === this.selectedStatus
);
}
if (this.searchTerm) {
const lowerCaseSearchTerm = this.searchTerm.toLowerCase();
processedData = processedData.filter((order) => {
// Holt den deutschen Status-Text für die aktuelle Bestellung
const statusText = this.statusTextMap.get(order.status) || '';
return (
order.user.name.toLowerCase().includes(lowerCaseSearchTerm) ||
order.user.email.toLowerCase().includes(lowerCaseSearchTerm) ||
order.id.toLowerCase().includes(lowerCaseSearchTerm) ||
// === NEU: Suche im Status-Text ===
statusText.toLowerCase().includes(lowerCaseSearchTerm)
);
});
}
this.filteredData = processedData;
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
this.displayedOrders = this.data.slice(startIndex, endIndex);
this.displayedOrders = this.filteredData.slice(startIndex, endIndex);
}
}
}

View File

@@ -7,7 +7,6 @@ import {
AfterViewInit,
} from '@angular/core';
import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common';
import { SearchBarComponent } from '../search-bar/search-bar.component';
import { UserProfileComponent } from '../user-profile/user-profile.component';
import { SlideToggleComponent } from '../../form/slide-toggle/slide-toggle.component';
import { FormsModule } from '@angular/forms';
@@ -17,7 +16,6 @@ import { FormsModule } from '@angular/forms';
standalone: true,
imports: [
CommonModule,
SearchBarComponent,
UserProfileComponent,
SlideToggleComponent,
FormsModule,

View File

@@ -5,7 +5,7 @@
.search-bar {
position: relative;
width: 300px;
width: 100%
}
.search-bar svg {
position: absolute;

View File

@@ -3,5 +3,5 @@
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input type="text" placeholder="Dashboard durchsuchen..." (input)="onSearch($event)" />
<input type="text" placeholder="Durchsuchen..." (input)="onSearch($event)" />
</div>

View File

@@ -1,6 +1,6 @@
/* Globale Variablen für dieses Bauteil */
:host {
--sidebar-width-expanded: 280px;
--sidebar-width-expanded: 200px;
--sidebar-width-collapsed: 96px;
--sidebar-padding: 1rem;
--sidebar-margin: 1rem;
@@ -15,9 +15,11 @@
flex-direction: column;
gap: 1.5rem; /* Reduzierter Abstand */
width: var(--sidebar-width-expanded);
height: calc(100vh - 2rem - 46px);
height: calc(100vh - 4rem - 46px);
padding: var(--sidebar-padding);
/* border-right: 1px solid var(--color-border); */
padding-top:0;
/* border: 1px solid var(--color-border); */
/* border-radius: var(--border-radius-md); */
transition: width var(--transition-speed) var(--transition-ease);
/* background-color: var(--color-surface); */
@@ -45,16 +47,17 @@
white-space: nowrap;
transition: background-color 0.2s, color 0.2s;
overflow: hidden;
position: relative;
position: relative;
cursor: pointer;
}
.nav-item:hover {
background-color: var(--color-body-bg-hover);
background-color: var(--color-surface);
color: var(--color-text);
}
.nav-item.active {
background: var(--color-primary);
color: #fff;
background: var(--color-body-bg-active);
color: var(--color-text);
box-shadow: var(--box-shadow-sm);
}
.nav-item app-icon {
@@ -64,7 +67,7 @@
position: relative;
z-index: 2;
background-color: transparent;
transition: background-color 0.2s;
transition: background-color 0.2s;
}
.nav-item span {
white-space: nowrap;
@@ -94,8 +97,8 @@
/* 4. Zustandsabhängige Hintergrundfarben für das Icon */
.nav-item:hover app-icon {
background-color: var(--color-body-bg-hover);
background: transparent;
}
.nav-item.active app-icon {
background: var(--color-primary);
}
background: transparent;
}

View File

@@ -28,6 +28,4 @@
</div>
</nav>
</aside>

View File

@@ -1,29 +1,66 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Component, Inject, PLATFORM_ID, OnInit } from '@angular/core';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { IconComponent } from '../../ui/icon/icon.component';
import { ButtonComponent } from '../../ui/button/button.component';
@Component({
selector: 'app-sidebar',
standalone: true,
imports: [CommonModule, IconComponent,ButtonComponent],
imports: [CommonModule, IconComponent],
templateUrl: './sidebar.component.html',
styleUrl: './sidebar.component.css'
})
export class SidebarComponent {
// Dummy-Eigenschaft für die aktive Route, damit der Code funktioniert
export class SidebarComponent implements OnInit { // 1. OnInit implementieren
// Key für localStorage, genau wie beim Dark Mode
private readonly sidebarCollapsedKey = 'app-sidebar-collapsed-setting';
// Dummy-Eigenschaft für die aktive Route
activeRoute = 'dashboard';
// NEU: Eigenschaft, um den Zustand der Sidebar zu speichern
// Der Standardwert ist 'false' (aufgeklappt), wird aber sofort überschrieben
public isCollapsed = false;
// Dummy-Methode, damit der Code funktioniert
// 2. PLATFORM_ID injizieren, um localStorage sicher zu verwenden
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}
// 3. Beim Start der Komponente den gespeicherten Zustand laden
ngOnInit(): void {
this.loadCollapsedState();
}
// Dummy-Methode
setActive(route: string): void {
this.activeRoute = route;
}
// NEU: Methode, um den Zustand umzuschalten
// 4. Die Umschalt-Methode aktualisieren, damit sie den neuen Zustand speichert
toggleSidebar(): void {
// Zuerst den Zustand ändern
this.isCollapsed = !this.isCollapsed;
// Dann den neuen Zustand speichern
this.saveCollapsedState();
}
// 5. Methode zum Laden des Zustands (kopiert vom Dark-Mode-Muster)
private loadCollapsedState(): void {
if (isPlatformBrowser(this.platformId)) {
try {
const storedValue = localStorage.getItem(this.sidebarCollapsedKey);
// Setze den Zustand der Komponente basierend auf dem gespeicherten Wert
this.isCollapsed = storedValue === 'true';
} catch (e) {
console.error('Could not access localStorage for sidebar state:', e);
}
}
}
// 6. Methode zum Speichern des Zustands (kopiert vom Dark-Mode-Muster)
private saveCollapsedState(): void {
if (isPlatformBrowser(this.platformId)) {
try {
localStorage.setItem(this.sidebarCollapsedKey, String(this.isCollapsed));
} catch (e) {
console.error('Could not write to localStorage for sidebar state:', e);
}
}
}
}

View File

@@ -1,11 +1,3 @@
<span
class="status-pill"
[ngClass]="{
'pill-success': status === 'success',
'pill-warning': status === 'warning',
'pill-danger': status === 'danger',
'pill-info': status === 'info'
}">
<!-- Der Text für die Pille wird von außen über ng-content eingefügt -->
<ng-content></ng-content>
</span>
<div class="status-pill" [ngClass]="cssClass">
{{ displayText }}
</div>

View File

@@ -1,16 +1,34 @@
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
// Definiert die erlaubten Status-Typen für Typsicherheit
type PillStatus = 'success' | 'warning' | 'danger' | 'info';
import { Component, Input, OnChanges } from '@angular/core';
import { CommonModule, NgClass } from '@angular/common';
import { OrderStatus } from '../../../../core/types/order';
@Component({
selector: 'app-status-pill',
standalone: true,
imports: [CommonModule],
imports: [CommonModule, NgClass],
templateUrl: './status-pill.component.html',
styleUrl: './status-pill.component.css'
})
export class StatusPillComponent {
@Input() status: PillStatus = 'info';
export class StatusPillComponent implements OnChanges {
// Nimmt jetzt den neuen, sprechenden Status entgegen
@Input() status: OrderStatus = 'info';
// Diese Eigenschaften werden vom Template verwendet
public displayText = '';
public cssClass = '';
// Eine Map, die Statusnamen auf Text und CSS-Klasse abbildet
private statusMap = new Map<OrderStatus, { text: string, css: string }>([
['completed', { text: 'Abgeschlossen', css: 'pill-success' }],
['processing', { text: 'In Bearbeitung', css: 'pill-warning' }],
['cancelled', { text: 'Storniert', css: 'pill-danger' }],
['info', { text: 'Info', css: 'pill-info' }]
]);
ngOnChanges(): void {
// Wenn sich der Input-Status ändert, aktualisieren wir Text und Klasse
const details = this.statusMap.get(this.status) || this.statusMap.get('info')!;
this.displayText = details.text;
this.cssClass = details.css;
}
}