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

View File

@ -1,11 +1,7 @@
import { import {
AfterViewInit,
Component, Component,
effect,
ElementRef, ElementRef,
inject, inject,
Injector,
OnInit,
signal, signal,
Type, Type,
ViewChild, ViewChild,
@ -17,27 +13,28 @@ import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid'; import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list'; import listPlugin from '@fullcalendar/list';
import interactionPlugin from '@fullcalendar/interaction'; 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 { CreateEventForm } from '../create-event-form/create-event-form';
import { CalendarService } from '../../services/calendar.service'; 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 { map } from 'rxjs';
import { SingleEventDashboard } from './single-event-dashboard/single-event-dashboard'; import { SingleEventDashboard } from './single-event-dashboard/single-event-dashboard';
import { CommonModule, JsonPipe, NgComponentOutlet } from '@angular/common'; import { CommonModule, NgComponentOutlet } from '@angular/common';
import { import {
SingleEventDashboardEventDelete SingleEventDashboardEventDelete,
} from './single-event-dashboard-event-delete/single-event-dashboard-event-delete'; } from './single-event-dashboard-event-delete/single-event-dashboard-event-delete';
import { import {
SingleEventDashboardEventCancel SingleEventDashboardEventCancel,
} from './single-event-dashboard-event-cancel/single-event-dashboard-event-cancel'; } 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({ @Component({
selector: 'app-calendar-view', selector: 'app-calendar-view',
imports: [FullCalendarModule, CommonModule, Modal,NgComponentOutlet, CreateEventForm, SingleEventDashboard, JsonPipe], imports: [FullCalendarModule, CommonModule, Modal, NgComponentOutlet, CreateEventForm, SingleEventDashboard],
templateUrl: './calendar-view.html', templateUrl: './calendar-view.html',
styleUrl: './calendar-view.css', styleUrl: './calendar-view.css',
}) })
export class CalendarView implements OnInit, AfterViewInit { export class CalendarView {
@ViewChild('startHour') startHour!: ElementRef; @ViewChild('startHour') startHour!: ElementRef;
@ViewChild('calendar') calendarComponent: FullCalendarComponent | undefined; @ViewChild('calendar') calendarComponent: FullCalendarComponent | undefined;
@ -45,8 +42,8 @@ export class CalendarView implements OnInit, AfterViewInit {
calendarService = inject(CalendarService); calendarService = inject(CalendarService);
workflow = signal<string>(''); workflow = signal<string>('');
selectedDate = signal<Date|undefined>(undefined); selectedDate = signal<Date | undefined>(undefined);
selectedEvent = signal<CalendarEventDto|undefined>(undefined) selectedEvent = signal<CalendarEventDto | undefined>(undefined);
calendarOptions: CalendarOptions; calendarOptions: CalendarOptions;
dialogs: DialogConfig[] = []; dialogs: DialogConfig[] = [];
@ -69,26 +66,17 @@ export class CalendarView implements OnInit, AfterViewInit {
eventClick: (info) => { eventClick: (info) => {
this.selectedEvent.set(info.event.extendedProps['event']); this.selectedEvent.set(info.event.extendedProps['event']);
this.workflow.set('event-dashboard'); this.workflow.set('event-dashboard');
this.selectedDate.set(undefined);
}, },
dateClick: (info) => { dateClick: (info) => {
console.info('create new evet for day ',info); this.workflow.set('event-create');
this.workflow.set('create');
this.selectedDate.set(info.date); this.selectedDate.set(info.date);
this.selectedEvent.set(undefined); this.selectedEvent.set(undefined);
}, },
}; };
const injector = Injector.create({
providers: [
{
provide: 'closeDialog',
useValue: () => this.closeDialog()
}
]
});
this.dialogs = [ this.dialogs = [
{ {
component: SingleEventDashboardEventDelete, component: SingleEventDashboardEventDelete,
@ -99,10 +87,9 @@ export class CalendarView implements OnInit, AfterViewInit {
componentInputs: () => { componentInputs: () => {
return { return {
'event': this.selectedEvent(), 'event': this.selectedEvent(),
'onAction': this.handleAction 'onAction': this.handleAction,
} };
}, },
componentOutputs: () => injector
}, },
@ -115,13 +102,24 @@ export class CalendarView implements OnInit, AfterViewInit {
componentInputs: () => { componentInputs: () => {
return { return {
'event': this.selectedEvent(), '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 { 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); console.info('fetching events', fetchInfo);
const start = fetchInfo.start; const start = fetchInfo.start;
const end = fetchInfo.end; const end = fetchInfo.end;
// if ( fetchInfo ){
// console.info("fetchinfo", fetchInfo);
// successCallback([]);
// return;
// }
this.calendarService.getEventsInRange({ this.calendarService.getEventsInRange({
startTime: start, startTime: start,
endTime: end, endTime: end,
}) })
.pipe( .pipe(
map(value => { map(value => {
console.info("vent got" , value ); const events: EventInput[] = value.map((model) => {
const events: EventInput[] = value.map( (model) => {
const calendarEvent: EventInput = { const calendarEvent: EventInput = {
start: new Date(model.startTime), start: new Date(model.startTime),
// end: model.end_time, // end: model.end_time,
title: model.title, title: model.title,
extendedProps: { extendedProps: {
event: model event: model,
} },
}; };
if ( model.eventType){ if (model.eventType) {
calendarEvent.borderColor = model.eventType.color; calendarEvent.borderColor = model.eventType.color;
} }
// calendarEvent.backgroundColor = "#00ff00" // calendarEvent.backgroundColor = "#00ff00"
return calendarEvent; return calendarEvent;
}) });
return events; return events;
}), }),
) )
.subscribe({ .subscribe({
next: (events) => { next: (events) => {
console.info("calendar events", events); console.info('calendar events', events);
successCallback(events); successCallback(events);
} }
, ,
@ -173,44 +165,38 @@ export class CalendarView implements OnInit, AfterViewInit {
); );
} }
ngAfterViewInit(): void { // protected addEvent($event: PointerEvent) {
// let hourStr = this.startHour.nativeElement.value;
} // const hour = parseInt(hourStr, 10);
// const date = new Date();
ngOnInit(): void { // const start = new Date(date.getTime());
// start.setHours(hour, 0, 0);
} // const end = new Date(date.getTime());
// // end.setHours(3,0,0)
protected addEvent($event: PointerEvent) { //
let hourStr = this.startHour.nativeElement.value; // this.calendarComponent?.getApi().addEvent({
const hour = parseInt(hourStr, 10); // title: 'Event at ' + hour,
const date = new Date(); // start,
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() { closeDialog() {
this.workflow.set(''); this.workflow.set('');
} }
onDashboardAction(action: string){ onDashboardAction(action: string) {
console.info("dashboard event", action); console.info('dashboard event', action);
switch (action) { switch (action) {
case 'event_delete': case 'event_delete':
this.workflow.set('event-delete'); this.workflow.set('event-delete');
break; break;
case 'event_cancel': case 'event_cancel':
console.info("event cancel clicked");
this.workflow.set('event-cancel'); this.workflow.set('event-cancel');
break; 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 // This function is passed into the child
handleAction = (msg: string) => { handleAction = (msg: string) => {
console.log('Parent received:', msg); console.log('Parent received:', msg);
if ( msg == 'close'){ if (msg == 'close') {
this.closeDialog(); this.closeDialog();
} }
@ -226,11 +212,10 @@ export class CalendarView implements OnInit, AfterViewInit {
} }
export interface DialogConfig{ export interface DialogConfig {
component: Type<any>; component: Type<any>;
componentInputs?: () => { [key: string]: any }; componentInputs?: () => { [key: string]: any };
closeClick: () => void, closeClick: () => void,
modalBoxStyleClass: string modalBoxStyleClass: string
isRendered: () => boolean; 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 --> <!-- Generated by the CLI -->
<div class="p-4 md:p-8"> <div class="">
<div class="card bg-base-100 shadow-xl mx-auto">
<div class="card-body">
<h2 class="card-title text-3xl"> <h2 class="card-title text-3xl">
Esemény {{ isEditMode ? 'szerkesztése' : 'létrehozása' }} Esemény {{ isEditMode ? 'szerkesztése' : 'létrehozása' }}
</h2> </h2>
@ -38,7 +36,7 @@
<input type="checkbox" formControlName="is_recurring" class="checkbox" /> <input type="checkbox" formControlName="is_recurring" class="checkbox" />
</label></div> </label></div>
@if (isRecurring?.value) { @if (isRecurring?.value === true) {
<div formGroupName="recurrenceRule" class="space-y-4 p-4 border border-base-300 rounded-lg"> <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> <h3 class="text-lg font-semibold">Ismétlődés</h3>
@ -114,6 +112,4 @@
</button> </button>
</div> </div>
</form> </form>
</div>
</div>
</div> </div>

View File

@ -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 { 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 { EventService } from '../../../events/services/event.service';
import { mergeMap, switchMap, tap } from 'rxjs/operators'; import { mergeMap, switchMap, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs'; 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 { EventTypeService } from '../../../event-type/services/event-type.service';
import { CalendarService } from '../../services/calendar.service'; import { CalendarService } from '../../services/calendar.service';
import { EventFormDTO } from '../../models/event-form-dto.model'; import { EventFormDTO } from '../../models/event-form-dto.model';
import { format } from 'date-fns';
export type Frequency = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'; export type Frequency = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
export type FrequencyOption = { export type FrequencyOption = {
@ -16,11 +17,10 @@ export type FrequencyOption = {
label: string; label: string;
} }
@Component({ @Component({
selector: 'app-create-event-form', selector: 'app-create-event-form',
imports: [ imports: [
ReactiveFormsModule ReactiveFormsModule,
], ],
templateUrl: './create-event-form.html', templateUrl: './create-event-form.html',
styleUrl: './create-event-form.css', styleUrl: './create-event-form.css',
@ -29,27 +29,27 @@ export class CreateEventForm implements OnInit {
form: FormGroup; form: FormGroup;
isEditMode = false; isEditMode = false;
ready = output<void>(); ready = output<void>();
id= input<number | undefined>() ; id = input<number | undefined>();
date = input<Date|undefined>(); date = input<Date | undefined>();
eventTypes = signal<EventType[]>([]) eventTypes = signal<EventType[]>([]);
private numericFields: (keyof EventFormDTO)[] = ["event_type_id"]; private numericFields: (keyof EventFormDTO)[] = ['event_type_id'];
frequencyOptions: FrequencyOption[] = [ frequencyOptions: FrequencyOption[] = [
{ {
frequency: 'DAILY', frequency: 'DAILY',
label: 'Napi' label: 'Napi',
}, },
{ {
frequency: 'WEEKLY', frequency: 'WEEKLY',
label: 'Heti' label: 'Heti',
}, { }, {
frequency: 'MONTHLY', frequency: 'MONTHLY',
label: 'Havi' label: 'Havi',
},{ }, {
frequency: 'YEARLY', frequency: 'YEARLY',
label: 'Éves' label: 'Éves',
} },
]; ];
weekDayOptions = [ weekDayOptions = [
@ -68,36 +68,29 @@ export class CreateEventForm implements OnInit {
private router: Router, private router: Router,
private eventService: EventService, private eventService: EventService,
private calendarService: CalendarService, private calendarService: CalendarService,
private eventTypeService: EventTypeService private eventTypeService: EventTypeService,
) { ) {
this.form = this.fb.group({ this.form = this.fb.group({
eventTypeId: [null,Validators.required], eventTypeId: [null, Validators.required],
title: [null,Validators.required], title: [null, Validators.required],
description: [null], description: [null],
startTime: [null,Validators.required], startTime: [null, Validators.required],
endTime: [null,Validators.required], endTime: [null, Validators.required],
is_recurring: [false], is_recurring: [false],
recurrenceRule: this.fb.group({ recurrenceRule: this.fb.group({
frequency: ['DAILY'] ,//'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'; frequency: ['DAILY'],//'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
interval: [1] ,//number interval: [1],//number
endDate: [null], // Date endDate: [null], // Date
count: [null], // number count: [null], // number
byDay: [null], // string byDay: [null], // string
byMonthDay: [null], // string byMonthDay: [null], // string
byMonth: [null], // string byMonth: [null], // string
}) }),
});
effect(() => {
console.info("effect run")
}); });
} }
ngOnInit(): void { ngOnInit(): void {
console.info("oninit run")
this.isRecurring?.valueChanges.subscribe(isRecurring => { this.isRecurring?.valueChanges.subscribe(isRecurring => {
const recurringRule = this.form.get('recurrenceRule'); const recurringRule = this.form.get('recurrenceRule');
const frequency = recurringRule?.get('frequency'); const frequency = recurringRule?.get('frequency');
@ -118,51 +111,83 @@ export class CreateEventForm implements OnInit {
of(this.id()).pipe( of(this.id()).pipe(
tap(id => { tap(id => {
if (id) { if (id) {
console.info("edit mode", 'edit')
this.isEditMode = true; 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(() => { mergeMap(() => {
return this.eventTypeService.find({}); return this.eventTypeService.find({});
} },
), ),
tap(eventTypes => { tap(eventTypes => {
this.eventTypes.set(eventTypes.data); this.eventTypes.set(eventTypes.data);
}), }),
switchMap(() => { switchMap(() => {
if (this.isEditMode && this.id()) { if (this.isEditMode && this.id()) {
console.info('edit mode', 'edit', this.id());
return this.eventService.findOne(this.id()!); return this.eventService.findOne(this.id()!);
} }
return of(null); return of(null);
}), }),
).subscribe(event => { ).subscribe(event => {
console.info("subscribe form done")
if (event) { 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) { onByDayChange(event: any) {
const target = event.target as HTMLInputElement; const target = event.target as HTMLInputElement;
const day = target.value; const day = target.value;
@ -189,7 +214,7 @@ export class CreateEventForm implements OnInit {
return; return;
} }
const payload: EventFormDTO = { ...this.form.value }; const payload: EventFormDTO = { ...this.form.value };
if (!payload.is_recurring) { if (!payload.is_recurring) {
delete payload.recurrenceRule; delete payload.recurrenceRule;
@ -197,13 +222,13 @@ export class CreateEventForm implements OnInit {
for (const field of this.numericFields) { for (const field of this.numericFields) {
if (payload[field] != null && payload[field] !== '') { 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; payload[field] = parseFloat(payload[field] as string) as never;
} }
} }
} }
let action$: Observable<Event|undefined>; let action$: Observable<Event | undefined>;
if (this.isEditMode && this.id()) { if (this.isEditMode && this.id()) {
action$ = this.calendarService.update(this.id()!, payload); action$ = this.calendarService.update(this.id()!, payload);
@ -213,45 +238,48 @@ export class CreateEventForm implements OnInit {
} }
action$.subscribe({ action$.subscribe({
next: () => this.router.navigate(['/events']), next: () => this.router.navigate(['/calendar']),
error: (err) => console.error('Failed to save event', err) error: (err) => console.error('Failed to save event', err),
}); });
} }
get eventType(){ get eventType() {
return this.form.get('eventTypeId'); return this.form.get('eventTypeId');
} }
get title(){ get title() {
return this.form.get('title'); return this.form.get('title');
} }
get description(){
get description() {
return this.form.get('description'); return this.form.get('description');
} }
get startTime(){
get startTime() {
return this.form.get('startTime'); return this.form.get('startTime');
} }
get endTime(){
get endTime() {
return this.form.get('endTime'); return this.form.get('endTime');
} }
get isRecurring(){ get isRecurring() {
return this.form.get('is_recurring'); return this.form.get('is_recurring');
} }
get recurringRule(){ get recurringRule() {
return this.form.get('recurrenceRule'); return this.form.get('recurrenceRule');
} }
get frequency(){ get frequency() {
return this.form.get('recurrenceRule')?.get('frequency'); return this.form.get('recurrenceRule')?.get('frequency');
} }
get interval(){ get interval() {
return this.form.get('recurrenceRule')?.get('interval'); return this.form.get('recurrenceRule')?.get('interval');
} }
get endDate(){ get endDate() {
return this.form.get('recurrenceRule')?.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'); return this.form.get('recurrenceRule')?.get('byMonth');
} }
doReady(){ doReady() {
this.ready.emit(); this.ready.emit();
} }
} }

View File

@ -37,7 +37,7 @@ export class CalendarService {
} }
public update(id: number,data: UpdateEventFormDTO): Observable<Event> { 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 // dvbooking-cli/src/templates/angular/model.ts.tpl
// Generated by the CLI // Generated by the CLI
export interface Event { export interface Event {
id: number; id: number;
event_type_id: number;
title: string; title: string;
description: string; description: string;
start_time: Date; startTime: string;
end_time: Date; endTime: string;
timezone: string; timezone: string;
is_recurring: boolean; isRecurring: boolean;
created_at: Date; created_at: string;
updated_at: Date; 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. * Find a single record by its ID.
*/ */
public findOne(id: number): Observable<Event> { 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 { export class CalendarController {
constructor(private readonly calendarService: CalendarService) {} 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() @Get()
getCalendarEvents(@Query() getCalendarDto: GetCalendarDto) { getCalendarEvents(@Query() getCalendarDto: GetCalendarDto) {
return this.calendarService.getEventsInRange( return this.calendarService.getEventsInRange(

View File

@ -148,7 +148,7 @@ export class CalendarService {
if (!freq) continue; if (!freq) continue;
let untilDate: Date|undefined = undefined; let untilDate: Date | undefined = undefined;
// typeorm does not convert postgres type 'date' to javascript date object // typeorm does not convert postgres type 'date' to javascript date object
if (event?.recurrenceRule?.endDate) { if (event?.recurrenceRule?.endDate) {
untilDate = new Date(event.recurrenceRule.endDate); untilDate = new Date(event.recurrenceRule.endDate);
@ -204,9 +204,6 @@ export class CalendarService {
// --- Other service methods (createEvent, etc.) remain unchanged --- // --- Other service methods (createEvent, etc.) remain unchanged ---
async createEvent(createEventDto: CreateEventDto): Promise<Event> { 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 { recurrenceRule, ...eventData } = createEventDto;
const newEvent: Omit< const newEvent: Omit<
@ -224,19 +221,26 @@ export class CalendarService {
// check if event type exists // check if event type exists
if (eventData.eventTypeId) { 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({ const eventType = await this.eventTypeRepository.findOneBy({
id: eventData.eventTypeId, id: eventData.eventTypeId,
}); });
if (!eventType) { 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( throw new BadRequestException(
{}, {},
'Event type not found ' + eventData.eventTypeId, 'Event type not found ' + eventData.eventTypeId,
); );
} }
newEvent.eventType = eventType; newEvent.eventType = eventType;
console.log('[CalendarService] Successfully attached event type:', eventType); console.log(
'[CalendarService] Successfully attached event type:',
eventType,
);
} else { } else {
console.log('[CalendarService] No eventTypeId provided.'); console.log('[CalendarService] No eventTypeId provided.');
} }
@ -246,10 +250,15 @@ export class CalendarService {
console.log('[CalendarService] Saving event entity to the database...'); console.log('[CalendarService] Saving event entity to the database...');
const savedEvent = await this.eventRepository.save(event); 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) { 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({ const rule = this.recurrenceRuleRepository.create({
...recurrenceRule, ...recurrenceRule,
event: savedEvent, event: savedEvent,
@ -257,10 +266,14 @@ export class CalendarService {
await this.recurrenceRuleRepository.save(rule); await this.recurrenceRuleRepository.save(rule);
console.log('[CalendarService] Recurrence rule saved successfully.'); console.log('[CalendarService] Recurrence rule saved successfully.');
} else { } 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); const finalEvent = await this.getEventById(savedEvent.id);
console.log('[CalendarService] Exiting createEvent method.'); console.log('[CalendarService] Exiting createEvent method.');
return finalEvent; return finalEvent;
@ -333,7 +346,59 @@ export class CalendarService {
id: number, id: number,
updateEventDto: CreateEventDto, updateEventDto: CreateEventDto,
): Promise<Event> { ): 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); return this.getEventById(id);
} }

View File

@ -80,7 +80,10 @@ export class EventsService {
} }
async findOne(id: number) { 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) { if (!record) {
throw new NotFoundException(`Event with ID ${id} not found`); throw new NotFoundException(`Event with ID ${id} not found`);
} }