From 453d02612c66100c97faef2c36fe84780895cdae Mon Sep 17 00:00:00 2001 From: Roland Schneider Date: Thu, 11 Dec 2025 22:47:54 +0100 Subject: [PATCH] refactor admin to use eventbus --- .../calendar-view/calendar-view.html | 10 +- .../components/calendar-view/calendar-view.ts | 146 +++++++++++------- .../single-event-booking-cancel.css | 0 .../single-event-booking-cancel.html | 1 + .../single-event-booking-cancel.ts | 14 ++ .../single-event-booking-create.css | 0 .../single-event-booking-create.html | 1 + .../single-event-booking-create.ts | 15 ++ .../single-event-booking-list.css | 0 .../single-event-booking-list.html | 1 + .../single-event-booking-list.ts | 14 ++ ...nt-dashboard-event-activation.component.ts | 14 +- .../single-event-dashboard-event-delete.html | 11 +- .../single-event-dashboard-event-delete.ts | 73 +++------ .../single-event-dashboard-event-edit.html | 8 +- .../single-event-dashboard-event-edit.ts | 24 +-- .../single-event-dashboard.html | 9 +- .../single-event-dashboard.ts | 54 ++----- .../create-event-form/create-event-form.ts | 3 +- .../calendar/services/calendar.service.ts | 23 ++- admin/src/app/services/event-bus.service.ts | 29 ++++ admin/src/types.ts | 21 +++ server/package-lock.json | 32 ++-- server/package.json | 2 +- server/src/calendar/calendar.controller.ts | 7 +- server/src/calendar/calendar.service.ts | 4 +- server/src/calendar/dto/create-booking.dto.ts | 7 +- 27 files changed, 298 insertions(+), 225 deletions(-) create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.css create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.html create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.ts create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.css create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.html create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.ts create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.css create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.html create mode 100644 admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.ts create mode 100644 admin/src/app/services/event-bus.service.ts diff --git a/admin/src/app/features/calendar/components/calendar-view/calendar-view.html b/admin/src/app/features/calendar/components/calendar-view/calendar-view.html index 7735e4f..5c1a323 100644 --- a/admin/src/app/features/calendar/components/calendar-view/calendar-view.html +++ b/admin/src/app/features/calendar/components/calendar-view/calendar-view.html @@ -1,23 +1,19 @@

Naptár

- - - -
-@if (workflow() == 'event-create') { +@if (workflow() == 'event_create') { } -@if (workflow() == 'event-dashboard' && selectedEvent()) { +@if (workflow() == 'event_dashboard' && selectedEvent()) { } diff --git a/admin/src/app/features/calendar/components/calendar-view/calendar-view.ts b/admin/src/app/features/calendar/components/calendar-view/calendar-view.ts index 3bd0d38..d67a91e 100644 --- a/admin/src/app/features/calendar/components/calendar-view/calendar-view.ts +++ b/admin/src/app/features/calendar/components/calendar-view/calendar-view.ts @@ -27,6 +27,24 @@ import { SingleEventDashboardEventActivation, } from './single-event-dashboard-event-activation/single-event-dashboard-event-activation.component'; import { SingleEventDashboardEventEdit } from './single-event-dashboard-event-edit/single-event-dashboard-event-edit'; +import { EventBusService } from '../../../../services/event-bus.service'; +import { EventType } from '../../../../../types'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { SingleEventBookingCreate } from './single-event-booking-create/single-event-booking-create'; +import { SingleEventBookingList } from './single-event-booking-list/single-event-booking-list'; + + +export type WORKFLOW_TYPE = 'NO_DIALOG' + | 'event_delete' + | 'event_cancel' + | 'event_dashboard' + | 'event_create' + | 'event_activate' + | 'event_edit' + | 'booking_create' + | 'booking_list' + | 'booking_cancel' + ; @Component({ selector: 'app-calendar-view', @@ -34,14 +52,16 @@ import { SingleEventDashboardEventEdit } from './single-event-dashboard-event-ed templateUrl: './calendar-view.html', styleUrl: './calendar-view.css', }) -export class CalendarView { +export class CalendarView { @ViewChild('startHour') startHour!: ElementRef; @ViewChild('calendar') calendarComponent: FullCalendarComponent | undefined; + private eventBus = inject(EventBusService); + calendarService = inject(CalendarService); - workflow = signal(''); + workflow = signal('NO_DIALOG'); selectedDate = signal(undefined); selectedEvent = signal(undefined); @@ -50,6 +70,24 @@ export class CalendarView { constructor() { + this.eventBus.on(EventType.CALENDAR_VIEW_EVENT_SAVED) + .pipe(takeUntilDestroyed()) + .subscribe({ + next: (_) => { + this.closeDialog(); + this.calendarComponent?.getApi().refetchEvents(); + } + }); + + this.eventBus.on(EventType.CALENDAR_VIEW_DIALOG_CLOSED) + .pipe(takeUntilDestroyed()) + .subscribe({ + next: (_) => { + this.closeDialog(); + } + }) + + this.calendarOptions = { plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin], initialView: 'dayGridMonth', @@ -65,12 +103,12 @@ export class CalendarView { eventClick: (info) => { this.selectedEvent.set(info.event.extendedProps['event']); - this.workflow.set('event-dashboard'); + this.workflow.set('event_dashboard'); this.selectedDate.set(undefined); }, dateClick: (info) => { - this.workflow.set('event-create'); + this.workflow.set('event_create'); this.selectedDate.set(info.date); this.selectedEvent.set(undefined); }, @@ -80,13 +118,12 @@ export class CalendarView { this.dialogs = [ { component: SingleEventDashboardEventDelete, - isRendered: () => this.workflow() == 'event-delete', + isRendered: () => this.workflow() == 'event_delete', closeClick: () => this.closeDialog(), modalBoxStyleClass: 'max-w-none w-2xl', componentInputs: () => { return { 'event': this.selectedEvent(), - 'onAction': this.handleAction, }; }, @@ -94,14 +131,13 @@ export class CalendarView { { component: SingleEventDashboardEventActivation, - isRendered: () => this.workflow() == 'event-cancel', + isRendered: () => this.workflow() == 'event_cancel', closeClick: () => this.closeDialog(), modalBoxStyleClass: 'max-w-none w-2xl', componentInputs: () => { return { 'mode': 'cancel', 'event': this.selectedEvent(), - 'onAction': this.handleAction, }; }, }, @@ -114,26 +150,61 @@ export class CalendarView { return { 'mode': 'activate', 'event': this.selectedEvent(), - 'onAction': this.handleAction, }; }, }, { component: SingleEventDashboardEventEdit, - isRendered: () => this.workflow() == 'event-edit', + isRendered: () => this.workflow() == 'event_edit', + // isRendered: () => true, + closeClick: () => this.closeDialog(), + modalBoxStyleClass: 'max-w-none w-2xl', + componentInputs: () => { + return { + 'event': this.selectedEvent(), + }; + }, + }, + { + component: SingleEventBookingCreate, + isRendered: () => this.workflow() == 'booking_create', + // isRendered: () => true, + closeClick: () => this.closeDialog(), + modalBoxStyleClass: 'max-w-none w-2xl', + componentInputs: () => { + return { + 'event': this.selectedEvent(), + }; + }, + }, + { + component: SingleEventBookingList, + isRendered: () => this.workflow() == 'booking_list', + // isRendered: () => true, + closeClick: () => this.closeDialog(), + modalBoxStyleClass: 'max-w-none w-2xl', + componentInputs: () => { + return { + 'event': this.selectedEvent(), + }; + }, + }, + { + component: SingleEventBookingList, + isRendered: () => this.workflow() == 'booking_cancel', // isRendered: () => true, closeClick: () => this.closeDialog(), modalBoxStyleClass: 'max-w-none w-2xl', componentInputs: () => { return { 'event': this.selectedEvent(), - 'onAction': this.handleAction, }; }, }, ]; } + fetchEvents(fetchInfo: any, successCallback: (events: EventInput[]) => void, failureCallback: (error: any) => void): void { console.info('fetching events', fetchInfo); @@ -183,58 +254,21 @@ export class CalendarView { ); } - // protected addEvent($event: PointerEvent) { - // let hourStr = this.startHour.nativeElement.value; - // const hour = parseInt(hourStr, 10); - // const date = new Date(); - // const start = new Date(date.getTime()); - // start.setHours(hour, 0, 0); - // const end = new Date(date.getTime()); - // // end.setHours(3,0,0) - // - // this.calendarComponent?.getApi().addEvent({ - // title: 'Event at ' + hour, - // start, - // }); - // } closeDialog() { - this.workflow.set(''); + this.workflow.set('NO_DIALOG'); } - onDashboardAction(action: string) { - console.info('dashboard event', action); - switch (action) { - case 'event_delete': - this.workflow.set('event-delete'); - break; - - case 'event_cancel': - this.workflow.set('event-cancel'); - break; - case 'event_edit': - this.workflow.set('event-edit'); - break; - default: - this.workflow.set(action); - break; - } + /** + * Set dashboard workflow + * @param workflowType + */ + setWorkFlow(workflowType: WORKFLOW_TYPE) { + console.info('set workflow to', workflowType); + this.workflow.set(workflowType); } - // This function is passed into the child - handleAction = (msg: string) => { - console.log('Parent received:', msg); - if (msg == 'close') { - this.closeDialog(); - } else if ( msg == 'save-event-success'){ - this.closeDialog(); - this.calendarComponent?.getApi().refetchEvents(); - }else{ - } - - }; - } export interface DialogConfig { diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.css b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.css new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.html b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.html new file mode 100644 index 0000000..2bb66ae --- /dev/null +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.html @@ -0,0 +1 @@ +

single-event-booking-cancel works!

diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.ts b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.ts new file mode 100644 index 0000000..5e73035 --- /dev/null +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-cancel/single-event-booking-cancel.ts @@ -0,0 +1,14 @@ +import { Component, inject, input } from '@angular/core'; +import { EventBusService } from '../../../../../services/event-bus.service'; +import { CalendarEventDto } from '../../../models/events-in-range-dto.model'; + +@Component({ + selector: 'app-single-event-booking-cancel', + imports: [], + templateUrl: './single-event-booking-cancel.html', + styleUrl: './single-event-booking-cancel.css', +}) +export class SingleEventBookingCancel { + eventBus = inject(EventBusService); + event = input(); +} diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.css b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.css new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.html b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.html new file mode 100644 index 0000000..f0a40a6 --- /dev/null +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.html @@ -0,0 +1 @@ +

single-event-booking-create works!

diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.ts b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.ts new file mode 100644 index 0000000..9cf38a7 --- /dev/null +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-create/single-event-booking-create.ts @@ -0,0 +1,15 @@ +import { Component, inject, input } from '@angular/core'; +import { CalendarEventDto } from '../../../models/events-in-range-dto.model'; +import { EventBusService } from '../../../../../services/event-bus.service'; + +@Component({ + selector: 'app-single-event-booking-create', + imports: [], + templateUrl: './single-event-booking-create.html', + styleUrl: './single-event-booking-create.css', +}) +export class SingleEventBookingCreate { + eventBus = inject(EventBusService); + event = input(); + +} diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.css b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.css new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.html b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.html new file mode 100644 index 0000000..fcf71eb --- /dev/null +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.html @@ -0,0 +1 @@ +

single-event-booking-list works!

diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.ts b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.ts new file mode 100644 index 0000000..ace9357 --- /dev/null +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-booking-list/single-event-booking-list.ts @@ -0,0 +1,14 @@ +import { Component, inject, input } from '@angular/core'; +import { EventBusService } from '../../../../../services/event-bus.service'; +import { CalendarEventDto } from '../../../models/events-in-range-dto.model'; + +@Component({ + selector: 'app-single-event-booking-list', + imports: [], + templateUrl: './single-event-booking-list.html', + styleUrl: './single-event-booking-list.css', +}) +export class SingleEventBookingList { + eventBus = inject(EventBusService); + event = input(); +} diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-activation/single-event-dashboard-event-activation.component.ts b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-activation/single-event-dashboard-event-activation.component.ts index a4622eb..87ff169 100644 --- a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-activation/single-event-dashboard-event-activation.component.ts +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-activation/single-event-dashboard-event-activation.component.ts @@ -1,5 +1,5 @@ import { Component, inject, input } from '@angular/core'; -import { CalendarEventDto, EventExceptionDto } from '../../../models/events-in-range-dto.model'; +import { CalendarEventDto } from '../../../models/events-in-range-dto.model'; import { SingleEventDashboardEventDetailsView, } from '../single-event-dashboard-event-details-view/single-event-dashboard-event-details-view'; @@ -8,6 +8,8 @@ import { SvgIcons } from '../../../../../svg-icons'; import { SafeHtmlPipe } from '../../../../../pipes/safe-html-pipe'; import { CalendarService } from '../../../services/calendar.service'; import { CreateExceptionDto } from '../../../models/event-exception.model'; +import { EventBusService } from '../../../../../services/event-bus.service'; +import { EventType } from '../../../../../../types'; export type ACTIVATION_TYPE = 'cancel' | 'activate'; @@ -23,11 +25,10 @@ export type ACTIVATION_TYPE = 'cancel' | 'activate'; }) export class SingleEventDashboardEventActivation { + eventBus = inject(EventBusService); mode = input('cancel'); calendarService = inject(CalendarService); event = input(); - onAction = input.required<(msg: string) => void>(); - protected readonly SvgIcons = SvgIcons; @@ -37,7 +38,7 @@ export class SingleEventDashboardEventActivation { console.info('setEventOccurrenceActivation', event); const eventId = this.event()?.id!; const startTime = this.event()?.startTime!; - let payload: CreateExceptionDto | undefined = undefined; + let payload: CreateExceptionDto | undefined; const eventException = event?.exceptions?.length ? event.exceptions[0] : undefined; if (eventException) { payload = { @@ -57,9 +58,10 @@ export class SingleEventDashboardEventActivation { this.calendarService.applyException(eventId, payload ).subscribe( { next: () => { - this.onAction()('save-event-success'); + this.eventBus.emit(EventType.CALENDAR_VIEW_EVENT_SAVED, 'Event saved') }, error: err => { + console.error(err); alert('Failed to change event'); }, }, @@ -75,7 +77,7 @@ export class SingleEventDashboardEventActivation { } protected closeDialog() { - this.onAction()('close'); + this.eventBus.emit(EventType.CALENDAR_VIEW_DIALOG_CLOSED, 'Event saved') } } diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-delete/single-event-dashboard-event-delete.html b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-delete/single-event-dashboard-event-delete.html index e5171ec..d760b1f 100644 --- a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-delete/single-event-dashboard-event-delete.html +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-delete/single-event-dashboard-event-delete.html @@ -1,17 +1,14 @@

Törlés

Esemény és az egész széria törlése

-@if (config) { - -} + +
- + Törlés - + Mégsem diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-delete/single-event-dashboard-event-delete.ts b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-delete/single-event-dashboard-event-delete.ts index 821b7c6..213f40a 100644 --- a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-delete/single-event-dashboard-event-delete.ts +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-delete/single-event-dashboard-event-delete.ts @@ -1,76 +1,41 @@ -import { Component, effect, inject, input, output } from '@angular/core'; +import { Component, inject, input } from '@angular/core'; import { CalendarEventDto } from '../../../models/events-in-range-dto.model'; -import { DetailView, DetailViewConfig } from '../../../../../components/detail-view/detail-view'; import { SvgIcons } from '../../../../../svg-icons'; import { SafeHtmlPipe } from '../../../../../pipes/safe-html-pipe'; import { Button } from '@rschneider/ng-daisyui'; import { CalendarService } from '../../../services/calendar.service'; +import { EventBusService } from '../../../../../services/event-bus.service'; +import { + SingleEventDashboardEventDetailsView +} from '../single-event-dashboard-event-details-view/single-event-dashboard-event-details-view'; +import { EventType } from '../../../../../../types'; @Component({ selector: 'app-single-event-dashboard-event-delete', imports: [ - - DetailView, SafeHtmlPipe, Button, + SingleEventDashboardEventDetailsView, ], templateUrl: './single-event-dashboard-event-delete.html', styleUrl: './single-event-dashboard-event-delete.css', }) export class SingleEventDashboardEventDelete { + eventBus = inject(EventBusService); calendarService = inject(CalendarService); event = input(); - // Define an input that expects a function - onAction = input.required<(msg: string) => void>(); - config: DetailViewConfig | undefined; - - constructor() { - effect(() => { - - this.config = { - data: this.event()!, - - rows: [{ - attribute: 'id', - getTitle: 'Esemény azonosító', - }, - { - attribute: 'title', - getTitle: 'Esemény neve', - }, - { - attribute: 'startTime', - getTitle: 'Kezdési időpont', - format: 'datetime', - }, - { - attribute: 'eventType', - getTitle: 'Esemény típusa', - getValue: obj => obj.eventType.name, - - }, - ], - }; - }); - } - - - triggerAction() { - // Call the function passed from the parent - this.onAction()('close'); - } - - doDelete() { - this.calendarService.deleteEvent(this.event()!.id).subscribe( - { - next: ( ) => { - this.onAction()('save-event-success'); - } - } - ) - // Call the function passed from the parent - } protected readonly SvgIcons = SvgIcons; + + closeDialog() { + // Call the function passed from the parent + this.eventBus.emit(EventType.CALENDAR_VIEW_DIALOG_CLOSED, '') + } + + + protected deleteEventTemplate() { + this.calendarService.delete(this.event()!.id).subscribe(() => this.eventBus.emit(EventType.CALENDAR_VIEW_EVENT_SAVED, 'Event saved')) + } + } diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.html b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.html index e756b34..cf165a1 100644 --- a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.html +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.html @@ -1,3 +1,5 @@ - + + diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.ts b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.ts index 1d18cc3..ff461ba 100644 --- a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.ts +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.ts @@ -1,31 +1,33 @@ -import { Component, input, signal } from '@angular/core'; +import { Component, inject, input, signal } from '@angular/core'; import { CalendarEventDto } from '../../../models/events-in-range-dto.model'; -import { SvgIcons } from '../../../../../svg-icons'; -import { CreateEventForm } from '../../create-event-form/create-event-form'; -import { Modal } from '@rschneider/ng-daisyui'; +import { CreateEventForm, ReadyType } from '../../create-event-form/create-event-form'; +import { EventBusService } from '../../../../../services/event-bus.service'; +import { EventType } from '../../../../../../types'; @Component({ selector: 'app-single-event-dashboard-event-edit', imports: [ CreateEventForm, - Modal, ], templateUrl: './single-event-dashboard-event-edit.html', styleUrl: './single-event-dashboard-event-edit.css', }) export class SingleEventDashboardEventEdit { + eventBus = inject(EventBusService); selectedDate = signal(undefined); event = input(); - onAction = input.required<(msg: string) => void>(); - - protected readonly SvgIcons = SvgIcons; /** * proxy to ready event from form to parent */ - protected triggerAction(action: string) { - console.info("event details dashboard", action) - this.onAction()(action) + protected triggerAction(action: ReadyType) { + if ( action == 'save-event-success'){ + this.eventBus.emit(EventType.CALENDAR_VIEW_EVENT_SAVED,''); + }else if ( action == 'save-event-failed'){ + this.eventBus.emit(EventType.CALENDAR_VIEW_DIALOG_CLOSED,''); + }else{ + this.eventBus.emit(EventType.CALENDAR_VIEW_DIALOG_CLOSED,''); + } } } diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard/single-event-dashboard.html b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard/single-event-dashboard.html index 1879aab..db34819 100644 --- a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard/single-event-dashboard.html +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard/single-event-dashboard.html @@ -4,11 +4,8 @@ @if (!event()?.isRecurring) {

Esemény

} -@if (config) { - -} + +
@for (card of cards(); let i = $index; track i) { @@ -19,7 +16,7 @@ [description]="card.description" [buttonTitle]="card.buttonTitle" [styleClass]="'flex-1'" - (onAction)="onCardAction(card.action)" + (onAction)="onCardAction(card.workflow)" > } diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard/single-event-dashboard.ts b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard/single-event-dashboard.ts index 27f8307..9546bc7 100644 --- a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard/single-event-dashboard.ts +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard/single-event-dashboard.ts @@ -3,12 +3,17 @@ import { CalendarEventDto } from '../../../models/events-in-range-dto.model'; import { DetailView, DetailViewConfig } from '../../../../../components/detail-view/detail-view'; import { SvgIcons } from '../../../../../svg-icons'; import { SingleEventDashboardCard } from '../single-event-dashboard-card/single-event-dashboard-card'; +import { WORKFLOW_TYPE } from '../calendar-view'; +import { + SingleEventDashboardEventDetailsView +} from '../single-event-dashboard-event-details-view/single-event-dashboard-event-details-view'; @Component({ selector: 'app-single-event-dashboard', imports: [ DetailView, SingleEventDashboardCard, + SingleEventDashboardEventDetailsView, ], templateUrl: './single-event-dashboard.html', styleUrl: './single-event-dashboard.css', @@ -16,49 +21,20 @@ import { SingleEventDashboardCard } from '../single-event-dashboard-card/single- export class SingleEventDashboard { event = input(); - action = output(); - config: DetailViewConfig | undefined; - + action = output(); cards = signal([]); constructor() { - effect(() => { - console.info("dashboard", this.event()); - this.config = { - data: this.event()!, - - rows: [{ - attribute: 'id', - getTitle: 'Esemény azonosító', - }, - { - attribute: 'title', - getTitle: 'Esemény neve', - }, - { - attribute: 'startTime', - getTitle: 'Kezdési időpont', - format: 'datetime', - }, - { - attribute: 'eventType', - getTitle: 'Esemény típusa', - getValue: obj => obj.eventType.name, - - }, - ], - }; - this.cards.set( [ { buttonTitle: 'Szerkesztés', title: 'Szerkesztés', svgIcon: SvgIcons.heorPencilSquare, description: 'Az esemény módosítása', - action: 'event_edit', + workflow: 'event_edit', }, this.event()?.isCancelled ? @@ -68,48 +44,48 @@ export class SingleEventDashboard { title: 'Előfordulás aktiválása', svgIcon: SvgIcons.heroPlay, description: 'Az esemény ezen előfordulásának aktiválása', - action: 'event_activate', + workflow: 'event_activate', } : { buttonTitle: 'Lemondás', title: 'Előfordulás lemondása', svgIcon: SvgIcons.heroXcircle, description: 'Az esemény ezen előfordulásának lemondása', - action: 'event_cancel', + workflow: 'event_cancel', }, { buttonTitle: 'Törlés', title: 'Esemény törlése', svgIcon: SvgIcons.heroTrash, description: 'Az esemény törlése', - action: 'event_delete', + workflow: 'event_delete', }, { buttonTitle: 'Megnézem', title: 'Foglalások', svgIcon: SvgIcons.heroUserGroup, description: 'Foglalások megtekintése', - action: 'event_bookings', + workflow: 'booking_list', }, { buttonTitle: 'Bejelentkezés', title: 'Időpont foglalás', svgIcon: SvgIcons.heroUserPlus, description: 'Időpont foglalása eseményre', - action: 'user_booking', + workflow: 'booking_create', }, { buttonTitle: 'Lemondás', title: 'Lemondás', svgIcon: SvgIcons.heroUserMinus, description: 'Az időpont lemondása', - action: 'user_cancel', + workflow: 'booking_cancel', }, ]); }); } - onCardAction (action: string|undefined) { + onCardAction (action: WORKFLOW_TYPE) { if ( action ){ this.action.emit(action); } @@ -125,5 +101,5 @@ export interface CardConfig { description?: string; buttonTitle?: string; svgIcon?: string; - action?: string; + workflow: WORKFLOW_TYPE; } diff --git a/admin/src/app/features/calendar/components/create-event-form/create-event-form.ts b/admin/src/app/features/calendar/components/create-event-form/create-event-form.ts index e6d45be..1e56379 100644 --- a/admin/src/app/features/calendar/components/create-event-form/create-event-form.ts +++ b/admin/src/app/features/calendar/components/create-event-form/create-event-form.ts @@ -16,6 +16,7 @@ export type FrequencyOption = { frequency: Frequency, label: string; } +export type ReadyType = 'close' | 'save-event-success' | 'save-event-failed'; @Component({ selector: 'app-create-event-form', @@ -28,7 +29,7 @@ export type FrequencyOption = { export class CreateEventForm implements OnInit { form: FormGroup; isEditMode = false; - ready = output(); + ready = output(); id = input(); date = input(); eventTypes = signal([]); diff --git a/admin/src/app/features/calendar/services/calendar.service.ts b/admin/src/app/features/calendar/services/calendar.service.ts index 1868342..1eb75a0 100644 --- a/admin/src/app/features/calendar/services/calendar.service.ts +++ b/admin/src/app/features/calendar/services/calendar.service.ts @@ -9,18 +9,17 @@ import { CreateExceptionDto } from '../models/event-exception.model'; @Injectable({ - providedIn: 'root', + providedIn: 'root' }) export class CalendarService { private readonly apiUrl: string; constructor( private http: HttpClient, - private configService: ConfigurationService, + private configService: ConfigurationService ) { this.apiUrl = `${this.configService.getApiUrl()}/calendar`; } - /** * get events in range */ @@ -28,26 +27,24 @@ export class CalendarService { const params = new HttpParams() .set('startDate', eventsInRangeDto.startTime!.toISOString()) .set('endDate', eventsInRangeDto.endTime!.toISOString()); - return this.http.get(this.apiUrl + '', { params }); + return this.http.get(this.apiUrl+'', { params }); } /** * Create a new record. */ public create(data: EventFormDTO): Observable { - return this.http.post(this.apiUrl + '/events', data); + return this.http.post(this.apiUrl+'/events', data); } - public update(id: number, data: UpdateEventFormDTO): Observable { - return this.http.patch(this.apiUrl + '/events/' + id, data); + public update(id: number,data: UpdateEventFormDTO): Observable { + return this.http.patch(this.apiUrl+'/events/'+id, data); } - public applyException(eventId: number, eventException: CreateExceptionDto) { - return this.http.post(this.apiUrl + `/events/${eventId}/exceptions`, eventException); + public applyException(eventId: number, eventException: CreateExceptionDto){ + return this.http.post(this.apiUrl+`/events/${eventId}/exceptions`, eventException); } - - public deleteEvent(id: number): Observable { - return this.http.delete(this.apiUrl + '/events/' + id); + public delete(eventId: number){ + return this.http.delete(this.apiUrl+`/events/${eventId}` ); } - } diff --git a/admin/src/app/services/event-bus.service.ts b/admin/src/app/services/event-bus.service.ts new file mode 100644 index 0000000..22a03ca --- /dev/null +++ b/admin/src/app/services/event-bus.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; +import { AppEvent, EventMap } from '../../types'; + +@Injectable({ + providedIn: 'root' // Makes this a singleton available everywhere +}) +export class EventBusService { + private subject$ = new Subject(); + + /** + * Publish an event to the bus + */ + emit(type: K, payload: EventMap[K]): void { + this.subject$.next({ type, payload }); + } + + /** + * Subscribe to specific event types + * Returns an Observable of the payload + */ + on(type: K): Observable { + return this.subject$.pipe( + filter(e => e.type === type), + map(e => e.payload) + ); + } +} diff --git a/admin/src/types.ts b/admin/src/types.ts index a46ce60..af49ce6 100644 --- a/admin/src/types.ts +++ b/admin/src/types.ts @@ -14,3 +14,24 @@ export interface PaginatedResponse { currentPage: number; }; } +export enum EventType { + USER_LOGGED_IN = 'USER_LOGGED_IN', + ORDER_CREATED = 'ORDER_CREATED', + THEME_CHANGED = 'THEME_CHANGED', + CALENDAR_VIEW_EVENT_SAVED = 'CALENDAR_VIEW_EVENT_SAVED', + CALENDAR_VIEW_DIALOG_CLOSED = 'CALENDAR_VIEW_DIALOG_CLOSED' +} + + +export interface EventMap { + [EventType.USER_LOGGED_IN]: { id: number; name: string }; + [EventType.THEME_CHANGED]: string; + [EventType.ORDER_CREATED]: { orderId: number }; + [EventType.CALENDAR_VIEW_EVENT_SAVED]: string; + [EventType.CALENDAR_VIEW_DIALOG_CLOSED]: string; +} + +export interface AppEvent { + type: EventType; + payload?: T; +} diff --git a/server/package-lock.json b/server/package-lock.json index 46af3b7..ff253f3 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -16,7 +16,7 @@ "@nestjs/mapped-types": "^2.1.0", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", - "@nestjs/swagger": "^11.2.1", + "@nestjs/swagger": "^11.2.3", "@nestjs/typeorm": "^11.0.0", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", @@ -2109,9 +2109,9 @@ } }, "node_modules/@microsoft/tsdoc": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", - "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { @@ -2620,17 +2620,17 @@ } }, "node_modules/@nestjs/swagger": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.1.tgz", - "integrity": "sha512-1MS7xf0pzc1mofG53xrrtrurnziafPUHkqzRm4YUVPA/egeiMaSerQBD/feiAeQ2BnX0WiLsTX4HQFO0icvOjQ==", + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.3.tgz", + "integrity": "sha512-a0xFfjeqk69uHIUpP8u0ryn4cKuHdra2Ug96L858i0N200Hxho+n3j+TlQXyOF4EstLSGjTfxI1Xb2E1lUxeNg==", "license": "MIT", "dependencies": { - "@microsoft/tsdoc": "0.15.1", + "@microsoft/tsdoc": "0.16.0", "@nestjs/mapped-types": "2.1.0", - "js-yaml": "4.1.0", + "js-yaml": "4.1.1", "lodash": "4.17.21", "path-to-regexp": "8.3.0", - "swagger-ui-dist": "5.29.4" + "swagger-ui-dist": "5.30.2" }, "peerDependencies": { "@fastify/static": "^8.0.0", @@ -7814,9 +7814,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -10202,9 +10202,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.29.4", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.4.tgz", - "integrity": "sha512-gJFDz/gyLOCQtWwAgqs6Rk78z9ONnqTnlW11gimG9nLap8drKa3AJBKpzIQMIjl5PD2Ix+Tn+mc/tfoT2tgsng==", + "version": "5.30.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.2.tgz", + "integrity": "sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" diff --git a/server/package.json b/server/package.json index cbb8e45..9c2cba3 100644 --- a/server/package.json +++ b/server/package.json @@ -30,7 +30,7 @@ "@nestjs/mapped-types": "^2.1.0", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", - "@nestjs/swagger": "^11.2.1", + "@nestjs/swagger": "^11.2.3", "@nestjs/typeorm": "^11.0.0", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", diff --git a/server/src/calendar/calendar.controller.ts b/server/src/calendar/calendar.controller.ts index e49c820..52b1569 100644 --- a/server/src/calendar/calendar.controller.ts +++ b/server/src/calendar/calendar.controller.ts @@ -9,6 +9,7 @@ import { Query, ParseIntPipe, UseGuards, + ValidationPipe, } from '@nestjs/common'; import { CalendarService } from './calendar.service'; import { GetCalendarDto } from './dto/get-calendar.dto'; @@ -18,8 +19,9 @@ import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { RolesGuard } from '../auth/roles.guard'; import { Roles } from '../auth/roles.decorator'; import { Role } from '../auth/role.enum'; -import { CreateBookingDto } from './dto/create-booking.dto'; +import { CalendarCreateBookingDto } from './dto/create-booking.dto'; import { CancelBookingDto } from './dto/cancel-booking.dto'; +import { ApiBody } from '@nestjs/swagger'; @Controller('calendar') @UseGuards(JwtAuthGuard, RolesGuard) @@ -73,9 +75,10 @@ export class CalendarController { // Create a booking for a specific event occurrence @Post('events/:id/bookings') + @ApiBody({ type: CalendarCreateBookingDto }) createBooking( @Param('id', ParseIntPipe) eventId: number, - @Body() createBookingDto: CreateBookingDto, + @Body(new ValidationPipe()) createBookingDto: CalendarCreateBookingDto, ) { return this.calendarService.createBooking(eventId, createBookingDto); } diff --git a/server/src/calendar/calendar.service.ts b/server/src/calendar/calendar.service.ts index 8fc2550..ec61cbc 100644 --- a/server/src/calendar/calendar.service.ts +++ b/server/src/calendar/calendar.service.ts @@ -14,7 +14,7 @@ import { CreateEventDto } from './dto/create-event.dto'; import { CreateExceptionDto } from './dto/create-exception.dto'; import { Booking } from '../entity/booking.entity'; import { CancelBookingDto } from './dto/cancel-booking.dto'; -import { CreateBookingDto } from './dto/create-booking.dto'; +import { CalendarCreateBookingDto } from './dto/create-booking.dto'; import { EventType } from '../entity/event-type.entity'; // --- Type-Safe Maps --- @@ -520,7 +520,7 @@ export class CalendarService { async createBooking( eventId: number, - createBookingDto: CreateBookingDto, + createBookingDto: CalendarCreateBookingDto, ): Promise { const { occurrenceStartTime, userId } = createBookingDto; diff --git a/server/src/calendar/dto/create-booking.dto.ts b/server/src/calendar/dto/create-booking.dto.ts index e68ee4b..260b3f4 100644 --- a/server/src/calendar/dto/create-booking.dto.ts +++ b/server/src/calendar/dto/create-booking.dto.ts @@ -7,23 +7,28 @@ import { Min, } from 'class-validator'; import { Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; -export class CreateBookingDto { +export class CalendarCreateBookingDto { @IsNotEmpty() @Type(() => Date) @IsDate() + @ApiProperty() occurrenceStartTime: Date; @IsNotEmpty() @IsInt() + @ApiProperty() userId: number; // Replaces userName/userEmail @IsOptional() @IsInt() @Min(1) + @ApiProperty() reservedSeatsCount?: number = 1; @IsOptional() @IsString() + @ApiProperty() notes?: string; }