Skip to content

Commit

Permalink
feat: add ability to edit event groups (#151)
Browse files Browse the repository at this point in the history
Closes: #24
  • Loading branch information
MaSch0212 authored Jul 27, 2024
1 parent 010c5de commit 6aac4a3
Show file tree
Hide file tree
Showing 31 changed files with 3,019 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { inject } from '@angular/core';
import { on } from '@ngrx/store';
import { produce } from 'immer';
import { switchMap } from 'rxjs';

import { EventAdministrationService } from '../../../api/services';
import { AuthService } from '../../../services/auth.service';
import { createHttpAction, handleHttpAction, onHttpAction } 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 setEditingEventInstancesAction = createHttpAction<
{ eventId: string; isEditing: boolean },
{ userIdEditingInstances: string | null }
>()(EVENTS_ACTION_SCOPE, 'Set Editing Event Instances');

export const setEditingEventInstancesReducers: Reducers<EventsFeatureState> = [
on(setEditingEventInstancesAction.success, (state, { props, response }) =>
eventEntityAdapter.mapOne(
{
id: props.eventId,
map: produce(draft => {
draft.userIdEditingInstances = response.userIdEditingInstances;
}),
},
state
)
),
handleHttpAction('setInstancesEditing', setEditingEventInstancesAction),
];

export const setEditingEventInstancesEffects: Effects = {
setEditingEventInstances$: createFunctionalEffect.dispatching(
(api = inject(EventAdministrationService), authService = inject(AuthService)) =>
onHttpAction(setEditingEventInstancesAction).pipe(
switchMap(({ props }) => setEditingEventInstances(api, props, authService))
)
),
};

async function setEditingEventInstances(
api: EventAdministrationService,
props: ReturnType<typeof setEditingEventInstancesAction>['props'],
authService: AuthService
) {
const response = await api.setEventInstancesEditing({
eventId: props.eventId,
body: { isEditing: props.isEditing },
});
return response.ok
? setEditingEventInstancesAction.success(props, {
userIdEditingInstances: props.isEditing ? (authService.user()?.id ?? null) : null,
})
: setEditingEventInstancesAction.error(props, response);
}
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);
}
8 changes: 5 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,18 @@
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 { setEditingEventInstancesAction } from './actions/set-editing-event-instances.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';
17 changes: 17 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,11 @@ 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 {
setEditingEventInstancesAction,
setEditingEventInstancesEffects,
} from './actions/set-editing-event-instances.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 +40,8 @@ export const eventsFeatureEffects: Effects[] = [
removeEventTimeslotEffects,
removeEventEffects,
removePlayerFromPreconfigEffects,
setEditingEventInstancesEffects,
setEventInstancesEffects,
startEventEffects,
updateEventEffects,
updateEventTimeslotEffects,
Expand Down Expand Up @@ -66,6 +73,16 @@ export const eventsFeatureEffects: Effects[] = [
map(event => eventTimeslotRegistrationChangedAction(event))
)
),
eventInstancesEditorChanged$: createFunctionalEffect.dispatching(() =>
inject(RealtimeEventsService).eventInstancesEditorChanged.pipe(
map(event =>
setEditingEventInstancesAction.success(
{ eventId: event.eventId, isEditing: !!event.userId },
{ userIdEditingInstances: event.userId ?? null }
)
)
)
),

onServerReconnected$: createFunctionalEffect.dispatching(() =>
inject(RealtimeEventsService).onReconnected$.pipe(
Expand Down
8 changes: 6 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,8 @@ 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 { setEditingEventInstancesReducers } from './actions/set-editing-event-instances.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 +28,6 @@ export const eventsReducer = createReducer<EventsFeatureState>(
...addEventReducers,
...addPlayerToEventPreconfigurationReducers,
...buildEventInstancesReducers,
...updateEventReducers,
...eventTimeslotRegistrationChangedReducers,
...loadEventReducers,
...loadEventsReducers,
Expand All @@ -35,6 +36,9 @@ export const eventsReducer = createReducer<EventsFeatureState>(
...removeEventReducers,
...removePlayerFromPreconfigReducers,
...resetEventsActionStateReducers,
...setEditingEventInstancesReducers,
...setEventInstancesReducers,
...startEventReducers,
...updateEventTimeslotReducers
...updateEventTimeslotReducers,
...updateEventReducers
);
10 changes: 8 additions & 2 deletions src/client/src/app/+state/events/events.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ export function selectEventsActionState(action: keyof EventsFeatureState['action

export function selectEvent(id: string | null | undefined) {
return createDistinctSelector(selectEventsFeature, state =>
id ? state.entities[id] ?? null : null
id ? (state.entities[id] ?? null) : null
);
}

export function selectEventEditor(id: string | null | undefined) {
return createDistinctSelector(selectEventsFeature, state =>
id ? (state.entities[id]?.userIdEditingInstances ?? null) : null
);
}

Expand All @@ -33,7 +39,7 @@ export function selectEventTimeslot(
) {
return createDistinctSelector(selectEventsFeature, state =>
eventId && timeslotId
? state.entities[eventId]?.timeslots.find(x => x.id === timeslotId) ?? null
? (state.entities[eventId]?.timeslots.find(x => x.id === timeslotId) ?? null)
: null
);
}
4 changes: 4 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,8 @@ export type EventsFeatureState = EntityState<Event> & {
addTimeslot: ActionState;
removeTimeslot: ActionState;
buildInstances: ActionState;
setInstances: ActionState;
setInstancesEditing: ActionState;
updateTimeslot: ActionState;
addPreconfig: ActionState;
removePreconfig: ActionState;
Expand All @@ -40,6 +42,8 @@ export const initialEventsFeatureState: EventsFeatureState = eventEntityAdapter.
addTimeslot: initialActionState,
removeTimeslot: initialActionState,
buildInstances: initialActionState,
setInstances: initialActionState,
setInstancesEditing: initialActionState,
updateTimeslot: initialActionState,
addPreconfig: initialActionState,
removePreconfig: initialActionState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,41 +73,39 @@

<div class="mt-4 flex flex-row items-center gap-2">
<h2 class="m-0 grow">{{ translations.events_facebookLink() }}</h2>
<div>
<p-inputGroup>
<button
type="button"
pButton
[label]="
externalUri() && externalUri() !== undefined && externalUri()?.length !== 0
? translations.shared_edit()
: translations.shared_add()
"
icon="i-[mdi--facebook]"
(click)="modifyExternalUriDialog.open(externalUri())"
></button>
<button
type="button"
pButton
class="bg-surface-a text-primary"
icon="i-[mdi--arrow-right-circle]"
[disabled]="
externalUri() && externalUri() !== undefined && externalUri()?.length !== 0
? false
: true
"
(click)="openExternalUri()"
></button>
</p-inputGroup>
</div>
<p-inputGroup class="w-auto">
<button
type="button"
pButton
[label]="
externalUri() && externalUri() !== undefined && externalUri()?.length !== 0
? translations.shared_edit()
: translations.shared_add()
"
icon="i-[mdi--facebook]"
(click)="modifyExternalUriDialog.open(externalUri())"
></button>
<button
type="button"
pButton
[outlined]="true"
icon="i-[mdi--arrow-right-circle]"
[disabled]="
externalUri() && externalUri() !== undefined && externalUri()?.length !== 0
? false
: true
"
(click)="openExternalUri()"
></button>
</p-inputGroup>
</div>

<div class="mt-4 flex flex-row items-center gap-2">
<h2 class="m-0 grow">{{ translations.events_timeslots() }}</h2>
<span
class="inline-block rounded-full bg-primary pr-2 text-center font-bold text-primary-text"
class="inline-block rounded-full bg-primary px-2 text-center font-bold text-primary-text"
>
<span class="i-[mdi--account] ml-2"></span> {{ playersAmount() }}
<span class="i-[mdi--account]"></span> {{ playersAmount() }}
</span>
@if (!event.startedAt && event.staged) {
<p-button
Expand Down Expand Up @@ -154,19 +152,40 @@ <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-button
icon="i-[mdi--shuffle]"
[label]="
hasInstances()
? translations.events_rebuildGroups()
: translations.events_buildGroups()
"
(onClick)="buildInstances()"
[loading]="isBuildBusy()"
/>
}
<p-inputGroup class="w-auto">
@if (!event.startedAt) {
<button
pButton
icon="i-[mdi--shuffle]"
[label]="
hasInstances()
? translations.events_rebuildGroups()
: translations.events_buildGroups()
"
(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>
</div>
@if (event.userIdEditingInstances) {
<div
class="self-end rounded-full bg-primary px-2 text-center font-bold text-primary-text"
>
<span class="i-[mdi--pencil]"></span>
{{ allUsers()[event.userIdEditingInstances]?.alias }}
</div>
}
<div class="flex shrink-0 flex-row overflow-auto text-xs">
@for (timeslot of timeslots(); track timeslot.id) {
<div class="flex w-1/3 min-w-28 shrink-0 flex-col gap-2 px-1">
Expand Down Expand Up @@ -234,3 +253,5 @@ <h1 class="m-0 text-center">{{ translations.events_notFound() }}</h1>
}
}
</div>

<app-event-instances-dialog #instancesDialog />
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ifTruthy } from '../../../utils/common.utils';
import { compareTimes } from '../../../utils/date.utils';
import { errorToastEffect, selectSignal } from '../../../utils/ngrx.utils';
import { EventFormComponent } from '../event-form/event-form.component';
import { EventInstancesDialogComponent } from '../event-instances-dialog/event-instances-dialog.component';
import { EventTimeslotDialogComponent } from '../event-timeslot-dialog/event-timeslot-dialog.component';

@Component({
Expand All @@ -47,6 +48,7 @@ import { EventTimeslotDialogComponent } from '../event-timeslot-dialog/event-tim
CardModule,
CommonModule,
EventFormComponent,
EventInstancesDialogComponent,
EventTimeslotDialogComponent,
FormsModule,
InputGroupAddonModule,
Expand Down
Loading

0 comments on commit 6aac4a3

Please sign in to comment.