diff --git a/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.css b/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.css
new file mode 100644
index 0000000..6227400
--- /dev/null
+++ b/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.css
@@ -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); }
+}
\ No newline at end of file
diff --git a/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.html b/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.html
new file mode 100644
index 0000000..16e98f4
--- /dev/null
+++ b/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.ts b/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.ts
new file mode 100644
index 0000000..d47fbd7
--- /dev/null
+++ b/src/app/features/components/products/product-category-dropdown/product-category-dropdown.component.ts
@@ -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 || ''; }
+}
\ No newline at end of file
diff --git a/src/app/features/components/products/product-create/product-create.component.ts b/src/app/features/components/products/product-create/product-create.component.ts
index 1ee3554..4d84de1 100644
--- a/src/app/features/components/products/product-create/product-create.component.ts
+++ b/src/app/features/components/products/product-create/product-create.component.ts
@@ -9,6 +9,7 @@ import {
FormArray,
ReactiveFormsModule,
Validators,
+ FormControl,
} from '@angular/forms';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { Observable, Subscription } from 'rxjs';
@@ -25,7 +26,6 @@ import { ProductService } from '../../../services/product.service';
import { CategoryService } from '../../../services/category.service';
import { SupplierService } from '../../../services/supplier.service';
import { SnackbarService } from '../../../../shared/services/snackbar.service';
-import { CardComponent } from '../../../../shared/components/ui/card/card.component';
import {
ImagePreview,
ProductFormComponent,
@@ -38,7 +38,7 @@ import { SelectOption } from '../../../../shared/components/form/form-select/for
imports: [
CommonModule,
ReactiveFormsModule,
- CardComponent,
+
ProductFormComponent,
],
templateUrl: './product-create.component.html',
@@ -81,7 +81,7 @@ export class ProductCreateComponent implements OnInit, OnDestroy {
isFeatured: [false],
featuredDisplayOrder: [0],
supplierId: [null],
- categorieIds: this.fb.array([]),
+ categorieIds: new FormControl([]),
});
}
@@ -125,7 +125,7 @@ export class ProductCreateComponent implements OnInit, OnDestroy {
this.productService.create(formData).subscribe({
next: () => {
this.snackbarService.show('Produkt erfolgreich erstellt');
- this.router.navigate(['/admin/products']);
+ this.router.navigate(['/shop/products']);
},
error: (err) => {
this.snackbarService.show('Ein Fehler ist aufgetreten.');
@@ -135,7 +135,7 @@ export class ProductCreateComponent implements OnInit, OnDestroy {
}
cancel(): void {
- this.router.navigate(['/admin/products']);
+ this.router.navigate(['/shop/products']);
}
onFilesSelected(files: File[]): void {
diff --git a/src/app/features/components/products/product-edit/product-edit.component.ts b/src/app/features/components/products/product-edit/product-edit.component.ts
index 32306e6..e35e218 100644
--- a/src/app/features/components/products/product-edit/product-edit.component.ts
+++ b/src/app/features/components/products/product-edit/product-edit.component.ts
@@ -3,26 +3,45 @@
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
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 { 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
-import { AdminProduct, ProductImage } from '../../../../core/models/product.model';
+import {
+ AdminProduct,
+ ProductImage,
+} from '../../../../core/models/product.model';
import { Category } from '../../../../core/models/category.model';
import { ProductService } from '../../../services/product.service';
import { CategoryService } from '../../../services/category.service';
import { SupplierService } from '../../../services/supplier.service';
import { SnackbarService } from '../../../../shared/services/snackbar.service';
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';
@Component({
selector: 'app-product-edit',
standalone: true,
- imports: [ CommonModule, ReactiveFormsModule, CardComponent, ProductFormComponent ],
+ imports: [CommonModule, ReactiveFormsModule, ProductFormComponent],
templateUrl: './product-edit.component.html',
styleUrls: ['./product-edit.component.css'],
})
@@ -49,7 +68,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
existingImages: ProductImage[] = [];
newImageFiles: File[] = [];
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() {
this.productForm = this.fb.group({
@@ -67,7 +86,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
isFeatured: [false],
featuredDisplayOrder: [0],
supplierId: [null],
- categorieIds: this.fb.array([]),
+ categorieIds: new FormControl([]),
imagesToDelete: this.fb.array([]),
});
}
@@ -80,7 +99,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
const id = this.route.snapshot.paramMap.get('id');
if (!id) {
this.snackbarService.show('Produkt-ID fehlt.');
- this.router.navigate(['/admin/products']);
+ this.router.navigate(['/shop/products']);
return;
}
this.productId = id;
@@ -91,8 +110,9 @@ export class ProductEditComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.nameChangeSubscription?.unsubscribe();
- this.allImagesForForm.forEach(image => {
- if (typeof image.url === 'object') { // Nur Object-URLs von neuen Bildern freigeben
+ this.allImagesForForm.forEach((image) => {
+ if (typeof image.url === 'object') {
+ // Nur Object-URLs von neuen Bildern freigeben
URL.revokeObjectURL(image.url as string);
}
});
@@ -101,44 +121,55 @@ export class ProductEditComponent implements OnInit, OnDestroy {
loadDropdownData(): void {
this.allCategories$ = this.categoryService.getAll();
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([])
);
}
loadProductData(): void {
this.isLoading = true;
- this.productService.getById(this.productId).pipe(
- finalize(() => { this.isLoading = false; })
- ).subscribe({
- next: (product) => {
- if (product) {
- this.populateForm(product);
- } else {
- this.snackbarService.show('Produkt nicht gefunden.');
- 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(['/admin/products']);
- },
- });
+ this.productService
+ .getById(this.productId)
+ .pipe(
+ finalize(() => {
+ this.isLoading = false;
+ })
+ )
+ .subscribe({
+ next: (product) => {
+ if (product) {
+ this.populateForm(product);
+ } else {
+ this.snackbarService.show('Produkt nicht gefunden.');
+ this.router.navigate(['/shop/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 {
- this.productForm.patchValue(product);
- const categories = this.productForm.get('categorieIds') as FormArray;
- categories.clear();
- product.categorieIds?.forEach(id => categories.push(new FormControl(id)));
+// AFTER (CORRECT)
+populateForm(product: AdminProduct): void {
+ // This single line handles ALL form controls, including setting
+ // the 'categorieIds' FormControl to the array from the product object.
+ this.productForm.patchValue(product);
- 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();
- }
+ // The obsolete FormArray logic has been removed.
+
+ 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 {
if (this.productForm.invalid) {
@@ -151,7 +182,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
this.productService.update(this.productId, formData).subscribe({
next: () => {
this.snackbarService.show('Produkt erfolgreich aktualisiert');
- this.router.navigate(['/admin/products']);
+ this.router.navigate(['/shop/products']);
},
error: (err) => {
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 ---
onFilesSelected(files: File[]): void {
@@ -177,17 +210,23 @@ export class ProductEditComponent implements OnInit, OnDestroy {
}
onDeleteImage(identifier: string): void {
- const isExisting = this.existingImages.some(img => img.id === identifier);
+ const isExisting = this.existingImages.some((img) => img.id === identifier);
if (isExisting) {
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 {
- this.newImageFiles = this.newImageFiles.filter(file => file.name !== identifier);
+ this.newImageFiles = this.newImageFiles.filter(
+ (file) => file.name !== identifier
+ );
}
if (this.mainImageIdentifier === identifier) {
- const firstExisting = this.existingImages.length > 0 ? this.existingImages[0].id : null;
- const firstNew = this.newImageFiles.length > 0 ? this.newImageFiles[0].name : null;
+ const firstExisting =
+ 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.rebuildAllImagesForForm();
@@ -195,19 +234,28 @@ export class ProductEditComponent implements OnInit, OnDestroy {
// --- Private Helfermethoden ---
private rebuildAllImagesForForm(): void {
- this.allImagesForForm.forEach(image => {
- if (typeof image.url === 'object') URL.revokeObjectURL(image.url as string);
+ this.allImagesForForm.forEach((image) => {
+ if (typeof image.url === 'object')
+ URL.revokeObjectURL(image.url as string);
});
const combined: ImagePreview[] = [];
this.existingImages
- .filter(img => !!img.url) // Stellt sicher, dass nur Bilder mit URL verwendet werden
- .forEach(img => {
- combined.push({ identifier: img.id, url: img.url!, isMainImage: img.id === this.mainImageIdentifier });
+ .filter((img) => !!img.url) // Stellt sicher, dass nur Bilder mit URL verwendet werden
+ .forEach((img) => {
+ combined.push({
+ identifier: img.id,
+ url: img.url!,
+ isMainImage: img.id === this.mainImageIdentifier,
+ });
+ });
+
+ this.newImageFiles.forEach((file) => {
+ combined.push({
+ identifier: file.name,
+ url: this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(file)),
+ isMainImage: file.name === this.mainImageIdentifier,
});
-
- this.newImageFiles.forEach(file => {
- combined.push({ identifier: file.name, url: this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(file)), isMainImage: file.name === this.mainImageIdentifier });
});
this.allImagesForForm = combined;
@@ -220,27 +268,30 @@ export class ProductEditComponent implements OnInit, OnDestroy {
Object.keys(formValue).forEach((key) => {
const value = formValue[key];
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 !== '') {
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) {
formData.append('MainImageFile', mainImageFile);
} else if (this.mainImageIdentifier) {
formData.append('MainImageId', this.mainImageIdentifier);
}
-
+
this.newImageFiles
- .filter(f => f.name !== this.mainImageIdentifier)
- .forEach(file => formData.append('AdditionalImageFiles', file));
-
+ .filter((f) => f.name !== this.mainImageIdentifier)
+ .forEach((file) => formData.append('AdditionalImageFiles', file));
+
return formData;
}
-
// Private Helfermethoden
private prepareSubmissionData(): void {
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 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
.toLowerCase()
.replace(/\s+/g, '-')
@@ -286,7 +337,7 @@ export class ProductEditComponent implements OnInit, OnDestroy {
.replace(/-+/g, '-');
}
- private generateSkuValue(name: string): string {
+ private generateSkuValue(name: string): string {
const prefix = name.substring(0, 4).toUpperCase().replace(/\s+/g, '');
const randomPart = Math.random().toString(36).substring(2, 8).toUpperCase();
return `${prefix}-${randomPart}`;
@@ -295,12 +346,4 @@ export class ProductEditComponent implements OnInit, OnDestroy {
private capitalizeFirstLetter(string: string): string {
return string.charAt(0).toUpperCase() + string.slice(1);
}
-
-
-
-
-
-
-
-
}
diff --git a/src/app/features/components/products/product-form/product-form.component.html b/src/app/features/components/products/product-form/product-form.component.html
index b276925..4b0c1ab 100644
--- a/src/app/features/components/products/product-form/product-form.component.html
+++ b/src/app/features/components/products/product-form/product-form.component.html
@@ -86,24 +86,18 @@
+
+
+
+
+
-
-
-
-
-
-
+
diff --git a/src/app/features/components/products/product-form/product-form.component.ts b/src/app/features/components/products/product-form/product-form.component.ts
index 38be0ac..d08cecf 100644
--- a/src/app/features/components/products/product-form/product-form.component.ts
+++ b/src/app/features/components/products/product-form/product-form.component.ts
@@ -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 {
- FormGroup,
- FormArray,
- ReactiveFormsModule,
- FormControl,
-} from '@angular/forms';
+import { FormGroup, ReactiveFormsModule } from '@angular/forms'; // FormArray and FormControl no longer needed here
import { SafeUrl } from '@angular/platform-browser';
// Models & UI Components
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 { IconComponent } from '../../../../shared/components/ui/icon/icon.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 { SnackbarService } from '../../../../shared/services/snackbar.service';
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 {
- identifier: string; // Eindeutiger Bezeichner (ID für existierende, Dateiname für neue)
+ identifier: string;
url: string | SafeUrl;
isMainImage: boolean;
}
@@ -37,64 +30,58 @@ export interface ImagePreview {
selector: 'app-product-form',
standalone: true,
imports: [
- CommonModule,
- NgClass,
- ReactiveFormsModule,
- CardComponent,
- ButtonComponent,
- IconComponent,
- FormFieldComponent,
- FormSelectComponent,
- FormTextareaComponent,
- SlideToggleComponent,
- FormGroupComponent,
- StatusPillComponent
+ CommonModule, NgClass, ReactiveFormsModule, ButtonComponent, IconComponent,
+ FormFieldComponent, FormSelectComponent, FormTextareaComponent,
+ SlideToggleComponent, FormGroupComponent, ProductCategoryDropdownComponent
],
templateUrl: './product-form.component.html',
styleUrls: ['./product-form.component.css'],
})
-export class ProductFormComponent {
- // --- Inputs für Daten ---
+export class ProductFormComponent {
+ // --- Inputs ---
@Input() productForm!: FormGroup;
@Input() allCategories: Category[] = [];
@Input() supplierOptions: SelectOption[] = [];
@Input() isLoading = false;
@Input() submitButtonText = 'Speichern';
-
- // NEU: Empfängt eine einzige, kombinierte Bildliste von der Elternkomponente
@Input() allImages: ImagePreview[] = [];
-
- // --- Outputs für Aktionen ---
+
+ // --- Outputs ---
@Output() formSubmit = new EventEmitter();
@Output() formCancel = new EventEmitter();
- @Output() filesSelected = new EventEmitter(); // Sendet alle neu ausgewählten Dateien
- @Output() setMainImage = new EventEmitter(); // Sendet den 'identifier' des Bildes
- @Output() deleteImage = new EventEmitter(); // Sendet den 'identifier' des Bildes
+ @Output() filesSelected = new EventEmitter();
+ @Output() setMainImage = new EventEmitter();
+ @Output() deleteImage = new EventEmitter();
private snackbarService = inject(SnackbarService);
+ public categoryOptions: SelectOption[] = [];
- // GETTER FÜR DAS TEMPLATE
- get categorieIds(): FormArray {
- return this.productForm.get('categorieIds') as FormArray;
- }
+ // --- GETTER ---
get hasImages(): boolean {
return this.allImages && this.allImages.length > 0;
}
- // --- EVENT-HANDLER ---
- onSubmit(): void {
- this.formSubmit.emit();
- }
- cancel(): void {
- this.formCancel.emit();
+ // --- LIFECYCLE HOOKS ---
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['allCategories']) {
+ this.categoryOptions = this.allCategories
+ .filter(cat => !!cat.name)
+ .map(cat => ({
+ value: cat.id,
+ label: cat.name!
+ }));
+ }
}
+ // --- EVENT-HANDLERS ---
+ onSubmit(): void { this.formSubmit.emit(); }
+ cancel(): void { this.formCancel.emit(); }
+
onFilesSelected(event: Event): void {
const files = (event.target as HTMLInputElement).files;
if (files && files.length > 0) {
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 = '';
}
@@ -103,11 +90,10 @@ export class ProductFormComponent {
}
requestImageDeletion(identifier: string, event: MouseEvent): void {
- event.stopPropagation(); // Verhindert, dass gleichzeitig setAsMainImage gefeuert wird
+ event.stopPropagation();
this.deleteImage.emit(identifier);
}
- // --- Helfermethoden (unverändert) ---
generateSku(): void {
const name = this.productForm.get('name')?.value || 'PROD';
const prefix = name.substring(0, 4).toUpperCase().replace(/\s+/g, '');
@@ -117,33 +103,5 @@ export class ProductFormComponent {
this.snackbarService.show('Neue SKU generiert!');
}
- onCategoryChange(event: Event): void {
- 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);
- }
-}
+ // ALL OBSOLETE CATEGORY HELPER FUNCTIONS HAVE BEEN REMOVED
+}
\ No newline at end of file
diff --git a/src/app/features/components/products/product-list/product-list.component.ts b/src/app/features/components/products/product-list/product-list.component.ts
index 1a30056..ab600e2 100644
--- a/src/app/features/components/products/product-list/product-list.component.ts
+++ b/src/app/features/components/products/product-list/product-list.component.ts
@@ -16,7 +16,7 @@ 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, GenericTableComponent, SearchBarComponent, ButtonComponent, IconComponent],
providers: [DatePipe],
templateUrl: './product-list.component.html',
styleUrl: './product-list.component.css'
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 f78888b..0109a57 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
@@ -2,12 +2,11 @@
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule, NgClass } from '@angular/common';
-import { IconComponent } from '../icon/icon.component'; // IconComponent importieren
@Component({
selector: 'app-status-pill',
standalone: true,
- imports: [CommonModule, NgClass, IconComponent], // IconComponent hinzufügen
+ imports: [CommonModule, NgClass], // IconComponent hinzufügen
templateUrl: './status-pill.component.html',
styleUrls: ['./status-pill.component.css']
})