From 80e349c5e48e973d073ffc40b32e4bbfe3a4c2a1 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sat, 24 Feb 2024 17:16:21 +0100 Subject: [PATCH 01/23] refactor(frontend): Remove unused style --- .../src/app/poll/create-poll/create-edit-poll.component.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/frontend/src/app/poll/create-poll/create-edit-poll.component.scss b/apps/frontend/src/app/poll/create-poll/create-edit-poll.component.scss index 0f47cfae..e69de29b 100644 --- a/apps/frontend/src/app/poll/create-poll/create-edit-poll.component.scss +++ b/apps/frontend/src/app/poll/create-poll/create-edit-poll.component.scss @@ -1,3 +0,0 @@ -.mwlFlatpickr { - width: 100%; -} From ffbb7505095e6d1a6074ae09f33d97ed78e91d62 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 12:50:45 +0100 Subject: [PATCH 02/23] wip(frontend): Refactor table component --- .../choose-events.component.html | 190 +----------------- .../choose-events.component.scss | 31 --- .../choose-events/choose-events.component.ts | 108 +--------- apps/frontend/src/app/poll/poll.module.ts | 2 + .../src/app/poll/services/poll.service.ts | 19 ++ .../src/app/poll/table/table.component.html | 177 ++++++++++++++++ .../src/app/poll/table/table.component.scss | 30 +++ .../src/app/poll/table/table.component.ts | 119 +++++++++++ tsconfig.base.json | 2 +- 9 files changed, 361 insertions(+), 317 deletions(-) create mode 100644 apps/frontend/src/app/poll/table/table.component.html create mode 100644 apps/frontend/src/app/poll/table/table.component.scss create mode 100644 apps/frontend/src/app/poll/table/table.component.ts diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.html b/apps/frontend/src/app/poll/choose-events/choose-events.component.html index 71abec4b..88f65113 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.html +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.html @@ -47,7 +47,7 @@

}
-
@@ -69,183 +69,15 @@

- @if (pollEvents) { -
- - - - - @for (event of pollEvents; track event) { - - } - - - - - @if (!closedReason) { - - - @for (event of pollEvents; track event) { - - } - - - } - @if (showResults) { - @for (participant of participants; track participant) { - - - @if (participant !== editParticipant) { - @for (pollEvent of pollEvents; track pollEvent; let n = $index) { - - } - } - @if (participant === editParticipant && editDto) { - @for (event of pollEvents; track event) { - - } - } - @if (participant === editParticipant) { - - } - @if (!editParticipant) { - - } - - } - @if (poll?.settings?.showResult !== ShowResultOptions.NEVER || isAdmin) { - - - @for (pollEvent of pollEvents; track pollEvent) { - - } - - - } - } - @if (poll && isAdmin) { - - - @for (pollEvent of pollEvents; track pollEvent; let i = $index) { - - } - - - } - -
- -
- - - - -
- -
-
- {{ poll?.settings?.anonymous ? 'Anonymous' : participant.name }} - @if (participant.createdAt) { - - {{ participant.createdAt | date: 'shortTime' }} - - } - @if (participant.updatedAt !== participant.createdAt) { - - • edited - - } - - @switch (participant.selection[pollEvent._id]) { - @case ('yes') { -
- } - @case ('no') { -
- } - @case ('maybe') { -
- } - @default { -
- } - } -
- - - -
- -
-
- @if (poll?.settings?.allowEdit && token === participant.token) { - - } - @if (isAdmin || token === participant.token) { - - } -
Sum - @if (bestOption === pollEvent.participants) { -
- {{ pollEvent.participants }} -
- } - @if (bestOption !== pollEvent.participants) { -
- {{ pollEvent.participants }} -
- } -
Select Events - - - -
-
+ @if (poll && pollEvents) { + } diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.scss b/apps/frontend/src/app/poll/choose-events/choose-events.component.scss index 989ae1a4..bb690f1f 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.scss +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.scss @@ -1,34 +1,3 @@ -.table-responsive td, .table-responsive th { - min-width: 100px; -} - -.form-check-hidden { - appearance: none; - cursor: pointer; -} - *:not(:hover) > .hover-info { visibility: hidden; } - -@media (min-width: 768px) { - th:first-child, th:last-child, - td:first-child, td:last-child { - position: sticky; - backdrop-filter: blur(5px); - } - - th:first-child, td:first-child { - background: linear-gradient(90deg, var(--bs-body-bg), rgba(var(--bs-body-bg), 0.75)); - left: 0; - } - - th:last-child, td:last-child { - background: linear-gradient(90deg, rgba(var(--bs-body-bg), 0.75), var(--bs-body-bg)); - right: 0; - } -} - -h5 { - margin: 0; -} diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts index 7ea325d4..3c68b7f9 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts @@ -1,16 +1,15 @@ import {Component, OnInit} from '@angular/core'; import {Meta, Title} from '@angular/platform-browser'; import {ActivatedRoute, Router} from '@angular/router'; -import {checkParticipant} from '@apollusia/logic'; -import {PollEventState} from "@apollusia/types"; import {ShowResultOptions} from '@apollusia/types/lib/schema/show-result-options'; import {ToastService} from '@mean-stream/ngbx'; import {forkJoin} from 'rxjs'; import {map, switchMap, tap} from 'rxjs/operators'; import {MailService, TokenService} from '../../core/services'; -import {CreateParticipantDto, Participant, ReadPoll, ReadPollEvent, UpdateParticipantDto} from '../../model'; +import {Participant, ReadPoll, ReadPollEvent} from '../../model'; import {PollService} from '../services/poll.service'; +import {PollEventState} from "@apollusia/types"; interface SortMethod { name: string; @@ -32,8 +31,6 @@ export class ChooseEventsComponent implements OnInit { isAdmin: boolean = false; // aux - bookedEvents: boolean[] = []; - bestOption: number = 1; closedReason?: string = undefined; hiddenReason?: string = undefined; showResults = false; @@ -72,18 +69,7 @@ export class ChooseEventsComponent implements OnInit { }, ] satisfies SortMethod[]; - // view state - newParticipant: CreateParticipantDto = { - name: '', - selection: {}, - token: '', - }; - editParticipant?: Participant; - editDto?: UpdateParticipantDto; - errors: string[] = []; - // helpers - readonly ShowResultOptions = ShowResultOptions; id: string = ''; url = globalThis.location?.href; now = Date.now(); @@ -102,7 +88,6 @@ export class ChooseEventsComponent implements OnInit { ) { this.mail = mailService.getMail(); this.token = tokenService.getToken(); - this.newParticipant.token = this.token; } ngOnInit(): void { @@ -116,16 +101,12 @@ export class ChooseEventsComponent implements OnInit { })), this.pollService.getEvents(id).pipe(tap(events => { this.pollEvents = events; - this.bookedEvents = new Array(this.pollEvents.length).fill(false); })), this.pollService.getParticipants(id).pipe(tap(participants => this.participants = participants)), this.pollService.isAdmin(id, this.token), ])), ).subscribe(([poll, events, participants, isAdmin]) => { this.setDescription(poll, events, participants); - this.clearSelection(); - this.validateNew(); - this.bookedEvents = events.map(e => poll.bookedEvents.includes(e._id)); this.isAdmin = isAdmin; this.updateHelpers(); }); @@ -157,49 +138,6 @@ export class ChooseEventsComponent implements OnInit { navigator.clipboard.writeText(this.url).then().catch(e => console.log(e)); } - submit() { - this.pollService.participate(this.id, this.newParticipant).subscribe(participant => { - this.participants.unshift(participant); - this.poll && this.poll.participants++; - this.updateHelpers(); - this.clearSelection(); - }, error => { - this.toastService.error('Submit', 'Failed to submit your participation', error); - }); - } - - setEditParticipant(participant: Participant) { - this.editParticipant = participant; - this.editDto = { - selection: {...participant.selection}, - }; - this.validateEdit(); - } - - cancelEdit() { - this.editParticipant = undefined; - } - - confirmEdit() { - if (!this.editParticipant || !this.editDto) { - return; - } - - this.pollService.editParticipant(this.id, this.editParticipant._id, this.editDto).subscribe(participant => { - this.cancelEdit(); - this.participants = this.participants.map(p => p._id === participant._id ? participant : p); - this.updateHelpers(); - }); - } - - deleteParticipation(participantId: string) { - this.pollService.deleteParticipant(this.id, participantId).subscribe(() => { - this.participants = this.participants.filter(p => p._id !== participantId); - this.poll && this.poll.participants--; - this.updateHelpers(); - }); - } - sort(sortMethod: SortMethod) { if (this.currentSort === sortMethod.name) { this.currentSortDirection *= -1; @@ -210,38 +148,9 @@ export class ChooseEventsComponent implements OnInit { this.participants.sortBy(sortMethod.by, this.currentSortDirection); } - book() { - const events = this.pollEvents.filter((e, i) => this.bookedEvents[i]).map(e => e._id); - this.pollService.book(this.id, events).subscribe(() => { - this.toastService.success('Booking', 'Booked events successfully'); - }); - } - - selectAll(state: PollEventState) { - for (const event of this.pollEvents) { - this.newParticipant.selection[event._id] = this.maxParticipantsReached(event) || this.isPastEvent(event) ? undefined : state; - } - } - - validateNew() { - this.errors = checkParticipant(this.newParticipant, this.poll!, this.participants); - } - - validateEdit() { - this.errors = checkParticipant(this.editDto!, this.poll!, this.participants, this.editParticipant!._id); - } - - // View Helpers - // TODO called from template, bad practice - - isPastEvent(event: ReadPollEvent) { - return Date.parse(event.start) < this.now; - } - // Helpers private updateHelpers() { - this.bestOption = Math.max(...this.pollEvents.map(event => event.participants) || 1); this.updateClosedReason(); this.updateHiddenReason(); } @@ -274,19 +183,6 @@ export class ChooseEventsComponent implements OnInit { } } - private clearSelection() { - this.newParticipant.name = this.poll?.settings?.anonymous ? 'Anonymous' : ''; - this.selectAll('no'); - } - - private maxParticipantsReached(event: ReadPollEvent) { - if (!this.poll?.settings.maxEventParticipants) { - return false; - } - - return event.participants >= this.poll.settings.maxEventParticipants; - } - private userVoted() { return this.participants.some(participant => participant.token === this.token); } diff --git a/apps/frontend/src/app/poll/poll.module.ts b/apps/frontend/src/app/poll/poll.module.ts index 9856fe4b..4ee35e19 100644 --- a/apps/frontend/src/app/poll/poll.module.ts +++ b/apps/frontend/src/app/poll/poll.module.ts @@ -15,6 +15,7 @@ import {EventHeadComponent} from './event-head/event-head.component'; import {MailAlertComponent} from './mail-alert/mail-alert.component'; import {PollRoutingModule} from './poll-routing.module'; import {ChooseDateService} from './services/choose-date.service'; +import {TableComponent} from "./table/table.component"; import {CoreModule} from '../core/core.module'; import {AutofillModalComponent, PostponeModalComponent} from '../modals'; import {SomePipe} from '../pipes'; @@ -30,6 +31,7 @@ import {SomePipe} from '../pipes'; EventHeadComponent, CheckButtonComponent, MailAlertComponent, + TableComponent, ], imports: [ CommonModule, diff --git a/apps/frontend/src/app/poll/services/poll.service.ts b/apps/frontend/src/app/poll/services/poll.service.ts index f1bd6518..fa652d0b 100644 --- a/apps/frontend/src/app/poll/services/poll.service.ts +++ b/apps/frontend/src/app/poll/services/poll.service.ts @@ -1,5 +1,6 @@ import {HttpClient} from '@angular/common/http'; import {Injectable} from '@angular/core'; +import type {PollEventState} from "@apollusia/types"; import {Observable} from 'rxjs'; import {environment} from '../../../environments/environment'; @@ -15,6 +16,24 @@ export class PollService { ) { } + selectAll(poll: ReadPoll, events: ReadPollEvent[], participant: CreateParticipantDto | UpdateParticipantDto, state: PollEventState) { + for (const event of events) { + participant.selection[event._id] = this.maxParticipantsReached(poll, event) || this.isPastEvent(event) ? undefined : state; + } + } + + maxParticipantsReached(poll: ReadPoll, event: ReadPollEvent) { + if (!poll?.settings.maxEventParticipants) { + return false; + } + + return event.participants >= poll.settings.maxEventParticipants; + } + + isPastEvent(event: ReadPollEvent) { + return Date.parse(event.start) < Date.now(); + } + getOwn(active?: boolean): Observable { return this.http.get(`${environment.backendURL}/poll`, { params: active !== undefined ? { diff --git a/apps/frontend/src/app/poll/table/table.component.html b/apps/frontend/src/app/poll/table/table.component.html new file mode 100644 index 00000000..f8f2a11f --- /dev/null +++ b/apps/frontend/src/app/poll/table/table.component.html @@ -0,0 +1,177 @@ +
+ + + + + @for (event of pollEvents; track event) { + + } + + + + + @if (canParticipate) { + + + @for (event of pollEvents; track event) { + + } + + + } + @if (showResults) { + @for (participant of participants; track participant) { + + + @if (participant !== editParticipant) { + @for (pollEvent of pollEvents; track pollEvent; let n = $index) { + + } + } + @if (participant === editParticipant && editDto) { + @for (event of pollEvents; track event) { + + } + } + @if (participant === editParticipant) { + + } + @if (!editParticipant) { + + } + + } + @if (poll.settings.showResult !== ShowResultOptions.NEVER || isAdmin) { + + + @for (pollEvent of pollEvents; track pollEvent) { + + } + + + } + } + @if (poll && isAdmin) { + + + @for (pollEvent of pollEvents; track pollEvent; let i = $index) { + + } + + + } + +
+ +
+ + + + +
+ +
+
+ {{ poll.settings.anonymous ? 'Anonymous' : participant.name }} + @if (participant.createdAt) { + + {{ participant.createdAt | date: 'shortTime' }} + + } + @if (participant.updatedAt !== participant.createdAt) { + + • edited + + } + + @switch (participant.selection[pollEvent._id]) { + @case ('yes') { +
+ } + @case ('no') { +
+ } + @case ('maybe') { +
+ } + @default { +
+ } + } +
+ + + +
+ +
+
+ @if (poll.settings.allowEdit && token === participant.token) { + + } + @if (isAdmin || token === participant.token) { + + } +
Sum + @if (bestOption === pollEvent.participants) { +
+ {{ pollEvent.participants }} +
+ } + @if (bestOption !== pollEvent.participants) { +
+ {{ pollEvent.participants }} +
+ } +
Select Events + + + +
+
diff --git a/apps/frontend/src/app/poll/table/table.component.scss b/apps/frontend/src/app/poll/table/table.component.scss new file mode 100644 index 00000000..c946d559 --- /dev/null +++ b/apps/frontend/src/app/poll/table/table.component.scss @@ -0,0 +1,30 @@ +.table-responsive td, .table-responsive th { + min-width: 100px; +} + +.form-check-hidden { + appearance: none; + cursor: pointer; +} + +@media (min-width: 768px) { + th:first-child, th:last-child, + td:first-child, td:last-child { + position: sticky; + backdrop-filter: blur(5px); + } + + th:first-child, td:first-child { + background: linear-gradient(90deg, var(--bs-body-bg), rgba(var(--bs-body-bg), 0.75)); + left: 0; + } + + th:last-child, td:last-child { + background: linear-gradient(90deg, rgba(var(--bs-body-bg), 0.75), var(--bs-body-bg)); + right: 0; + } +} + +h5 { + margin: 0; +} diff --git a/apps/frontend/src/app/poll/table/table.component.ts b/apps/frontend/src/app/poll/table/table.component.ts new file mode 100644 index 00000000..9a56a870 --- /dev/null +++ b/apps/frontend/src/app/poll/table/table.component.ts @@ -0,0 +1,119 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {checkParticipant} from "@apollusia/logic"; +import {ShowResultOptions} from "@apollusia/types/lib/schema/show-result-options"; +import {ToastService} from "@mean-stream/ngbx"; + +import {CreateParticipantDto, Participant, ReadPoll, ReadPollEvent, UpdateParticipantDto} from "../../model"; +import {PollService} from "../services/poll.service"; + +@Component({ + selector: 'apollusia-table', + templateUrl: './table.component.html', + styleUrl: './table.component.scss', +}) +export class TableComponent implements OnInit { + protected readonly ShowResultOptions = ShowResultOptions; + + @Input() poll: ReadPoll; + @Input() pollEvents: ReadPollEvent[] = []; + @Input() participants: Participant[] = []; + @Input() isAdmin: boolean = false; + @Input() canParticipate: boolean = false; + @Input() showResults: boolean = false; + @Input() token: string; + + @Output() changed = new EventEmitter(); + + bookedEvents: boolean[] = []; + bestOption: number = 1; + + newParticipant: CreateParticipantDto = { + name: '', + selection: {}, + token: '', + }; + editParticipant?: Participant; + editDto?: UpdateParticipantDto; + errors: string[] = []; + + constructor( + protected pollService: PollService, + private toastService: ToastService, + ) { + } + + ngOnInit() { + this.bookedEvents = this.pollEvents.map(e => this.poll.bookedEvents.includes(e._id)); + this.newParticipant.token = this.token; + this.clearSelection(); + this.validateNew(); + } + + submit() { + this.pollService.participate(this.poll!._id, this.newParticipant).subscribe(participant => { + this.participants.unshift(participant); + this.poll && this.poll.participants++; + this.updateHelpers(); + this.clearSelection(); + }, error => { + this.toastService.error('Submit', 'Failed to submit your participation', error); + }); + } + + setEditParticipant(participant: Participant) { + this.editParticipant = participant; + this.editDto = { + selection: {...participant.selection}, + }; + this.validateEdit(); + } + + cancelEdit() { + this.editParticipant = undefined; + } + + confirmEdit() { + if (!this.editParticipant || !this.editDto) { + return; + } + + this.pollService.editParticipant(this.poll!._id, this.editParticipant._id, this.editDto).subscribe(participant => { + this.cancelEdit(); + this.participants = this.participants.map(p => p._id === participant._id ? participant : p); + this.updateHelpers(); + }); + } + + deleteParticipation(participantId: string) { + this.pollService.deleteParticipant(this.poll!._id, participantId).subscribe(() => { + this.participants = this.participants.filter(p => p._id !== participantId); + this.poll && this.poll.participants--; + this.updateHelpers(); + }); + } + + validateNew() { + this.errors = checkParticipant(this.newParticipant, this.poll!, this.participants); + } + + validateEdit() { + this.errors = checkParticipant(this.editDto!, this.poll!, this.participants, this.editParticipant!._id); + } + + clearSelection() { + this.newParticipant.name = this.poll?.settings?.anonymous ? 'Anonymous' : ''; + this.pollService.selectAll(this.poll!, this.pollEvents, this.newParticipant, 'no'); + } + + book() { + const events = this.pollEvents.filter((e, i) => this.bookedEvents[i]).map(e => e._id); + this.pollService.book(this.poll!._id, events).subscribe(() => { + this.toastService.success('Booking', 'Booked events successfully'); + }); + } + + private updateHelpers() { + this.bestOption = Math.max(...this.pollEvents.map(event => event.participants) || 1); + this.changed.next(); + } +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 7ba19048..11580f31 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -17,7 +17,7 @@ "paths": { "@apollusia/logic": ["libs/types/src/lib/logic/index.ts"], "@apollusia/types": ["libs/types/src/index.ts"], - "@apollusia/types/*": ["libs/types/src/*"], + "@apollusia/types/*": ["libs/types/src/*"] } }, "exclude": ["node_modules", "tmp"] From 716e222043bd542c1c60faa775948b99cf525f51 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 13:01:19 +0100 Subject: [PATCH 03/23] refactor(frontend): Poll is not optional in TableComponent --- .../src/app/poll/table/table.component.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/frontend/src/app/poll/table/table.component.ts b/apps/frontend/src/app/poll/table/table.component.ts index 9a56a870..9c24a674 100644 --- a/apps/frontend/src/app/poll/table/table.component.ts +++ b/apps/frontend/src/app/poll/table/table.component.ts @@ -50,9 +50,9 @@ export class TableComponent implements OnInit { } submit() { - this.pollService.participate(this.poll!._id, this.newParticipant).subscribe(participant => { + this.pollService.participate(this.poll._id, this.newParticipant).subscribe(participant => { this.participants.unshift(participant); - this.poll && this.poll.participants++; + this.poll.participants++; this.updateHelpers(); this.clearSelection(); }, error => { @@ -77,7 +77,7 @@ export class TableComponent implements OnInit { return; } - this.pollService.editParticipant(this.poll!._id, this.editParticipant._id, this.editDto).subscribe(participant => { + this.pollService.editParticipant(this.poll._id, this.editParticipant._id, this.editDto).subscribe(participant => { this.cancelEdit(); this.participants = this.participants.map(p => p._id === participant._id ? participant : p); this.updateHelpers(); @@ -85,29 +85,29 @@ export class TableComponent implements OnInit { } deleteParticipation(participantId: string) { - this.pollService.deleteParticipant(this.poll!._id, participantId).subscribe(() => { + this.pollService.deleteParticipant(this.poll._id, participantId).subscribe(() => { this.participants = this.participants.filter(p => p._id !== participantId); - this.poll && this.poll.participants--; + this.poll.participants--; this.updateHelpers(); }); } validateNew() { - this.errors = checkParticipant(this.newParticipant, this.poll!, this.participants); + this.errors = checkParticipant(this.newParticipant, this.poll, this.participants); } validateEdit() { - this.errors = checkParticipant(this.editDto!, this.poll!, this.participants, this.editParticipant!._id); + this.errors = checkParticipant(this.editDto!, this.poll, this.participants, this.editParticipant!._id); } clearSelection() { - this.newParticipant.name = this.poll?.settings?.anonymous ? 'Anonymous' : ''; - this.pollService.selectAll(this.poll!, this.pollEvents, this.newParticipant, 'no'); + this.newParticipant.name = this.poll.settings.anonymous ? 'Anonymous' : ''; + this.pollService.selectAll(this.poll, this.pollEvents, this.newParticipant, 'no'); } book() { const events = this.pollEvents.filter((e, i) => this.bookedEvents[i]).map(e => e._id); - this.pollService.book(this.poll!._id, events).subscribe(() => { + this.pollService.book(this.poll._id, events).subscribe(() => { this.toastService.success('Booking', 'Booked events successfully'); }); } From 2f9229c5ae60c2cb097f396342883a3baf6cdf3c Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 13:09:47 +0100 Subject: [PATCH 04/23] fix(frontend): ChooseEventsComponent initialization --- .../choose-events.component.html | 2 +- .../choose-events/choose-events.component.ts | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.html b/apps/frontend/src/app/poll/choose-events/choose-events.component.html index 88f65113..22e8f859 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.html +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.html @@ -69,7 +69,7 @@

- @if (poll && pollEvents) { + @if (poll && pollEvents && participants) { this.pollEvents.findIndex(e => p.selection[e._id] === 'yes' || p.selection[e._id] === 'maybe'), + by: p => this.pollEvents?.findIndex(e => p.selection[e._id] === 'yes' || p.selection[e._id] === 'maybe'), }, ] satisfies SortMethod[]; @@ -98,21 +98,18 @@ export class ChooseEventsComponent implements OnInit { this.poll = poll; this.title.setTitle(`${poll.title} - Apollusia`); this.meta.updateTag({property: 'og:title', content: poll.title}); + this.setDescription(poll); })), - this.pollService.getEvents(id).pipe(tap(events => { - this.pollEvents = events; - })), + this.pollService.getEvents(id).pipe(tap(events => this.pollEvents = events)), this.pollService.getParticipants(id).pipe(tap(participants => this.participants = participants)), - this.pollService.isAdmin(id, this.token), + this.pollService.isAdmin(id, this.token).pipe(tap(isAdmin => this.isAdmin = isAdmin)), ])), - ).subscribe(([poll, events, participants, isAdmin]) => { - this.setDescription(poll, events, participants); - this.isAdmin = isAdmin; + ).subscribe(() => { this.updateHelpers(); }); } - private setDescription(poll: ReadPoll, events: ReadPollEvent[], participants: Participant[]) { + private setDescription(poll: ReadPoll) { let description = ''; if (poll.description) { description += poll.description + '\n\n'; @@ -127,7 +124,7 @@ export class ChooseEventsComponent implements OnInit { const timeZoneStr = timeZone ? ` (${timeZone})` : ''; description += `📅 Deadline: ${deadline}${timeZoneStr}\n`; } - description += `✅ ${events.length} Option${events.length !== 1 ? 's' : ''} - 👤 ${participants.length} Participant${participants.length !== 1 ? 's' : ''}`; + description += `✅ ${poll.events} Option${poll.events !== 1 ? 's' : ''} - 👤 ${poll.participants} Participant${poll.participants !== 1 ? 's' : ''}`; this.meta.updateTag({name: 'description', content: description}); this.meta.updateTag({property: 'og:description', content: description}); } @@ -145,7 +142,7 @@ export class ChooseEventsComponent implements OnInit { this.currentSort = sortMethod.name; this.currentSortDirection = sortMethod.defaultDirection; } - this.participants.sortBy(sortMethod.by, this.currentSortDirection); + this.participants?.sortBy(sortMethod.by, this.currentSortDirection); } // Helpers @@ -183,7 +180,7 @@ export class ChooseEventsComponent implements OnInit { } } - private userVoted() { - return this.participants.some(participant => participant.token === this.token); + private userVoted(): boolean { + return this.participants?.some(participant => participant.token === this.token) ?? false; } } From dadf28c05df3e776f2a8ac1c2c68a61e2e4ce634 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 13:17:01 +0100 Subject: [PATCH 05/23] refactor(frontend): Remove unused ChooseEventsComponent variables --- .../src/app/poll/choose-events/choose-events.component.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts index deee7631..56a93a22 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts @@ -70,9 +70,7 @@ export class ChooseEventsComponent implements OnInit { ] satisfies SortMethod[]; // helpers - id: string = ''; url = globalThis.location?.href; - now = Date.now(); mail: string | undefined; token: string; @@ -92,8 +90,7 @@ export class ChooseEventsComponent implements OnInit { ngOnInit(): void { this.route.params.pipe( - map(({id}) => this.id = id), - switchMap(id => forkJoin([ + switchMap(({id}) => forkJoin([ this.pollService.get(id).pipe(tap((poll) => { this.poll = poll; this.title.setTitle(`${poll.title} - Apollusia`); From 35b1e4118b81adac1d98bf6fc8fed4cce44c1dd8 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 13:19:06 +0100 Subject: [PATCH 06/23] refactor(frontend): Remove ChooseEventsComponent.showResults --- .../poll/choose-events/choose-events.component.html | 2 +- .../app/poll/choose-events/choose-events.component.ts | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.html b/apps/frontend/src/app/poll/choose-events/choose-events.component.html index 22e8f859..6a5570f5 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.html +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.html @@ -76,7 +76,7 @@

[participants]="participants" [isAdmin]="isAdmin" [canParticipate]="!closedReason" - [showResults]="showResults" + [showResults]="!hiddenReason" [token]="token" > } diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts index 56a93a22..856a0d6d 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts @@ -30,10 +30,8 @@ export class ChooseEventsComponent implements OnInit { participants?: Participant[]; isAdmin: boolean = false; - // aux - closedReason?: string = undefined; - hiddenReason?: string = undefined; - showResults = false; + closedReason?: string; + hiddenReason?: string; currentSort = 'Created'; currentSortDirection: 1 | -1 = 1; @@ -165,15 +163,12 @@ export class ChooseEventsComponent implements OnInit { switch (this.poll?.settings.showResult) { case ShowResultOptions.NEVER: this.hiddenReason = this.isAdmin ? undefined : 'The results of this poll are hidden. You will only be able to see your own votes.'; - this.showResults = true; break; case ShowResultOptions.AFTER_PARTICIPATING: - this.showResults = this.isAdmin || this.userVoted(); - this.hiddenReason = this.showResults ? undefined : 'This is a blind poll. You can\'t see results or other user\'s votes until you participate yourself.'; + this.hiddenReason = this.isAdmin || this.userVoted() ? undefined : 'This is a blind poll. You can\'t see results or other user\'s votes until you participate yourself.'; break; default: this.hiddenReason = undefined; - this.showResults = true; } } From cc2df4e0363724d6e6c85b6ac12da660b427a8b3 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 13:24:38 +0100 Subject: [PATCH 07/23] fix(frontend): TableComponent best option --- .../src/app/poll/table/table.component.html | 13 +++---------- apps/frontend/src/app/poll/table/table.component.ts | 10 +++++----- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/apps/frontend/src/app/poll/table/table.component.html b/apps/frontend/src/app/poll/table/table.component.html index f8f2a11f..69af8995 100644 --- a/apps/frontend/src/app/poll/table/table.component.html +++ b/apps/frontend/src/app/poll/table/table.component.html @@ -132,16 +132,9 @@
Sum @for (pollEvent of pollEvents; track pollEvent) { - @if (bestOption === pollEvent.participants) { -
- {{ pollEvent.participants }} -
- } - @if (bestOption !== pollEvent.participants) { -
- {{ pollEvent.participants }} -
- } +
+ {{ pollEvent.participants }} +
} diff --git a/apps/frontend/src/app/poll/table/table.component.ts b/apps/frontend/src/app/poll/table/table.component.ts index 9c24a674..d08aebd1 100644 --- a/apps/frontend/src/app/poll/table/table.component.ts +++ b/apps/frontend/src/app/poll/table/table.component.ts @@ -45,6 +45,7 @@ export class TableComponent implements OnInit { ngOnInit() { this.bookedEvents = this.pollEvents.map(e => this.poll.bookedEvents.includes(e._id)); this.newParticipant.token = this.token; + this.bestOption = Math.max(...this.pollEvents.map(event => event.participants) || 1); // TODO update when participants change this.clearSelection(); this.validateNew(); } @@ -53,7 +54,7 @@ export class TableComponent implements OnInit { this.pollService.participate(this.poll._id, this.newParticipant).subscribe(participant => { this.participants.unshift(participant); this.poll.participants++; - this.updateHelpers(); + this.onChange(); this.clearSelection(); }, error => { this.toastService.error('Submit', 'Failed to submit your participation', error); @@ -80,7 +81,7 @@ export class TableComponent implements OnInit { this.pollService.editParticipant(this.poll._id, this.editParticipant._id, this.editDto).subscribe(participant => { this.cancelEdit(); this.participants = this.participants.map(p => p._id === participant._id ? participant : p); - this.updateHelpers(); + this.onChange(); }); } @@ -88,7 +89,7 @@ export class TableComponent implements OnInit { this.pollService.deleteParticipant(this.poll._id, participantId).subscribe(() => { this.participants = this.participants.filter(p => p._id !== participantId); this.poll.participants--; - this.updateHelpers(); + this.onChange(); }); } @@ -112,8 +113,7 @@ export class TableComponent implements OnInit { }); } - private updateHelpers() { - this.bestOption = Math.max(...this.pollEvents.map(event => event.participants) || 1); + private onChange() { this.changed.next(); } } From 9f22f71fea5faeed9b179c2825185f26accee4ee Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 13:27:36 +0100 Subject: [PATCH 08/23] refactor(frontend): Remove unnecessary "Sum" row condition --- .../src/app/poll/table/table.component.html | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/frontend/src/app/poll/table/table.component.html b/apps/frontend/src/app/poll/table/table.component.html index 69af8995..c9e2d406 100644 --- a/apps/frontend/src/app/poll/table/table.component.html +++ b/apps/frontend/src/app/poll/table/table.component.html @@ -127,19 +127,17 @@
} } - @if (poll.settings.showResult !== ShowResultOptions.NEVER || isAdmin) { - - Sum - @for (pollEvent of pollEvents; track pollEvent) { - -
- {{ pollEvent.participants }} -
- - } - - - } + + Sum + @for (pollEvent of pollEvents; track pollEvent) { + +
+ {{ pollEvent.participants }} +
+ + } + + } @if (poll && isAdmin) { From de7bac4f8b9ba5cf5bd2cb2da75518be44ec1e44 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 13:30:49 +0100 Subject: [PATCH 09/23] refactor(frontend): Better @if/@else for edit/non-edit rows --- .../src/app/poll/table/table.component.html | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/apps/frontend/src/app/poll/table/table.component.html b/apps/frontend/src/app/poll/table/table.component.html index c9e2d406..97a87c47 100644 --- a/apps/frontend/src/app/poll/table/table.component.html +++ b/apps/frontend/src/app/poll/table/table.component.html @@ -82,29 +82,6 @@
} } - } - @if (participant === editParticipant && editDto) { - @for (event of pollEvents; track event) { - - - - } - } - @if (participant === editParticipant) { - - -
- -
- - } - @if (!editParticipant) { @if (poll.settings.allowEdit && token === participant.token) {

} + } @else { + @if (editDto) { + @for (event of pollEvents; track event) { + + + + } + } + + +
+ +
+ } } From 473cabb6dd3ac666b87d9b50efec0cdc78039847 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 13:34:03 +0100 Subject: [PATCH 10/23] refactor(frontend): Simplify TableComponent a bit --- apps/frontend/src/app/poll/table/table.component.html | 4 ++-- apps/frontend/src/app/poll/table/table.component.ts | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/frontend/src/app/poll/table/table.component.html b/apps/frontend/src/app/poll/table/table.component.html index 97a87c47..3be9dc14 100644 --- a/apps/frontend/src/app/poll/table/table.component.html +++ b/apps/frontend/src/app/poll/table/table.component.html @@ -39,7 +39,7 @@ } -
+
@@ -116,7 +116,7 @@
} -
+
diff --git a/apps/frontend/src/app/poll/table/table.component.ts b/apps/frontend/src/app/poll/table/table.component.ts index d08aebd1..6790198b 100644 --- a/apps/frontend/src/app/poll/table/table.component.ts +++ b/apps/frontend/src/app/poll/table/table.component.ts @@ -1,6 +1,5 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {checkParticipant} from "@apollusia/logic"; -import {ShowResultOptions} from "@apollusia/types/lib/schema/show-result-options"; import {ToastService} from "@mean-stream/ngbx"; import {CreateParticipantDto, Participant, ReadPoll, ReadPollEvent, UpdateParticipantDto} from "../../model"; @@ -12,8 +11,6 @@ import {PollService} from "../services/poll.service"; styleUrl: './table.component.scss', }) export class TableComponent implements OnInit { - protected readonly ShowResultOptions = ShowResultOptions; - @Input() poll: ReadPoll; @Input() pollEvents: ReadPollEvent[] = []; @Input() participants: Participant[] = []; From 7e30c92c1f5ca028bbb98bc0fa495c4e32dd4f06 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 15:30:39 +0100 Subject: [PATCH 11/23] style(frontend): Fix lint problems --- .../src/app/poll/choose-events/choose-events.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts index 856a0d6d..05176f2e 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts @@ -4,12 +4,11 @@ import {ActivatedRoute, Router} from '@angular/router'; import {ShowResultOptions} from '@apollusia/types/lib/schema/show-result-options'; import {ToastService} from '@mean-stream/ngbx'; import {forkJoin} from 'rxjs'; -import {map, switchMap, tap} from 'rxjs/operators'; +import {switchMap, tap} from 'rxjs/operators'; import {MailService, TokenService} from '../../core/services'; import {Participant, ReadPoll, ReadPollEvent} from '../../model'; import {PollService} from '../services/poll.service'; -import {PollEventState} from "@apollusia/types"; interface SortMethod { name: string; From f4e9c7d3efa2dbfc455eec2ae606844b51bbe411 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 15:46:11 +0100 Subject: [PATCH 12/23] feat(frontend): Button to switch view (only one for now) --- .../choose-events.component.html | 31 +++++++++++++------ .../choose-events/choose-events.component.ts | 3 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.html b/apps/frontend/src/app/poll/choose-events/choose-events.component.html index 6a5570f5..1228b4d5 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.html +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.html @@ -47,6 +47,15 @@

}
+
+ @for (view of ['table']; track view) { + + + } +
@@ -70,14 +79,18 @@

@if (poll && pollEvents && participants) { - + @switch (view$ | async) { + @case ("table") { + + } + } }
diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts index 05176f2e..575187f6 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.ts +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.ts @@ -4,7 +4,7 @@ import {ActivatedRoute, Router} from '@angular/router'; import {ShowResultOptions} from '@apollusia/types/lib/schema/show-result-options'; import {ToastService} from '@mean-stream/ngbx'; import {forkJoin} from 'rxjs'; -import {switchMap, tap} from 'rxjs/operators'; +import {map, switchMap, tap} from 'rxjs/operators'; import {MailService, TokenService} from '../../core/services'; import {Participant, ReadPoll, ReadPollEvent} from '../../model'; @@ -70,6 +70,7 @@ export class ChooseEventsComponent implements OnInit { url = globalThis.location?.href; mail: string | undefined; token: string; + readonly view$ = this.route.queryParams.pipe(map(({view}) => view ?? 'table')); constructor( public route: ActivatedRoute, From 70605b897cb023e11ac3fdb5b028005cc77e1e05 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Sun, 25 Feb 2024 15:47:40 +0100 Subject: [PATCH 13/23] fix(frontend): Better button toolbar --- .../app/poll/choose-events/choose-events.component.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/app/poll/choose-events/choose-events.component.html b/apps/frontend/src/app/poll/choose-events/choose-events.component.html index 1228b4d5..01bf24a2 100644 --- a/apps/frontend/src/app/poll/choose-events/choose-events.component.html +++ b/apps/frontend/src/app/poll/choose-events/choose-events.component.html @@ -46,8 +46,8 @@

You can select up to {{ limit }} choices. } -
-
+