Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

156 as admin be able to addremove a player tofrom a timeslot #157

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -186,21 +186,63 @@ <h2 class="m-0 grow">
</p-accordion>
}

<h2 class="m-0 mt-4">
{{ translations.events_timeslot_registeredPlayers() }} ({{ timeslot.playerIds.length }})
</h2>
<div class="mt-4 flex flex-row items-center gap-2">
<h2 class="m-0 grow">
{{ translations.events_timeslot_registeredPlayers() }}
</h2>
<span
class="inline-block rounded-full bg-primary px-2 text-center font-bold text-primary-text"
>
<span class="i-[mdi--account]"></span>{{ timeslot.playerIds.length }}
</span>
@if (!event.startedAt) {
<p-button
[label]="translations.shared_add()"
icon="i-[mdi--plus]"
[disabled]="isAddPreconfigBusy()"
(onClick)="addPlayerPanel.show($event)"
/>
<p-overlayPanel #addPlayerPanel class="hidden" styleClass="p-0">
<ng-template pTemplate="content" styleClass="p-0">
<p-listbox
styleClass="border-none"
[options]="availablePlayers()"
optionLabel="alias"
optionValue="id"
[filter]="true"
(onChange)="addPlayer($event.value); addPlayerPanel.hide()"
>
<ng-template let-user pTemplate="listItem">
<app-user-item [user]="user" />
</ng-template>
</p-listbox>
</ng-template>
</p-overlayPanel>
}
</div>

<div class="rounded-lg border-[1px] border-solid border-surface-d bg-surface-a">
@for (player of players(); track player.id; let index = $index) {
<div
class="min-w-0 truncate border-0 border-solid border-surface-d px-4 py-2 leading-8 hover:bg-surface-c"
class="flex min-w-0 flex-row items-center gap-2 border-0 border-solid border-surface-d p-2 pl-4"
[class.border-t-[1px]]="index > 0"
>
@if (player.alias) {
{{ player.alias }}
} @else {
&lt;{{ translations.events_timeslot_unknownPlayer() }}&gt;
<span class="opacity-50">({{ player.id }})</span>
}
<div class="min-w-0 grow">
@if (player.alias) {
{{ player.alias }}
} @else {
&lt;{{ translations.events_timeslot_unknownPlayer() }}&gt;
<span class="opacity-50">({{ player.id }})</span>
}
</div>
<p-button
icon="i-[mdi--delete]"
[rounded]="true"
[text]="true"
size="small"
severity="danger"
(onClick)="removeUser(player)"
/>
</div>
}
@if (timeslot.playerIds.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import { Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { AccordionModule } from 'primeng/accordion';
import { ConfirmationService } from 'primeng/api';
import { ConfirmationService, MessageService } from 'primeng/api';
import { ButtonModule } from 'primeng/button';
import { CardModule } from 'primeng/card';
import { DropdownModule } from 'primeng/dropdown';
import { ListboxModule } from 'primeng/listbox';
import { MessagesModule } from 'primeng/messages';
import { OverlayPanelModule } from 'primeng/overlaypanel';
import { ProgressSpinnerModule } from 'primeng/progressspinner';
import { TooltipModule } from 'primeng/tooltip';
import { map } from 'rxjs';
Expand All @@ -35,17 +37,27 @@
selectUsersActionState,
userSelectors,
} from '../../../+state/users';
import { EventInstancePreconfiguration, User } from '../../../models/parsed-models';
import { EventsService } from '../../../api/services';
import {
EventInstance,
EventInstancePreconfiguration,
EventTimeslot,
User,
} from '../../../models/parsed-models';
import { TranslateService } from '../../../services/translate.service';
import { ifTruthy, isNullish } from '../../../utils/common.utils';
import { ifTruthy, isNullish, notNullish } from '../../../utils/common.utils';
import { dateWithTime, timeToString } from '../../../utils/date.utils';
import { errorToastEffect, selectSignal } from '../../../utils/ngrx.utils';
import { UserItemComponent } from '../../users/user-item/user-item.component';
import { EventTimeslotDialogComponent } from '../event-timeslot-dialog/event-timeslot-dialog.component';

function asString(value: unknown): string | null {
return typeof value === 'string' ? value : null;
}

type EventInstances = { timeslot: EventTimeslot; instances: EventInstance[] }[];

Check warning on line 58 in src/client/src/app/components/events/event-timeslot/event-timeslot.component.ts

View workflow job for this annotation

GitHub Actions / Build

'EventInstances' is defined but never used. Allowed unused vars must match /^_/u
AnSch1510 marked this conversation as resolved.
Show resolved Hide resolved
type EventPlayer = Partial<User> & { id: string };

@Component({
selector: 'app-event-timeslot',
standalone: true,
Expand All @@ -57,9 +69,12 @@
DropdownModule,
EventTimeslotDialogComponent,
FormsModule,
ListboxModule,
MessagesModule,
OverlayPanelModule,
ProgressSpinnerModule,
TooltipModule,
UserItemComponent,
],
templateUrl: './event-timeslot.component.html',
styleUrl: './event-timeslot.component.scss',
Expand All @@ -71,6 +86,8 @@
private readonly _activatedRoute = inject(ActivatedRoute);
private readonly _translateService = inject(TranslateService);
private readonly _confirmationService = inject(ConfirmationService);
private readonly _eventService = inject(EventsService);
private readonly _messageService = inject(MessageService);

protected readonly translations = this._translateService.translations;
protected readonly locale = this._translateService.language;
Expand Down Expand Up @@ -110,11 +127,16 @@
this.timeslot(),
timeslot =>
timeslot.playerIds
.map<Partial<User> & { id: string }>(x => this.allUsers()[x] ?? { id: x })
.map<EventPlayer>(x => this.allUsers()[x] ?? { id: x })
.sort((a, b) => (a?.alias ?? '').localeCompare(b?.alias ?? '')),
[]
)
);
protected readonly availablePlayers = computed(() =>
Object.values(this.allUsers())
.filter(notNullish)
.filter(u => !this.players().some(p => p.id === u.id))
);
protected readonly preconfigPlayerOptions = computed(() =>
this.players().filter(
x => !this.timeslot()?.preconfigurations.some(p => p.playerIds.includes(x.id))
Expand Down Expand Up @@ -168,6 +190,67 @@
}
}

protected async addPlayer(userId: string) {
const eventId = this.eventId();
const timeslotId = this.timeslotId();

if (isNullish(eventId) || isNullish(timeslotId)) return;

const response = await this._eventService.patchPlayerEventRegistrations({
eventId,
body: { userId: userId, timeslotId: timeslotId, isRegistered: true },
});
if (response.ok) {
this._messageService.add({
severity: 'success',
summary: this.translations.events_timeslot_playerAdded(),
life: 2000,
});
} else {
this._messageService.add({
severity: 'error',
summary: this.translations.events_timeslot_error_playerAdded(),
life: 2000,
});
}
}

protected async removeUser(user: EventPlayer) {
const eventId = this.eventId();
const timeslotId = this.timeslotId();

if (isNullish(eventId) || isNullish(timeslotId)) return;

this._confirmationService.confirm({
header: this.translations.events_timeslot_playerRemoveDialog_header(user),
message: this.translations.events_timeslot_playerRemoveDialog_message(),
acceptLabel: this.translations.shared_delete(),
acceptButtonStyleClass: 'p-button-danger',
acceptIcon: 'p-button-icon-left i-[mdi--delete]',
rejectLabel: this.translations.shared_cancel(),
rejectButtonStyleClass: 'p-button-text',
accept: async () => {
const response = await this._eventService.patchPlayerEventRegistrations({
eventId,
body: { userId: user.id, timeslotId: timeslotId, isRegistered: false },
});
if (response.ok) {
this._messageService.add({
severity: 'success',
summary: this.translations.events_timeslot_playerRemoveDialog_playerRemoved(),
life: 2000,
});
} else {
this._messageService.add({
severity: 'error',
summary: this.translations.events_timeslot_playerRemoveDialog_error_playerRemoved(),
life: 2000,
});
}
},
});
}

protected addPreconfig() {
const eventId = this.eventId();
const timeslotId = this.timeslotId();
Expand Down
14 changes: 13 additions & 1 deletion src/client/src/app/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,19 @@
"time": "Startzeit",
"mapToPlay": "Zu spielende Bahn",
"map": "Bahn",
"isFallbackAllowed": "Spieler können einen Ausweichzeitslot festlegen"
"isFallbackAllowed": "Spieler können einen Ausweichzeitslot festlegen",
"playerAdded": "Spieler wurde diesem Zeitslot hinzugefügt",
"playerRemoveDialog": {
"header": "Spieler \"{{alias}}\" entfernen?",
"message": "Möchtest du den Spieler wirklich aus dem Zeitslot entfernen?",
"playerRemoved": "Spieler wurde aus diesem Zeitslot entfernt",
"error": {
"playerRemoved": "Spieler wurde aus diesem Zeitslot nicht entfernt"
}
},
"error": {
"playerAdded": "Spieler wurde diesem Zeitslot nicht hinzugefügt"
}
},
"createDialog": {
"title": "Veranstaltung erstellen",
Expand Down
14 changes: 13 additions & 1 deletion src/client/src/app/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,19 @@
"time": "Start time",
"mapToPlay": "Map to be played",
"map": "Map",
"isFallbackAllowed": "Players can define a fallback timeslot"
"isFallbackAllowed": "Players can define a fallback timeslot",
"playerAdded": "Player has been added to this timeslot",
"playerRemoveDialog": {
"header": "Remove Player \"{{alias}}\"",
"message": "Do you really want to remove the player from this timeslot?",
"playerRemoved": "Player has been removed from this timeslot",
"error": {
"playerRemoved": "Player has not been removed from this timeslot"
}
},
"error": {
"playerAdded": "Player has not been added to this timeslot"
}
},
"createDialog": {
"title": "Create event",
Expand Down
Loading
Loading