Skip to content

Commit

Permalink
feat: add server endpoint for editing event instances
Browse files Browse the repository at this point in the history
Refs: #24
  • Loading branch information
MaSch0212 committed Jul 27, 2024
1 parent 8550f8e commit 7c2fb94
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { inject } from '@angular/core';
import { on } from '@ngrx/store';
import { produce, castDraft } from 'immer';
import { switchMap } from 'rxjs';

import { ApiEventTimeslotInstances } from '../../../api/models';
import { EventAdministrationService } from '../../../api/services';
import { createHttpAction, handleHttpAction, onHttpAction, toHttpAction } from '../../action-state';
import { createFunctionalEffect } from '../../functional-effect';
import { Effects, Reducers } from '../../utils';
import { EVENTS_ACTION_SCOPE } from '../consts';
import { eventEntityAdapter, EventsFeatureState } from '../events.state';

export const setEventInstancesAction = createHttpAction<{
eventId: string;
instances: ApiEventTimeslotInstances[];
}>()(EVENTS_ACTION_SCOPE, 'Set Event Instances');

export const setEventInstancesReducers: Reducers<EventsFeatureState> = [
on(setEventInstancesAction.success, (state, { props }) =>
eventEntityAdapter.mapOne(
{
id: props.eventId,
map: produce(draft => {
for (const timeslot of draft.timeslots) {
timeslot.instances = castDraft(
props.instances.find(x => x.timeslotId === timeslot.id)?.instances || []
);
}
}),
},
state
)
),
handleHttpAction('setInstances', setEventInstancesAction),
];

export const setEventInstancesEffects: Effects = {
setEventInstances$: createFunctionalEffect.dispatching(
(api = inject(EventAdministrationService)) =>
onHttpAction(setEventInstancesAction).pipe(
switchMap(({ props }) =>
toHttpAction(setEventInstances(api, props), setEventInstancesAction, props)
)
)
),
};

async function setEventInstances(
api: EventAdministrationService,
props: ReturnType<typeof setEventInstancesAction>['props']
) {
const response = await api.putEventInstances({
eventId: props.eventId,
body: { instances: props.instances },
});
return response.ok
? setEventInstancesAction.success(props, undefined)
: setEventInstancesAction.error(props, response);
}
7 changes: 4 additions & 3 deletions src/client/src/app/+state/events/events.actions.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
export { addEventPreconfigAction } from './actions/add-event-preconfig.action';
export { addEventTimeslotAction } from './actions/add-event-timeslot.action';
export { addEventAction } from './actions/add-event.action';
export { updateEventAction } from './actions/update-event.action';
export { eventTimeslotRegistrationChangedAction } from './actions/event-timeslot-registration-changed.action';
export { addPlayerToEventPreconfigurationAction } from './actions/add-player-to-preconfig.action';
export { buildEventInstancesAction } from './actions/build-event-instances.action';
export { eventTimeslotRegistrationChangedAction } from './actions/event-timeslot-registration-changed.action';
export { loadEventAction } from './actions/load-event.action';
export { loadEventsAction } from './actions/load-events.action';
export { removeEventAction } from './actions/remove-event.action';
export { removeEventPreconfigAction } from './actions/remove-event-preconfig.action';
export { removeEventTimeslotAction } from './actions/remove-event-timeslot.action';
export { removeEventAction } from './actions/remove-event.action';
export { removePlayerFromPreconfigAction } from './actions/remove-player-from-preconfig.action';
export { resetEventsActionStateAction } from './actions/reset-events-action-state.action';
export { setEventInstancesAction } from './actions/set-event-instances.action';
export { startEventAction } from './actions/start-event.action';
export { updateEventTimeslotAction } from './actions/update-event-timeslot.action';
export { updateEventAction } from './actions/update-event.action';
2 changes: 2 additions & 0 deletions src/client/src/app/+state/events/events.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { removeEventPreconfigEffects } from './actions/remove-event-preconfig.ac
import { removeEventTimeslotEffects } from './actions/remove-event-timeslot.action';
import { removeEventAction, removeEventEffects } from './actions/remove-event.action';
import { removePlayerFromPreconfigEffects } from './actions/remove-player-from-preconfig.action';
import { setEventInstancesEffects } from './actions/set-event-instances.action';
import { startEventEffects } from './actions/start-event.action';
import { updateEventTimeslotEffects } from './actions/update-event-timeslot.action';
import { updateEventEffects } from './actions/update-event.action';
Expand All @@ -35,6 +36,7 @@ export const eventsFeatureEffects: Effects[] = [
removeEventTimeslotEffects,
removeEventEffects,
removePlayerFromPreconfigEffects,
setEventInstancesEffects,
startEventEffects,
updateEventEffects,
updateEventTimeslotEffects,
Expand Down
6 changes: 4 additions & 2 deletions src/client/src/app/+state/events/events.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { removeEventTimeslotReducers } from './actions/remove-event-timeslot.act
import { removeEventReducers } from './actions/remove-event.action';
import { removePlayerFromPreconfigReducers } from './actions/remove-player-from-preconfig.action';
import { resetEventsActionStateReducers } from './actions/reset-events-action-state.action';
import { setEventInstancesReducers } from './actions/set-event-instances.action';
import { startEventReducers } from './actions/start-event.action';
import { updateEventTimeslotReducers } from './actions/update-event-timeslot.action';
import { updateEventReducers } from './actions/update-event.action';
Expand All @@ -26,7 +27,6 @@ export const eventsReducer = createReducer<EventsFeatureState>(
...addEventReducers,
...addPlayerToEventPreconfigurationReducers,
...buildEventInstancesReducers,
...updateEventReducers,
...eventTimeslotRegistrationChangedReducers,
...loadEventReducers,
...loadEventsReducers,
Expand All @@ -35,6 +35,8 @@ export const eventsReducer = createReducer<EventsFeatureState>(
...removeEventReducers,
...removePlayerFromPreconfigReducers,
...resetEventsActionStateReducers,
...setEventInstancesReducers,
...startEventReducers,
...updateEventTimeslotReducers
...updateEventTimeslotReducers,
...updateEventReducers
);
2 changes: 2 additions & 0 deletions src/client/src/app/+state/events/events.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type EventsFeatureState = EntityState<Event> & {
addTimeslot: ActionState;
removeTimeslot: ActionState;
buildInstances: ActionState;
setInstances: ActionState;
updateTimeslot: ActionState;
addPreconfig: ActionState;
removePreconfig: ActionState;
Expand All @@ -40,6 +41,7 @@ export const initialEventsFeatureState: EventsFeatureState = eventEntityAdapter.
addTimeslot: initialActionState,
removeTimeslot: initialActionState,
buildInstances: initialActionState,
setInstances: initialActionState,
updateTimeslot: initialActionState,
addPreconfig: initialActionState,
removePreconfig: initialActionState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ <h2 class="m-0 grow">{{ translations.events_timeslots() }}</h2>
@if (canBuildInstances()) {
<div class="flex flex-row items-center pt-4">
<h2 class="m-0 grow">{{ translations.events_groups() }}</h2>
@if (!event.startedAt) {
<p-inputGroup class="w-auto">
<p-inputGroup class="w-auto">
@if (!event.startedAt) {
<button
pButton
icon="i-[mdi--shuffle]"
Expand All @@ -165,18 +165,18 @@ <h2 class="m-0 grow">{{ translations.events_groups() }}</h2>
(click)="buildInstances()"
[loading]="isBuildBusy()"
></button>
@if (hasInstances()) {
<button
pButton
icon="i-[mdi--pencil]"
[outlined]="true"
[pTooltip]="translations.events_editGroups()"
(click)="instancesDialog.open(event)"
[loading]="isBuildBusy()"
></button>
}
</p-inputGroup>
}
}
@if (hasInstances()) {
<button
pButton
icon="i-[mdi--pencil]"
[outlined]="true"
[pTooltip]="translations.events_editGroups()"
(click)="instancesDialog.open(event)"
[loading]="isBuildBusy()"
></button>
}
</p-inputGroup>
</div>
<div class="flex shrink-0 flex-row overflow-auto text-xs">
@for (timeslot of timeslots(); track timeslot.id) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { produce } from 'immer';
import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { InputGroupModule } from 'primeng/inputgroup';
import { ListboxModule } from 'primeng/listbox';
import { OverlayPanelModule } from 'primeng/overlaypanel';
import { filter } from 'rxjs';

import { isActionBusy } from '../../../+state/action-state';
import { selectEventsActionState, setEventInstancesAction } from '../../../+state/events';
import { userSelectors } from '../../../+state/users';
import { Event, EventInstance, EventTimeslot } from '../../../models/parsed-models';
import { Logger } from '../../../services/logger.service';
import { TranslateService } from '../../../services/translate.service';
import { notNullish } from '../../../utils/common.utils';
import { selectSignal } from '../../../utils/ngrx.utils';
import { errorToastEffect, selectSignal } from '../../../utils/ngrx.utils';
import { UserItemComponent } from '../../users/user-item/user-item.component';

type EventInstances = { timeslot: EventTimeslot; instances: EventInstance[] }[];
Expand All @@ -33,15 +39,18 @@ type EventInstances = { timeslot: EventTimeslot; instances: EventInstance[] }[];
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EventInstancesDialogComponent {
private readonly _store = inject(Store);
protected readonly translations = inject(TranslateService).translations;

private readonly _removeItem: EventInstance = {
id: '<remove>',
groupCode: '',
playerIds: [],
};
private readonly _actionState = selectSignal(selectEventsActionState('setInstances'));

protected readonly visible = signal(false);
protected readonly event = signal<Event | null>(null);
protected readonly instances = signal<EventInstances>([]);
protected readonly allUsers = selectSignal(userSelectors.selectEntities);
protected readonly unassignedUsers = computed(() =>
Expand Down Expand Up @@ -90,9 +99,23 @@ export class EventInstancesDialogComponent {
)
);

protected readonly isBusy = signal(false);
protected readonly isBusy = computed(() => isActionBusy(this._actionState()));

constructor() {
errorToastEffect(this.translations.events_error_changeGroups, this._actionState);

const actions$ = inject(Actions);
actions$
.pipe(
ofType(setEventInstancesAction.success),
filter(({ props }) => props.eventId === this.event()?.id),
takeUntilDestroyed()
)
.subscribe(() => this.visible.set(false));
}

public open(event: Event) {
this.event.set(event);
this.instances.set(
event.timeslots.map(timeslot => ({ timeslot, instances: timeslot.instances }))
);
Expand Down Expand Up @@ -138,7 +161,17 @@ export class EventInstancesDialogComponent {
}

protected submit() {
Logger.logDebug('EventInstancesDialogComponent', 'submit', { instances: this.instances() });
// TODO: Implement Server-side logic and dispatch action
const event = this.event();
const instances = this.instances();
Logger.logDebug('EventInstancesDialogComponent', 'submit', { event, instances });

if (!event) return;

this._store.dispatch(
setEventInstancesAction({
eventId: event.id,
instances: instances.map(x => ({ timeslotId: x.timeslot.id, instances: x.instances })),
})
);
}
}
3 changes: 2 additions & 1 deletion src/client/src/app/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@
"deleteTimeslot": "Fehler beim Löschen des Zeitslots.",
"deletePreconfig": "Fehler beim Löschen der vordefinierten Gruppe.",
"start": "Fehler beim Starten der Veranstaltung.",
"buildGroups": "Fehler beim Bilden der Gruppen."
"buildGroups": "Fehler beim Bilden der Gruppen.",
"changeGroups": "Fehler beim Ändern der Gruppen."
}
},
"maps": {
Expand Down
3 changes: 2 additions & 1 deletion src/client/src/app/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@
"deleteTimeslot": "Failed to delete timeslot.",
"deletePreconfig": "Failed to delete preconfigured group.",
"start": "Failed to start event.",
"buildGroups": "Failed to build groups."
"buildGroups": "Failed to build groups.",
"changeGroups": "Failed to change groups."
}
},
"maps": {
Expand Down
Loading

0 comments on commit 7c2fb94

Please sign in to comment.