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 01f29c5..7735e4f 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,18 +1,18 @@

Naptár

-
- - add -
+ + + +
- - @if (workflow() == 'create') { +@if (workflow() == 'event-create') { + - } +} @if (workflow() == 'event-dashboard' && selectedEvent()) { @@ -27,7 +27,7 @@ @if (dialogDefinition.isRendered()) { } 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 22500ca..1d2404b 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 @@ -1,11 +1,7 @@ import { - AfterViewInit, Component, - effect, ElementRef, inject, - Injector, - OnInit, signal, Type, ViewChild, @@ -17,27 +13,28 @@ import dayGridPlugin from '@fullcalendar/daygrid'; import timeGridPlugin from '@fullcalendar/timegrid'; import listPlugin from '@fullcalendar/list'; import interactionPlugin from '@fullcalendar/interaction'; -import { Button, Modal } from '@rschneider/ng-daisyui'; +import { Modal } from '@rschneider/ng-daisyui'; import { CreateEventForm } from '../create-event-form/create-event-form'; import { CalendarService } from '../../services/calendar.service'; -import { CalendarEventDto, EventsInRangeDTO } from '../../models/events-in-range-dto.model'; +import { CalendarEventDto } from '../../models/events-in-range-dto.model'; import { map } from 'rxjs'; import { SingleEventDashboard } from './single-event-dashboard/single-event-dashboard'; -import { CommonModule, JsonPipe, NgComponentOutlet } from '@angular/common'; +import { CommonModule, NgComponentOutlet } from '@angular/common'; import { - SingleEventDashboardEventDelete + SingleEventDashboardEventDelete, } from './single-event-dashboard-event-delete/single-event-dashboard-event-delete'; import { - SingleEventDashboardEventCancel + SingleEventDashboardEventCancel, } from './single-event-dashboard-event-cancel/single-event-dashboard-event-cancel'; +import { SingleEventDashboardEventEdit } from './single-event-dashboard-event-edit/single-event-dashboard-event-edit'; @Component({ selector: 'app-calendar-view', - imports: [FullCalendarModule, CommonModule, Modal,NgComponentOutlet, CreateEventForm, SingleEventDashboard, JsonPipe], + imports: [FullCalendarModule, CommonModule, Modal, NgComponentOutlet, CreateEventForm, SingleEventDashboard], templateUrl: './calendar-view.html', styleUrl: './calendar-view.css', }) -export class CalendarView implements OnInit, AfterViewInit { +export class CalendarView { @ViewChild('startHour') startHour!: ElementRef; @ViewChild('calendar') calendarComponent: FullCalendarComponent | undefined; @@ -45,8 +42,8 @@ export class CalendarView implements OnInit, AfterViewInit { calendarService = inject(CalendarService); workflow = signal(''); - selectedDate = signal(undefined); - selectedEvent = signal(undefined) + selectedDate = signal(undefined); + selectedEvent = signal(undefined); calendarOptions: CalendarOptions; dialogs: DialogConfig[] = []; @@ -69,26 +66,17 @@ export class CalendarView implements OnInit, AfterViewInit { eventClick: (info) => { this.selectedEvent.set(info.event.extendedProps['event']); this.workflow.set('event-dashboard'); + this.selectedDate.set(undefined); }, dateClick: (info) => { - console.info('create new evet for day ',info); - this.workflow.set('create'); + this.workflow.set('event-create'); this.selectedDate.set(info.date); this.selectedEvent.set(undefined); }, }; - - const injector = Injector.create({ - providers: [ - { - provide: 'closeDialog', - useValue: () => this.closeDialog() - } - ] - }); this.dialogs = [ { component: SingleEventDashboardEventDelete, @@ -99,10 +87,9 @@ export class CalendarView implements OnInit, AfterViewInit { componentInputs: () => { return { 'event': this.selectedEvent(), - 'onAction': this.handleAction - } + 'onAction': this.handleAction, + }; }, - componentOutputs: () => injector }, @@ -115,13 +102,24 @@ export class CalendarView implements OnInit, AfterViewInit { componentInputs: () => { return { 'event': this.selectedEvent(), - 'onAction': this.handleAction - } + 'onAction': this.handleAction, + }; }, - componentOutputs: () => injector - - } - ] + }, + { + component: SingleEventDashboardEventEdit, + isRendered: () => this.workflow() == 'event-edit', + // 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 { @@ -129,39 +127,33 @@ export class CalendarView implements OnInit, AfterViewInit { console.info('fetching events', fetchInfo); const start = fetchInfo.start; const end = fetchInfo.end; - // if ( fetchInfo ){ - // console.info("fetchinfo", fetchInfo); - // successCallback([]); - // return; - // } this.calendarService.getEventsInRange({ startTime: start, endTime: end, }) .pipe( map(value => { - console.info("vent got" , value ); - const events: EventInput[] = value.map( (model) => { + const events: EventInput[] = value.map((model) => { const calendarEvent: EventInput = { start: new Date(model.startTime), // end: model.end_time, title: model.title, extendedProps: { - event: model - } + event: model, + }, }; - if ( model.eventType){ + if (model.eventType) { calendarEvent.borderColor = model.eventType.color; } // calendarEvent.backgroundColor = "#00ff00" return calendarEvent; - }) + }); return events; }), ) .subscribe({ next: (events) => { - console.info("calendar events", events); + console.info('calendar events', events); successCallback(events); } , @@ -173,44 +165,38 @@ export class CalendarView implements OnInit, AfterViewInit { ); } - ngAfterViewInit(): void { - - } - - ngOnInit(): void { - - } - - 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, - }); - } + // 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(''); } - onDashboardAction(action: string){ - console.info("dashboard event", action); + onDashboardAction(action: string) { + console.info('dashboard event', action); switch (action) { case 'event_delete': this.workflow.set('event-delete'); break; case 'event_cancel': - console.info("event cancel clicked"); this.workflow.set('event-cancel'); break; + case 'event_edit': + this.workflow.set('event-edit'); + break; } } @@ -218,7 +204,7 @@ export class CalendarView implements OnInit, AfterViewInit { // This function is passed into the child handleAction = (msg: string) => { console.log('Parent received:', msg); - if ( msg == 'close'){ + if (msg == 'close') { this.closeDialog(); } @@ -226,11 +212,10 @@ export class CalendarView implements OnInit, AfterViewInit { } -export interface DialogConfig{ +export interface DialogConfig { component: Type; componentInputs?: () => { [key: string]: any }; closeClick: () => void, modalBoxStyleClass: string isRendered: () => boolean; - componentOutputs: () => Injector } diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.css b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.css new file mode 100644 index 0000000..e69de29 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 new file mode 100644 index 0000000..5d6f6e2 --- /dev/null +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.html @@ -0,0 +1,3 @@ + 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 new file mode 100644 index 0000000..7340727 --- /dev/null +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-edit/single-event-dashboard-event-edit.ts @@ -0,0 +1,28 @@ +import { Component, 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'; + +@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 { + + selectedDate = signal(undefined); + event = input(); + onAction = input.required<(msg: string) => void>(); + + protected readonly SvgIcons = SvgIcons; + + protected triggerAction() { + + this.onAction()('close') + } +} diff --git a/admin/src/app/features/calendar/components/create-event-form/create-event-form.html b/admin/src/app/features/calendar/components/create-event-form/create-event-form.html index fa09136..ef8c8ad 100644 --- a/admin/src/app/features/calendar/components/create-event-form/create-event-form.html +++ b/admin/src/app/features/calendar/components/create-event-form/create-event-form.html @@ -1,7 +1,5 @@ -
-
-
+

Esemény {{ isEditMode ? 'szerkesztése' : 'létrehozása' }}

@@ -38,7 +36,7 @@
- @if (isRecurring?.value) { + @if (isRecurring?.value === true) {

Ismétlődés

@@ -114,6 +112,4 @@
-
-
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 7a6cc49..5709b6b 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 @@ -1,6 +1,6 @@ -import { Component, effect, input, OnInit, output, signal } from '@angular/core'; +import { Component, input, OnInit, output, signal } from '@angular/core'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { EventService } from '../../../events/services/event.service'; import { mergeMap, switchMap, tap } from 'rxjs/operators'; import { Observable, of } from 'rxjs'; @@ -9,6 +9,7 @@ import { EventType } from '../../../event-type/models/event-type.model'; import { EventTypeService } from '../../../event-type/services/event-type.service'; import { CalendarService } from '../../services/calendar.service'; import { EventFormDTO } from '../../models/event-form-dto.model'; +import { format } from 'date-fns'; export type Frequency = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'; export type FrequencyOption = { @@ -16,11 +17,10 @@ export type FrequencyOption = { label: string; } - @Component({ selector: 'app-create-event-form', imports: [ - ReactiveFormsModule + ReactiveFormsModule, ], templateUrl: './create-event-form.html', styleUrl: './create-event-form.css', @@ -29,27 +29,27 @@ export class CreateEventForm implements OnInit { form: FormGroup; isEditMode = false; ready = output(); - id= input() ; - date = input(); - eventTypes = signal([]) + id = input(); + date = input(); + eventTypes = signal([]); - private numericFields: (keyof EventFormDTO)[] = ["event_type_id"]; + private numericFields: (keyof EventFormDTO)[] = ['event_type_id']; frequencyOptions: FrequencyOption[] = [ { frequency: 'DAILY', - label: 'Napi' + label: 'Napi', }, { frequency: 'WEEKLY', - label: 'Heti' + label: 'Heti', }, { frequency: 'MONTHLY', - label: 'Havi' - },{ + label: 'Havi', + }, { frequency: 'YEARLY', - label: 'Éves' - } + label: 'Éves', + }, ]; weekDayOptions = [ @@ -68,36 +68,29 @@ export class CreateEventForm implements OnInit { private router: Router, private eventService: EventService, private calendarService: CalendarService, - private eventTypeService: EventTypeService + private eventTypeService: EventTypeService, ) { this.form = this.fb.group({ - eventTypeId: [null,Validators.required], - title: [null,Validators.required], + eventTypeId: [null, Validators.required], + title: [null, Validators.required], description: [null], - startTime: [null,Validators.required], - endTime: [null,Validators.required], + startTime: [null, Validators.required], + endTime: [null, Validators.required], is_recurring: [false], recurrenceRule: this.fb.group({ - frequency: ['DAILY'] ,//'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'; - interval: [1] ,//number + frequency: ['DAILY'],//'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'; + interval: [1],//number endDate: [null], // Date count: [null], // number byDay: [null], // string byMonthDay: [null], // string byMonth: [null], // string - }) - }); - - effect(() => { - console.info("effect run") + }), }); } - - ngOnInit(): void { - console.info("oninit run") this.isRecurring?.valueChanges.subscribe(isRecurring => { const recurringRule = this.form.get('recurrenceRule'); const frequency = recurringRule?.get('frequency'); @@ -118,51 +111,83 @@ export class CreateEventForm implements OnInit { of(this.id()).pipe( tap(id => { if (id) { - console.info("edit mode", 'edit') this.isEditMode = true; - }else{ - - const start = this.date() || new Date(); - const end = this.date() || new Date(); - - console.info("staring form with date", start,end); - start.setMinutes(0,0); - end.setHours(start.getHours()+1,0,0); - start.setMinutes(start.getMinutes() - start.getTimezoneOffset()); - const startString = start.toISOString().slice(0,16); - - end.setMinutes(end.getMinutes() - end.getTimezoneOffset()); - const endTime = end.toISOString().slice(0,16); - - console.info("Date start",start.toLocaleString("hu-HU", {timeStyle: 'short', dateStyle: 'short'})); - this.form.patchValue({ - // start_time: new Date().getTime().toString(), - startTime: startString, - endTime: endTime - }) } }), mergeMap(() => { - return this.eventTypeService.find({}); - } + return this.eventTypeService.find({}); + }, ), tap(eventTypes => { this.eventTypes.set(eventTypes.data); }), switchMap(() => { if (this.isEditMode && this.id()) { + console.info('edit mode', 'edit', this.id()); return this.eventService.findOne(this.id()!); } return of(null); }), ).subscribe(event => { - console.info("subscribe form done") if (event) { - this.form.patchValue(event); + const startTime = this.formatIsoStringForInput(event.startTime); + const endTime = this.formatIsoStringForInput(event.endTime); + this.form.patchValue( + { + title: event.title, + description: event.description, + startTime, + endTime, + eventTypeId: event?.eventType?.id, + is_recurring: event.isRecurring , + recurrenceRule: { + frequency: event.recurrenceRule?.frequency, + interval: event.recurrenceRule?.interval, + endDate: event.recurrenceRule?.endDate, + count: event.recurrenceRule?.count, + byDay: event.recurrenceRule?.byDay, + byMonthDay: event.recurrenceRule?.byMonthDay, + byMonth: event.recurrenceRule?.byMonth, + + }, + }, + ); + } else { + + const start = this.createDate(this.date()); + const end = this.createDate(this.date(), start.getHours() + 1); + + const startTime = this.formatDateForInput(start); + const endTime = this.formatDateForInput(end); + + this.form.patchValue({ + startTime, + endTime, + }); } }); } + + createDate(date?: Date, hours?: number) { + const start = date ? new Date(date?.getTime()) : new Date(); + if (hours != undefined) { + start.setHours(start.getHours() + hours, 0, 0); + } else { + start.setMinutes(0, 0); + } + return start; + } + + formatDateForInput(dateObject: Date) { + return format(dateObject, 'yyyy-MM-dd\'T\'HH:mm'); + } + + formatIsoStringForInput(dateString: string) { + const date = new Date(dateString); + return this.formatDateForInput(date); + } + onByDayChange(event: any) { const target = event.target as HTMLInputElement; const day = target.value; @@ -189,7 +214,7 @@ export class CreateEventForm implements OnInit { return; } - const payload: EventFormDTO = { ...this.form.value }; + const payload: EventFormDTO = { ...this.form.value }; if (!payload.is_recurring) { delete payload.recurrenceRule; @@ -197,13 +222,13 @@ export class CreateEventForm implements OnInit { for (const field of this.numericFields) { if (payload[field] != null && payload[field] !== '') { - if ( typeof payload[field] !== 'number'){ + if (typeof payload[field] !== 'number') { payload[field] = parseFloat(payload[field] as string) as never; } } } - let action$: Observable; + let action$: Observable; if (this.isEditMode && this.id()) { action$ = this.calendarService.update(this.id()!, payload); @@ -213,45 +238,48 @@ export class CreateEventForm implements OnInit { } action$.subscribe({ - next: () => this.router.navigate(['/events']), - error: (err) => console.error('Failed to save event', err) + next: () => this.router.navigate(['/calendar']), + error: (err) => console.error('Failed to save event', err), }); } - get eventType(){ + get eventType() { return this.form.get('eventTypeId'); } - get title(){ + get title() { return this.form.get('title'); } - get description(){ + + get description() { return this.form.get('description'); } - get startTime(){ + + get startTime() { return this.form.get('startTime'); } - get endTime(){ + + get endTime() { return this.form.get('endTime'); } - get isRecurring(){ + get isRecurring() { return this.form.get('is_recurring'); } - get recurringRule(){ + get recurringRule() { return this.form.get('recurrenceRule'); } - get frequency(){ + get frequency() { return this.form.get('recurrenceRule')?.get('frequency'); } - get interval(){ + get interval() { return this.form.get('recurrenceRule')?.get('interval'); } - get endDate(){ + get endDate() { return this.form.get('recurrenceRule')?.get('endDate'); } @@ -267,7 +295,7 @@ export class CreateEventForm implements OnInit { return this.form.get('recurrenceRule')?.get('byMonth'); } - doReady(){ + doReady() { this.ready.emit(); } } diff --git a/admin/src/app/features/calendar/services/calendar.service.ts b/admin/src/app/features/calendar/services/calendar.service.ts index d9db062..91d4463 100644 --- a/admin/src/app/features/calendar/services/calendar.service.ts +++ b/admin/src/app/features/calendar/services/calendar.service.ts @@ -37,7 +37,7 @@ export class CalendarService { } public update(id: number,data: UpdateEventFormDTO): Observable { - return this.http.put(this.apiUrl+'/events', data); + return this.http.patch(this.apiUrl+'/events/'+id, data); } } diff --git a/admin/src/app/features/events/models/event.model.ts b/admin/src/app/features/events/models/event.model.ts index 0e95fd3..7fd3cda 100644 --- a/admin/src/app/features/events/models/event.model.ts +++ b/admin/src/app/features/events/models/event.model.ts @@ -1,15 +1,35 @@ // dvbooking-cli/src/templates/angular/model.ts.tpl // Generated by the CLI + export interface Event { id: number; - event_type_id: number; title: string; description: string; - start_time: Date; - end_time: Date; + startTime: string; + endTime: string; timezone: string; - is_recurring: boolean; - created_at: Date; - updated_at: Date; + isRecurring: boolean; + created_at: string; + updated_at: string; + eventType: EventType; + recurrenceRule: RecurrenceRule +} +export interface EventType{ + id: number; + description:string; + name: string; + color: string; +} + + +export interface RecurrenceRule{ + + frequency?: string, + interval?: number, + endDate?: string, + count?: number, + byDay?: string, + byMonthDay?: string, + byMonth?: string, } diff --git a/admin/src/app/features/events/services/event.service.ts b/admin/src/app/features/events/services/event.service.ts index 2ccdcb9..6f8570e 100644 --- a/admin/src/app/features/events/services/event.service.ts +++ b/admin/src/app/features/events/services/event.service.ts @@ -59,7 +59,8 @@ export class EventService { * Find a single record by its ID. */ public findOne(id: number): Observable { - return this.http.get(`${this.apiUrl}/${id}`); + return this.http.get(`${this.apiUrl}/${id}` + ); } /** @@ -82,4 +83,4 @@ export class EventService { public remove(id: number): Observable { return this.http.delete(`${this.apiUrl}/${id}`); } -} \ No newline at end of file +} diff --git a/server/src/calendar/calendar.controller.ts b/server/src/calendar/calendar.controller.ts index aef7b93..26fea73 100644 --- a/server/src/calendar/calendar.controller.ts +++ b/server/src/calendar/calendar.controller.ts @@ -27,24 +27,6 @@ import { CancelBookingDto } from './dto/cancel-booking.dto'; export class CalendarController { constructor(private readonly calendarService: CalendarService) {} - @Get('test') - getCalendarTest( - @Query('startDate') startDate: string, - @Query('endDate') endDate: string, - ) { - console.log('--- TEST ENDPOINT ---'); - console.log('startDate received:', startDate, '| Type:', typeof startDate); - console.log('endDate received:', endDate, '| Type:', typeof endDate); - return { - message: 'Test successful. Check your server console logs.', - received: { - startDate, - endDate, - }, - }; - } - - // The primary endpoint to get event occurrences @Get() getCalendarEvents(@Query() getCalendarDto: GetCalendarDto) { return this.calendarService.getEventsInRange( diff --git a/server/src/calendar/calendar.service.ts b/server/src/calendar/calendar.service.ts index 2e1203f..46e94a0 100644 --- a/server/src/calendar/calendar.service.ts +++ b/server/src/calendar/calendar.service.ts @@ -148,7 +148,7 @@ export class CalendarService { if (!freq) continue; - let untilDate: Date|undefined = undefined; + let untilDate: Date | undefined = undefined; // typeorm does not convert postgres type 'date' to javascript date object if (event?.recurrenceRule?.endDate) { untilDate = new Date(event.recurrenceRule.endDate); @@ -204,9 +204,6 @@ export class CalendarService { // --- Other service methods (createEvent, etc.) remain unchanged --- async createEvent(createEventDto: CreateEventDto): Promise { - console.log('[CalendarService] Entering createEvent method.'); - console.log('[CalendarService] Received DTO:', JSON.stringify(createEventDto, null, 2)); - const { recurrenceRule, ...eventData } = createEventDto; const newEvent: Omit< @@ -224,32 +221,44 @@ export class CalendarService { // check if event type exists if (eventData.eventTypeId) { - console.log(`[CalendarService] Event has eventTypeId: ${eventData.eventTypeId}. Fetching event type...`); + console.log( + `[CalendarService] Event has eventTypeId: ${eventData.eventTypeId}. Fetching event type...`, + ); const eventType = await this.eventTypeRepository.findOneBy({ id: eventData.eventTypeId, }); if (!eventType) { - console.error(`[CalendarService] Event type with ID ${eventData.eventTypeId} not found.`); + console.error( + `[CalendarService] Event type with ID ${eventData.eventTypeId} not found.`, + ); throw new BadRequestException( {}, 'Event type not found ' + eventData.eventTypeId, ); } newEvent.eventType = eventType; - console.log('[CalendarService] Successfully attached event type:', eventType); + console.log( + '[CalendarService] Successfully attached event type:', + eventType, + ); } else { console.log('[CalendarService] No eventTypeId provided.'); } console.log('[CalendarService] Creating event entity...'); const event = this.eventRepository.create(newEvent); - + console.log('[CalendarService] Saving event entity to the database...'); const savedEvent = await this.eventRepository.save(event); - console.log('[CalendarService] Event saved successfully. Saved event ID:', savedEvent.id); + console.log( + '[CalendarService] Event saved successfully. Saved event ID:', + savedEvent.id, + ); if (savedEvent.isRecurring && recurrenceRule) { - console.log(`[CalendarService] Event is recurring. Creating recurrence rule...`); + console.log( + `[CalendarService] Event is recurring. Creating recurrence rule...`, + ); const rule = this.recurrenceRuleRepository.create({ ...recurrenceRule, event: savedEvent, @@ -257,10 +266,14 @@ export class CalendarService { await this.recurrenceRuleRepository.save(rule); console.log('[CalendarService] Recurrence rule saved successfully.'); } else { - console.log('[CalendarService] Event is not recurring or no recurrence rule provided.'); + console.log( + '[CalendarService] Event is not recurring or no recurrence rule provided.', + ); } - console.log(`[CalendarService] Fetching final event by ID ${savedEvent.id} to return.`); + console.log( + `[CalendarService] Fetching final event by ID ${savedEvent.id} to return.`, + ); const finalEvent = await this.getEventById(savedEvent.id); console.log('[CalendarService] Exiting createEvent method.'); return finalEvent; @@ -333,7 +346,59 @@ export class CalendarService { id: number, updateEventDto: CreateEventDto, ): Promise { - await this.eventRepository.update(id, updateEventDto); + // 1. Fetch the existing event (ensure relations are loaded if needed) + const event = await this.getEventById(id); + + // 2. Update basic fields + event.updatedAt = new Date(); + event.title = updateEventDto.title; + event.description = updateEventDto.description; + event.startTime = updateEventDto.startTime; + event.endTime = updateEventDto.endTime; + event.isRecurring = !!updateEventDto.isRecurring; + + // 3. Handle Event Type Relationship + if (updateEventDto.eventTypeId) { + const eventType = await this.eventTypeRepository.findOneBy({ + id: updateEventDto.eventTypeId, + }); + if (!eventType) { + throw new NotFoundException( + `EventType with ID ${updateEventDto.eventTypeId} not found`, + ); + } + // FIX: You forgot to assign this in your original code + event.eventType = eventType; + } else { + // Handle case where event type is removed/null + event.eventType = undefined; + } + + // 4. Handle Recurrence Rule + // Because your entity has @OneToOne(..., { cascade: true }), + // modifying event.recurrenceRule and calling eventRepository.save(event) + // will automatically save/update the rule. + if (event.isRecurring && updateEventDto.recurrenceRule) { + const updateRRule = updateEventDto.recurrenceRule; + + // If no rule exists yet, initialize a new one + if (!event.recurrenceRule) { + event.recurrenceRule = this.recurrenceRuleRepository.create({}); + } + + // Update the properties on the relation object + event.recurrenceRule.frequency = updateRRule.frequency; + event.recurrenceRule.interval = updateRRule.interval; + event.recurrenceRule.byDay = updateRRule.byDay as string; + event.recurrenceRule.endDate = updateRRule.endDate as Date; + event.recurrenceRule.count = updateRRule.count as number; + } + + // 5. Use SAVE instead of UPDATE + // This handles the relations correctly and prevents the "one-to-many" error. + await this.eventRepository.save(event); + + // 6. Return the updated event return this.getEventById(id); } diff --git a/server/src/event/events.service.ts b/server/src/event/events.service.ts index 507c7ce..23efc90 100644 --- a/server/src/event/events.service.ts +++ b/server/src/event/events.service.ts @@ -80,7 +80,10 @@ export class EventsService { } async findOne(id: number) { - const record = await this.eventRepository.findOneBy({ id: id as any }); + const record = await this.eventRepository.findOne({ + where: { id: id }, + relations: ['eventType', 'recurrenceRule', 'exceptions'], + }); if (!record) { throw new NotFoundException(`Event with ID ${id} not found`); } @@ -100,4 +103,4 @@ export class EventsService { } return { deleted: true, id }; } -} \ No newline at end of file +}