good i guess
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
/* /src/app/shared/components/form/multi-select-dropdown/multi-select-dropdown.component.css */
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-display {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 2.5rem 0.5rem 1rem;
|
||||||
|
min-height: 54px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-display:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-pills {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-text {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 1rem;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-display:focus ~ .form-label,
|
||||||
|
.form-label.has-value {
|
||||||
|
top: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-display::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 1rem;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0.8em;
|
||||||
|
height: 0.5em;
|
||||||
|
background-color: var(--color-text-light);
|
||||||
|
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-select-wrapper.is-open .select-display::after {
|
||||||
|
transform: translateY(-50%) rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-list {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 4px);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
box-shadow: var(--box-shadow-md);
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 0.5rem;
|
||||||
|
animation: fadeInDown 0.2s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-checkbox-group {
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-checkbox-group label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-checkbox-group label:hover {
|
||||||
|
background-color: var(--color-body-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<!-- /src/app/shared/components/form/multi-select-dropdown/multi-select-dropdown.component.html -->
|
||||||
|
|
||||||
|
<div class="custom-select-wrapper" [class.is-open]="isOpen">
|
||||||
|
<!-- Der klickbare Bereich, der die Pills anzeigt -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="form-input select-display"
|
||||||
|
(click)="toggleDropdown($event)">
|
||||||
|
|
||||||
|
<div class="selected-pills">
|
||||||
|
<app-status-pill
|
||||||
|
*ngFor="let value of selectedValues"
|
||||||
|
[text]="getLabelForValue(value)"
|
||||||
|
status="info"
|
||||||
|
[removable]="true"
|
||||||
|
(remove)="onPillRemove(value, $event)">
|
||||||
|
</app-status-pill>
|
||||||
|
<span *ngIf="selectedValues.length === 0" class="placeholder-text">{{ placeholder }}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Das schwebende Label -->
|
||||||
|
<label class="form-label" [class.has-value]="selectedValues.length > 0">
|
||||||
|
{{ label }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Die aufklappbare Liste mit den Checkboxen -->
|
||||||
|
<div *ngIf="isOpen" class="options-list">
|
||||||
|
<div class="category-checkbox-group">
|
||||||
|
<label *ngFor="let option of options">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[value]="option.value"
|
||||||
|
[checked]="isSelected(option.value)"
|
||||||
|
(change)="onOptionToggle(option.value)" />
|
||||||
|
{{ option.label }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// /src/app/shared/components/form/product-category-dropdown/product-category-dropdown.component.ts
|
||||||
|
|
||||||
|
import { Component, Input, forwardRef, HostListener, ElementRef, inject } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
|
import { StatusPillComponent } from '../../../../shared/components/ui/status-pill/status-pill.component';
|
||||||
|
|
||||||
|
// Eine wiederverwendbare Schnittstelle für die Optionen
|
||||||
|
export interface SelectOption {
|
||||||
|
value: any;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-product-category-dropdown',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, StatusPillComponent],
|
||||||
|
templateUrl: './product-category-dropdown.component.html',
|
||||||
|
styleUrls: ['./product-category-dropdown.component.css'],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => ProductCategoryDropdownComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ProductCategoryDropdownComponent implements ControlValueAccessor {
|
||||||
|
@Input() label = '';
|
||||||
|
@Input() options: SelectOption[] = [];
|
||||||
|
@Input() placeholder = 'Bitte wählen...';
|
||||||
|
|
||||||
|
public isOpen = false;
|
||||||
|
public selectedValues: any[] = [];
|
||||||
|
private elementRef = inject(ElementRef);
|
||||||
|
|
||||||
|
onChange: (value: any[]) => void = () => {};
|
||||||
|
onTouched: () => void = () => {};
|
||||||
|
|
||||||
|
writeValue(values: any[]): void { this.selectedValues = Array.isArray(values) ? values : []; }
|
||||||
|
registerOnChange(fn: any): void { this.onChange = fn; }
|
||||||
|
registerOnTouched(fn: any): void { this.onTouched = fn; }
|
||||||
|
|
||||||
|
@HostListener('document:click', ['$event'])
|
||||||
|
onDocumentClick(event: Event): void {
|
||||||
|
if (this.isOpen && !this.elementRef.nativeElement.contains(event.target)) {
|
||||||
|
this.closeDropdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDropdown(event: Event): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
if (!this.isOpen) { this.onTouched(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDropdown(): void {
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.isOpen = false;
|
||||||
|
this.onTouched();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOptionToggle(value: any): void {
|
||||||
|
const index = this.selectedValues.indexOf(value);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedValues.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
this.selectedValues.push(value);
|
||||||
|
}
|
||||||
|
this.onChange([...this.selectedValues]); // Wichtig: Neue Array-Instanz senden
|
||||||
|
}
|
||||||
|
|
||||||
|
onPillRemove(value: any, event: any): void { // Typ korrigiert
|
||||||
|
event.stopPropagation();
|
||||||
|
this.onOptionToggle(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected(value: any): boolean { return this.selectedValues.includes(value); }
|
||||||
|
getLabelForValue(value: any): string { return this.options.find(opt => opt.value === value)?.label || ''; }
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
FormArray,
|
FormArray,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
Validators,
|
Validators,
|
||||||
|
FormControl,
|
||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
@@ -25,7 +26,6 @@ import { ProductService } from '../../../services/product.service';
|
|||||||
import { CategoryService } from '../../../services/category.service';
|
import { CategoryService } from '../../../services/category.service';
|
||||||
import { SupplierService } from '../../../services/supplier.service';
|
import { SupplierService } from '../../../services/supplier.service';
|
||||||
import { SnackbarService } from '../../../../shared/services/snackbar.service';
|
import { SnackbarService } from '../../../../shared/services/snackbar.service';
|
||||||
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
|
|
||||||
import {
|
import {
|
||||||
ImagePreview,
|
ImagePreview,
|
||||||
ProductFormComponent,
|
ProductFormComponent,
|
||||||
@@ -38,7 +38,7 @@ import { SelectOption } from '../../../../shared/components/form/form-select/for
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
CardComponent,
|
|
||||||
ProductFormComponent,
|
ProductFormComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './product-create.component.html',
|
templateUrl: './product-create.component.html',
|
||||||
@@ -81,7 +81,7 @@ export class ProductCreateComponent implements OnInit, OnDestroy {
|
|||||||
isFeatured: [false],
|
isFeatured: [false],
|
||||||
featuredDisplayOrder: [0],
|
featuredDisplayOrder: [0],
|
||||||
supplierId: [null],
|
supplierId: [null],
|
||||||
categorieIds: this.fb.array([]),
|
categorieIds: new FormControl([]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ export class ProductCreateComponent implements OnInit, OnDestroy {
|
|||||||
this.productService.create(formData).subscribe({
|
this.productService.create(formData).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackbarService.show('Produkt erfolgreich erstellt');
|
this.snackbarService.show('Produkt erfolgreich erstellt');
|
||||||
this.router.navigate(['/admin/products']);
|
this.router.navigate(['/shop/products']);
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.snackbarService.show('Ein Fehler ist aufgetreten.');
|
this.snackbarService.show('Ein Fehler ist aufgetreten.');
|
||||||
@@ -135,7 +135,7 @@ export class ProductCreateComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancel(): void {
|
cancel(): void {
|
||||||
this.router.navigate(['/admin/products']);
|
this.router.navigate(['/shop/products']);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilesSelected(files: File[]): void {
|
onFilesSelected(files: File[]): void {
|
||||||
|
|||||||
@@ -3,26 +3,45 @@
|
|||||||
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { FormBuilder, FormGroup, FormArray, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
import {
|
||||||
|
FormBuilder,
|
||||||
|
FormGroup,
|
||||||
|
FormArray,
|
||||||
|
FormControl,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { map, startWith, debounceTime, distinctUntilChanged, finalize } from 'rxjs/operators';
|
import {
|
||||||
|
map,
|
||||||
|
startWith,
|
||||||
|
debounceTime,
|
||||||
|
distinctUntilChanged,
|
||||||
|
finalize,
|
||||||
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
// Models, Services und UI-Komponenten
|
// Models, Services und UI-Komponenten
|
||||||
import { AdminProduct, ProductImage } from '../../../../core/models/product.model';
|
import {
|
||||||
|
AdminProduct,
|
||||||
|
ProductImage,
|
||||||
|
} from '../../../../core/models/product.model';
|
||||||
import { Category } from '../../../../core/models/category.model';
|
import { Category } from '../../../../core/models/category.model';
|
||||||
import { ProductService } from '../../../services/product.service';
|
import { ProductService } from '../../../services/product.service';
|
||||||
import { CategoryService } from '../../../services/category.service';
|
import { CategoryService } from '../../../services/category.service';
|
||||||
import { SupplierService } from '../../../services/supplier.service';
|
import { SupplierService } from '../../../services/supplier.service';
|
||||||
import { SnackbarService } from '../../../../shared/services/snackbar.service';
|
import { SnackbarService } from '../../../../shared/services/snackbar.service';
|
||||||
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
|
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
|
||||||
import { ImagePreview, ProductFormComponent } from '../product-form/product-form.component';
|
import {
|
||||||
|
ImagePreview,
|
||||||
|
ProductFormComponent,
|
||||||
|
} from '../product-form/product-form.component';
|
||||||
import { SelectOption } from '../../../../shared/components/form/form-select/form-select.component';
|
import { SelectOption } from '../../../../shared/components/form/form-select/form-select.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-product-edit',
|
selector: 'app-product-edit',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ CommonModule, ReactiveFormsModule, CardComponent, ProductFormComponent ],
|
imports: [CommonModule, ReactiveFormsModule, ProductFormComponent],
|
||||||
templateUrl: './product-edit.component.html',
|
templateUrl: './product-edit.component.html',
|
||||||
styleUrls: ['./product-edit.component.css'],
|
styleUrls: ['./product-edit.component.css'],
|
||||||
})
|
})
|
||||||
@@ -49,7 +68,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
existingImages: ProductImage[] = [];
|
existingImages: ProductImage[] = [];
|
||||||
newImageFiles: File[] = [];
|
newImageFiles: File[] = [];
|
||||||
mainImageIdentifier: string | null = null; // Hält die ID oder den Dateinamen des Hauptbildes
|
mainImageIdentifier: string | null = null; // Hält die ID oder den Dateinamen des Hauptbildes
|
||||||
allImagesForForm: ImagePreview[] = []; // Die kombinierte Liste für die Child-Komponente
|
allImagesForForm: ImagePreview[] = []; // Die kombinierte Liste für die Child-Komponente
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.productForm = this.fb.group({
|
this.productForm = this.fb.group({
|
||||||
@@ -67,7 +86,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
isFeatured: [false],
|
isFeatured: [false],
|
||||||
featuredDisplayOrder: [0],
|
featuredDisplayOrder: [0],
|
||||||
supplierId: [null],
|
supplierId: [null],
|
||||||
categorieIds: this.fb.array([]),
|
categorieIds: new FormControl([]),
|
||||||
imagesToDelete: this.fb.array([]),
|
imagesToDelete: this.fb.array([]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -80,7 +99,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
const id = this.route.snapshot.paramMap.get('id');
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
if (!id) {
|
if (!id) {
|
||||||
this.snackbarService.show('Produkt-ID fehlt.');
|
this.snackbarService.show('Produkt-ID fehlt.');
|
||||||
this.router.navigate(['/admin/products']);
|
this.router.navigate(['/shop/products']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.productId = id;
|
this.productId = id;
|
||||||
@@ -91,8 +110,9 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.nameChangeSubscription?.unsubscribe();
|
this.nameChangeSubscription?.unsubscribe();
|
||||||
this.allImagesForForm.forEach(image => {
|
this.allImagesForForm.forEach((image) => {
|
||||||
if (typeof image.url === 'object') { // Nur Object-URLs von neuen Bildern freigeben
|
if (typeof image.url === 'object') {
|
||||||
|
// Nur Object-URLs von neuen Bildern freigeben
|
||||||
URL.revokeObjectURL(image.url as string);
|
URL.revokeObjectURL(image.url as string);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -101,44 +121,55 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
loadDropdownData(): void {
|
loadDropdownData(): void {
|
||||||
this.allCategories$ = this.categoryService.getAll();
|
this.allCategories$ = this.categoryService.getAll();
|
||||||
this.supplierOptions$ = this.supplierService.getAll().pipe(
|
this.supplierOptions$ = this.supplierService.getAll().pipe(
|
||||||
map(suppliers => suppliers.map(s => ({ value: s.id, label: s.name || 'Unbenannt' }))),
|
map((suppliers) =>
|
||||||
|
suppliers.map((s) => ({ value: s.id, label: s.name || 'Unbenannt' }))
|
||||||
|
),
|
||||||
startWith([])
|
startWith([])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadProductData(): void {
|
loadProductData(): void {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.productService.getById(this.productId).pipe(
|
this.productService
|
||||||
finalize(() => { this.isLoading = false; })
|
.getById(this.productId)
|
||||||
).subscribe({
|
.pipe(
|
||||||
next: (product) => {
|
finalize(() => {
|
||||||
if (product) {
|
this.isLoading = false;
|
||||||
this.populateForm(product);
|
})
|
||||||
} else {
|
)
|
||||||
this.snackbarService.show('Produkt nicht gefunden.');
|
.subscribe({
|
||||||
this.router.navigate(['/admin/products']);
|
next: (product) => {
|
||||||
}
|
if (product) {
|
||||||
},
|
this.populateForm(product);
|
||||||
error: (err) => {
|
} else {
|
||||||
this.snackbarService.show('Fehler beim Laden des Produkts.');
|
this.snackbarService.show('Produkt nicht gefunden.');
|
||||||
console.error('Fehler beim Laden der Produktdaten:', err);
|
this.router.navigate(['/shop/products']);
|
||||||
this.router.navigate(['/admin/products']);
|
}
|
||||||
},
|
},
|
||||||
});
|
error: (err) => {
|
||||||
|
this.snackbarService.show('Fehler beim Laden des Produkts.');
|
||||||
|
console.error('Fehler beim Laden der Produktdaten:', err);
|
||||||
|
this.router.navigate(['/shop/products']);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
populateForm(product: AdminProduct): void {
|
// AFTER (CORRECT)
|
||||||
this.productForm.patchValue(product);
|
populateForm(product: AdminProduct): void {
|
||||||
const categories = this.productForm.get('categorieIds') as FormArray;
|
// This single line handles ALL form controls, including setting
|
||||||
categories.clear();
|
// the 'categorieIds' FormControl to the array from the product object.
|
||||||
product.categorieIds?.forEach(id => categories.push(new FormControl(id)));
|
this.productForm.patchValue(product);
|
||||||
|
|
||||||
this.existingImages = product.images || [];
|
// The obsolete FormArray logic has been removed.
|
||||||
const mainImage = this.existingImages.find(img => img.isMainImage);
|
|
||||||
this.mainImageIdentifier = mainImage?.id || (this.existingImages.length > 0 ? this.existingImages[0].id : null);
|
|
||||||
|
|
||||||
this.rebuildAllImagesForForm();
|
this.existingImages = product.images || [];
|
||||||
}
|
const mainImage = this.existingImages.find((img) => img.isMainImage);
|
||||||
|
this.mainImageIdentifier =
|
||||||
|
mainImage?.id ||
|
||||||
|
(this.existingImages.length > 0 ? this.existingImages[0].id : null);
|
||||||
|
|
||||||
|
this.rebuildAllImagesForForm();
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
if (this.productForm.invalid) {
|
if (this.productForm.invalid) {
|
||||||
@@ -151,7 +182,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
this.productService.update(this.productId, formData).subscribe({
|
this.productService.update(this.productId, formData).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackbarService.show('Produkt erfolgreich aktualisiert');
|
this.snackbarService.show('Produkt erfolgreich aktualisiert');
|
||||||
this.router.navigate(['/admin/products']);
|
this.router.navigate(['/shop/products']);
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.snackbarService.show('Ein Fehler ist aufgetreten.');
|
this.snackbarService.show('Ein Fehler ist aufgetreten.');
|
||||||
@@ -160,7 +191,9 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(): void { this.router.navigate(['/admin/products']); }
|
cancel(): void {
|
||||||
|
this.router.navigate(['/shop/products']);
|
||||||
|
}
|
||||||
|
|
||||||
// --- NEUE EVENT-HANDLER FÜR BILDER ---
|
// --- NEUE EVENT-HANDLER FÜR BILDER ---
|
||||||
onFilesSelected(files: File[]): void {
|
onFilesSelected(files: File[]): void {
|
||||||
@@ -177,17 +210,23 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDeleteImage(identifier: string): void {
|
onDeleteImage(identifier: string): void {
|
||||||
const isExisting = this.existingImages.some(img => img.id === identifier);
|
const isExisting = this.existingImages.some((img) => img.id === identifier);
|
||||||
if (isExisting) {
|
if (isExisting) {
|
||||||
this.imagesToDelete.push(new FormControl(identifier));
|
this.imagesToDelete.push(new FormControl(identifier));
|
||||||
this.existingImages = this.existingImages.filter(img => img.id !== identifier);
|
this.existingImages = this.existingImages.filter(
|
||||||
|
(img) => img.id !== identifier
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.newImageFiles = this.newImageFiles.filter(file => file.name !== identifier);
|
this.newImageFiles = this.newImageFiles.filter(
|
||||||
|
(file) => file.name !== identifier
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.mainImageIdentifier === identifier) {
|
if (this.mainImageIdentifier === identifier) {
|
||||||
const firstExisting = this.existingImages.length > 0 ? this.existingImages[0].id : null;
|
const firstExisting =
|
||||||
const firstNew = this.newImageFiles.length > 0 ? this.newImageFiles[0].name : null;
|
this.existingImages.length > 0 ? this.existingImages[0].id : null;
|
||||||
|
const firstNew =
|
||||||
|
this.newImageFiles.length > 0 ? this.newImageFiles[0].name : null;
|
||||||
this.mainImageIdentifier = firstExisting || firstNew;
|
this.mainImageIdentifier = firstExisting || firstNew;
|
||||||
}
|
}
|
||||||
this.rebuildAllImagesForForm();
|
this.rebuildAllImagesForForm();
|
||||||
@@ -195,19 +234,28 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
// --- Private Helfermethoden ---
|
// --- Private Helfermethoden ---
|
||||||
private rebuildAllImagesForForm(): void {
|
private rebuildAllImagesForForm(): void {
|
||||||
this.allImagesForForm.forEach(image => {
|
this.allImagesForForm.forEach((image) => {
|
||||||
if (typeof image.url === 'object') URL.revokeObjectURL(image.url as string);
|
if (typeof image.url === 'object')
|
||||||
|
URL.revokeObjectURL(image.url as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
const combined: ImagePreview[] = [];
|
const combined: ImagePreview[] = [];
|
||||||
this.existingImages
|
this.existingImages
|
||||||
.filter(img => !!img.url) // Stellt sicher, dass nur Bilder mit URL verwendet werden
|
.filter((img) => !!img.url) // Stellt sicher, dass nur Bilder mit URL verwendet werden
|
||||||
.forEach(img => {
|
.forEach((img) => {
|
||||||
combined.push({ identifier: img.id, url: img.url!, isMainImage: img.id === this.mainImageIdentifier });
|
combined.push({
|
||||||
|
identifier: img.id,
|
||||||
|
url: img.url!,
|
||||||
|
isMainImage: img.id === this.mainImageIdentifier,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.newImageFiles.forEach(file => {
|
this.newImageFiles.forEach((file) => {
|
||||||
combined.push({ identifier: file.name, url: this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(file)), isMainImage: file.name === this.mainImageIdentifier });
|
combined.push({
|
||||||
|
identifier: file.name,
|
||||||
|
url: this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(file)),
|
||||||
|
isMainImage: file.name === this.mainImageIdentifier,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.allImagesForForm = combined;
|
this.allImagesForForm = combined;
|
||||||
@@ -220,13 +268,17 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
Object.keys(formValue).forEach((key) => {
|
Object.keys(formValue).forEach((key) => {
|
||||||
const value = formValue[key];
|
const value = formValue[key];
|
||||||
if (key === 'categorieIds' || key === 'imagesToDelete') {
|
if (key === 'categorieIds' || key === 'imagesToDelete') {
|
||||||
(value as string[]).forEach((id) => formData.append(this.capitalizeFirstLetter(key), id));
|
(value as string[]).forEach((id) =>
|
||||||
|
formData.append(this.capitalizeFirstLetter(key), id)
|
||||||
|
);
|
||||||
} else if (value !== null && value !== undefined && value !== '') {
|
} else if (value !== null && value !== undefined && value !== '') {
|
||||||
formData.append(this.capitalizeFirstLetter(key), value.toString());
|
formData.append(this.capitalizeFirstLetter(key), value.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const mainImageFile = this.newImageFiles.find(f => f.name === this.mainImageIdentifier);
|
const mainImageFile = this.newImageFiles.find(
|
||||||
|
(f) => f.name === this.mainImageIdentifier
|
||||||
|
);
|
||||||
if (mainImageFile) {
|
if (mainImageFile) {
|
||||||
formData.append('MainImageFile', mainImageFile);
|
formData.append('MainImageFile', mainImageFile);
|
||||||
} else if (this.mainImageIdentifier) {
|
} else if (this.mainImageIdentifier) {
|
||||||
@@ -234,13 +286,12 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.newImageFiles
|
this.newImageFiles
|
||||||
.filter(f => f.name !== this.mainImageIdentifier)
|
.filter((f) => f.name !== this.mainImageIdentifier)
|
||||||
.forEach(file => formData.append('AdditionalImageFiles', file));
|
.forEach((file) => formData.append('AdditionalImageFiles', file));
|
||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Private Helfermethoden
|
// Private Helfermethoden
|
||||||
private prepareSubmissionData(): void {
|
private prepareSubmissionData(): void {
|
||||||
const nameControl = this.productForm.get('name');
|
const nameControl = this.productForm.get('name');
|
||||||
@@ -259,7 +310,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private subscribeToNameChanges(): void {
|
private subscribeToNameChanges(): void {
|
||||||
const nameControl = this.productForm.get('name');
|
const nameControl = this.productForm.get('name');
|
||||||
const slugControl = this.productForm.get('slug');
|
const slugControl = this.productForm.get('slug');
|
||||||
|
|
||||||
@@ -274,7 +325,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateSlug(name: string): string {
|
private generateSlug(name: string): string {
|
||||||
return name
|
return name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/\s+/g, '-')
|
.replace(/\s+/g, '-')
|
||||||
@@ -286,7 +337,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
.replace(/-+/g, '-');
|
.replace(/-+/g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateSkuValue(name: string): string {
|
private generateSkuValue(name: string): string {
|
||||||
const prefix = name.substring(0, 4).toUpperCase().replace(/\s+/g, '');
|
const prefix = name.substring(0, 4).toUpperCase().replace(/\s+/g, '');
|
||||||
const randomPart = Math.random().toString(36).substring(2, 8).toUpperCase();
|
const randomPart = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||||
return `${prefix}-${randomPart}`;
|
return `${prefix}-${randomPart}`;
|
||||||
@@ -295,12 +346,4 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
private capitalizeFirstLetter(string: string): string {
|
private capitalizeFirstLetter(string: string): string {
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,23 +87,17 @@
|
|||||||
<app-form-select label="Lieferant" [options]="supplierOptions" formControlName="supplierId"></app-form-select>
|
<app-form-select label="Lieferant" [options]="supplierOptions" formControlName="supplierId"></app-form-select>
|
||||||
|
|
||||||
|
|
||||||
<div class="multi-select-container">
|
<app-product-category-dropdown
|
||||||
<div class="selected-pills">
|
label="Kategorien"
|
||||||
<app-status-pill
|
|
||||||
*ngFor="let catId of categorieIds.value"
|
[options]="categoryOptions"
|
||||||
[text]="getCategoryName(catId)"
|
formControlName="categorieIds"
|
||||||
status="info"
|
>
|
||||||
(remove)="removeCategoryById(catId)">
|
|
||||||
</app-status-pill>
|
|
||||||
<span *ngIf="categorieIds.length === 0" class="placeholder">Keine ausgewählt</span>
|
</app-product-category-dropdown>
|
||||||
</div>
|
|
||||||
<div class="category-checkbox-group">
|
|
||||||
<label *ngFor="let category of allCategories">
|
|
||||||
<input type="checkbox" [value]="category.id" [checked]="isCategorySelected(category.id)" (change)="onCategoryChange($event)" />
|
|
||||||
{{ category.name }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</app-form-group>
|
</app-form-group>
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
// /src/app/features/admin/components/products/product-form/product-form.component.ts
|
// /src/app/features/admin/components/products/product-form/product-form.component.ts (CORRECTED)
|
||||||
|
|
||||||
import { Component, Input, Output, EventEmitter, inject } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, inject, SimpleChanges } from '@angular/core';
|
||||||
import { CommonModule, NgClass } from '@angular/common';
|
import { CommonModule, NgClass } from '@angular/common';
|
||||||
import {
|
import { FormGroup, ReactiveFormsModule } from '@angular/forms'; // FormArray and FormControl no longer needed here
|
||||||
FormGroup,
|
|
||||||
FormArray,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
FormControl,
|
|
||||||
} from '@angular/forms';
|
|
||||||
import { SafeUrl } from '@angular/platform-browser';
|
import { SafeUrl } from '@angular/platform-browser';
|
||||||
|
|
||||||
// Models & UI Components
|
// Models & UI Components
|
||||||
import { Category } from '../../../../core/models/category.model';
|
import { Category } from '../../../../core/models/category.model';
|
||||||
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
|
|
||||||
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
||||||
import { IconComponent } from '../../../../shared/components/ui/icon/icon.component';
|
import { IconComponent } from '../../../../shared/components/ui/icon/icon.component';
|
||||||
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
|
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
|
||||||
@@ -24,11 +18,10 @@ import { FormTextareaComponent } from '../../../../shared/components/form/form-t
|
|||||||
import { SlideToggleComponent } from '../../../../shared/components/form/slide-toggle/slide-toggle.component';
|
import { SlideToggleComponent } from '../../../../shared/components/form/slide-toggle/slide-toggle.component';
|
||||||
import { SnackbarService } from '../../../../shared/services/snackbar.service';
|
import { SnackbarService } from '../../../../shared/services/snackbar.service';
|
||||||
import { FormGroupComponent } from '../../../../shared/components/form/form-group/form-group.component';
|
import { FormGroupComponent } from '../../../../shared/components/form/form-group/form-group.component';
|
||||||
import { StatusPillComponent } from '../../../../shared/components/ui/status-pill/status-pill.component';
|
import { ProductCategoryDropdownComponent } from '../product-category-dropdown/product-category-dropdown.component';
|
||||||
|
|
||||||
// Interface für eine einheitliche Bild-Datenstruktur, die von der Elternkomponente kommt
|
|
||||||
export interface ImagePreview {
|
export interface ImagePreview {
|
||||||
identifier: string; // Eindeutiger Bezeichner (ID für existierende, Dateiname für neue)
|
identifier: string;
|
||||||
url: string | SafeUrl;
|
url: string | SafeUrl;
|
||||||
isMainImage: boolean;
|
isMainImage: boolean;
|
||||||
}
|
}
|
||||||
@@ -37,64 +30,58 @@ export interface ImagePreview {
|
|||||||
selector: 'app-product-form',
|
selector: 'app-product-form',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule, NgClass, ReactiveFormsModule, ButtonComponent, IconComponent,
|
||||||
NgClass,
|
FormFieldComponent, FormSelectComponent, FormTextareaComponent,
|
||||||
ReactiveFormsModule,
|
SlideToggleComponent, FormGroupComponent, ProductCategoryDropdownComponent
|
||||||
CardComponent,
|
|
||||||
ButtonComponent,
|
|
||||||
IconComponent,
|
|
||||||
FormFieldComponent,
|
|
||||||
FormSelectComponent,
|
|
||||||
FormTextareaComponent,
|
|
||||||
SlideToggleComponent,
|
|
||||||
FormGroupComponent,
|
|
||||||
StatusPillComponent
|
|
||||||
],
|
],
|
||||||
templateUrl: './product-form.component.html',
|
templateUrl: './product-form.component.html',
|
||||||
styleUrls: ['./product-form.component.css'],
|
styleUrls: ['./product-form.component.css'],
|
||||||
})
|
})
|
||||||
export class ProductFormComponent {
|
export class ProductFormComponent {
|
||||||
// --- Inputs für Daten ---
|
// --- Inputs ---
|
||||||
@Input() productForm!: FormGroup;
|
@Input() productForm!: FormGroup;
|
||||||
@Input() allCategories: Category[] = [];
|
@Input() allCategories: Category[] = [];
|
||||||
@Input() supplierOptions: SelectOption[] = [];
|
@Input() supplierOptions: SelectOption[] = [];
|
||||||
@Input() isLoading = false;
|
@Input() isLoading = false;
|
||||||
@Input() submitButtonText = 'Speichern';
|
@Input() submitButtonText = 'Speichern';
|
||||||
|
|
||||||
// NEU: Empfängt eine einzige, kombinierte Bildliste von der Elternkomponente
|
|
||||||
@Input() allImages: ImagePreview[] = [];
|
@Input() allImages: ImagePreview[] = [];
|
||||||
|
|
||||||
// --- Outputs für Aktionen ---
|
// --- Outputs ---
|
||||||
@Output() formSubmit = new EventEmitter<void>();
|
@Output() formSubmit = new EventEmitter<void>();
|
||||||
@Output() formCancel = new EventEmitter<void>();
|
@Output() formCancel = new EventEmitter<void>();
|
||||||
@Output() filesSelected = new EventEmitter<File[]>(); // Sendet alle neu ausgewählten Dateien
|
@Output() filesSelected = new EventEmitter<File[]>();
|
||||||
@Output() setMainImage = new EventEmitter<string>(); // Sendet den 'identifier' des Bildes
|
@Output() setMainImage = new EventEmitter<string>();
|
||||||
@Output() deleteImage = new EventEmitter<string>(); // Sendet den 'identifier' des Bildes
|
@Output() deleteImage = new EventEmitter<string>();
|
||||||
|
|
||||||
private snackbarService = inject(SnackbarService);
|
private snackbarService = inject(SnackbarService);
|
||||||
|
public categoryOptions: SelectOption[] = [];
|
||||||
|
|
||||||
// GETTER FÜR DAS TEMPLATE
|
// --- GETTER ---
|
||||||
get categorieIds(): FormArray {
|
|
||||||
return this.productForm.get('categorieIds') as FormArray;
|
|
||||||
}
|
|
||||||
get hasImages(): boolean {
|
get hasImages(): boolean {
|
||||||
return this.allImages && this.allImages.length > 0;
|
return this.allImages && this.allImages.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- EVENT-HANDLER ---
|
// --- LIFECYCLE HOOKS ---
|
||||||
onSubmit(): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
this.formSubmit.emit();
|
if (changes['allCategories']) {
|
||||||
}
|
this.categoryOptions = this.allCategories
|
||||||
cancel(): void {
|
.filter(cat => !!cat.name)
|
||||||
this.formCancel.emit();
|
.map(cat => ({
|
||||||
|
value: cat.id,
|
||||||
|
label: cat.name!
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- EVENT-HANDLERS ---
|
||||||
|
onSubmit(): void { this.formSubmit.emit(); }
|
||||||
|
cancel(): void { this.formCancel.emit(); }
|
||||||
|
|
||||||
onFilesSelected(event: Event): void {
|
onFilesSelected(event: Event): void {
|
||||||
const files = (event.target as HTMLInputElement).files;
|
const files = (event.target as HTMLInputElement).files;
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
this.filesSelected.emit(Array.from(files));
|
this.filesSelected.emit(Array.from(files));
|
||||||
}
|
}
|
||||||
// Wichtig: Input-Wert zurücksetzen, damit die gleichen Dateien erneut ausgewählt werden können
|
|
||||||
(event.target as HTMLInputElement).value = '';
|
(event.target as HTMLInputElement).value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,11 +90,10 @@ export class ProductFormComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestImageDeletion(identifier: string, event: MouseEvent): void {
|
requestImageDeletion(identifier: string, event: MouseEvent): void {
|
||||||
event.stopPropagation(); // Verhindert, dass gleichzeitig setAsMainImage gefeuert wird
|
event.stopPropagation();
|
||||||
this.deleteImage.emit(identifier);
|
this.deleteImage.emit(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Helfermethoden (unverändert) ---
|
|
||||||
generateSku(): void {
|
generateSku(): void {
|
||||||
const name = this.productForm.get('name')?.value || 'PROD';
|
const name = this.productForm.get('name')?.value || 'PROD';
|
||||||
const prefix = name.substring(0, 4).toUpperCase().replace(/\s+/g, '');
|
const prefix = name.substring(0, 4).toUpperCase().replace(/\s+/g, '');
|
||||||
@@ -117,33 +103,5 @@ export class ProductFormComponent {
|
|||||||
this.snackbarService.show('Neue SKU generiert!');
|
this.snackbarService.show('Neue SKU generiert!');
|
||||||
}
|
}
|
||||||
|
|
||||||
onCategoryChange(event: Event): void {
|
// ALL OBSOLETE CATEGORY HELPER FUNCTIONS HAVE BEEN REMOVED
|
||||||
const checkbox = event.target as HTMLInputElement;
|
|
||||||
const categoryId = checkbox.value;
|
|
||||||
if (checkbox.checked) {
|
|
||||||
if (!this.categorieIds.value.includes(categoryId)) {
|
|
||||||
this.categorieIds.push(new FormControl(categoryId));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const index = this.categorieIds.controls.findIndex(
|
|
||||||
(x) => x.value === categoryId
|
|
||||||
);
|
|
||||||
if (index !== -1) this.categorieIds.removeAt(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isCategorySelected(categoryId: string): boolean {
|
|
||||||
return this.categorieIds.value.includes(categoryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCategoryName(categoryId: string): string {
|
|
||||||
return this.allCategories.find((c) => c.id === categoryId)?.name || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
removeCategoryById(categoryId: string): void {
|
|
||||||
const index = this.categorieIds.controls.findIndex(
|
|
||||||
(x) => x.value === categoryId
|
|
||||||
);
|
|
||||||
if (index !== -1) this.categorieIds.removeAt(index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ import { IconComponent } from '../../../../shared/components/ui/icon/icon.compon
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-product-list',
|
selector: 'app-product-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, CurrencyPipe, DatePipe, GenericTableComponent, SearchBarComponent, ButtonComponent, IconComponent],
|
imports: [CommonModule, CurrencyPipe, GenericTableComponent, SearchBarComponent, ButtonComponent, IconComponent],
|
||||||
providers: [DatePipe],
|
providers: [DatePipe],
|
||||||
templateUrl: './product-list.component.html',
|
templateUrl: './product-list.component.html',
|
||||||
styleUrl: './product-list.component.css'
|
styleUrl: './product-list.component.css'
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { CommonModule, NgClass } from '@angular/common';
|
import { CommonModule, NgClass } from '@angular/common';
|
||||||
import { IconComponent } from '../icon/icon.component'; // IconComponent importieren
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-status-pill',
|
selector: 'app-status-pill',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, NgClass, IconComponent], // IconComponent hinzufügen
|
imports: [CommonModule, NgClass], // IconComponent hinzufügen
|
||||||
templateUrl: './status-pill.component.html',
|
templateUrl: './status-pill.component.html',
|
||||||
styleUrls: ['./status-pill.component.css']
|
styleUrls: ['./status-pill.component.css']
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user