From c066476cc3bcdffe54148179dcc426485ce706b7 Mon Sep 17 00:00:00 2001 From: "Tizian.Breuch" Date: Wed, 17 Sep 2025 21:17:27 +0200 Subject: [PATCH] init --- src/app/app.routes.ts | 2 +- .../default-layout.component.css | 6 +- src/app/core/models/dashboard.ts | 9 ++ src/app/core/models/order.ts | 19 +++ src/app/core/types/dashboard.ts | 1 + src/app/core/types/order.ts | 1 + src/app/core/types/status-pill.ts | 1 + .../dashboard-page.component.css | 0 .../dashboard-page.component.html | 75 +++++++++++ .../dashboard-page.component.ts | 85 +++++++++++++ .../kpi-card/kpi-card.component.css | 0 .../kpi-card/kpi-card.component.html | 0 .../kpi-card/kpi-card.component.ts | 6 +- .../features/dashboard/dashboard.routes.ts | 2 +- .../dashboard-page.component.html | 56 --------- .../dashboard-page.component.ts | 11 -- ...mo2.component.ts => demo2.component.2.txt} | 11 +- ...mo2.component.html => demo2.component.txt} | 0 src/app/features/demo/demo.routes.ts | 14 +-- .../orders-table/orders-table.component.css | 40 +++++- .../orders-table/orders-table.component.html | 24 +++- .../orders-table/orders-table.component.ts | 118 +++++++++++++----- .../page-header/page-header.component.ts | 2 - .../search-bar/search-bar.component.css | 2 +- .../search-bar/search-bar.component.html | 2 +- .../layout/sidebar/sidebar.component.css | 25 ++-- .../layout/sidebar/sidebar.component.html | 2 - .../layout/sidebar/sidebar.component.ts | 55 ++++++-- .../ui/status-pill/status-pill.component.html | 14 +-- .../ui/status-pill/status-pill.component.ts | 34 +++-- src/styles.css | 42 +++++-- 31 files changed, 480 insertions(+), 179 deletions(-) create mode 100644 src/app/core/models/dashboard.ts create mode 100644 src/app/core/models/order.ts create mode 100644 src/app/core/types/dashboard.ts create mode 100644 src/app/core/types/order.ts create mode 100644 src/app/core/types/status-pill.ts rename src/app/features/dashboard/{pages => components}/dashboard-page/dashboard-page.component.css (100%) create mode 100644 src/app/features/dashboard/components/dashboard-page/dashboard-page.component.html create mode 100644 src/app/features/dashboard/components/dashboard-page/dashboard-page.component.ts rename src/app/{shared/components/data-display => features/dashboard/components}/kpi-card/kpi-card.component.css (100%) rename src/app/{shared/components/data-display => features/dashboard/components}/kpi-card/kpi-card.component.html (100%) rename src/app/{shared/components/data-display => features/dashboard/components}/kpi-card/kpi-card.component.ts (69%) delete mode 100644 src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.html delete mode 100644 src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.ts rename src/app/features/demo/components/demo2/{demo2.component.ts => demo2.component.2.txt} (98%) rename src/app/features/demo/components/demo2/{demo2.component.html => demo2.component.txt} (100%) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index a9937ad..ccae308 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -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 = [ { diff --git a/src/app/core/components/default-layout/default-layout.component.css b/src/app/core/components/default-layout/default-layout.component.css index 8c0827f..430f963 100644 --- a/src/app/core/components/default-layout/default-layout.component.css +++ b/src/app/core/components/default-layout/default-layout.component.css @@ -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 */ } diff --git a/src/app/core/models/dashboard.ts b/src/app/core/models/dashboard.ts new file mode 100644 index 0000000..393f23b --- /dev/null +++ b/src/app/core/models/dashboard.ts @@ -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; +} \ No newline at end of file diff --git a/src/app/core/models/order.ts b/src/app/core/models/order.ts new file mode 100644 index 0000000..7c1d72f --- /dev/null +++ b/src/app/core/models/order.ts @@ -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; +} \ No newline at end of file diff --git a/src/app/core/types/dashboard.ts b/src/app/core/types/dashboard.ts new file mode 100644 index 0000000..0dfe432 --- /dev/null +++ b/src/app/core/types/dashboard.ts @@ -0,0 +1 @@ +export type KpiColor = 'blue' | 'green' | 'orange' | 'purple'; \ No newline at end of file diff --git a/src/app/core/types/order.ts b/src/app/core/types/order.ts new file mode 100644 index 0000000..d57aad1 --- /dev/null +++ b/src/app/core/types/order.ts @@ -0,0 +1 @@ +export type OrderStatus = 'completed' | 'processing' | 'cancelled' | 'info'; \ No newline at end of file diff --git a/src/app/core/types/status-pill.ts b/src/app/core/types/status-pill.ts new file mode 100644 index 0000000..8a59cfe --- /dev/null +++ b/src/app/core/types/status-pill.ts @@ -0,0 +1 @@ +export type PillStatus = 'success' | 'warning' | 'danger' | 'info'; \ No newline at end of file diff --git a/src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.css b/src/app/features/dashboard/components/dashboard-page/dashboard-page.component.css similarity index 100% rename from src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.css rename to src/app/features/dashboard/components/dashboard-page/dashboard-page.component.css diff --git a/src/app/features/dashboard/components/dashboard-page/dashboard-page.component.html b/src/app/features/dashboard/components/dashboard-page/dashboard-page.component.html new file mode 100644 index 0000000..e4c3664 --- /dev/null +++ b/src/app/features/dashboard/components/dashboard-page/dashboard-page.component.html @@ -0,0 +1,75 @@ +
+
+ + +
+
+ + +
+
+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. diff --git a/src/app/features/dashboard/components/dashboard-page/dashboard-page.component.ts b/src/app/features/dashboard/components/dashboard-page/dashboard-page.component.ts new file mode 100644 index 0000000..a213179 --- /dev/null +++ b/src/app/features/dashboard/components/dashboard-page/dashboard-page.component.ts @@ -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 + } + +} diff --git a/src/app/shared/components/data-display/kpi-card/kpi-card.component.css b/src/app/features/dashboard/components/kpi-card/kpi-card.component.css similarity index 100% rename from src/app/shared/components/data-display/kpi-card/kpi-card.component.css rename to src/app/features/dashboard/components/kpi-card/kpi-card.component.css diff --git a/src/app/shared/components/data-display/kpi-card/kpi-card.component.html b/src/app/features/dashboard/components/kpi-card/kpi-card.component.html similarity index 100% rename from src/app/shared/components/data-display/kpi-card/kpi-card.component.html rename to src/app/features/dashboard/components/kpi-card/kpi-card.component.html diff --git a/src/app/shared/components/data-display/kpi-card/kpi-card.component.ts b/src/app/features/dashboard/components/kpi-card/kpi-card.component.ts similarity index 69% rename from src/app/shared/components/data-display/kpi-card/kpi-card.component.ts rename to src/app/features/dashboard/components/kpi-card/kpi-card.component.ts index bbbfe33..b4b680a 100644 --- a/src/app/shared/components/data-display/kpi-card/kpi-card.component.ts +++ b/src/app/features/dashboard/components/kpi-card/kpi-card.component.ts @@ -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', diff --git a/src/app/features/dashboard/dashboard.routes.ts b/src/app/features/dashboard/dashboard.routes.ts index 3c33b63..646d156 100644 --- a/src/app/features/dashboard/dashboard.routes.ts +++ b/src/app/features/dashboard/dashboard.routes.ts @@ -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 = [ diff --git a/src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.html b/src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.html deleted file mode 100644 index 0ccc14c..0000000 --- a/src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.html +++ /dev/null @@ -1,56 +0,0 @@ -

dashboard-page works!

-

- 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. -

- -
-

- 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. -

-
-

- 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. -

-
-

- 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. -

-
diff --git a/src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.ts b/src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.ts deleted file mode 100644 index 403d78f..0000000 --- a/src/app/features/dashboard/pages/dashboard-page/dashboard-page.component.ts +++ /dev/null @@ -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 { - -} diff --git a/src/app/features/demo/components/demo2/demo2.component.ts b/src/app/features/demo/components/demo2/demo2.component.2.txt similarity index 98% rename from src/app/features/demo/components/demo2/demo2.component.ts rename to src/app/features/demo/components/demo2/demo2.component.2.txt index 7812449..d5504c7 100644 --- a/src/app/features/demo/components/demo2/demo2.component.ts +++ b/src/app/features/demo/components/demo2/demo2.component.2.txt @@ -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'; diff --git a/src/app/features/demo/components/demo2/demo2.component.html b/src/app/features/demo/components/demo2/demo2.component.txt similarity index 100% rename from src/app/features/demo/components/demo2/demo2.component.html rename to src/app/features/demo/components/demo2/demo2.component.txt diff --git a/src/app/features/demo/demo.routes.ts b/src/app/features/demo/demo.routes.ts index 68ae8cc..9bfc4a9 100644 --- a/src/app/features/demo/demo.routes.ts +++ b/src/app/features/demo/demo.routes.ts @@ -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 }, diff --git a/src/app/shared/components/data-display/orders-table/orders-table.component.css b/src/app/shared/components/data-display/orders-table/orders-table.component.css index d850394..c6be0d2 100644 --- a/src/app/shared/components/data-display/orders-table/orders-table.component.css +++ b/src/app/shared/components/data-display/orders-table/orders-table.component.css @@ -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); -} \ No newline at end of file +} + +.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; + } +} diff --git a/src/app/shared/components/data-display/orders-table/orders-table.component.html b/src/app/shared/components/data-display/orders-table/orders-table.component.html index 00c83b4..536acef 100644 --- a/src/app/shared/components/data-display/orders-table/orders-table.component.html +++ b/src/app/shared/components/data-display/orders-table/orders-table.component.html @@ -1,3 +1,19 @@ +
+ +
+ + {{ option.label }} + +
+
+
@@ -28,9 +44,9 @@ #{{ order.id }}
- {{ - order.statusText - }} + + {{ order.status }} {{ order.amount }} @@ -66,7 +82,7 @@
diff --git a/src/app/shared/components/data-display/orders-table/orders-table.component.ts b/src/app/shared/components/data-display/orders-table/orders-table.component.ts index 9a53309..a461d99 100644 --- a/src/app/shared/components/data-display/orders-table/orders-table.component.ts +++ b/src/app/shared/components/data-display/orders-table/orders-table.component.ts @@ -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(); @Output() edit = new EventEmitter(); @Output() delete = new EventEmitter(); - 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([ + ['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); } - -} \ No newline at end of file +} diff --git a/src/app/shared/components/layout/page-header/page-header.component.ts b/src/app/shared/components/layout/page-header/page-header.component.ts index 0c4e545..51ae88a 100644 --- a/src/app/shared/components/layout/page-header/page-header.component.ts +++ b/src/app/shared/components/layout/page-header/page-header.component.ts @@ -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, diff --git a/src/app/shared/components/layout/search-bar/search-bar.component.css b/src/app/shared/components/layout/search-bar/search-bar.component.css index be23a2a..0a95b6d 100644 --- a/src/app/shared/components/layout/search-bar/search-bar.component.css +++ b/src/app/shared/components/layout/search-bar/search-bar.component.css @@ -5,7 +5,7 @@ .search-bar { position: relative; - width: 300px; + width: 100% } .search-bar svg { position: absolute; diff --git a/src/app/shared/components/layout/search-bar/search-bar.component.html b/src/app/shared/components/layout/search-bar/search-bar.component.html index 55fd03c..8bcc8ff 100644 --- a/src/app/shared/components/layout/search-bar/search-bar.component.html +++ b/src/app/shared/components/layout/search-bar/search-bar.component.html @@ -3,5 +3,5 @@ - +
\ No newline at end of file diff --git a/src/app/shared/components/layout/sidebar/sidebar.component.css b/src/app/shared/components/layout/sidebar/sidebar.component.css index 459f35e..9f86d6e 100644 --- a/src/app/shared/components/layout/sidebar/sidebar.component.css +++ b/src/app/shared/components/layout/sidebar/sidebar.component.css @@ -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); -} \ No newline at end of file + background: transparent; +} diff --git a/src/app/shared/components/layout/sidebar/sidebar.component.html b/src/app/shared/components/layout/sidebar/sidebar.component.html index bdfcd42..17054e6 100644 --- a/src/app/shared/components/layout/sidebar/sidebar.component.html +++ b/src/app/shared/components/layout/sidebar/sidebar.component.html @@ -28,6 +28,4 @@ - - diff --git a/src/app/shared/components/layout/sidebar/sidebar.component.ts b/src/app/shared/components/layout/sidebar/sidebar.component.ts index f31248f..dc96989 100644 --- a/src/app/shared/components/layout/sidebar/sidebar.component.ts +++ b/src/app/shared/components/layout/sidebar/sidebar.component.ts @@ -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); + } + } } } \ No newline at end of file diff --git a/src/app/shared/components/ui/status-pill/status-pill.component.html b/src/app/shared/components/ui/status-pill/status-pill.component.html index a9d21b0..30e8bcb 100644 --- a/src/app/shared/components/ui/status-pill/status-pill.component.html +++ b/src/app/shared/components/ui/status-pill/status-pill.component.html @@ -1,11 +1,3 @@ - - - - \ No newline at end of file +
+ {{ displayText }} +
\ No newline at end of file diff --git a/src/app/shared/components/ui/status-pill/status-pill.component.ts b/src/app/shared/components/ui/status-pill/status-pill.component.ts index 6bb837e..d5cbc2a 100644 --- a/src/app/shared/components/ui/status-pill/status-pill.component.ts +++ b/src/app/shared/components/ui/status-pill/status-pill.component.ts @@ -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([ + ['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; + } } \ No newline at end of file diff --git a/src/styles.css b/src/styles.css index 12da42f..5856915 100644 --- a/src/styles.css +++ b/src/styles.css @@ -19,7 +19,8 @@ /* Neutrale Farben (Light Mode) */ --color-text: #2c3e50; --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-hover: #f2f3f5; --color-surface: #ffffff; @@ -41,11 +42,32 @@ } body.dark-theme { + /* Textfarben werden heller */ --color-text: #ecf0f1; --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-hover: rgb(34, 41, 56); + + /* Der Hintergrund für Elemente, die darauf liegen (etwas heller) */ --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; } @@ -85,16 +107,16 @@ body.no-scroll { text-align: center !important; } -/* .dashboard-grid { +.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.5rem; -} */ +} main { - display: grid; - grid-template-columns: repeat(4, 1fr); - padding: 1rem; + display: flex; + flex-direction: column; + /* padding: 1rem; */ gap: 1rem; } @@ -142,12 +164,12 @@ main { } @media (max-width: 1200px) { - .dashboard-grid { + .grid { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 768px) { - .dashboard-grid { + .grid { grid-template-columns: 1fr; } } @@ -166,4 +188,4 @@ main { display: flex; flex-wrap: wrap; gap: 0.5rem; -} \ No newline at end of file +}