Feature: Create Event - First Step Frontend #17
|
@ -5,6 +5,7 @@ import {
|
||||||
signal,
|
signal,
|
||||||
WritableSignal,
|
WritableSignal,
|
||||||
effect,
|
effect,
|
||||||
|
ViewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ import { TicketsStepComponent } from './steps/tickets-step.component';
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class CreateEventComponent {
|
export class CreateEventComponent {
|
||||||
|
@ViewChild(BasicStepComponent) public basicStep!: BasicStepComponent;
|
||||||
public currentStep: WritableSignal<number> = signal(0);
|
public currentStep: WritableSignal<number> = signal(0);
|
||||||
public readonly steps: string[] = ['Basic', 'Tickets', 'Review'];
|
public readonly steps: string[] = ['Basic', 'Tickets', 'Review'];
|
||||||
public form!: FormGroup;
|
public form!: FormGroup;
|
||||||
|
@ -94,6 +96,7 @@ export class CreateEventComponent {
|
||||||
this.hasAttemptedNextStep.set(false);
|
this.hasAttemptedNextStep.set(false);
|
||||||
} else {
|
} else {
|
||||||
this.hasAttemptedNextStep.set(true);
|
this.hasAttemptedNextStep.set(true);
|
||||||
|
this.markCurrentStepAsTouched();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,13 +109,20 @@ export class CreateEventComponent {
|
||||||
|
|
||||||
public getNextButtonClass(): string {
|
public getNextButtonClass(): string {
|
||||||
return this.hasAttemptedNextStep() && !this.isCurrentStepValid()
|
return this.hasAttemptedNextStep() && !this.isCurrentStepValid()
|
||||||
? 'btn btn-outline btn-warning'
|
? 'btn btn-outline btn-error'
|
||||||
: 'btn btn-primary btn-outline';
|
: 'btn btn-primary btn-outline';
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNextButtonText(): string {
|
public getNextButtonText(): string {
|
||||||
return this.hasAttemptedNextStep() && !this.isCurrentStepValid()
|
return this.hasAttemptedNextStep() && !this.isCurrentStepValid()
|
||||||
? 'Required Fields Missing'
|
? 'Required Fields Need Attention'
|
||||||
: `Next to ${this.steps[this.currentStep() + 1]}`;
|
: `Next to ${this.steps[this.currentStep() + 1]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private markCurrentStepAsTouched(): void {
|
||||||
|
if (this.currentStep() === 0 && this.basicStep) {
|
||||||
|
this.basicStep.markAllAsTouched();
|
||||||
|
}
|
||||||
|
// step 1 and 2 missing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,13 @@
|
||||||
class="input input-bordered w-full" />
|
class="input input-bordered w-full" />
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text-alt"></span>
|
<span class="label-text-alt"></span>
|
||||||
<span class="label-text-alt"></span>
|
<span class="label-text-alt text-error">
|
||||||
|
{{ getErrorMessage('eventTitle') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="form-control w-full">
|
<div class="form-control w-full mb-4">
|
||||||
<app-dropdown
|
<app-dropdown
|
||||||
formControlName="eventLocation"
|
formControlName="eventLocation"
|
||||||
label="Event Location"
|
label="Event Location"
|
||||||
|
@ -28,6 +30,8 @@
|
||||||
newItemText="Create new Location"
|
newItemText="Create new Location"
|
||||||
emptyStateText="No matching Locations found"
|
emptyStateText="No matching Locations found"
|
||||||
[items]="items"
|
[items]="items"
|
||||||
|
[required]="true"
|
||||||
|
[errorMessage]="getErrorMessage('eventLocation')"
|
||||||
(submitNewItems)="onDropdownSubmit()"></app-dropdown>
|
(submitNewItems)="onDropdownSubmit()"></app-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -45,7 +49,9 @@
|
||||||
class="input input-bordered w-full" />
|
class="input input-bordered w-full" />
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text-alt"></span>
|
<span class="label-text-alt"></span>
|
||||||
<span class="label-text-alt"></span>
|
<span class="label-text-alt text-error">
|
||||||
|
{{ getErrorMessage('eventDate') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
@ -62,7 +68,9 @@
|
||||||
class="input input-bordered w-full" />
|
class="input input-bordered w-full" />
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text-alt"></span>
|
<span class="label-text-alt"></span>
|
||||||
<span class="label-text-alt"></span>
|
<span class="label-text-alt text-error">
|
||||||
|
{{ getErrorMessage('eventTime') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,7 +6,12 @@ import {
|
||||||
ViewChild,
|
ViewChild,
|
||||||
input,
|
input,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import {
|
||||||
|
FormGroup,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ValidationErrors,
|
||||||
|
} from '@angular/forms';
|
||||||
|
|
||||||
import { DropdownComponent } from '../../../../shared/components/dropdown/dropdown.component';
|
import { DropdownComponent } from '../../../../shared/components/dropdown/dropdown.component';
|
||||||
import {
|
import {
|
||||||
|
@ -31,6 +36,8 @@ import {
|
||||||
export class BasicStepComponent {
|
export class BasicStepComponent {
|
||||||
@ViewChild(LocationDialogComponent)
|
@ViewChild(LocationDialogComponent)
|
||||||
public locationModal!: LocationDialogComponent;
|
public locationModal!: LocationDialogComponent;
|
||||||
|
@ViewChild(DropdownComponent)
|
||||||
|
public dropdownComponent!: DropdownComponent;
|
||||||
public items: string[] = ['Nachtigal Köln'];
|
public items: string[] = ['Nachtigal Köln'];
|
||||||
public form: InputSignal<FormGroup> = input.required<FormGroup>();
|
public form: InputSignal<FormGroup> = input.required<FormGroup>();
|
||||||
|
|
||||||
|
@ -53,6 +60,71 @@ export class BasicStepComponent {
|
||||||
//TODO: save location
|
//TODO: save location
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public markAllAsTouched(): void {
|
||||||
|
Object.values(this.form().controls).forEach((control) => {
|
||||||
|
control.markAsTouched();
|
||||||
|
});
|
||||||
|
if (this.dropdownComponent) {
|
||||||
|
this.dropdownComponent.markAsTouched();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getErrorMessage(controlName: string): string {
|
||||||
|
const control = this.form().get(controlName);
|
||||||
|
|
||||||
|
if (control && control.touched && control.errors) {
|
||||||
|
return this.getErrorMessageFromValidationErrors(
|
||||||
|
control.errors,
|
||||||
|
controlName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getErrorMessageFromValidationErrors(
|
||||||
|
errors: ValidationErrors,
|
||||||
|
controlName: string
|
||||||
|
): string {
|
||||||
|
switch (controlName) {
|
||||||
|
case 'eventTitle':
|
||||||
|
if (errors['required']) {
|
||||||
|
return 'Event title is required to uniquely identify your event in our system and for attendees.';
|
||||||
|
}
|
||||||
|
if (errors['minlength']) {
|
||||||
|
return `Please provide a more descriptive title (minimum ${errors['minlength'].requiredLength} characters) to improve discoverability.`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'eventLocation':
|
||||||
|
if (errors['required']) {
|
||||||
|
return 'Location is crucial for attendees and for our logistics planning. Please select or add a venue.';
|
||||||
|
}
|
||||||
|
if (errors['invalidOption']) {
|
||||||
|
return 'Please select a valid location from the list or add a new one to ensure accurate event information.';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'eventDate':
|
||||||
|
if (errors['required']) {
|
||||||
|
return 'Event date is essential for scheduling and marketing. Please select a date for your event.';
|
||||||
|
}
|
||||||
|
// You might add a custom validator for dates in the past
|
||||||
|
if (errors['pastDate']) {
|
||||||
|
return 'Please select a future date. Our platform is designed for upcoming events.';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'eventTime':
|
||||||
|
if (errors['required']) {
|
||||||
|
return 'Start time helps attendees plan their schedule. Please specify when your event begins.';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic messages for any other errors
|
||||||
|
if (errors['required']) {
|
||||||
|
return 'This information is required to ensure a complete and accurate event listing.';
|
||||||
|
}
|
||||||
|
return 'Please check this field to ensure all event details are correct and complete.';
|
||||||
|
}
|
||||||
|
|
||||||
private openLocationModal(): void {
|
private openLocationModal(): void {
|
||||||
this.locationModal.openModal();
|
this.locationModal.openModal();
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text-alt"></span>
|
||||||
|
<span class="label-text-alt text-error">
|
||||||
|
{{ errorMessage() }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text-alt">{{ hintBottomLeft() }}</span>
|
<span class="label-text-alt">{{ hintBottomLeft() }}</span>
|
||||||
<span class="label-text-alt">{{ hintBottomRight() }}</span>
|
<span class="label-text-alt">{{ hintBottomRight() }}</span>
|
||||||
|
|
|
@ -41,6 +41,7 @@ import {
|
||||||
})
|
})
|
||||||
export class DropdownComponent implements ControlValueAccessor, Validator {
|
export class DropdownComponent implements ControlValueAccessor, Validator {
|
||||||
public label: InputSignal<string> = input.required<string>();
|
public label: InputSignal<string> = input.required<string>();
|
||||||
|
public errorMessage: InputSignal<string> = input.required<string>();
|
||||||
public placeholder: InputSignal<string> = input.required<string>();
|
public placeholder: InputSignal<string> = input.required<string>();
|
||||||
public items: InputSignal<string[]> = input.required<string[]>();
|
public items: InputSignal<string[]> = input.required<string[]>();
|
||||||
public emptyStateText: InputSignal<string> = input<string>('');
|
public emptyStateText: InputSignal<string> = input<string>('');
|
||||||
|
@ -59,6 +60,7 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
||||||
public itemSelected: OutputEmitterRef<string> = output<string>();
|
public itemSelected: OutputEmitterRef<string> = output<string>();
|
||||||
public submitNewItems: OutputEmitterRef<boolean> = output<boolean>();
|
public submitNewItems: OutputEmitterRef<boolean> = output<boolean>();
|
||||||
public itemEdit: OutputEmitterRef<string> = output<string>();
|
public itemEdit: OutputEmitterRef<string> = output<string>();
|
||||||
|
public touched: WritableSignal<boolean> = signal(false);
|
||||||
private isMouseInDropdown: WritableSignal<boolean> = signal<boolean>(false);
|
private isMouseInDropdown: WritableSignal<boolean> = signal<boolean>(false);
|
||||||
|
|
||||||
public constructor(private readonly elementRef: ElementRef) {}
|
public constructor(private readonly elementRef: ElementRef) {}
|
||||||
|
@ -78,6 +80,7 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
||||||
this.searchTerm.set('');
|
this.searchTerm.set('');
|
||||||
this.selectedItem.set(null);
|
this.selectedItem.set(null);
|
||||||
}
|
}
|
||||||
|
this.touched.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerOnChange(fn: (value: string) => void): void {
|
public registerOnChange(fn: (value: string) => void): void {
|
||||||
|
@ -85,7 +88,10 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerOnTouched(fn: () => void): void {
|
public registerOnTouched(fn: () => void): void {
|
||||||
this.onTouched = fn;
|
this.onTouched = (): void => {
|
||||||
|
this.touched.set(true);
|
||||||
|
fn();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public validate(control: AbstractControl): ValidationErrors | null {
|
public validate(control: AbstractControl): ValidationErrors | null {
|
||||||
|
@ -148,6 +154,7 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getInputClass(): string {
|
public getInputClass(): string {
|
||||||
|
if (this.touched()) {
|
||||||
if (this.selectedItem()) {
|
if (this.selectedItem()) {
|
||||||
return 'input-success';
|
return 'input-success';
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -158,9 +165,17 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
||||||
} else if (this.required() && this.searchTerm().trim() === '') {
|
} else if (this.required() && this.searchTerm().trim() === '') {
|
||||||
return 'input-error';
|
return 'input-error';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public markAsTouched(): void {
|
||||||
|
if (!this.touched()) {
|
||||||
|
this.touched.set(true);
|
||||||
|
this.onTouched();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public editItem(item: string, event: MouseEvent): void {
|
public editItem(item: string, event: MouseEvent): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
Loading…
Reference in New Issue