MVP Reached

This commit is contained in:
Tara Wilson 2024-12-07 19:20:24 -05:00
parent 35ddedb5c3
commit 19529085e7
22 changed files with 993 additions and 892 deletions

View File

@ -15,7 +15,8 @@ namespace api.Controllers;
[Route("[controller]")]
public class TicketController(
IQrCodeGenerator qr,
ITicketManager ticketManager) : ControllerBase
ITicketManager ticketManager,
IEventManager eventManager) : ControllerBase
{
/// <summary>
/// Generates a Base64 String Qr Code and Saves Qr Code and Ticket to DB
@ -41,7 +42,7 @@ public class TicketController(
QrCode = qrCode,
Type = request.Type,
EventId = request.EventId,
SeasonId = request.SeasonId != Guid.Empty ? request.SeasonId : Guid.Empty,
SeasonId = request.SeasonId,
Patron = request.Patron,
};
@ -51,8 +52,8 @@ public class TicketController(
//return
var response = new MintResponse
{
QrCode = ticket.QrCode,
Type = ticket.Type
Ticket = ticket,
Details = eventManager.GetEvent(ticket.EventId)
};
return Ok(response);

View File

@ -1,5 +1,6 @@
using models.Core;
using models.Request;
using models.Response;
namespace api.Interfaces;
@ -9,4 +10,5 @@ public interface IEventManager
void PatchEvent(PatchEvent request);
List<Event> GetEvents(DateTime? startDate, DateTime? endDate);
List<Event> GetAllEvents();
EventDetails GetEvent(Guid id);
}

View File

@ -2,6 +2,7 @@ using api.Interfaces;
using data.Events;
using models.Core;
using models.Request;
using models.Response;
namespace api.Services;
@ -38,4 +39,9 @@ public class EventManager : IEventManager
{
return new GetAll().Execute();
}
public EventDetails GetEvent(Guid id)
{
return new GetDetails().Execute(id);
}
}

View File

@ -49,20 +49,20 @@ public class TicketManager : ITicketManager
return new MintResponse()
{
QrCode = ticket.QrCode,
Type = ticket.Type,
Ticket = ticket,
Details = new data.Events.GetDetails().Execute(ticket.EventId),
};
}
private static TicketValidity DetermineValidity(Event @event)
{
if (@event.Date < DateTime.Now)
if (@event.Date.Day != DateTime.Today.Day) return TicketValidity.Invalid;
if (@event.Date.Day < DateTime.Now.Day)
{
return TicketValidity.Expired;
}
if (@event.Date.Day != DateTime.Today.Day) return TicketValidity.Invalid;
if (DateTime.Now.Hour < @event.Date.Hour - 2)
{
return TicketValidity.Early;

View File

@ -0,0 +1,27 @@
using models.Core;
using models.Response;
using MongoDB.Driver;
namespace data.Events;
public class GetDetails
{
public EventDetails Execute(Guid eventId)
{
var database = MongoFactory.GetDatabase();
var collection = database.GetCollection<Event>("events");
var filter = Builders<Event>.Filter.Eq(e => e.Id, eventId);
var @event = collection.Find(filter).FirstOrDefault();
var details = new EventDetails
{
Name = @event.Name,
Description = @event.Description,
Date = @event.Date,
Venue = @event.Venue,
Talent = @event.Talent,
};
return details;
}
}

View File

@ -5,7 +5,7 @@ namespace models.Core;
public class Ticket
{
public Guid Id { get; set; }
public Guid SeasonId { get; set; }
public Guid? SeasonId { get; set; }
public Guid EventId { get; set; }
public TicketType Type { get; set; }
public required string QrCode { get; set; }

View File

@ -7,6 +7,6 @@ public class AddTicket
{
public TicketType Type { get; set; }
public Guid EventId { get; set; }
public Guid SeasonId { get; set; } = Guid.Empty;
public Guid? SeasonId { get; set; } = Guid.Empty;
public required Patron Patron { get; set; }
}

View File

@ -0,0 +1,12 @@
using models.Core;
namespace models.Response;
public class EventDetails
{
public DateTime Date { get; set; }
public required string Name { get; set; }
public string? Description { get; set; }
public required Venue Venue { get; set; }
public required Talent Talent { get; set; }
}

View File

@ -1,9 +1,9 @@
using models.Enumerations;
using models.Core;
namespace models.Response;
public class MintResponse
{
public required string QrCode { get; set; }
public TicketType Type { get; set; }
public required Ticket Ticket { get; set; }
public required EventDetails Details { get; set; }
}

View File

@ -21,6 +21,7 @@
"@zxing/browser": "^0.1.5",
"@zxing/library": "^0.21.3",
"@zxing/ngx-scanner": "^19.0.0",
"guid-typescript": "^1.0.9",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,7 +1,22 @@
@if(qrCode().length > 0) {
<div class="card">
@if (ticket$() !== null) {
<div class="card column">
<div class="card row">
<img src="pso-logo.png" width="250" height="100" alt="Parma Symphony Orchestra Logo"/>
</div>
<div class="row">
<img class="qrcode" alt="Embedded QR Code" src="data:image/png;base64,{{qrCode().toString()}}" />
<img class="qrcode" alt="Embedded QR Code" src="data:image/png;base64,{{qrCode}}"/>
</div>
<div class="row">
<div class="card">
<span class="row">{{talent?.name}}</span>
<span class="row">{{details?.name}}</span>
<span class="row">{{details?.description}}</span>
<span class="row">{{details?.date | date: 'medium'}}</span>
<span class="row">{{venue?.name}}</span>
<span class="row">{{venue?.description}}</span>
<span class="row">{{venue?.addressOne}}</span>
<span class="row">{{venue?.city}}, {{venue?.state}} {{venue?.zip}}</span>
</div>
</div>
</div>
}

View File

@ -1,13 +1,20 @@
import {Component, inject} from '@angular/core';
import {TicketService} from '../../services/ticket.service';
import {DatePipe} from '@angular/common';
@Component({
selector: 'app-generated-ticket',
imports: [],
imports: [
DatePipe
],
templateUrl: './generated-ticket.component.html',
styleUrl: './generated-ticket.component.scss'
})
export class GeneratedTicketComponent {
private ticket = inject(TicketService);
public qrCode = this.ticket.dataSignal$;
public ticket$ = this.ticket.mintResponse$;
public qrCode = this.ticket$()?.ticket.qrCode;
public details = this.ticket$()?.details;
public talent = this.ticket$()?.details.talent;
public venue = this.ticket$()?.details.venue;
}

View File

@ -1,21 +1,29 @@
@if (ticketSearch$() != null) {
<div class="card">
<!-- Show Validity, If Valid Show Ticket Type and Indicate Season -->
<div class="column">
<div class="column">
<table>
<tr>
<td>
<img [src]="getValidityImage()" width="100px" height="100px" [alt]="getValidityText()"/>
<span class="row">{{getValidityText()}}</span>
</div>
</td>
<td>{{ getValidityText() }}</td>
</tr>
@if (ticketValidity() === TicketValidity.Valid) {
<div class="column">
<img [src]="getTypeImage()" width="50px" height="50px" [alt]="getTypeText()"/>
<span class="row">{{getTypeText()}}</span>
</div>
<tr>
<td>
<img [src]="getTypeImage()" width="100px" height="100px" [alt]="getTypeText()"/>
</td>
<td>{{ getTypeText() }}</td>
</tr>
@if (IsSeasonTicket(ticketType())) {
<img src="season.svg" width="50px" height="50px" alt="Season Ticket"/>
<span class="row">Seasonal</span>
<tr>
<td>
<img src="season.svg" width="100px" height="100px" alt="Season Ticket"/>
</td>
<td>Seasonal</td>
</tr>
}
}
</div>
</table>
</div>
}

View File

@ -2,6 +2,7 @@ import {Component, inject} from '@angular/core';
import {ZXingScannerModule} from '@zxing/ngx-scanner';
import {ScanResultComponent} from '../../components/scan-result/scan-result.component';
import {ScanService} from '../../services/scan.service';
import {TicketTypeEnum} from '../../../models/enums/ticket-type.enum';
@Component({
selector: 'app-scan',
@ -22,7 +23,7 @@ export class ScanComponent {
public click(): void {
this.hideScanner = !this.hideScanner;
this.scan.ticketValid$.set(null);
this.scan.ticketType$.set(null);
this.scan.ticketType$.set(TicketTypeEnum.Null);
this.scan.ticketSearch$.set(null);
}
}

View File

@ -11,6 +11,6 @@
<button class="button" (click)="saveTicket()">Save Ticket</button>
</div>
@if(qrCode().length > 0) {
@if(ticket$() !== null) {
<app-generated-ticket />
}

View File

@ -7,6 +7,7 @@ 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';
import {Guid} from 'guid-typescript';
@Component({
selector: 'app-ticket',
@ -27,7 +28,7 @@ export class TicketComponent {
@ViewChild(SeasonBrowserComponent) seasonBrowserComponent!: SeasonBrowserComponent;
public ticketService = inject(TicketService);
public qrCode = this.ticketService.dataSignal$;
public ticket$ = this.ticketService.mintResponse$;
public saveTicket(): void {
const addTicket: AddTicket = {
@ -35,7 +36,7 @@ export class TicketComponent {
type: this.ticketTypeSelector.selectedTicketType,
seasonId:
IsSeasonTicket(this.ticketTypeSelector.selectedTicketType)
? this.seasonBrowserComponent.selectedSeasonId : '',
? this.seasonBrowserComponent.selectedSeasonId : null,
patron: this.patronInfoComponent.buildPatron()
}

View File

@ -14,7 +14,7 @@ import {SnackbarService} from './snackbar.service';
})
export class ScanService extends ApiUtils {
public ticketValid$: WritableSignal<TicketValidity | null> = signal(TicketValidity.Invalid);
public ticketType$: WritableSignal<TicketTypeEnum | null> = signal(TicketTypeEnum.Null);
public ticketType$: WritableSignal<TicketTypeEnum> = signal(TicketTypeEnum.Null);
public ticketSearch$: WritableSignal<TicketSearch | null> = signal(null);
private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService);

View File

@ -8,12 +8,13 @@ import {AddTicket} from '../../models/request/add-ticket';
import {ApiUtils} from './api-utils';
import {TicketSearch} from '../../models/response/ticket-search';
import {SnackbarService} from './snackbar.service';
import {Ticket} from '../../models/core/ticket';
@Injectable({
providedIn: 'root'
})
export class TicketService extends ApiUtils {
public dataSignal$: WritableSignal<string> = signal('');
public mintResponse$: WritableSignal<MintResponse | null> = signal(null);
private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService);
@ -34,7 +35,7 @@ export class TicketService extends ApiUtils {
})
).subscribe((res: MintResponse) => {
if (res !== undefined) {
this.dataSignal$.set(res.qrCode);
this.mintResponse$.set(res);
}
});
}

View File

@ -0,0 +1,10 @@
import {Venue} from '../core/venue';
import {Talent} from '../core/talent';
export interface EventDetails {
date: Date,
name: string,
description: string | null,
venue: Venue,
talent: Talent
}

View File

@ -1,3 +1,7 @@
import {Ticket} from '../core/ticket';
import {EventDetails} from './event-details';
export interface MintResponse {
qrCode: string,
ticket: Ticket,
details: EventDetails
}

File diff suppressed because it is too large Load Diff