added partial first page of basic event setup step

This commit is contained in:
Igor Hrenowitsch Propisnov 2024-07-30 10:07:08 +02:00
parent f8e7b816bc
commit 0314855a0d
7 changed files with 287 additions and 68 deletions

View File

@ -1,6 +1,25 @@
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<div class="flex-grow overflow-y-auto px-8 py-8"> <div class="w-full bg-neutral max-w-full sticky top-0 z-20 pt-2">
<div class="w-full flex flex-col"> <ul class="steps steps-horizontal w-full py-2">
@for (step of steps; track $index) {
<li
[class.step-primary]="$index === currentStep()"
[class.step-success]="$index < currentStep() && isStepValid($index)"
[class.step-warning]="$index < currentStep() && !isStepValid($index)"
[attr.data-content]="getStepContent($index)"
class="step text-sm md:text-base"
(click)="goToStep($index)"
[class.cursor-pointer]="
$index <= currentStep() || canAdvanceToStep($index)
">
<span>{{ step }}</span>
</li>
}
</ul>
</div>
<div class="flex-grow overflow-y-auto px-4 sm:px-8 py-8">
<div class="w-full max-w-4xl mx-auto">
<div class=""> <div class="">
@if (currentStep() === 0) { @if (currentStep() === 0) {
<app-basic-step></app-basic-step> <app-basic-step></app-basic-step>
@ -10,23 +29,34 @@
</div> </div>
<div <div
[ngStyle]="actionbar" class="w-full bg-neutral max-w-full sticky bottom-0 z-20 px-4 sm:px-8 py-4">
class="w-full max-w-full bg-primary sticky bottom-0 z-50"> <div class="flex justify-between max-w-4xl mx-auto">
<!-- <button type="button" class="btn btn-primary" (click)="prevStep()"> <button
Prev type="button"
</button> class="btn btn-primary"
<button type="button" class="btn btn-primary" (click)="nextStep()"> (click)="prevStep()"
Next [disabled]="currentStep() === 0"
</button> --> [class.opacity-50]="currentStep() === 0">
@if (currentStep() > 0) {
<ul class="steps steps-horizontal w-full"> <span>
@for (step of steps; track $index) { Back to
<li <span class="font-bold">{{ steps[currentStep() - 1] }}</span>
[class.step-accent]="$index <= currentStep()" </span>
class="step text-sm md:text-base"> }
<span>{{ step }}</span> </button>
</li> <button
} type="button"
</ul> class="btn btn-primary"
(click)="nextStep()"
[disabled]="currentStep() === steps.length - 1"
[class.opacity-50]="currentStep() === steps.length - 1">
@if (currentStep() < steps.length - 1) {
<span>
Next to
<span class="font-bold">{{ steps[currentStep() + 1] }}</span>
</span>
}
</button>
</div>
</div> </div>
</div> </div>

View File

@ -4,15 +4,9 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
signal, signal,
WritableSignal, WritableSignal,
ElementRef,
OnInit, OnInit,
} from '@angular/core'; } from '@angular/core';
import {
BackgroundPatternService,
ThemeService,
} from '../../../shared/service';
import { BasicStepComponent } from './steps/basic-step.component'; import { BasicStepComponent } from './steps/basic-step.component';
@Component({ @Component({
@ -24,42 +18,51 @@ import { BasicStepComponent } from './steps/basic-step.component';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class CreateEventComponent implements OnInit { export class CreateEventComponent implements OnInit {
public actionbar: { 'background-image': string } | null = null;
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 constructor( public constructor() {}
private readonly backgroundPatternService: BackgroundPatternService,
private readonly themeService: ThemeService,
private readonly el: ElementRef
) {}
public ngOnInit(): void { public ngOnInit(): void {}
this.setBackground();
public getStepContent(index: number): string {
if (index < this.currentStep()) {
return this.isStepValid(index) ? '✓' : '?';
} else {
return (index + 1).toString();
}
} }
public setBackground(): void { public goToStep(stepIndex: number): void {
const theme = this.themeService.getTheme(); if (stepIndex < this.currentStep()) {
let opacity: number; this.currentStep.set(stepIndex);
} else if (this.canAdvanceToStep(stepIndex)) {
if (theme === 'dark') { this.currentStep.set(stepIndex);
opacity = 0.05;
} else {
opacity = 0.1;
} }
}
const colorPrimaryC = getComputedStyle( public canAdvanceToStep(stepIndex: number): boolean {
this.el.nativeElement for (let i = this.currentStep(); i < stepIndex; i++) {
).getPropertyValue('--pc'); if (!this.isStepValid(i)) {
return false;
}
}
return true;
}
const svgUrlActionbar = this.backgroundPatternService.getBankNotePattern( public isStepValid(stepIndex: number): boolean {
colorPrimaryC, // TODO: Implementiere die tatsächliche Validierungslogik hier
opacity // Für Demonstrationszwecke nehmen wir an, dass der erste Schritt immer gültig ist,
); // und die anderen zufällig gültig oder ungültig sind
return true;
this.actionbar = { // In Zukunft könnte es so aussehen:
'background-image': `url("${svgUrlActionbar}")`, // switch(stepIndex) {
}; // case 0: return this.isBasicStepValid();
// case 1: return this.isTicketsStepValid();
// case 2: return this.isReviewStepValid();
// default: return false;
// }
} }
public nextStep(): void { public nextStep(): void {

View File

@ -1,18 +1,33 @@
<form class="flex flex-col"> <form class="flex flex-col">
<h2 class="text-2xl font-bold mb-6">Event Basic Information</h2> <h1 class="text-2xl font-bold mb-6">Event Basic Information</h1>
<div class="form-control mb-4"> <label class="form-control w-full mb-4">
<label class="label" for="event-title"> <div class="label">
<span class="label-text">Event Title</span> <span class="label-text">Event Title</span>
</label> <span class="label-text-alt">Make it catchy and descriptive</span>
</div>
<input <input
type="text" type="text"
id="event-title"
placeholder="Enter your event title" placeholder="Enter your event title"
class="input input-bordered w-full" /> class="input input-bordered w-full" />
<div class="label">
<span class="label-text-alt"></span>
<span class="label-text-alt"></span>
</div>
</label>
<div class="form-control w-full mb-4">
<app-dropdown
label="Event Location"
placeholder="Search for a Location"
hintTopRight="Select or create a new Location by using the dropdown"
dividerText="Or add a new Location"
newItemText="Create new Location"
emptyStateText="No matching Locations found"
[items]="items"></app-dropdown>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> <!-- <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="event-date"> <label class="label" for="event-date">
<span class="label-text">Date</span> <span class="label-text">Date</span>
@ -25,8 +40,8 @@
</label> </label>
<input type="time" id="event-time" class="input input-bordered" /> <input type="time" id="event-time" class="input input-bordered" />
</div> </div>
</div> </div> -->
<!--
<div class="form-control mb-4"> <div class="form-control mb-4">
<label class="label" for="location-title"> <label class="label" for="location-title">
<span class="label-text">Location</span> <span class="label-text">Location</span>
@ -36,9 +51,9 @@
id="location-title" id="location-title"
placeholder="Enter your event title" placeholder="Enter your event title"
class="input input-bordered w-full" /> class="input input-bordered w-full" />
</div> </div> -->
<div class="form-control mb-4"> <!-- <div class="form-control mb-4">
<label class="label" for="event-street"> <label class="label" for="event-street">
<span class="label-text">Street</span> <span class="label-text">Street</span>
</label> </label>
@ -47,9 +62,9 @@
id="event-street" id="event-street"
placeholder="Street name" placeholder="Street name"
class="input input-bordered" /> class="input input-bordered" />
</div> </div> -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> <!-- <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="event-number"> <label class="label" for="event-number">
<span class="label-text">Street Number</span> <span class="label-text">Street Number</span>
@ -70,9 +85,9 @@
placeholder="Postal code" placeholder="Postal code"
class="input input-bordered" /> class="input input-bordered" />
</div> </div>
</div> </div> -->
<div class="form-control mb-6"> <!-- <div class="form-control mb-6">
<label class="label" for="event-city"> <label class="label" for="event-city">
<span class="label-text">City</span> <span class="label-text">City</span>
</label> </label>
@ -81,5 +96,5 @@
id="event-city" id="event-city"
placeholder="City" placeholder="City"
class="input input-bordered" /> class="input input-bordered" />
</div> </div> -->
</form> </form>

View File

@ -1,13 +1,19 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DropdownComponent } from '../../../../shared/components/dropdown/dropdown.component';
@Component({ @Component({
selector: 'app-basic-step', selector: 'app-basic-step',
standalone: true, standalone: true,
templateUrl: './basic-step.component.html', templateUrl: './basic-step.component.html',
providers: [], providers: [],
imports: [], imports: [CommonModule, FormsModule, DropdownComponent],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class BasicStepComponent { export class BasicStepComponent {
public items: string[] = ['Option 1', 'Option 2', 'Option 3'];
public constructor() {} public constructor() {}
} }

View File

@ -0,0 +1,95 @@
<div class="form-control w-full mb-4">
<div class="label">
<span class="label-text">{{ label() }}</span>
<span class="label-text-alt">
{{ hintTopRight() }}
</span>
</div>
<div class="relative">
<label class="input input-bordered flex items-center gap-2">
<input
type="text"
[placeholder]="placeholder()"
class="grow"
(focus)="onFocus()"
(blur)="onBlur()"
(input)="onInput($event)"
[(ngModel)]="searchTerm" />
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
[class.rotate-180]="showDropdown()">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</label>
@if (showDropdown()) {
<ul
class="menu bg-base-100 w-full mt-1 shadow-lg rounded-box absolute top-full left-0 z-10">
@if (filteredItems().length > 0) {
@for (item of filteredItems(); track item) {
<li>
<a
(mousedown)="selectItem(item)"
class="flex justify-between items-center"
[class.font-bold]="item === selectedItem()"
[class.text-primary]="item === selectedItem()">
<span>{{ item }}</span>
@if (item === selectedItem()) {
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m4.5 12.75 6 6 9-13.5" />
</svg>
}
</a>
</li>
}
<div class="divider">{{ dividerText() }}</div>
} @else if (searchTerm()) {
<li
(mousedown)="submitNewItem()"
class="text-center py-2 text-gray-500">
{{ emptyStateText() }}
</li>
}
<li>
<a
(mousedown)="submitNewItem()"
class="flex items-center text-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 mr-2">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 4.5v15m7.5-7.5h-15" />
</svg>
{{ newItemText() }}
</a>
</li>
</ul>
}
</div>
<div class="label">
<span class="label-text-alt">{{ hintBottomLeft() }}</span>
<span class="label-text-alt">{{ hintBottomRight() }}</span>
</div>
</div>

View File

@ -0,0 +1,69 @@
import { CommonModule } from '@angular/common';
import {
Component,
input,
InputSignal,
output,
OutputEmitterRef,
signal,
WritableSignal,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-dropdown',
templateUrl: './dropdown.component.html',
standalone: true,
imports: [CommonModule, FormsModule],
})
export class DropdownComponent {
public label: 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>('');
public dividerText: InputSignal<string> = input<string>('');
public newItemText: InputSignal<string> = input<string>('');
public required: InputSignal<boolean> = input<boolean>(false);
public hintTopRight: InputSignal<string> = input<string>('');
public hintBottomLeft: InputSignal<string> = input<string>('');
public hintBottomRight: InputSignal<string> = input<string>('');
public itemSelected: OutputEmitterRef<string> = output<string>();
public searchTerm: WritableSignal<string> = signal('');
public showDropdown: WritableSignal<boolean> = signal<boolean>(false);
public filteredItems: WritableSignal<string[]> = signal<string[]>([]);
public selectedItem: WritableSignal<string | null> = signal<string | null>(
null
);
public onInput(event: Event): void {
const value = (event.target as HTMLInputElement).value;
this.searchTerm.set(value);
this.filteredItems.set(
this.items().filter((item) =>
item.toLowerCase().includes(value.toLowerCase())
)
);
this.showDropdown.set(true);
}
public selectItem(item: string): void {
this.searchTerm.set(item);
this.selectedItem.set(item);
this.showDropdown.set(false);
this.itemSelected.emit(item);
}
public submitNewItem(): void {}
public onFocus(): void {
this.showDropdown.set(true);
this.filteredItems.set(this.items());
}
public onBlur(): void {
setTimeout(() => {
this.showDropdown.set(false);
}, 200);
}
}

View File

@ -0,0 +1 @@
export * from './dropdown/dropdown.component';