Fixing bugs

Refactoring scanning
Adding data connections
This commit is contained in:
Tara Wilson 2024-12-07 17:25:55 -05:00
parent 2358ef1c96
commit 35ddedb5c3
38 changed files with 419 additions and 231 deletions

View File

@ -61,7 +61,7 @@ public class TicketManager : ITicketManager
return TicketValidity.Expired; return TicketValidity.Expired;
} }
if (@event.Date != DateTime.Today) return TicketValidity.Invalid; if (@event.Date.Day != DateTime.Today.Day) return TicketValidity.Invalid;
if (DateTime.Now.Hour < @event.Date.Hour - 2) if (DateTime.Now.Hour < @event.Date.Hour - 2)
{ {

View File

@ -2,8 +2,8 @@ namespace models.Enumerations;
public enum TicketValidity public enum TicketValidity
{ {
Valid,
Expired, Expired,
Early, Early,
Invalid Invalid,
Valid,
} }

View File

@ -1,9 +1,11 @@
<div class="card"> <div class="card card-fit-parent">
<label for="events">Event</label> <div class="row row-space-between">
<select name="Events" id="events"> <label class="label">Event</label>
<option>Select...</option> <select #event name="Events" class="select" (change)="click(event.value)">
<option value="">Select...</option>
@for(event of events$(); track event.id) { @for(event of events$(); track event.id) {
<option [value]="event.id">{{event.name}}</option> <option [value]="event.id">{{event.name}}</option>
} }
</select> </select>
</div> </div>
</div>

View File

@ -1,3 +0,0 @@
label {
padding-right: 15px;
}

View File

@ -1,17 +1,27 @@
import {Component, inject, OnInit} from '@angular/core'; import {Component, inject, OnInit, signal, WritableSignal} from '@angular/core';
import {EventService} from '../../services/event.service'; import {EventService} from '../../services/event.service';
import {ReactiveFormsModule} from '@angular/forms';
@Component({ @Component({
selector: 'app-event-browser', selector: 'app-event-browser',
imports: [], imports: [
ReactiveFormsModule
],
templateUrl: './event-browser.component.html', templateUrl: './event-browser.component.html',
styleUrl: './event-browser.component.scss' styleUrl: './event-browser.component.scss'
}) })
export class EventBrowserComponent implements OnInit { export class EventBrowserComponent implements OnInit {
private eventService = inject(EventService); private eventService = inject(EventService);
public events$ = this.eventService.events$; public events$ = this.eventService.events$;
public selectedEventId!: string;
public selectedEventId$: WritableSignal<string> = signal('');
public ngOnInit() { public ngOnInit() {
this.eventService.searchAllEvents(); this.eventService.searchAllEvents();
} }
public click(eventId: string): void {
this.selectedEventId = eventId;
this.selectedEventId$.set(eventId);
}
} }

View File

@ -2,12 +2,12 @@ import {Component, inject} from '@angular/core';
import {TicketService} from '../../services/ticket.service'; import {TicketService} from '../../services/ticket.service';
@Component({ @Component({
selector: 'app-ticket', selector: 'app-generated-ticket',
imports: [], imports: [],
templateUrl: './ticket.component.html', templateUrl: './generated-ticket.component.html',
styleUrl: './ticket.component.scss' styleUrl: './generated-ticket.component.scss'
}) })
export class TicketComponent { export class GeneratedTicketComponent {
private ticket = inject(TicketService); private ticket = inject(TicketService);
public qrCode = this.ticket.dataSignal$; public qrCode = this.ticket.dataSignal$;
} }

View File

@ -1,45 +1,45 @@
<div class="card"> <div class="card">
<h3>Patron Information</h3> <h3>Patron Information</h3>
<div class="column"> <div class="column">
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>First Name</label> <label class="label" for="firstName">First Name</label>
<input [formControl]="firstName" type="text"/> <input id="firstName" [formControl]="firstName" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Middle Name</label> <label class="label" for="middleName">Middle Name</label>
<input [formControl]="middleName" type="text"/> <input id="middleName" [formControl]="middleName" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Last Name</label> <label class="label" for='lastName'>Last Name</label>
<input [formControl]="lastName" type="text"/> <input id="lastName" [formControl]="lastName" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Email</label> <label class="label" for="email">Email</label>
<input [formControl]="email" type="text"/> <input id="email" [formControl]="email" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Phone</label> <label class="label" for="phoneNumber">Phone</label>
<input [formControl]="phoneNumber" type="text"/> <input id="phoneNumber" [formControl]="phoneNumber" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Address</label> <label class="label" for="addressOne">Address</label>
<input [formControl]="addressOne" type="text"/> <input id="addressOne" [formControl]="addressOne" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Address</label> <label class="label" for="addressTwo">Address</label>
<input [formControl]="addressTwo" type="text"/> <input id="addressTwo" [formControl]="addressTwo" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>City</label> <label class="label" for="city">City</label>
<input [formControl]="city" type="text"/> <input id="city" [formControl]="city" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>State</label> <label class="label" for="state">State</label>
<input [formControl]="state" type="text"/> <input id="state" [formControl]="state" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Zip</label> <label class="label" for="zip">Zip</label>
<input [formControl]="zip" type="text"/> <input id="zip" [formControl]="zip" type="text" class="input"/>
</span> </div>
</div> </div>
</div> </div>

View File

@ -1,3 +0,0 @@
label {
padding-right: 15px;
}

View File

@ -1,5 +1,6 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms'; import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {Patron} from '../../../models/core/patron';
@Component({ @Component({
selector: 'app-patron-info', selector: 'app-patron-info',
@ -10,14 +11,29 @@ import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
styleUrl: './patron-info.component.scss' styleUrl: './patron-info.component.scss'
}) })
export class PatronInfoComponent { export class PatronInfoComponent {
public firstName = new FormControl('', [Validators.required]); public firstName = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public middleName = new FormControl(''); public middleName = new FormControl('');
public lastName = new FormControl('', [Validators.required]); public lastName = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public email = new FormControl('', [Validators.required]); public email = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public phoneNumber = new FormControl(''); public phoneNumber = new FormControl('');
public addressOne = new FormControl('', [Validators.required]); public addressOne = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public addressTwo = new FormControl(''); public addressTwo = new FormControl('');
public city = new FormControl('', [Validators.required]); public city = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public state = new FormControl('', [Validators.required]); public state = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public zip = new FormControl('', [Validators.required]); public zip = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public buildPatron(): Patron {
return {
firstName: this.firstName.value,
middleName: this.middleName.value,
lastName: this.lastName.value,
email: this.email.value,
phoneNumber: this.phoneNumber.value,
addressOne: this.addressOne.value,
addressTwo: this.addressTwo.value,
city: this.city.value,
state: this.state.value,
zip: this.zip.value,
};
}
} }

View File

@ -1,57 +1,21 @@
@if (ticketValidity() !== TicketValidity.Invalid && ticketType() !== TicketTypeEnum.Null) { @if (ticketSearch$() != null) {
<div class="card column">
<div class="card"> <div class="card">
@if (ticketValidity() == TicketValidity.Valid) { <!-- Show Validity, If Valid Show Ticket Type and Indicate Season -->
<img ngSrc="pass.svg" width="100px" height="100px" alt="Ticket Valid"/> <div class="column">
<span>Valid</span> <div class="column">
} @else { <img [src]="getValidityImage()" width="100px" height="100px" [alt]="getValidityText()"/>
<img ngSrc="fail.svg" width="100px" height="100px" alt="Ticket Invalid"/> <span class="row">{{getValidityText()}}</span>
<span>Invalid</span>
}
</div> </div>
@if (ticketValidity() == TicketValidity.Valid) { @if(ticketValidity() === TicketValidity.Valid) {
<div class="card"> <div class="column">
@switch (ticketType()){ <img [src]="getTypeImage()" width="50px" height="50px" [alt]="getTypeText()"/>
@case (TicketTypeEnum.Family) { <span class="row">{{getTypeText()}}</span>
<img ngSrc="group.svg" width="25px" height="25px" alt="Family"/> </div>
<span>Family</span> @if(IsSeasonTicket(ticketType())) {
} <img src="season.svg" width="50px" height="50px" alt="Season Ticket"/>
@case (TicketTypeEnum.FamilySeason) { <span class="row">Seasonal</span>
<img ngSrc="group.svg" width="25px" height="25px" alt="Family"/>
<span>Family</span>
}
@case (TicketTypeEnum.Single) {
<img ngSrc="single.svg" width="25px" height="25px" alt="Single"/>
<span>Single</span>
}
@case (TicketTypeEnum.SingleSeason) {
<img ngSrc="single.svg" width="25px" height="25px" alt="Single"/>
<span>Single</span>
}
@case (TicketTypeEnum.Senior) {
<img ngSrc="senior.svg" width="25px" height="25px" alt="Senior"/>
<span>Senior</span>
}
@case (TicketTypeEnum.SeniorSeason) {
<img ngSrc="senior.svg" width="25px" height="25px" alt="Senior"/>
<span>Senior</span>
} }
} }
</div> </div>
<div class="card">
@if (ticketType() !== TicketTypeEnum.FamilySeason) {
<img ngSrc="group.svg" width="25px" height="25px" alt="Family"/>
<span>Season</span>
}
@if (ticketType() !== TicketTypeEnum.SingleSeason) {
<img ngSrc="single.svg" width="25px" height="25px" alt="Single"/>
<span>Season</span>
}
@if (ticketType() !== TicketTypeEnum.SeniorSeason) {
<img ngSrc="single.svg" width="25px" height="25px" alt="Single"/>
<span>Season</span>
}
</div>
}
</div> </div>
} }

View File

@ -1,14 +1,11 @@
import {Component, inject} from '@angular/core'; import {Component, inject} from '@angular/core';
import {TicketValidity} from '../../../models/enums/ticket-validity.enum'; import {TicketValidity} from '../../../models/enums/ticket-validity.enum';
import {TicketTypeEnum} from '../../../models/enums/ticket-type.enum'; import {IsSeasonTicket, TicketTypeEnum} from '../../../models/enums/ticket-type.enum';
import {ScanService} from '../../services/scan.service'; import {ScanService} from '../../services/scan.service';
import {NgOptimizedImage} from '@angular/common';
@Component({ @Component({
selector: 'app-scan-result', selector: 'app-scan-result',
imports: [ imports: [],
NgOptimizedImage
],
templateUrl: './scan-result.component.html', templateUrl: './scan-result.component.html',
styleUrl: './scan-result.component.scss' styleUrl: './scan-result.component.scss'
}) })
@ -16,6 +13,61 @@ export class ScanResultComponent {
private scan = inject(ScanService); private scan = inject(ScanService);
public ticketValidity = this.scan.ticketValid$; public ticketValidity = this.scan.ticketValid$;
public ticketType = this.scan.ticketType$; public ticketType = this.scan.ticketType$;
public ticketSearch$ = this.scan.ticketSearch$;
protected readonly TicketValidity = TicketValidity; protected readonly TicketValidity = TicketValidity;
protected readonly TicketTypeEnum = TicketTypeEnum; protected readonly IsSeasonTicket = IsSeasonTicket;
public getValidityText(): string {
switch (this.ticketValidity()) {
case TicketValidity.Valid:
return 'Valid';
case TicketValidity.Expired:
return 'Expired';
case TicketValidity.Early:
return 'Early';
default:
return 'Invalid';
}
}
public getValidityImage(): string {
switch (this.ticketValidity()) {
case TicketValidity.Valid:
return 'pass.svg';
default:
return 'fail.svg';
}
}
public getTypeImage(): string {
switch (this.ticketType()) {
case TicketTypeEnum.SingleSeason:
case TicketTypeEnum.Single:
return 'single.svg';
case TicketTypeEnum.FamilySeason:
case TicketTypeEnum.Family:
return 'group.svg';
case TicketTypeEnum.SeniorSeason:
case TicketTypeEnum.Senior:
return 'senior.svg';
default:
return '';
}
}
public getTypeText(): string {
switch (this.ticketType()) {
case TicketTypeEnum.SingleSeason:
case TicketTypeEnum.Single:
return 'Single Ticket';
case TicketTypeEnum.FamilySeason:
case TicketTypeEnum.Family:
return 'Family Ticket';
case TicketTypeEnum.SeniorSeason:
case TicketTypeEnum.Senior:
return 'Senior Ticket';
default:
return '';
}
}
} }

View File

@ -1,9 +1,11 @@
<div class="card"> <div class="card card-fit-parent">
<label for="seasons">Season</label> <div class="row row-space-between">
<select name="Seasons" id="seasons"> <label class="label">Season</label>
<option>No Season</option> <select #season name="Seasons" class="select" (change)="click(season.value)">
<option value="">No Season</option>
@for(season of seasons$(); track season.id) { @for(season of seasons$(); track season.id) {
<option [value]="season.id">{{season.name}}</option> <option [value]="season.id">{{season.name}}</option>
} }
</select> </select>
</div> </div>
</div>

View File

@ -1,3 +0,0 @@
label {
padding-right: 15px;
}

View File

@ -1,17 +1,28 @@
import {Component, inject, OnInit} from '@angular/core'; import {Component, inject, OnInit, signal, WritableSignal} from '@angular/core';
import {SeasonService} from '../../services/season.service'; import {SeasonService} from '../../services/season.service';
import {Season} from '../../../models/core/season';
import {ReactiveFormsModule} from '@angular/forms';
@Component({ @Component({
selector: 'app-season-browser', selector: 'app-season-browser',
imports: [], imports: [
ReactiveFormsModule
],
templateUrl: './season-browser.component.html', templateUrl: './season-browser.component.html',
styleUrl: './season-browser.component.scss' styleUrl: './season-browser.component.scss'
}) })
export class SeasonBrowserComponent implements OnInit { export class SeasonBrowserComponent implements OnInit {
private seasonService = inject(SeasonService); private seasonService = inject(SeasonService);
public seasons$ = this.seasonService.seasons$; public seasons$ = this.seasonService.seasons$;
public selectedSeasonId$: WritableSignal<string> = signal('');
public selectedSeasonId!: string;
public ngOnInit() { public ngOnInit() {
this.seasonService.getSeasons(); this.seasonService.getSeasons();
} }
public click(selectedSeasonId: string): void {
this.selectedSeasonId$.set(selectedSeasonId);
this.selectedSeasonId = selectedSeasonId;
}
} }

View File

@ -1,9 +1,10 @@
<div class="card"> <div class="card card-fit-parent">
<label for="types">Ticket Type</label> <div class="row row-space-between">
<select name="Types" id="types"> <label class="label">Ticket Type</label>
<option>Select...</option> <select #types name="Types" class="select" (change)="click(types.selectedIndex)">
@for(type of ticketTypes | keyvalue; track type) { @for(type of ticketTypes | keyvalue; track type) {
<option [value]="type.key">{{type.value}}</option> <option [value]="type.key">{{type.value}}</option>
} }
</select> </select>
</div> </div>
</div>

View File

@ -1,3 +0,0 @@
label {
padding-right: 15px;
}

View File

@ -1,15 +1,24 @@
import { Component } from '@angular/core'; import {Component, signal, WritableSignal} from '@angular/core';
import {TicketTypeEnumLabel} from '../../../models/enums/ticket-type.enum'; import {TicketTypeEnum, TicketTypeEnumLabel} from '../../../models/enums/ticket-type.enum';
import {KeyValuePipe} from '@angular/common'; import {KeyValuePipe} from '@angular/common';
import {ReactiveFormsModule} from '@angular/forms';
@Component({ @Component({
selector: 'app-ticket-type-selector', selector: 'app-ticket-type-selector',
imports: [ imports: [
KeyValuePipe KeyValuePipe,
ReactiveFormsModule
], ],
templateUrl: './ticket-type-selector.component.html', templateUrl: './ticket-type-selector.component.html',
styleUrl: './ticket-type-selector.component.scss' styleUrl: './ticket-type-selector.component.scss'
}) })
export class TicketTypeSelectorComponent { export class TicketTypeSelectorComponent {
public ticketTypes = TicketTypeEnumLabel; public ticketTypes = TicketTypeEnumLabel;
public selectedTicketType$: WritableSignal<TicketTypeEnum> = signal(TicketTypeEnum.Null);
public selectedTicketType!: TicketTypeEnum;
public click(ticketType: number): void {
this.selectedTicketType$.set(ticketType as TicketTypeEnum);
this.selectedTicketType = ticketType as TicketTypeEnum;
}
} }

View File

@ -2,22 +2,22 @@
<!-- TODO: Move this to a separate component --> <!-- TODO: Move this to a separate component -->
<h3>Add Season</h3> <h3>Add Season</h3>
<div class="column"> <div class="column">
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Season Name</label> <label class="label" for="seasonName">Season Name</label>
<input [formControl]="seasonName" type="text"/> <input id="seasonName" [formControl]="seasonName" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Season Description</label> <label class="label" for="seasonDescription">Season Description</label>
<input [formControl]="seasonDescription" type="text"/> <input id="seasonDescription" [formControl]="seasonDescription" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Season Start Date</label> <label class="label" for="seasonStartDate">Season Start Date</label>
<input [formControl]="seasonStartDate" type="date"/> <input id="seasonStartDate" [formControl]="seasonStartDate" type="date" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Season End Date</label> <label class="label" for="seasonEndDate">Season End Date</label>
<input [formControl]="seasonEndDate" type="date"/> <input id="seasonEndDate" [formControl]="seasonEndDate" type="date" class="input"/>
</span> </div>
</div> </div>
<button class="button" (click)="saveSeason()">Save</button> <button class="button" (click)="saveSeason()">Save</button>
</div> </div>
@ -26,18 +26,18 @@
<!-- TODO: Move this to a separate component --> <!-- TODO: Move this to a separate component -->
<h3>Add Event</h3> <h3>Add Event</h3>
<div class="column"> <div class="column">
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Event Name</label> <label class="label" for="eventName">Event Name</label>
<input [formControl]="eventName" type="text"/> <input id="eventName" [formControl]="eventName" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Event Description</label> <label class="label" for="eventDescription">Event Description</label>
<input [formControl]="eventDescription" type="text"/> <input id="eventDescription" [formControl]="eventDescription" type="text" class="input"/>
</span> </div>
<span class="row row-space-between"> <div class="row row-space-between row-bottom-margin">
<label>Event Date</label> <label class="label" for="eventDate">Event Date</label>
<input [formControl]="eventDate" type="date"/> <input id="eventDate" [formControl]="eventDate" type="date" class="input"/>
</span> </div>
</div> </div>
<app-season-browser /> <app-season-browser />
<button class="button" (click)="saveEvent()">Save</button> <button class="button" (click)="saveEvent()">Save</button>

View File

@ -1,3 +0,0 @@
label {
padding-right: 15px;
}

View File

@ -1,8 +1,12 @@
import {Component, inject} from '@angular/core'; import {Component, inject, ViewChild} from '@angular/core';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms'; import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {EventService} from '../../services/event.service'; import {EventService} from '../../services/event.service';
import {SeasonService} from '../../services/season.service'; import {SeasonService} from '../../services/season.service';
import {SeasonBrowserComponent} from '../../components/season-browser/season-browser.component'; import {SeasonBrowserComponent} from '../../components/season-browser/season-browser.component';
import {AddEventRequest} from '../../../models/request/add-event-request';
import {AddSeason} from '../../../models/request/add-season';
import {VALLEY_FORGE_HIGH_SCHOOL} from '../../../models/core/venue';
import {PARMA_SYMPHONY_ORCHESTRA} from '../../../models/core/talent';
@Component({ @Component({
selector: 'app-event', selector: 'app-event',
@ -11,23 +15,40 @@ import {SeasonBrowserComponent} from '../../components/season-browser/season-bro
styleUrl: './event.component.scss' styleUrl: './event.component.scss'
}) })
export class EventComponent { export class EventComponent {
public eventName = new FormControl('', [Validators.required]); @ViewChild(SeasonBrowserComponent) seasonBrowser!: SeasonBrowserComponent;
public eventName = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public eventDescription = new FormControl(''); public eventDescription = new FormControl('');
public eventDate = new FormControl(new Date, [Validators.required]); public eventDate = new FormControl(new Date, {nonNullable: true, validators: [Validators.required]});
public seasonName = new FormControl('', [Validators.required]); public seasonName = new FormControl('', {nonNullable: true, validators: [Validators.required]});
public seasonDescription = new FormControl(''); public seasonDescription = new FormControl('');
public seasonStartDate = new FormControl(new Date, [Validators.required]); public seasonStartDate = new FormControl(new Date, {nonNullable: true, validators: [Validators.required]});
public seasonEndDate = new FormControl(new Date, [Validators.required]); public seasonEndDate = new FormControl(new Date, {nonNullable: true, validators: [Validators.required]});
private eventService = inject(EventService); private eventService = inject(EventService);
private seasonService = inject(SeasonService); private seasonService = inject(SeasonService);
public saveEvent(): void { public saveEvent(): void {
const request: AddEventRequest = {
date: this.eventDate.value,
name: this.eventName.value,
description: this.eventDescription.value,
venue: VALLEY_FORGE_HIGH_SCHOOL,
talent: PARMA_SYMPHONY_ORCHESTRA,
seasonId: this.seasonBrowser.selectedSeasonId
};
this.eventService.addEvent(request);
} }
public saveSeason(): void { public saveSeason(): void {
const request: AddSeason = {
startDate: this.seasonStartDate.value,
endDate: this.seasonEndDate.value,
name: this.seasonName.value,
description: this.seasonDescription.value,
};
this.seasonService.saveSeason(request);
} }
} }

View File

@ -1,4 +1,4 @@
<button class="button" (click)="hideScanner = !hideScanner"> <button class="button" (click)="click()">
@if(hideScanner) { @if(hideScanner) {
Stop Stop
} @else { } @else {

View File

@ -18,4 +18,11 @@ export class ScanComponent {
this.scan.searchTicket(resultString); this.scan.searchTicket(resultString);
console.log(resultString); console.log(resultString);
} }
public click(): void {
this.hideScanner = !this.hideScanner;
this.scan.ticketValid$.set(null);
this.scan.ticketType$.set(null);
this.scan.ticketSearch$.set(null);
}
} }

View File

@ -1,7 +1,16 @@
<div class="card"> <div class="card">
<h3>Generate Tickets</h3> <h3>Generate Tickets</h3>
<div class="column">
<app-ticket-type-selector #ticketType></app-ticket-type-selector>
<app-event-browser /> <app-event-browser />
<app-ticket-type-selector /> @if(IsSeasonTicket(ticketType.selectedTicketType$())) {
<app-season-browser />
}
<app-patron-info /> <app-patron-info />
</div>
<button class="button" (click)="saveTicket()">Save Ticket</button> <button class="button" (click)="saveTicket()">Save Ticket</button>
</div> </div>
@if(qrCode().length > 0) {
<app-generated-ticket />
}

View File

@ -1,23 +1,46 @@
import {Component, inject} from '@angular/core'; import {Component, inject, ViewChild} from '@angular/core';
import {EventBrowserComponent} from '../../components/event-browser/event-browser.component'; import {EventBrowserComponent} from '../../components/event-browser/event-browser.component';
import {TicketTypeSelectorComponent} from '../../components/ticket-type-selector/ticket-type-selector.component'; import {TicketTypeSelectorComponent} from '../../components/ticket-type-selector/ticket-type-selector.component';
import {PatronInfoComponent} from '../../components/patron-info/patron-info.component'; import {PatronInfoComponent} from '../../components/patron-info/patron-info.component';
import {TicketService} from '../../services/ticket.service'; import {TicketService} from '../../services/ticket.service';
import {AddTicket} from '../../../models/request/add-ticket';
import {SeasonBrowserComponent} from '../../components/season-browser/season-browser.component';
import {IsSeasonTicket} from '../../../models/enums/ticket-type.enum';
import {GeneratedTicketComponent} from '../../components/generated-ticket/generated-ticket.component';
@Component({ @Component({
selector: 'app-ticket', selector: 'app-ticket',
imports: [ imports: [
EventBrowserComponent, EventBrowserComponent,
TicketTypeSelectorComponent, TicketTypeSelectorComponent,
PatronInfoComponent PatronInfoComponent,
SeasonBrowserComponent,
GeneratedTicketComponent
], ],
templateUrl: './ticket.component.html', templateUrl: './ticket.component.html',
styleUrl: './ticket.component.scss' styleUrl: './ticket.component.scss'
}) })
export class TicketComponent { export class TicketComponent {
@ViewChild(TicketTypeSelectorComponent) ticketTypeSelector!: TicketTypeSelectorComponent;
@ViewChild(PatronInfoComponent) patronInfoComponent!: PatronInfoComponent;
@ViewChild(EventBrowserComponent) eventBrowserComponent!: EventBrowserComponent;
@ViewChild(SeasonBrowserComponent) seasonBrowserComponent!: SeasonBrowserComponent;
public ticketService = inject(TicketService); public ticketService = inject(TicketService);
public qrCode = this.ticketService.dataSignal$;
public saveTicket(): void { public saveTicket(): void {
const addTicket: AddTicket = {
eventId: this.eventBrowserComponent.selectedEventId,
type: this.ticketTypeSelector.selectedTicketType,
seasonId:
IsSeasonTicket(this.ticketTypeSelector.selectedTicketType)
? this.seasonBrowserComponent.selectedSeasonId : '',
patron: this.patronInfoComponent.buildPatron()
}
this.ticketService.addTicket(addTicket);
} }
protected readonly IsSeasonTicket = IsSeasonTicket;
} }

View File

@ -22,32 +22,21 @@ export class EventService extends ApiUtils {
public addEvent(request: AddEventRequest): void { public addEvent(request: AddEventRequest): void {
//TODO: Remove hard coded venue and talent information //TODO: Remove hard coded venue and talent information
request.talent = {
name: 'Parma Symphony Orchestra',
description: 'Parma Symphony Orchestra is a Northeast Ohio community orchestra with over 50 years of history bringing classical music to people of all ages, with opportunities for local students and professional guests to perform a wide ranging repertoire.',
};
request.venue = {
name: 'Valley Forge High School',
description: 'Auditorium',
addressOne: '9999 Independence Blvd',
addressTwo: null,
city: 'Parma',
state: 'Ohio',
zip: '44130'
};
const options = this.setHttpRequestOptions(); const options = this.setHttpRequestOptions();
const url = environment.apiBase + Endpoints.EVENT; const url = environment.apiBase + Endpoints.EVENT;
this.httpClient.post<AddEventRequest>(url, JSON.stringify(request), options) this.httpClient.post(url, JSON.stringify(request), options)
.pipe( .pipe(
catchError(error => { catchError(error => {
this.snackbar.showError(error.error); this.snackbar.showError(error.error);
return of(undefined); return of(undefined);
}) })
).subscribe(); ).subscribe(res => {
if(res !== null) {
this.snackbar.showMessage('Event added successfully.');
this.searchAllEvents();
}
});
} }
public searchAllEvents(): void { public searchAllEvents(): void {

View File

@ -13,8 +13,9 @@ import {SnackbarService} from './snackbar.service';
providedIn: 'root' providedIn: 'root'
}) })
export class ScanService extends ApiUtils { export class ScanService extends ApiUtils {
public ticketValid$: WritableSignal<TicketValidity> = signal(TicketValidity.Null); public ticketValid$: WritableSignal<TicketValidity | null> = signal(TicketValidity.Invalid);
public ticketType$: WritableSignal<TicketTypeEnum> = signal(TicketTypeEnum.Null); public ticketType$: WritableSignal<TicketTypeEnum | null> = signal(TicketTypeEnum.Null);
public ticketSearch$: WritableSignal<TicketSearch | null> = signal(null);
private httpClient = inject(HttpClient); private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService); private snackbar = inject(SnackbarService);
@ -35,8 +36,9 @@ export class ScanService extends ApiUtils {
}) })
).subscribe((res: TicketSearch) => { ).subscribe((res: TicketSearch) => {
if (res !== undefined) { if (res !== undefined) {
this.ticketType$.set(res.ticketType); this.ticketSearch$.set(res);
this.ticketValid$.set(res.ticketValidity) this.ticketType$.set(res.type as TicketTypeEnum);
this.ticketValid$.set(res.validity as TicketValidity);
} }
}); });
} }

View File

@ -48,7 +48,12 @@ export class SeasonService extends ApiUtils {
this.snackbar.showError(error.error); this.snackbar.showError(error.error);
return of(undefined); return of(undefined);
}) })
).subscribe(); ).subscribe(res => {
if (res !== null) {
this.snackbar.showMessage('Season added successfully.');
this.getSeasons();
}
});
} }
public addEventToSeason(eventId: string, seasonId: string): void { public addEventToSeason(eventId: string, seasonId: string): void {

View File

@ -16,4 +16,14 @@ export class SnackbarService {
this.snackbarMessage$.set(''); this.snackbarMessage$.set('');
}, 5000); }, 5000);
} }
public showMessage(message: string): void {
this.showSnackbar$.set(true);
this.snackbarMessage$.set(message);
setTimeout(() => {
this.showSnackbar$.set(false);
this.snackbarMessage$.set('');
}, 5000);
}
} }

View File

@ -2,3 +2,8 @@ export interface Talent {
name: string; name: string;
description: string | null; description: string | null;
} }
export const PARMA_SYMPHONY_ORCHESTRA: Talent = {
name: 'Parma Symphony Orchestra',
description: 'Parma Symphony Orchestra is a Northeast Ohio community orchestra with over 50 years of history bringing classical music to people of all ages, with opportunities for local students and professional guests to perform a wide ranging repertoire.',
}

View File

@ -7,3 +7,13 @@ export interface Venue {
state: string, state: string,
zip: string, zip: string,
} }
export const VALLEY_FORGE_HIGH_SCHOOL: Venue = {
name: 'Valley Forge High School',
description: 'Auditorium',
addressOne: '9999 Independence Blvd',
addressTwo: null,
city: 'Parma',
state: 'Ohio',
zip: '44130'
}

View File

@ -9,6 +9,7 @@ export enum TicketTypeEnum {
} }
export const TicketTypeEnumLabel = new Map<number, string>([ export const TicketTypeEnumLabel = new Map<number, string>([
[TicketTypeEnum.Null, 'Select'],
[TicketTypeEnum.Single, 'Single'], [TicketTypeEnum.Single, 'Single'],
[TicketTypeEnum.SingleSeason, 'Single Season'], [TicketTypeEnum.SingleSeason, 'Single Season'],
[TicketTypeEnum.Family, 'Family'], [TicketTypeEnum.Family, 'Family'],
@ -16,3 +17,9 @@ export const TicketTypeEnumLabel = new Map<number, string>([
[TicketTypeEnum.Senior, 'Senior'], [TicketTypeEnum.Senior, 'Senior'],
[TicketTypeEnum.SeniorSeason, 'Senior Season'], [TicketTypeEnum.SeniorSeason, 'Senior Season'],
]); ]);
export function IsSeasonTicket(type: TicketTypeEnum): boolean {
return type === TicketTypeEnum.SingleSeason
|| type === TicketTypeEnum.FamilySeason
|| type === TicketTypeEnum.SeniorSeason;
}

View File

@ -1,7 +1,6 @@
export enum TicketValidity { export enum TicketValidity {
Null,
Valid,
Expired, Expired,
Early, Early,
Invalid Invalid,
Valid,
} }

View File

@ -4,7 +4,8 @@ import {Talent} from '../core/talent';
export interface AddEventRequest { export interface AddEventRequest {
date: Date, date: Date,
name: string, name: string,
description: string, description: string | null,
venue: Venue, venue: Venue,
talent: Talent, talent: Talent,
seasonId: string
} }

View File

@ -1,6 +1,9 @@
import {TicketTypeEnum} from '../enums/ticket-type.enum'; import {TicketTypeEnum} from '../enums/ticket-type.enum';
import {Patron} from '../core/patron';
export interface AddTicket { export interface AddTicket {
ticketType: TicketTypeEnum, type: TicketTypeEnum,
eventId: string eventId: string,
seasonId: string | null,
patron: Patron,
} }

View File

@ -2,6 +2,6 @@ import {TicketTypeEnum} from '../enums/ticket-type.enum';
import {TicketValidity} from '../enums/ticket-validity.enum'; import {TicketValidity} from '../enums/ticket-validity.enum';
export interface TicketSearch { export interface TicketSearch {
ticketType: TicketTypeEnum, type: TicketTypeEnum,
ticketValidity: TicketValidity validity: TicketValidity
} }

View File

@ -18,6 +18,10 @@ body {
&-large { &-large {
width: 250px; width: 250px;
} }
&-fit-parent {
width: auto;
}
} }
.button { .button {
@ -39,18 +43,59 @@ body {
} }
} }
.select {
padding: 5px;
backdrop-filter: blur(25px) saturate(112%);
-webkit-backdrop-filter: blur(25px) saturate(112%);
background-color: rgba(255, 255, 255, 0.11);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.125);
width: auto;
&:hover {
background-color: rgba(255, 255, 255, 0.0);
}
}
.input {
padding: 5px;
backdrop-filter: blur(25px) saturate(112%);
-webkit-backdrop-filter: blur(25px) saturate(112%);
background-color: rgba(255, 255, 255, 0.11);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.125);
width: auto;
&:hover {
background-color: rgba(255, 255, 255, 0.0);
}
}
.label {
padding-right: 15px;
}
.row { .row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: baseline;
&-space-between { &-space-between {
justify-content: space-between; justify-content: space-between;
} }
&-bottom-margin {
margin-bottom: 10px;
}
} }
.column { .column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
&-space-between {
justify-content: space-between;
}
} }