diff --git a/frontend/src/app/pages/event-root/create-event/create-event.component.html b/frontend/src/app/pages/event-root/create-event/create-event.component.html index 526014c..7faf76d 100644 --- a/frontend/src/app/pages/event-root/create-event/create-event.component.html +++ b/frontend/src/app/pages/event-root/create-event/create-event.component.html @@ -21,7 +21,7 @@
@if (currentStep() === 0) { - + } @if (currentStep() === 1) { diff --git a/frontend/src/app/pages/event-root/create-event/create-event.component.ts b/frontend/src/app/pages/event-root/create-event/create-event.component.ts index af279e2..b2faf48 100644 --- a/frontend/src/app/pages/event-root/create-event/create-event.component.ts +++ b/frontend/src/app/pages/event-root/create-event/create-event.component.ts @@ -4,8 +4,8 @@ import { ChangeDetectionStrategy, signal, WritableSignal, - OnInit, } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { BasicStepComponent } from './steps/basic-step.component'; import { TicketsStepComponent } from './steps/tickets-step.component'; @@ -18,13 +18,19 @@ import { TicketsStepComponent } from './steps/tickets-step.component'; templateUrl: './create-event.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CreateEventComponent implements OnInit { +export class CreateEventComponent { public currentStep: WritableSignal = signal(0); public readonly steps: string[] = ['Basic', 'Tickets', 'Review']; + public form!: FormGroup; - public constructor() {} - - public ngOnInit(): void {} + public constructor(private readonly formBuilder: FormBuilder) { + this.form = this.formBuilder.group({ + eventTitle: ['', [Validators.required, Validators.minLength(3)]], + eventLocation: ['', Validators.required], + eventDate: ['', Validators.required], + eventTime: ['', Validators.required], + }); + } public getStepContent(index: number): string { if (index < this.currentStep()) { diff --git a/frontend/src/app/pages/event-root/create-event/steps/basic-step.component.html b/frontend/src/app/pages/event-root/create-event/steps/basic-step.component.html index 4520f5b..63c697d 100644 --- a/frontend/src/app/pages/event-root/create-event/steps/basic-step.component.html +++ b/frontend/src/app/pages/event-root/create-event/steps/basic-step.component.html @@ -1,4 +1,4 @@ -
+

Event Basic Information

@@ -18,6 +20,7 @@
Date Select the event date
- +
@@ -46,12 +54,21 @@ Time Choose the event time
- +
+ +
+
{{ form().value | json }}
+
diff --git a/frontend/src/app/pages/event-root/create-event/steps/basic-step.component.ts b/frontend/src/app/pages/event-root/create-event/steps/basic-step.component.ts index a0e85d8..0528008 100644 --- a/frontend/src/app/pages/event-root/create-event/steps/basic-step.component.ts +++ b/frontend/src/app/pages/event-root/create-event/steps/basic-step.component.ts @@ -1,6 +1,12 @@ import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core'; -import { FormsModule } from '@angular/forms'; +import { + ChangeDetectionStrategy, + Component, + InputSignal, + ViewChild, + input, +} from '@angular/core'; +import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { DropdownComponent } from '../../../../shared/components/dropdown/dropdown.component'; import { @@ -18,6 +24,7 @@ import { FormsModule, DropdownComponent, LocationDialogComponent, + ReactiveFormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -25,6 +32,7 @@ export class BasicStepComponent { @ViewChild(LocationDialogComponent) public locationModal!: LocationDialogComponent; public items: string[] = ['Nachtigal Köln']; + public form: InputSignal = input.required(); public constructor() {} @@ -32,8 +40,17 @@ export class BasicStepComponent { this.openLocationModal(); } + public getInputClass(controlName: string): string { + const control = this.form().get(controlName); + + if (control?.touched) { + return control.valid ? 'input-success' : 'input-error'; + } + return ''; + } + public onLocationSubmit(location: EventLocation): void { - console.log(location); + //TODO: save location } private openLocationModal(): void { diff --git a/frontend/src/app/shared/components/dropdown/dropdown.component.html b/frontend/src/app/shared/components/dropdown/dropdown.component.html index c5e477b..15ee958 100644 --- a/frontend/src/app/shared/components/dropdown/dropdown.component.html +++ b/frontend/src/app/shared/components/dropdown/dropdown.component.html @@ -1,4 +1,4 @@ -
+
+ diff --git a/frontend/src/app/shared/components/dropdown/dropdown.component.ts b/frontend/src/app/shared/components/dropdown/dropdown.component.ts index e8817b6..b9d034e 100644 --- a/frontend/src/app/shared/components/dropdown/dropdown.component.ts +++ b/frontend/src/app/shared/components/dropdown/dropdown.component.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { Component, + forwardRef, input, InputSignal, output, @@ -8,15 +9,35 @@ import { signal, WritableSignal, } from '@angular/core'; -import { FormsModule } from '@angular/forms'; +import { + AbstractControl, + ControlValueAccessor, + FormsModule, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, +} from '@angular/forms'; @Component({ selector: 'app-dropdown', templateUrl: './dropdown.component.html', standalone: true, imports: [CommonModule, FormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DropdownComponent), + multi: true, + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DropdownComponent), + multi: true, + }, + ], }) -export class DropdownComponent { +export class DropdownComponent implements ControlValueAccessor, Validator { public label: InputSignal = input.required(); public placeholder: InputSignal = input.required(); public items: InputSignal = input.required(); @@ -37,6 +58,36 @@ export class DropdownComponent { public submitNewItems: OutputEmitterRef = output(); public itemEdit: OutputEmitterRef = output(); + public writeValue(value: string | null): void { + if (value && this.isValidOption(value)) { + this.searchTerm.set(value); + this.selectedItem.set(value); + } else { + this.searchTerm.set(''); + this.selectedItem.set(null); + } + } + + public registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + public registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + public validate(control: AbstractControl): ValidationErrors | null { + const value = control.value; + + if (this.required() && (!value || value.trim().length === 0)) { + return { required: true }; + } + if (value && value.trim().length > 0 && !this.isValidOption(value)) { + return { invalidOption: true }; + } + return null; + } + public onInput(event: Event): void { const value = (event.target as HTMLInputElement).value; @@ -54,13 +105,30 @@ export class DropdownComponent { if (exactMatch) { this.selectedItem.set(exactMatch); + this.onChange(exactMatch); this.itemSelected.emit(exactMatch); } else { this.selectedItem.set(null); + this.onChange(''); this.itemSelected.emit(''); } this.showDropdown.set(true); + this.onTouched(); + } + + public getInputClass(): string { + if (this.selectedItem()) { + return 'input-success'; + } else if ( + this.searchTerm().trim() !== '' && + !this.isValidOption(this.searchTerm()) + ) { + return 'input-error'; + } else if (this.required() && this.searchTerm().trim() === '') { + return 'input-error'; + } + return ''; } public editItem(item: string, event: MouseEvent): void { @@ -71,16 +139,27 @@ export class DropdownComponent { } public selectItem(item: string): void { - this.searchTerm.set(item); - this.selectedItem.set(item); - this.showDropdown.set(false); - this.itemSelected.emit(item); + if (this.isValidOption(item)) { + this.searchTerm.set(item); + this.selectedItem.set(item); + this.showDropdown.set(false); + this.itemSelected.emit(item); + this.onChange(item); + this.onTouched(); + } } public submitNewItem(): void { this.submitNewItems.emit(true); } + public onInputClear(): void { + this.searchTerm.set(''); + this.selectedItem.set(null); + this.onChange(''); + this.onTouched(); + } + public onFocus(): void { this.showDropdown.set(true); this.filteredItems.set(this.items()); @@ -89,6 +168,14 @@ export class DropdownComponent { public onBlur(): void { setTimeout(() => { this.showDropdown.set(false); + this.onTouched(); }, 100); } + + private isValidOption(value: string): boolean { + return this.items().includes(value); + } + + private onChange: (value: string) => void = () => {}; + private onTouched: () => void = () => {}; }