|
|
|
|
@@ -3,12 +3,18 @@
|
|
|
|
|
import { Component, OnInit, inject } from '@angular/core';
|
|
|
|
|
import { Router } from '@angular/router';
|
|
|
|
|
import { CommonModule, CurrencyPipe, DatePipe } from '@angular/common';
|
|
|
|
|
import { AdminProduct, ProductImage } from '../../../../core/models/product.model';
|
|
|
|
|
import {
|
|
|
|
|
AdminProduct,
|
|
|
|
|
ProductImage,
|
|
|
|
|
} from '../../../../core/models/product.model';
|
|
|
|
|
import { ProductService } from '../../../services/product.service';
|
|
|
|
|
import { SupplierService } from '../../../services/supplier.service';
|
|
|
|
|
import { SnackbarService } from '../../../../shared/services/snackbar.service';
|
|
|
|
|
import { StorageService } from '../../../../core/services/storage.service';
|
|
|
|
|
import { GenericTableComponent, ColumnConfig } from '../../../../shared/components/data-display/generic-table/generic-table.component';
|
|
|
|
|
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';
|
|
|
|
|
@@ -16,10 +22,18 @@ import { IconComponent } from '../../../../shared/components/ui/icon/icon.compon
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'app-product-list',
|
|
|
|
|
standalone: true,
|
|
|
|
|
imports: [CommonModule, CurrencyPipe, DatePipe, GenericTableComponent, SearchBarComponent, ButtonComponent, IconComponent],
|
|
|
|
|
imports: [
|
|
|
|
|
CommonModule,
|
|
|
|
|
CurrencyPipe,
|
|
|
|
|
DatePipe,
|
|
|
|
|
GenericTableComponent,
|
|
|
|
|
SearchBarComponent,
|
|
|
|
|
ButtonComponent,
|
|
|
|
|
IconComponent,
|
|
|
|
|
],
|
|
|
|
|
providers: [DatePipe],
|
|
|
|
|
templateUrl: './product-list.component.html',
|
|
|
|
|
styleUrl: './product-list.component.css'
|
|
|
|
|
styleUrl: './product-list.component.css',
|
|
|
|
|
})
|
|
|
|
|
export class ProductListComponent implements OnInit {
|
|
|
|
|
private productService = inject(ProductService);
|
|
|
|
|
@@ -31,18 +45,62 @@ export class ProductListComponent implements OnInit {
|
|
|
|
|
|
|
|
|
|
private readonly TABLE_SETTINGS_KEY = 'product-table-columns';
|
|
|
|
|
|
|
|
|
|
allProducts: (AdminProduct & { mainImage?: string; supplierName?: string })[] = [];
|
|
|
|
|
filteredProducts: (AdminProduct & { mainImage?: string; supplierName?: string })[] = [];
|
|
|
|
|
allProducts: (AdminProduct & {
|
|
|
|
|
mainImage?: string;
|
|
|
|
|
supplierName?: string;
|
|
|
|
|
})[] = [];
|
|
|
|
|
filteredProducts: (AdminProduct & {
|
|
|
|
|
mainImage?: string;
|
|
|
|
|
supplierName?: string;
|
|
|
|
|
})[] = [];
|
|
|
|
|
isColumnFilterVisible = false;
|
|
|
|
|
|
|
|
|
|
readonly allTableColumns: ColumnConfig[] = [
|
|
|
|
|
{ key: 'mainImage', title: 'Bild', type: 'image' },
|
|
|
|
|
{ key: 'name', title: 'Name', type: 'text', subKey: 'sku' },
|
|
|
|
|
{ key: 'price', title: 'Preis', type: 'currency', cssClass: 'text-right' },
|
|
|
|
|
{ key: 'stockQuantity', title: 'Lager', type: 'text', cssClass: 'text-right' },
|
|
|
|
|
{
|
|
|
|
|
key: 'stockQuantity',
|
|
|
|
|
title: 'Lager',
|
|
|
|
|
type: 'text',
|
|
|
|
|
cssClass: 'text-right',
|
|
|
|
|
},
|
|
|
|
|
{ key: 'supplierName', title: 'Lieferant', type: 'text' },
|
|
|
|
|
{ key: 'isActive', title: 'Aktiv', type: 'status' },
|
|
|
|
|
{ key: 'actions', title: 'Aktionen', type: 'actions', cssClass: 'text-right' }
|
|
|
|
|
{ key: 'id', title: 'ID', type: 'text' },
|
|
|
|
|
{ key: 'description', title: 'Beschreibung', type: 'text' },
|
|
|
|
|
{
|
|
|
|
|
key: 'oldPrice',
|
|
|
|
|
title: 'Alter Preis',
|
|
|
|
|
type: 'currency',
|
|
|
|
|
cssClass: 'text-right',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'purchasePrice',
|
|
|
|
|
title: 'Einkaufspreis',
|
|
|
|
|
type: 'currency',
|
|
|
|
|
cssClass: 'text-right',
|
|
|
|
|
},
|
|
|
|
|
{ key: 'isInStock', title: 'Auf Lager', type: 'status' },
|
|
|
|
|
{ key: 'weight', title: 'Gewicht', type: 'number', cssClass: 'text-right' },
|
|
|
|
|
{ key: 'slug', title: 'Slug', type: 'text' },
|
|
|
|
|
{ key: 'createdDate', title: 'Erstellt am', type: 'date' },
|
|
|
|
|
{ key: 'lastModifiedDate', title: 'Zuletzt geändert', type: 'date' },
|
|
|
|
|
{ key: 'supplierId', title: 'Lieferanten-ID', type: 'text' },
|
|
|
|
|
{ key: 'categorieIds', title: 'Kategorie-IDs', type: 'text' },
|
|
|
|
|
{ key: 'isFeatured', title: 'Hervorgehoben', type: 'status' },
|
|
|
|
|
{
|
|
|
|
|
key: 'featuredDisplayOrder',
|
|
|
|
|
title: 'Anzeigereihenfolge (hervorgehoben)',
|
|
|
|
|
type: 'number',
|
|
|
|
|
cssClass: 'text-right',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'actions',
|
|
|
|
|
title: 'Aktionen',
|
|
|
|
|
type: 'actions',
|
|
|
|
|
cssClass: 'text-right',
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
visibleTableColumns: ColumnConfig[] = [];
|
|
|
|
|
|
|
|
|
|
@@ -55,15 +113,16 @@ export class ProductListComponent implements OnInit {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadProducts(): void {
|
|
|
|
|
this.productService.getAll().subscribe(products => {
|
|
|
|
|
this.supplierService.getAll().subscribe(suppliers => {
|
|
|
|
|
this.allProducts = products.map(p => {
|
|
|
|
|
const supplier = suppliers.find(s => s.id === p.supplierId);
|
|
|
|
|
this.productService.getAll().subscribe((products) => {
|
|
|
|
|
this.supplierService.getAll().subscribe((suppliers) => {
|
|
|
|
|
this.allProducts = products.map((p) => {
|
|
|
|
|
const supplier = suppliers.find((s) => s.id === p.supplierId);
|
|
|
|
|
return {
|
|
|
|
|
...p,
|
|
|
|
|
mainImage: this.getMainImageUrl(p.images),
|
|
|
|
|
supplierName: supplier?.name || '-',
|
|
|
|
|
createdDate: this.datePipe.transform(p.createdDate, 'dd.MM.yyyy HH:mm') || '-',
|
|
|
|
|
createdDate:
|
|
|
|
|
this.datePipe.transform(p.createdDate, 'dd.MM.yyyy HH:mm') || '-',
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
this.onSearch('');
|
|
|
|
|
@@ -73,7 +132,8 @@ export class ProductListComponent implements OnInit {
|
|
|
|
|
|
|
|
|
|
onSearch(term: string): void {
|
|
|
|
|
const lowerTerm = term.toLowerCase();
|
|
|
|
|
this.filteredProducts = this.allProducts.filter(p =>
|
|
|
|
|
this.filteredProducts = this.allProducts.filter(
|
|
|
|
|
(p) =>
|
|
|
|
|
p.name?.toLowerCase().includes(lowerTerm) ||
|
|
|
|
|
p.sku?.toLowerCase().includes(lowerTerm)
|
|
|
|
|
);
|
|
|
|
|
@@ -97,14 +157,26 @@ export class ProductListComponent implements OnInit {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private loadTableSettings(): void {
|
|
|
|
|
const savedKeys = this.storageService.getItem<string[]>(this.TABLE_SETTINGS_KEY);
|
|
|
|
|
const defaultKeys = ['mainImage', 'name', 'price', 'stockQuantity', 'isActive', 'actions'];
|
|
|
|
|
const keysToUse = (savedKeys && savedKeys.length > 0) ? savedKeys : defaultKeys;
|
|
|
|
|
this.visibleTableColumns = this.allTableColumns.filter(c => keysToUse.includes(c.key));
|
|
|
|
|
const savedKeys = this.storageService.getItem<string[]>(
|
|
|
|
|
this.TABLE_SETTINGS_KEY
|
|
|
|
|
);
|
|
|
|
|
const defaultKeys = [
|
|
|
|
|
'mainImage',
|
|
|
|
|
'name',
|
|
|
|
|
'price',
|
|
|
|
|
'stockQuantity',
|
|
|
|
|
'isActive',
|
|
|
|
|
'actions',
|
|
|
|
|
];
|
|
|
|
|
const keysToUse =
|
|
|
|
|
savedKeys && savedKeys.length > 0 ? savedKeys : defaultKeys;
|
|
|
|
|
this.visibleTableColumns = this.allTableColumns.filter((c) =>
|
|
|
|
|
keysToUse.includes(c.key)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private saveTableSettings(): void {
|
|
|
|
|
const visibleKeys = this.visibleTableColumns.map(c => c.key);
|
|
|
|
|
const visibleKeys = this.visibleTableColumns.map((c) => c.key);
|
|
|
|
|
this.storageService.setItem(this.TABLE_SETTINGS_KEY, visibleKeys);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -113,26 +185,29 @@ export class ProductListComponent implements OnInit {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isColumnVisible(columnKey: string): boolean {
|
|
|
|
|
return this.visibleTableColumns.some(c => c.key === columnKey);
|
|
|
|
|
return this.visibleTableColumns.some((c) => c.key === columnKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onColumnToggle(column: ColumnConfig, event: Event): void {
|
|
|
|
|
const checkbox = event.target as HTMLInputElement;
|
|
|
|
|
if (checkbox.checked) {
|
|
|
|
|
this.visibleTableColumns.push(column);
|
|
|
|
|
this.visibleTableColumns.sort((a, b) =>
|
|
|
|
|
this.allTableColumns.findIndex(c => c.key === a.key) -
|
|
|
|
|
this.allTableColumns.findIndex(c => c.key === b.key)
|
|
|
|
|
this.visibleTableColumns.sort(
|
|
|
|
|
(a, b) =>
|
|
|
|
|
this.allTableColumns.findIndex((c) => c.key === a.key) -
|
|
|
|
|
this.allTableColumns.findIndex((c) => c.key === b.key)
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
this.visibleTableColumns = this.visibleTableColumns.filter(c => c.key !== column.key);
|
|
|
|
|
this.visibleTableColumns = this.visibleTableColumns.filter(
|
|
|
|
|
(c) => c.key !== column.key
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
this.saveTableSettings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getMainImageUrl(images?: ProductImage[]): string {
|
|
|
|
|
if (!images || images.length === 0) return 'https://via.placeholder.com/50';
|
|
|
|
|
const mainImage = images.find(img => img.isMainImage);
|
|
|
|
|
const mainImage = images.find((img) => img.isMainImage);
|
|
|
|
|
return mainImage?.url || images[0]?.url || 'https://via.placeholder.com/50';
|
|
|
|
|
}
|
|
|
|
|
}
|