add calendar dashboard edit

This commit is contained in:
Schneider Roland 2025-12-01 22:39:39 +01:00
parent a043d64229
commit fa098f4a1b
13 changed files with 310 additions and 199 deletions

View File

@ -1,18 +1,18 @@
<div>
<h1>Naptár</h1>
<div>
<input type="text" name="startHour" id="starthour" #startHour>
<a class="btn" (click)="addEvent($event)">add</a>
</div>
<!-- <div>-->
<!-- <input type="text" name="startHour" id="starthour" #startHour>-->
<!-- <a class="btn" (click)="addEvent($event)">add</a>-->
<!-- </div>-->
<full-calendar #calendar [options]="calendarOptions"></full-calendar>
</div>
<rs-daisy-modal [isOpen]="workflow() == 'create'" (closeClick)="closeDialog()">
@if (workflow() == 'create') {
@if (workflow() == 'event-create') {
<rs-daisy-modal [isOpen]="true" (closeClick)="closeDialog()" [modalBoxStyleClass]="'max-w-none w-2xl'">
<app-create-event-form (ready)="closeDialog()" [date]="selectedDate()"
[id]="undefined"></app-create-event-form>
}
</rs-daisy-modal>
}
@if (workflow() == 'event-dashboard' && selectedEvent()) {
<rs-daisy-modal [isOpen]="true" (closeClick)="closeDialog()" [modalBoxStyleClass]="'max-w-none w-2xl'">
@ -27,7 +27,7 @@
@if (dialogDefinition.isRendered()) {
<rs-daisy-modal [isOpen]="true" (closeClick)="closeDialog()" [modalBoxStyleClass]="'max-w-none w-2xl'">
<ng-container
*ngComponentOutlet="dialogDefinition.component; inputs: dialogDefinition.componentInputs ? dialogDefinition.componentInputs() : {}; injector: dialogDefinition.componentOutputs()"
*ngComponentOutlet="dialogDefinition.component; inputs: dialogDefinition.componentInputs ? dialogDefinition.componentInputs() : {}; "
></ng-container>
</rs-daisy-modal>
}

View File

@ -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<string>('');
selectedDate = signal<Date|undefined>(undefined);
selectedEvent = signal<CalendarEventDto|undefined>(undefined)
selectedDate = signal<Date | undefined>(undefined);
selectedEvent = signal<CalendarEventDto | undefined>(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<any>;
componentInputs?: () => { [key: string]: any };
closeClick: () => void,
modalBoxStyleClass: string
isRendered: () => boolean;
componentOutputs: () => Injector
}

View File

@ -0,0 +1,3 @@
<app-create-event-form (ready)="onAction()" [date]="selectedDate()"
[id]="event()?.id"></app-create-event-form>

View File

@ -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<Date | undefined>(undefined);
event = input<CalendarEventDto>();
onAction = input.required<(msg: string) => void>();
protected readonly SvgIcons = SvgIcons;
protected triggerAction() {
this.onAction()('close')
}
}

View File

@ -1,7 +1,5 @@
<!-- Generated by the CLI -->
<div class="p-4 md:p-8">
<div class="card bg-base-100 shadow-xl mx-auto">
<div class="card-body">
<div class="">
<h2 class="card-title text-3xl">
Esemény {{ isEditMode ? 'szerkesztése' : 'létrehozása' }}
</h2>
@ -38,7 +36,7 @@
<input type="checkbox" formControlName="is_recurring" class="checkbox" />
</label></div>
@if (isRecurring?.value) {
@if (isRecurring?.value === true) {
<div formGroupName="recurrenceRule" class="space-y-4 p-4 border border-base-300 rounded-lg">
<h3 class="text-lg font-semibold">Ismétlődés</h3>
@ -114,6 +112,4 @@
</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
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 { EventService } from '../../../events/services/event.service';
@ -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<void>();
id= input<number | undefined>() ;
date = input<Date|undefined>();
eventTypes = signal<EventType[]>([])
id = input<number | undefined>();
date = input<Date | undefined>();
eventTypes = signal<EventType[]>([]);
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({});
}
},
),
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;
@ -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<Event|undefined>;
let action$: Observable<Event | undefined>;
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();
}
}

View File

@ -37,7 +37,7 @@ export class CalendarService {
}
public update(id: number,data: UpdateEventFormDTO): Observable<Event> {
return this.http.put<Event>(this.apiUrl+'/events', data);
return this.http.patch<Event>(this.apiUrl+'/events/'+id, data);
}
}

View File

@ -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,
}

View File

@ -59,7 +59,8 @@ export class EventService {
* Find a single record by its ID.
*/
public findOne(id: number): Observable<Event> {
return this.http.get<Event>(`${this.apiUrl}/${id}`);
return this.http.get<Event>(`${this.apiUrl}/${id}`
);
}
/**

View File

@ -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(

View File

@ -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<Event> {
console.log('[CalendarService] Entering createEvent method.');
console.log('[CalendarService] Received DTO:', JSON.stringify(createEventDto, null, 2));
const { recurrenceRule, ...eventData } = createEventDto;
const newEvent: Omit<
@ -224,19 +221,26 @@ 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.');
}
@ -246,10 +250,15 @@ export class CalendarService {
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<Event> {
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);
}

View File

@ -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`);
}