Compare commits
4 Commits
c10e6b4faa
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec6e6bdd7a | ||
|
|
05c2b6b5c9 | ||
|
|
dfe631edf6 | ||
|
|
ac42f8b1b9 |
38
package-lock.json
generated
38
package-lock.json
generated
@@ -746,9 +746,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/ssr": {
|
||||
"version": "19.2.17",
|
||||
"resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-19.2.17.tgz",
|
||||
"integrity": "sha512-9ABOYrHrCYnOiihkeHvN+QIaIN+1Js4QfT4cXAtZs/f+yFaURGre7yvyK1KncMBKwxXzjcqvu1QquFMU/m/JLw==",
|
||||
"version": "19.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-19.2.19.tgz",
|
||||
"integrity": "sha512-7HqC3K99DdzDakB/4mkqGqY6REQNMxskU1VVkH9D7SthZSuxhWIMVBojVhBDd+JOUYiyQlwEGMBevbrgbtfKlQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -4262,9 +4262,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/package-json/node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -6658,9 +6658,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cacache/node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -9796,9 +9796,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11437,9 +11437,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
|
||||
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
|
||||
"dev": true,
|
||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||
"engines": {
|
||||
@@ -14075,10 +14075,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz",
|
||||
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
|
||||
"license": "ISC",
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz",
|
||||
"integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
|
||||
1070
src/_styles_OLD.txt
1070
src/_styles_OLD.txt
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,9 @@ export interface CreateAddress {
|
||||
postalCode: string;
|
||||
country: string;
|
||||
type: AddressType;
|
||||
// FEHLTEN:
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface UpdateAddress extends CreateAddress {
|
||||
|
||||
@@ -34,6 +34,9 @@ export interface OrderDetail {
|
||||
totalAmount: number;
|
||||
shippingAddress: Address;
|
||||
billingAddress: Address;
|
||||
shippingAddressId?: string;
|
||||
billingAddressId?: string;
|
||||
paymentMethodId?: string;
|
||||
paymentMethod?: string;
|
||||
shippingTrackingNumber?: string;
|
||||
shippedDate?: string;
|
||||
|
||||
9
src/app/core/models/shared.models.ts
Normal file
9
src/app/core/models/shared.models.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// Das Backend sendet dies bei Validierungsfehlern (400 Bad Request)
|
||||
export interface ValidationProblemDetails {
|
||||
type?: string;
|
||||
title?: string;
|
||||
status?: number;
|
||||
detail?: string;
|
||||
instance?: string;
|
||||
errors?: { [key: string]: string[] };
|
||||
}
|
||||
@@ -6,4 +6,7 @@ export interface ShippingMethod {
|
||||
isActive: boolean;
|
||||
minDeliveryDays: number;
|
||||
maxDeliveryDays: number;
|
||||
// NEU: Gewichtsgrenzen
|
||||
minWeight: number;
|
||||
maxWeight: number;
|
||||
}
|
||||
@@ -8,6 +8,9 @@ export interface User {
|
||||
lastActive?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
phoneNumber?: string;
|
||||
defaultShippingAddressId?: string;
|
||||
defaultBillingAddressId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserRolesRequest {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
.form-header {
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3[card-header] {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</div>
|
||||
|
||||
<ng-template #formContent>
|
||||
<div class="form-header">
|
||||
<div class="page-header">
|
||||
<h3 card-header>Neues Produkt erstellen</h3>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
.form-header {
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3[card-header] {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
|
||||
<ng-template #formContent>
|
||||
<div class="form-header">
|
||||
<div class="page-header">
|
||||
<h3 card-header>Produkt bearbeiten</h3>
|
||||
</div>
|
||||
<app-product-form
|
||||
|
||||
@@ -282,6 +282,11 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
if (mainImagePreview && !this.newImageFiles.has(mainImagePreview.identifier)) {
|
||||
console.log('Setze bestehendes Bild als Main:', mainImagePreview.identifier);
|
||||
formData.append('SetMainImageId', mainImagePreview.identifier);
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
app-search-bar {
|
||||
flex-grow: 1;
|
||||
max-width: 400px;
|
||||
@@ -20,7 +28,9 @@ app-search-bar {
|
||||
}
|
||||
|
||||
.column-filter-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.column-filter-dropdown {
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
<!-- /src/app/features/admin/components/products/product-list/product-list.component.html -->
|
||||
|
||||
<div class="page-container">
|
||||
<div class="header">
|
||||
<h1 class="page-title">Produktübersicht</h1>
|
||||
<app-button buttonType="primary" (click)="onAddNew()">
|
||||
<app-icon iconName="plus"></app-icon> Neues Produkt
|
||||
</app-button>
|
||||
<div>
|
||||
<h3 class="page-header">Produktübersicht</h3>
|
||||
</div>
|
||||
|
||||
<div class="table-header">
|
||||
<app-search-bar placeholder="Produkte nach Name oder SKU suchen..." (search)="onSearch($event)"></app-search-bar>
|
||||
<app-search-bar
|
||||
placeholder="Produkte nach Name oder SKU suchen..."
|
||||
(search)="onSearch($event)"
|
||||
></app-search-bar>
|
||||
|
||||
<div class="column-filter-container">
|
||||
<app-button buttonType="stroked" (click)="toggleColumnFilter()" iconName="filter">Spalten</app-button>
|
||||
<app-button buttonType="primary" iconName="plus" (click)="onAddNew()">
|
||||
Neues Produkt
|
||||
</app-button>
|
||||
|
||||
<app-button
|
||||
buttonType="stroked"
|
||||
(click)="toggleColumnFilter()"
|
||||
iconName="filter"
|
||||
>Spalten</app-button
|
||||
>
|
||||
<div class="column-filter-dropdown" *ngIf="isColumnFilterVisible">
|
||||
<label *ngFor="let col of allTableColumns">
|
||||
<input type="checkbox" [checked]="isColumnVisible(col.key)" (change)="onColumnToggle(col, $event)">
|
||||
<input
|
||||
type="checkbox"
|
||||
[checked]="isColumnVisible(col.key)"
|
||||
(change)="onColumnToggle(col, $event)"
|
||||
/>
|
||||
{{ col.title }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -25,6 +39,7 @@
|
||||
[data]="filteredProducts"
|
||||
[columns]="visibleTableColumns"
|
||||
(edit)="onEditProduct($event.id)"
|
||||
(delete)="onDeleteProduct($event.id)">
|
||||
(delete)="onDeleteProduct($event.id)"
|
||||
>
|
||||
</app-generic-table>
|
||||
</div>
|
||||
@@ -11,12 +11,11 @@ import { StorageService } from '../../../../core/services/storage.service';
|
||||
import { GenericTableComponent, ColumnConfig } from '../../../../shared/components/data-display/generic-table/generic-table.component';
|
||||
import { SearchBarComponent } from '../../../../shared/components/layout/search-bar/search-bar.component';
|
||||
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
||||
import { IconComponent } from '../../../../shared/components/ui/icon/icon.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-list',
|
||||
standalone: true,
|
||||
imports: [CommonModule, GenericTableComponent, SearchBarComponent, ButtonComponent, IconComponent],
|
||||
imports: [CommonModule, GenericTableComponent, SearchBarComponent, ButtonComponent],
|
||||
providers: [DatePipe],
|
||||
templateUrl: './product-list.component.html',
|
||||
styleUrl: './product-list.component.css'
|
||||
@@ -59,6 +58,8 @@ export class ProductListComponent implements OnInit {
|
||||
];
|
||||
visibleTableColumns: ColumnConfig[] = [];
|
||||
|
||||
public readonly fallbackImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MCIgaGVpZ2h0PSI1MCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNjY2NjY2MiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHg9IjMiIHk9IjMiIHJ4PSIyIiByeT0iMiIvPjxjaXJjbGUgY3g9IjkiIGN5PSI5IiByPSIyIi8+PHBhdGggZD0ibTIxIDE1LTUtNWwtNSA1bC0yLTJsLTUgNSIvPjwvc3ZnPg==';
|
||||
|
||||
constructor() {
|
||||
this.loadTableSettings();
|
||||
}
|
||||
@@ -144,8 +145,8 @@ export class ProductListComponent implements OnInit {
|
||||
}
|
||||
|
||||
getMainImageUrl(images?: ProductImage[]): string {
|
||||
if (!images || images.length === 0) return 'https://via.placeholder.com/50';
|
||||
if (!images || images.length === 0) return this.fallbackImage;
|
||||
const mainImage = images.find(img => img.isMainImage);
|
||||
return mainImage?.url || images[0]?.url || 'https://via.placeholder.com/50';
|
||||
return mainImage?.url || images[0]?.url || this.fallbackImage;
|
||||
}
|
||||
}
|
||||
@@ -22,27 +22,51 @@
|
||||
<input type="number" formControlName="cost" placeholder="Kosten" />
|
||||
</div>
|
||||
|
||||
<!-- +++ NEUE FELDER HINZUGEFÜGT +++ -->
|
||||
<div>
|
||||
<label>Minimale Liefertage:</label>
|
||||
<input
|
||||
type="number"
|
||||
formControlName="minDeliveryDays"
|
||||
placeholder="z.B. 1"
|
||||
/>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<div>
|
||||
<label>Minimale Liefertage:</label>
|
||||
<input
|
||||
type="number"
|
||||
formControlName="minDeliveryDays"
|
||||
placeholder="z.B. 1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Maximale Liefertage:</label>
|
||||
<input
|
||||
type="number"
|
||||
formControlName="maxDeliveryDays"
|
||||
placeholder="z.B. 3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Maximale Liefertage:</label>
|
||||
<input
|
||||
type="number"
|
||||
formControlName="maxDeliveryDays"
|
||||
placeholder="z.B. 3"
|
||||
/>
|
||||
<!-- +++ NEUE GEWICHTS FELDER +++ -->
|
||||
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
||||
<div>
|
||||
<label>Gewicht von (kg):</label>
|
||||
<input
|
||||
type="number"
|
||||
formControlName="minWeight"
|
||||
placeholder="0"
|
||||
step="0.01"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Gewicht bis (kg):</label>
|
||||
<input
|
||||
type="number"
|
||||
formControlName="maxWeight"
|
||||
placeholder="10"
|
||||
step="0.01"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- +++ ENDE NEU +++ -->
|
||||
|
||||
<div>
|
||||
<div style="margin-top: 10px;">
|
||||
<label><input type="checkbox" formControlName="isActive" /> Aktiv</label>
|
||||
</div>
|
||||
|
||||
@@ -55,16 +79,26 @@
|
||||
Abbrechen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Bestehende Methoden</h2>
|
||||
<ul>
|
||||
<li *ngFor="let method of methods$ | async">
|
||||
{{ method.name }}
|
||||
({{ method.cost | currency : "EUR" }}) - Lieferzeit:
|
||||
{{ method.minDeliveryDays }}-{{ method.maxDeliveryDays }} Tage - Aktiv:
|
||||
{{ method.isActive }}
|
||||
<button (click)="selectMethod(method)">Bearbeiten</button>
|
||||
<button (click)="onDelete(method.id)">Löschen</button>
|
||||
<li *ngFor="let method of methods$ | async" style="margin-bottom: 10px; border-bottom: 1px solid #ccc; padding-bottom: 5px;">
|
||||
<strong>{{ method.name }}</strong> ({{ method.cost | currency : "EUR" }}) <br/>
|
||||
|
||||
<!-- Anzeige der Details -->
|
||||
<small>
|
||||
Lieferzeit: {{ method.minDeliveryDays }}-{{ method.maxDeliveryDays }} Tage |
|
||||
<!-- NEU: Gewichtsanzeige -->
|
||||
Gewicht: {{ method.minWeight }}kg - {{ method.maxWeight }}kg |
|
||||
Aktiv: {{ method.isActive ? 'Ja' : 'Nein' }}
|
||||
</small>
|
||||
|
||||
<div style="margin-top: 5px;">
|
||||
<button (click)="selectMethod(method)">Bearbeiten</button>
|
||||
<button (click)="onDelete(method.id)">Löschen</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -26,7 +26,10 @@ export class ShippingMethodListComponent implements OnInit {
|
||||
cost: [0, [Validators.required, Validators.min(0)]],
|
||||
isActive: [true],
|
||||
minDeliveryDays: [1, [Validators.required, Validators.min(0)]],
|
||||
maxDeliveryDays: [3, [Validators.required, Validators.min(0)]]
|
||||
maxDeliveryDays: [3, [Validators.required, Validators.min(0)]],
|
||||
// NEU: Validierung für Gewicht
|
||||
minWeight: [0, [Validators.required, Validators.min(0)]],
|
||||
maxWeight: [10, [Validators.required, Validators.min(0)]]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,15 +49,16 @@ export class ShippingMethodListComponent implements OnInit {
|
||||
cost: 0,
|
||||
isActive: true,
|
||||
minDeliveryDays: 1,
|
||||
maxDeliveryDays: 3
|
||||
maxDeliveryDays: 3,
|
||||
// NEU: Reset Werte
|
||||
minWeight: 0,
|
||||
maxWeight: 10
|
||||
});
|
||||
}
|
||||
|
||||
// --- KORREKTUR: onSubmit sendet jetzt direkt das Formularwert-Objekt als JSON ---
|
||||
onSubmit(): void {
|
||||
if (this.methodForm.invalid) return;
|
||||
|
||||
// Das Formular-Objekt hat bereits die richtige Struktur, die das Backend erwartet.
|
||||
const dataToSend: ShippingMethod = {
|
||||
id: this.selectedMethodId || '00000000-0000-0000-0000-000000000000',
|
||||
...this.methodForm.value
|
||||
@@ -66,7 +70,6 @@ export class ShippingMethodListComponent implements OnInit {
|
||||
this.shippingMethodService.create(dataToSend).subscribe(() => this.reset());
|
||||
}
|
||||
}
|
||||
// --- ENDE KORREKTUR ---
|
||||
|
||||
onDelete(id: string): void {
|
||||
if (confirm('Versandmethode wirklich löschen?')) {
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
|
||||
<ng-container *ngSwitchCase="'image-text'">
|
||||
<div class="user-cell">
|
||||
<img [src]="getProperty(item, col.imageKey!) || 'https://via.placeholder.com/40'"
|
||||
[alt]="'Bild von ' + getProperty(item, col.key)" />
|
||||
<img [src]="getProperty(item, col.imageKey!) || fallbackImage"
|
||||
alt="{{ item.name }}" />
|
||||
<div>
|
||||
<div class="user-name">{{ getProperty(item, col.key) }}</div>
|
||||
<div class="user-email">{{ getProperty(item, col.subKey!) }}</div>
|
||||
@@ -45,8 +45,8 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'image'">
|
||||
<img [src]="getProperty(item, col.key) || 'https://via.placeholder.com/50'"
|
||||
alt="Bild"
|
||||
<img [src]="getProperty(item, col.key) || fallbackImage"
|
||||
alt="{{ item.name }}"
|
||||
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export class GenericTableComponent implements OnChanges, OnInit {
|
||||
|
||||
public displayedData: any[] = [];
|
||||
public currentPage = 1;
|
||||
public readonly fallbackImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MCIgaGVpZ2h0PSI1MCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNjY2NjY2MiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHg9IjMiIHk9IjMiIHJ4PSIyIiByeT0iMiIvPjxjaXJjbGUgY3g9IjkiIGN5PSI5IiByPSIyIi8+PHBhdGggZD0ibTIxIDE1LTUtNWwtNSA1bC0yLTJsLTUgNSIvPjwvc3ZnPg==';
|
||||
|
||||
ngOnInit(): void { this.updatePagination(); }
|
||||
ngOnChanges(changes: SimpleChanges): void { if (changes['data']) { this.currentPage = 1; this.updatePagination(); } }
|
||||
@@ -47,6 +48,7 @@ export class GenericTableComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
getProperty(item: any, key: string): any {
|
||||
|
||||
if (!key) return '';
|
||||
return key.split('.').reduce((obj, part) => obj && obj[part], item);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
iconName="chevron_backward"
|
||||
(click)="goToPrevious()"
|
||||
[disabled]="currentPage === 1"
|
||||
tooltip="Vorherige Seite">
|
||||
>
|
||||
|
||||
</app-button>
|
||||
<app-button
|
||||
@@ -16,7 +16,7 @@
|
||||
iconName="chevron_forward"
|
||||
(click)="goToNext()"
|
||||
[disabled]="currentPage === totalPages"
|
||||
tooltip="Nächste Seite">
|
||||
>
|
||||
|
||||
</app-button>
|
||||
</div>
|
||||
|
||||
@@ -1,128 +1,128 @@
|
||||
<aside class="sidebar" [class.collapsed]="isCollapsed">
|
||||
<nav class="sidebar-nav">
|
||||
<!-- Toggle bleibt wie er ist, da er keine Route ist -->
|
||||
<div class="nav-item" (click)="toggleSidebar()">
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>Toggle</span>
|
||||
</div>
|
||||
|
||||
<!-- Products -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'home'"
|
||||
(click)="setActive('home')"
|
||||
routerLink="/shop/products"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
<span>products</span>
|
||||
</div>
|
||||
|
||||
<!-- Home -->
|
||||
<div
|
||||
class="nav-item"
|
||||
routerLink="/shop/home"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
<span>Home</span>
|
||||
</div>
|
||||
|
||||
<!-- Categories -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'categories'"
|
||||
(click)="setActive('categories')"
|
||||
routerLink="/shop/categories"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>categories</span>
|
||||
</div>
|
||||
|
||||
<!-- Discounts -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'discounts'"
|
||||
(click)="setActive('discounts')"
|
||||
routerLink="/shop/discounts"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>discounts</span>
|
||||
</div>
|
||||
|
||||
<!-- Orders -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'orders'"
|
||||
(click)="setActive('orders')"
|
||||
routerLink="/shop/orders"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>orders</span>
|
||||
</div>
|
||||
|
||||
<!-- Payment Methods -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'payment-methods'"
|
||||
(click)="setActive('payment-methods')"
|
||||
routerLink="/shop/payment-methods"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>payment-methods</span>
|
||||
</div>
|
||||
|
||||
<!-- Reviews -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'products'"
|
||||
(click)="setActive('products')"
|
||||
routerLink="/shop/reviews"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>products</span>
|
||||
</div>
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'reviews'"
|
||||
(click)="setActive('reviews')"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>reviews</span>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'settings'"
|
||||
(click)="setActive('settings')"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>settings</span>
|
||||
</div> -->
|
||||
<!-- Shipping Methods -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'shipping-methods'"
|
||||
(click)="setActive('shipping-methods')"
|
||||
routerLink="/shop/shipping-methods"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>shipping-methods</span>
|
||||
</div>
|
||||
|
||||
<!-- Shop Info -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'shop-info'"
|
||||
(click)="setActive('shop-info')"
|
||||
routerLink="/shop/shop-info"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>shop-info</span>
|
||||
</div>
|
||||
|
||||
<!-- Supplier List -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'supplier-list'"
|
||||
(click)="setActive('supplier-list')"
|
||||
routerLink="/shop/supplier-list"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>supplier-list</span>
|
||||
</div>
|
||||
|
||||
<!-- Users -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'users'"
|
||||
(click)="setActive('users')"
|
||||
routerLink="/shop/users"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>users</span>
|
||||
</div>
|
||||
|
||||
<!-- Analytics -->
|
||||
<div
|
||||
class="nav-item"
|
||||
[class.active]="activeRoute === 'analytics'"
|
||||
(click)="setActive('analytics')"
|
||||
routerLink="/shop/analytics"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<app-icon [iconName]="'placeholder'" [svgColor]="'#8e44ad'"></app-icon>
|
||||
|
||||
<span>analytics</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,51 +1,40 @@
|
||||
// /src/app/core/components/default-layout/sidebar/sidebar.component.ts
|
||||
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router'; // RouterLink und RouterLinkActive importieren
|
||||
// WICHTIG: RouterLink und RouterLinkActive importieren
|
||||
import { RouterLink, RouterLinkActive, Router } from '@angular/router';
|
||||
import { IconComponent } from '../../ui/icon/icon.component';
|
||||
import { StorageService } from '../../../../core/services/storage.service';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sidebar',
|
||||
standalone: true,
|
||||
imports: [CommonModule, IconComponent], // RouterLink und RouterLinkActive hier hinzufügen
|
||||
// WICHTIG: Hier im Array hinzufügen
|
||||
imports: [CommonModule, IconComponent, RouterLink, RouterLinkActive],
|
||||
templateUrl: './sidebar.component.html',
|
||||
styleUrl: './sidebar.component.css',
|
||||
})
|
||||
export class SidebarComponent implements OnInit {
|
||||
// --- Abhängigkeiten mit moderner inject()-Syntax ---
|
||||
private storageService = inject(StorageService);
|
||||
|
||||
private readonly sidebarCollapsedKey = 'app-sidebar-collapsed';
|
||||
|
||||
public isCollapsed = false;
|
||||
|
||||
activeRoute = 'dashboard';
|
||||
constructor(private router: Router) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadCollapsedState();
|
||||
}
|
||||
|
||||
setActive(route: string): void {
|
||||
this.activeRoute = route;
|
||||
this.router.navigateByUrl('/shop/' + route);
|
||||
}
|
||||
|
||||
toggleSidebar(): void {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
this.saveCollapsedState();
|
||||
}
|
||||
|
||||
private loadCollapsedState(): void {
|
||||
// Der Service kümmert sich um die Browser-Prüfung und gibt boolean oder null zurück
|
||||
this.isCollapsed =
|
||||
this.storageService.getItem<boolean>(this.sidebarCollapsedKey) ?? false;
|
||||
this.isCollapsed = this.storageService.getItem<boolean>(this.sidebarCollapsedKey) ?? false;
|
||||
}
|
||||
|
||||
private saveCollapsedState(): void {
|
||||
// Der Service kümmert sich um die Serialisierung des booleans
|
||||
this.storageService.setItem(this.sidebarCollapsedKey, this.isCollapsed);
|
||||
}
|
||||
}
|
||||
@@ -135,11 +135,20 @@
|
||||
transform: translateX(-50%) translateY(-12px);
|
||||
}
|
||||
|
||||
|
||||
.btn.is-loading {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.btn-content {
|
||||
display: flex;
|
||||
}
|
||||
.btn-content span {
|
||||
display: flex;
|
||||
height: auto;
|
||||
align-content: center;
|
||||
flex-wrap: wrap-reverse;
|
||||
}
|
||||
|
||||
.btn-content.is-hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
|
||||
Reference in New Issue
Block a user