Feature: Create Event - First Step Frontend #17
|
@ -5,6 +5,7 @@ import {
|
|||
signal,
|
||||
WritableSignal,
|
||||
effect,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
|
@ -20,6 +21,7 @@ import { TicketsStepComponent } from './steps/tickets-step.component';
|
|||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CreateEventComponent {
|
||||
@ViewChild(BasicStepComponent) public basicStep!: BasicStepComponent;
|
||||
public currentStep: WritableSignal<number> = signal(0);
|
||||
public readonly steps: string[] = ['Basic', 'Tickets', 'Review'];
|
||||
public form!: FormGroup;
|
||||
|
@ -94,6 +96,7 @@ export class CreateEventComponent {
|
|||
this.hasAttemptedNextStep.set(false);
|
||||
} else {
|
||||
this.hasAttemptedNextStep.set(true);
|
||||
this.markCurrentStepAsTouched();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,13 +109,20 @@ export class CreateEventComponent {
|
|||
|
||||
public getNextButtonClass(): string {
|
||||
return this.hasAttemptedNextStep() && !this.isCurrentStepValid()
|
||||
? 'btn btn-outline btn-warning'
|
||||
? 'btn btn-outline btn-error'
|
||||
: 'btn btn-primary btn-outline';
|
||||
}
|
||||
|
||||
public getNextButtonText(): string {
|
||||
return this.hasAttemptedNextStep() && !this.isCurrentStepValid()
|
||||
? 'Required Fields Missing'
|
||||
? 'Required Fields Need Attention'
|
||||
: `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" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt text-error">
|
||||
{{ getErrorMessage('eventTitle') }}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<div class="form-control w-full mb-4">
|
||||
<app-dropdown
|
||||
formControlName="eventLocation"
|
||||
label="Event Location"
|
||||
|
@ -28,6 +30,8 @@
|
|||
newItemText="Create new Location"
|
||||
emptyStateText="No matching Locations found"
|
||||
[items]="items"
|
||||
[required]="true"
|
||||
[errorMessage]="getErrorMessage('eventLocation')"
|
||||
(submitNewItems)="onDropdownSubmit()"></app-dropdown>
|
||||
</div>
|
||||
|
||||
|
@ -45,7 +49,9 @@
|
|||
class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt text-error">
|
||||
{{ getErrorMessage('eventDate') }}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
|
@ -62,7 +68,9 @@
|
|||
class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt text-error">
|
||||
{{ getErrorMessage('eventTime') }}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,12 @@ import {
|
|||
ViewChild,
|
||||
input,
|
||||
} 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 {
|
||||
|
@ -31,6 +36,8 @@ import {
|
|||
export class BasicStepComponent {
|
||||
@ViewChild(LocationDialogComponent)
|
||||
public locationModal!: LocationDialogComponent;
|
||||
@ViewChild(DropdownComponent)
|
||||
public dropdownComponent!: DropdownComponent;
|
||||
public items: string[] = ['Nachtigal Köln'];
|
||||
public form: InputSignal<FormGroup> = input.required<FormGroup>();
|
||||
|
||||
|
@ -53,6 +60,71 @@ export class BasicStepComponent {
|
|||
//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 {
|
||||
this.locationModal.openModal();
|
||||
}
|
||||
|
|
|
@ -114,6 +114,12 @@
|
|||
</ul>
|
||||
}
|
||||
</div>
|
||||
<div class="label">
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt text-error">
|
||||
{{ errorMessage() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">{{ hintBottomLeft() }}</span>
|
||||
<span class="label-text-alt">{{ hintBottomRight() }}</span>
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
})
|
||||
export class DropdownComponent implements ControlValueAccessor, Validator {
|
||||
public label: InputSignal<string> = input.required<string>();
|
||||
public errorMessage: InputSignal<string> = input.required<string>();
|
||||
public placeholder: InputSignal<string> = input.required<string>();
|
||||
public items: InputSignal<string[]> = input.required<string[]>();
|
||||
public emptyStateText: InputSignal<string> = input<string>('');
|
||||
|
@ -59,6 +60,7 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
public itemSelected: OutputEmitterRef<string> = output<string>();
|
||||
public submitNewItems: OutputEmitterRef<boolean> = output<boolean>();
|
||||
public itemEdit: OutputEmitterRef<string> = output<string>();
|
||||
public touched: WritableSignal<boolean> = signal(false);
|
||||
private isMouseInDropdown: WritableSignal<boolean> = signal<boolean>(false);
|
||||
|
||||
public constructor(private readonly elementRef: ElementRef) {}
|
||||
|
@ -78,6 +80,7 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
this.searchTerm.set('');
|
||||
this.selectedItem.set(null);
|
||||
}
|
||||
this.touched.set(false);
|
||||
}
|
||||
|
||||
public registerOnChange(fn: (value: string) => void): void {
|
||||
|
@ -85,7 +88,10 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
}
|
||||
|
||||
public registerOnTouched(fn: () => void): void {
|
||||
this.onTouched = fn;
|
||||
this.onTouched = (): void => {
|
||||
this.touched.set(true);
|
||||
fn();
|
||||
};
|
||||
}
|
||||
|
||||
public validate(control: AbstractControl): ValidationErrors | null {
|
||||
|
@ -148,19 +154,28 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
|
|||
}
|
||||
|
||||
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';
|
||||
if (this.touched()) {
|
||||
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 markAsTouched(): void {
|
||||
if (!this.touched()) {
|
||||
this.touched.set(true);
|
||||
this.onTouched();
|
||||
}
|
||||
}
|
||||
|
||||
public editItem(item: string, event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
|
Loading…
Reference in New Issue