init
This commit is contained in:
@@ -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 { 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 = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,11 +34,13 @@ app-page-header {
|
|||||||
}
|
}
|
||||||
app-sidebar {
|
app-sidebar {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
|
margin-top:0;
|
||||||
grid-area: 2 / 1 / 6 / 2;
|
grid-area: 2 / 1 / 6 / 2;
|
||||||
}
|
}
|
||||||
.main-content {
|
.main-content {
|
||||||
background-color: var(--color-body-bg);
|
border-radius: var(--border-radius-md) 0 0 0;
|
||||||
margin: 1rem;
|
background-color: var(--color-body-bg-lighter);
|
||||||
|
padding: 1rem;
|
||||||
grid-area: 2 / 2 / 6 / 6;
|
grid-area: 2 / 2 / 6 / 6;
|
||||||
overflow-y: auto; /* Fügt Scrollen hinzu, falls der Inhalt den Bereich übersteigt */
|
overflow-y: auto; /* Fügt Scrollen hinzu, falls der Inhalt den Bereich übersteigt */
|
||||||
}
|
}
|
||||||
|
|||||||
9
src/app/core/models/dashboard.ts
Normal file
9
src/app/core/models/dashboard.ts
Normal 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;
|
||||||
|
}
|
||||||
19
src/app/core/models/order.ts
Normal file
19
src/app/core/models/order.ts
Normal 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;
|
||||||
|
}
|
||||||
1
src/app/core/types/dashboard.ts
Normal file
1
src/app/core/types/dashboard.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type KpiColor = 'blue' | 'green' | 'orange' | 'purple';
|
||||||
1
src/app/core/types/order.ts
Normal file
1
src/app/core/types/order.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type OrderStatus = 'completed' | 'processing' | 'cancelled' | 'info';
|
||||||
1
src/app/core/types/status-pill.ts
Normal file
1
src/app/core/types/status-pill.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type PillStatus = 'success' | 'warning' | 'danger' | 'info';
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { IconComponent } from '../../ui/icon/icon.component';
|
import { IconComponent } from '../../../../shared/components/ui/icon/icon.component';
|
||||||
import { CardComponent } from '../../ui/card/card.component';
|
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
|
||||||
|
|
||||||
export type KpiColor = 'blue' | 'green' | 'orange' | 'purple';
|
import { KpiColor } from '../../../../core/types/dashboard';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-kpi-card',
|
selector: 'app-kpi-card',
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Routes } from '@angular/router';
|
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
|
// Importiere dein spezielles Layout für Auth-Seiten und alle Komponenten
|
||||||
|
|
||||||
export const DASHBOARD_ROUTES: Routes = [
|
export const DASHBOARD_ROUTES: Routes = [
|
||||||
|
|||||||
@@ -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 />
|
|
||||||
@@ -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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,14 @@ 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 {
|
import { KpiCardComponent } from '../../../dashboard/components/kpi-card/kpi-card.component';
|
||||||
KpiCardComponent,
|
|
||||||
KpiColor,
|
import { KpiColor } from '../../../../core/types/dashboard';
|
||||||
} from '../../../../shared/components/data-display/kpi-card/kpi-card.component';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OrdersTableComponent,
|
OrdersTableComponent,
|
||||||
Order,
|
|
||||||
} from '../../../../shared/components/data-display/orders-table/orders-table.component';
|
} from '../../../../shared/components/data-display/orders-table/orders-table.component';
|
||||||
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
|
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
|
||||||
import { FormGroupComponent } from '../../../../shared/components/form/form-group/form-group.component';
|
import { FormGroupComponent } from '../../../../shared/components/form/form-group/form-group.component';
|
||||||
@@ -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 },
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mono {
|
.mono {
|
||||||
font-family: 'Courier New', Courier, monospace;
|
font-family: "Courier New", Courier, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-cell {
|
.actions-cell {
|
||||||
@@ -102,3 +102,39 @@
|
|||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
color: var(--color-text-light);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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">
|
<div class="table-container">
|
||||||
<table class="modern-table">
|
<table class="modern-table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -28,9 +44,9 @@
|
|||||||
<span class="mono">#{{ order.id }}</span>
|
<span class="mono">#{{ order.id }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<app-status-pill [status]="order.status">{{
|
<app-status-pill [status]="order.status">
|
||||||
order.statusText
|
{{ order.status }}</app-status-pill
|
||||||
}}</app-status-pill>
|
>
|
||||||
</td>
|
</td>
|
||||||
<td class="amount">{{ order.amount }}</td>
|
<td class="amount">{{ order.amount }}</td>
|
||||||
<td class="actions-cell text-right">
|
<td class="actions-cell text-right">
|
||||||
@@ -66,7 +82,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<app-paginator
|
<app-paginator
|
||||||
[currentPage]="currentPage"
|
[currentPage]="currentPage"
|
||||||
[totalItems]="data.length"
|
[totalItems]="filteredData.length"
|
||||||
[itemsPerPage]="itemsPerPage"
|
[itemsPerPage]="itemsPerPage"
|
||||||
(pageChange)="onPageChange($event)"
|
(pageChange)="onPageChange($event)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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 { CommonModule } from '@angular/common';
|
||||||
import { StatusPillComponent } from '../../ui/status-pill/status-pill.component';
|
import { StatusPillComponent } from '../../ui/status-pill/status-pill.component';
|
||||||
import { ButtonComponent } from '../../ui/button/button.component';
|
import { ButtonComponent } from '../../ui/button/button.component';
|
||||||
import { PaginatorComponent } from '../paginator/paginator.component';
|
import { PaginatorComponent } from '../paginator/paginator.component';
|
||||||
|
|
||||||
// Interfaces für die Datenstruktur
|
import { Order } from '../../../../core/models/order';
|
||||||
export interface OrderUser {
|
import { StatusOption } from '../../../../core/models/order';
|
||||||
name: string;
|
import { OrderStatus } from '../../../../core/types/order';
|
||||||
email: string;
|
import { SearchBarComponent } from '../../layout/search-bar/search-bar.component';
|
||||||
avatarUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Order {
|
|
||||||
id: string;
|
|
||||||
user: OrderUser;
|
|
||||||
amount: string;
|
|
||||||
status: 'success' | 'warning' | 'danger';
|
|
||||||
statusText: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-orders-table',
|
selector: 'app-orders-table',
|
||||||
@@ -26,42 +22,100 @@ export interface Order {
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
StatusPillComponent,
|
StatusPillComponent,
|
||||||
ButtonComponent,
|
ButtonComponent,
|
||||||
PaginatorComponent
|
PaginatorComponent,
|
||||||
|
SearchBarComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './orders-table.component.html',
|
templateUrl: './orders-table.component.html',
|
||||||
styleUrl: './orders-table.component.css'
|
styleUrl: './orders-table.component.css',
|
||||||
})
|
})
|
||||||
export class OrdersTableComponent {
|
export class OrdersTableComponent {
|
||||||
// Nimmt die anzuzeigenden Bestelldaten entgegen
|
// Nimmt die anzuzeigenden Bestelldaten entgegen
|
||||||
@Input() data: Order[] = [];
|
@Input() data: Order[] = [];
|
||||||
@Input() itemsPerPage = 5;
|
@Input() itemsPerPage = 5;
|
||||||
|
|
||||||
currentPage = 1;
|
|
||||||
displayedOrders: Order[] = [];
|
|
||||||
|
|
||||||
// Gibt Events für die Aktionen aus, mit der ID der betroffenen Bestellung
|
// Gibt Events für die Aktionen aus, mit der ID der betroffenen Bestellung
|
||||||
@Output() view = new EventEmitter<string>();
|
@Output() view = new EventEmitter<string>();
|
||||||
@Output() edit = new EventEmitter<string>();
|
@Output() edit = new EventEmitter<string>();
|
||||||
@Output() delete = new EventEmitter<string>();
|
@Output() delete = new EventEmitter<string>();
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
public searchTerm = '';
|
||||||
// Wenn sich die Eingabedaten (allOrders) ändern, aktualisieren wir die Ansicht
|
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']) {
|
if (changes['data']) {
|
||||||
this.updateDisplayedOrders();
|
this.applyFiltersAndPagination();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wird aufgerufen, wenn der Paginator die Seite wechselt
|
// Called on input from the search field
|
||||||
|
onSearchChange(term: string): void {
|
||||||
|
this.searchTerm = term;
|
||||||
|
this.applyFiltersAndPagination();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when a status pill is clicked
|
||||||
|
onStatusChange(status: OrderStatus | 'all'): void {
|
||||||
|
this.selectedStatus = status;
|
||||||
|
this.applyFiltersAndPagination();
|
||||||
|
}
|
||||||
|
|
||||||
onPageChange(newPage: number): void {
|
onPageChange(newPage: number): void {
|
||||||
this.currentPage = newPage;
|
this.currentPage = newPage;
|
||||||
this.updateDisplayedOrders();
|
this.applyFiltersAndPagination();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diese Methode berechnet, welcher Teil der Daten angezeigt werden soll
|
private applyFiltersAndPagination(): void {
|
||||||
private updateDisplayedOrders(): 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 startIndex = (this.currentPage - 1) * this.itemsPerPage;
|
||||||
const endIndex = startIndex + this.itemsPerPage;
|
const endIndex = startIndex + this.itemsPerPage;
|
||||||
this.displayedOrders = this.data.slice(startIndex, endIndex);
|
this.displayedOrders = this.filteredData.slice(startIndex, endIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common';
|
import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
import { SearchBarComponent } from '../search-bar/search-bar.component';
|
|
||||||
import { UserProfileComponent } from '../user-profile/user-profile.component';
|
import { UserProfileComponent } from '../user-profile/user-profile.component';
|
||||||
import { SlideToggleComponent } from '../../form/slide-toggle/slide-toggle.component';
|
import { SlideToggleComponent } from '../../form/slide-toggle/slide-toggle.component';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
@@ -17,7 +16,6 @@ import { FormsModule } from '@angular/forms';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SearchBarComponent,
|
|
||||||
UserProfileComponent,
|
UserProfileComponent,
|
||||||
SlideToggleComponent,
|
SlideToggleComponent,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 300px;
|
width: 100%
|
||||||
}
|
}
|
||||||
.search-bar svg {
|
.search-bar svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
<circle cx="11" cy="11" r="8"></circle>
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||||
</svg>
|
</svg>
|
||||||
<input type="text" placeholder="Dashboard durchsuchen..." (input)="onSearch($event)" />
|
<input type="text" placeholder="Durchsuchen..." (input)="onSearch($event)" />
|
||||||
</div>
|
</div>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* Globale Variablen für dieses Bauteil */
|
/* Globale Variablen für dieses Bauteil */
|
||||||
:host {
|
:host {
|
||||||
--sidebar-width-expanded: 280px;
|
--sidebar-width-expanded: 200px;
|
||||||
--sidebar-width-collapsed: 96px;
|
--sidebar-width-collapsed: 96px;
|
||||||
--sidebar-padding: 1rem;
|
--sidebar-padding: 1rem;
|
||||||
--sidebar-margin: 1rem;
|
--sidebar-margin: 1rem;
|
||||||
@@ -15,9 +15,11 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1.5rem; /* Reduzierter Abstand */
|
gap: 1.5rem; /* Reduzierter Abstand */
|
||||||
width: var(--sidebar-width-expanded);
|
width: var(--sidebar-width-expanded);
|
||||||
height: calc(100vh - 2rem - 46px);
|
height: calc(100vh - 4rem - 46px);
|
||||||
|
|
||||||
padding: var(--sidebar-padding);
|
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); */
|
/* border-radius: var(--border-radius-md); */
|
||||||
transition: width var(--transition-speed) var(--transition-ease);
|
transition: width var(--transition-speed) var(--transition-ease);
|
||||||
/* background-color: var(--color-surface); */
|
/* background-color: var(--color-surface); */
|
||||||
@@ -49,12 +51,13 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.nav-item:hover {
|
.nav-item:hover {
|
||||||
background-color: var(--color-body-bg-hover);
|
background-color: var(--color-surface);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
.nav-item.active {
|
.nav-item.active {
|
||||||
background: var(--color-primary);
|
background: var(--color-body-bg-active);
|
||||||
color: #fff;
|
color: var(--color-text);
|
||||||
|
|
||||||
box-shadow: var(--box-shadow-sm);
|
box-shadow: var(--box-shadow-sm);
|
||||||
}
|
}
|
||||||
.nav-item app-icon {
|
.nav-item app-icon {
|
||||||
@@ -94,8 +97,8 @@
|
|||||||
|
|
||||||
/* 4. Zustandsabhängige Hintergrundfarben für das Icon */
|
/* 4. Zustandsabhängige Hintergrundfarben für das Icon */
|
||||||
.nav-item:hover app-icon {
|
.nav-item:hover app-icon {
|
||||||
background-color: var(--color-body-bg-hover);
|
background: transparent;
|
||||||
}
|
}
|
||||||
.nav-item.active app-icon {
|
.nav-item.active app-icon {
|
||||||
background: var(--color-primary);
|
background: transparent;
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,4 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -1,29 +1,66 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, Inject, PLATFORM_ID, OnInit } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||||
import { IconComponent } from '../../ui/icon/icon.component';
|
import { IconComponent } from '../../ui/icon/icon.component';
|
||||||
import { ButtonComponent } from '../../ui/button/button.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sidebar',
|
selector: 'app-sidebar',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, IconComponent,ButtonComponent],
|
imports: [CommonModule, IconComponent],
|
||||||
templateUrl: './sidebar.component.html',
|
templateUrl: './sidebar.component.html',
|
||||||
styleUrl: './sidebar.component.css'
|
styleUrl: './sidebar.component.css'
|
||||||
})
|
})
|
||||||
export class SidebarComponent {
|
export class SidebarComponent implements OnInit { // 1. OnInit implementieren
|
||||||
// Dummy-Eigenschaft für die aktive Route, damit der Code funktioniert
|
// 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';
|
activeRoute = 'dashboard';
|
||||||
|
|
||||||
// NEU: Eigenschaft, um den Zustand der Sidebar zu speichern
|
// Der Standardwert ist 'false' (aufgeklappt), wird aber sofort überschrieben
|
||||||
public isCollapsed = false;
|
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 {
|
setActive(route: string): void {
|
||||||
this.activeRoute = route;
|
this.activeRoute = route;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEU: Methode, um den Zustand umzuschalten
|
// 4. Die Umschalt-Methode aktualisieren, damit sie den neuen Zustand speichert
|
||||||
toggleSidebar(): void {
|
toggleSidebar(): void {
|
||||||
|
// Zuerst den Zustand ändern
|
||||||
this.isCollapsed = !this.isCollapsed;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
<span
|
<div class="status-pill" [ngClass]="cssClass">
|
||||||
class="status-pill"
|
{{ displayText }}
|
||||||
[ngClass]="{
|
</div>
|
||||||
'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>
|
|
||||||
@@ -1,16 +1,34 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input, OnChanges } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule, NgClass } from '@angular/common';
|
||||||
|
import { OrderStatus } from '../../../../core/types/order';
|
||||||
// Definiert die erlaubten Status-Typen für Typsicherheit
|
|
||||||
type PillStatus = 'success' | 'warning' | 'danger' | 'info';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-status-pill',
|
selector: 'app-status-pill',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule],
|
imports: [CommonModule, NgClass],
|
||||||
templateUrl: './status-pill.component.html',
|
templateUrl: './status-pill.component.html',
|
||||||
styleUrl: './status-pill.component.css'
|
styleUrl: './status-pill.component.css'
|
||||||
})
|
})
|
||||||
export class StatusPillComponent {
|
export class StatusPillComponent implements OnChanges {
|
||||||
@Input() status: PillStatus = 'info';
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
/* Neutrale Farben (Light Mode) */
|
/* Neutrale Farben (Light Mode) */
|
||||||
--color-text: #2c3e50;
|
--color-text: #2c3e50;
|
||||||
--color-text-light: #7f8c8d;
|
--color-text-light: #7f8c8d;
|
||||||
--color-body-bg: #f4f7fa;
|
--color-body-bg: rgb(243, 243, 243);
|
||||||
|
--color-body-bg-lighter: #ffffff;
|
||||||
--color-body-bg-active: rgb(238, 238, 240);
|
--color-body-bg-active: rgb(238, 238, 240);
|
||||||
--color-body-bg-hover: #f2f3f5;
|
--color-body-bg-hover: #f2f3f5;
|
||||||
--color-surface: #ffffff;
|
--color-surface: #ffffff;
|
||||||
@@ -41,11 +42,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.dark-theme {
|
body.dark-theme {
|
||||||
|
/* Textfarben werden heller */
|
||||||
--color-text: #ecf0f1;
|
--color-text: #ecf0f1;
|
||||||
--color-text-light: #95a5a6;
|
--color-text-light: #95a5a6;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Die Hierarchie der Hintergrundfarben wird umgekehrt:
|
||||||
|
* Was im Light Mode hell war, ist jetzt dunkel.
|
||||||
|
* Was leicht abgedunkelt war (hover, active), wird jetzt leicht aufgehellt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Der Hintergrund für den Body (am dunkelsten) */
|
||||||
--color-body-bg: #1a202c;
|
--color-body-bg: #1a202c;
|
||||||
--color-body-bg-hover: rgb(34, 41, 56);
|
|
||||||
|
/* Der Hintergrund für Elemente, die darauf liegen (etwas heller) */
|
||||||
--color-surface: #2d3748;
|
--color-surface: #2d3748;
|
||||||
|
|
||||||
|
/* NEU: Die "hellste" Hintergrundfarbe aus dem Light Mode wird zur dunkelsten */
|
||||||
|
--color-body-bg-lighter: #131720; /* Ein sehr dunkler Ton für besondere Fälle */
|
||||||
|
|
||||||
|
/* Die Hover-Farbe ist jetzt heller als der Standard-Hintergrund */
|
||||||
|
--color-body-bg-hover: #374151; /* ÜBERARBEITET für besseren Kontrast */
|
||||||
|
|
||||||
|
/* NEU: Die "active"-Farbe ist noch einen Schritt heller als die Hover-Farbe */
|
||||||
|
--color-body-bg-active: #4a5568;
|
||||||
|
|
||||||
|
/* Die Rahmenfarbe ist heller als die Oberfläche, um sichtbar zu sein */
|
||||||
--color-border: #4a5568;
|
--color-border: #4a5568;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,16 +107,16 @@ body.no-scroll {
|
|||||||
text-align: center !important;
|
text-align: center !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .dashboard-grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
} */
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
flex-direction: column;
|
||||||
padding: 1rem;
|
/* padding: 1rem; */
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,12 +164,12 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.dashboard-grid {
|
.grid {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.dashboard-grid {
|
.grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user