Feature: Create Event - First Step Frontend #17
|
@ -21,7 +21,7 @@
|
|||
<div class="flex-grow overflow-y-auto px-4 sm:px-8 py-8">
|
||||
<div class="w-full max-w-4xl mx-auto">
|
||||
@if (currentStep() === 0) {
|
||||
<app-basic-step></app-basic-step>
|
||||
<app-basic-step [form]="form"></app-basic-step>
|
||||
}
|
||||
@if (currentStep() === 1) {
|
||||
<app-tickets-step></app-tickets-step>
|
||||
|
|
|
@ -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<number> = 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()) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<form class="flex flex-col">
|
||||
<form [formGroup]="form()" class="flex flex-col">
|
||||
<h1 class="text-2xl font-bold mb-6">Event Basic Information</h1>
|
||||
|
||||
<label class="form-control w-full mb-4">
|
||||
|
@ -7,6 +7,8 @@
|
|||
<span class="label-text-alt">Make it catchy and descriptive</span>
|
||||
</div>
|
||||
<input
|
||||
formControlName="eventTitle"
|
||||
[ngClass]="getInputClass('eventTitle')"
|
||||
type="text"
|
||||
placeholder="Enter your event title"
|
||||
class="input input-bordered w-full" />
|
||||
|
@ -18,6 +20,7 @@
|
|||
|
||||
<div class="form-control w-full">
|
||||
<app-dropdown
|
||||
formControlName="eventLocation"
|
||||
label="Event Location"
|
||||
placeholder="Search for a Location"
|
||||
hintTopRight="Select or create a new Location by using the dropdown"
|
||||
|
@ -34,7 +37,12 @@
|
|||
<span class="label-text">Date</span>
|
||||
<span class="label-text-alt">Select the event date</span>
|
||||
</div>
|
||||
<input type="date" id="event-date" class="input input-bordered w-full" />
|
||||
<input
|
||||
formControlName="eventDate"
|
||||
[ngClass]="getInputClass('eventDate')"
|
||||
type="date"
|
||||
id="event-date"
|
||||
class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt"></span>
|
||||
|
@ -46,12 +54,21 @@
|
|||
<span class="label-text">Time</span>
|
||||
<span class="label-text-alt">Choose the event time</span>
|
||||
</div>
|
||||
<input type="time" id="event-time" class="input input-bordered w-full" />
|
||||
<input
|
||||
formControlName="eventTime"
|
||||
[ngClass]="getInputClass('eventTime')"
|
||||
type="time"
|
||||
id="event-time"
|
||||
class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt"></span>
|
||||
<span class="label-text-alt"></span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<pre>{{ form().value | json }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -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<FormGroup> = input.required<FormGroup>();
|
||||
|
||||
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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="form-control w-full mb-4">
|
||||
<label class="form-control w-full mb-4">
|
||||
<div class="label">
|
||||
<span class="label-text">{{ label() }}</span>
|
||||
<span class="label-text-alt">
|
||||
|
@ -6,7 +6,9 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<label
|
||||
[ngClass]="getInputClass()"
|
||||
class="input input-bordered flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
[placeholder]="placeholder()"
|
||||
|
@ -14,6 +16,7 @@
|
|||
(focus)="onFocus()"
|
||||
(blur)="onBlur()"
|
||||
(input)="onInput($event)"
|
||||
(keyup.backspace)="onInputClear()"
|
||||
[(ngModel)]="searchTerm" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -112,4 +115,4 @@
|
|||
<span class="label-text-alt">{{ hintBottomLeft() }}</span>
|
||||
<span class="label-text-alt">{{ hintBottomRight() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
|
|
@ -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<string> = input.required<string>();
|
||||
public placeholder: InputSignal<string> = input.required<string>();
|
||||
public items: InputSignal<string[]> = input.required<string[]>();
|
||||
|
@ -37,6 +58,36 @@ export class DropdownComponent {
|
|||
public submitNewItems: OutputEmitterRef<boolean> = output<boolean>();
|
||||
public itemEdit: OutputEmitterRef<string> = output<string>();
|
||||
|
||||
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 = () => {};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue