diff --git a/admin/package-lock.json b/admin/package-lock.json index acb89f5..086e042 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -22,6 +22,7 @@ "@fullcalendar/timegrid": "^6.1.19", "@tailwindcss/postcss": "^4.1.17", "daisyui": "^5.4.5", + "date-fns": "^4.1.0", "jwt-decode": "^4.0.0", "postcss": "^8.5.6", "rxjs": "~7.8.0", @@ -4908,6 +4909,16 @@ "url": "https://github.com/saadeghi/daisyui?sponsor=1" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", diff --git a/admin/package.json b/admin/package.json index a7e5988..ae68b75 100644 --- a/admin/package.json +++ b/admin/package.json @@ -36,6 +36,7 @@ "@fullcalendar/timegrid": "^6.1.19", "@tailwindcss/postcss": "^4.1.17", "daisyui": "^5.4.5", + "date-fns": "^4.1.0", "jwt-decode": "^4.0.0", "postcss": "^8.5.6", "rxjs": "~7.8.0", diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings.css b/admin/projects/rschneider/ng-daisyui/src/lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings.css new file mode 100644 index 0000000..e69de29 diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings.html b/admin/projects/rschneider/ng-daisyui/src/lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings.html new file mode 100644 index 0000000..6410153 --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings.html @@ -0,0 +1,26 @@ +
+ @if (imageSrc()) { +
+ +
+ } +
+ @if (cardTitle()) { +

{{ cardTitle() }}

+ } + @if (cardText()) { +

A card component has a figure, a body part, and inside body there are title and actions parts

+ } + + @if (cardActionText()) { +
+ +
+ } +
+
diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings.ts b/admin/projects/rschneider/ng-daisyui/src/lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings.ts new file mode 100644 index 0000000..ad53e45 --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings.ts @@ -0,0 +1,19 @@ +import { Component, input, output, signal } from '@angular/core'; + +@Component({ + selector: 'rs-daisy-card-with-centered-content-and-paddings', + imports: [], + templateUrl: './card-with-centered-content-and-paddings.html', + styleUrl: './card-with-centered-content-and-paddings.css', +}) +export class CardWithCenteredContentAndPaddings { + imageSrc = input(null); + imageAlt = input(null); + imageClass = input(null); + cardTitle = input(null); + cardText = input(null); + cardActionText = input(null); + cardActionClick = output(); + cardClass = signal(null); + +} diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.css b/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.css new file mode 100644 index 0000000..e69de29 diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.html b/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.html new file mode 100644 index 0000000..d36d096 --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.html @@ -0,0 +1,15 @@ +{{ isOpen() }} + + + @if (backdrop()) { + + } + diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.spec.ts b/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.spec.ts new file mode 100644 index 0000000..45f7009 --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Modal } from './modal'; + +describe('Modal', () => { + let component: Modal; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Modal] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Modal); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.ts b/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.ts new file mode 100644 index 0000000..61b5110 --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/components/modal/modal.ts @@ -0,0 +1,95 @@ +import { + AfterViewInit, + Component, + effect, + ElementRef, + input, + model, OnDestroy, + OnInit, + output, + signal, + ViewChild, +} from '@angular/core'; + +@Component({ + selector: 'rs-daisy-modal', + imports: [], + templateUrl: './modal.html', + styleUrl: './modal.css', +}) +export class Modal implements OnInit , AfterViewInit, OnDestroy{ + dialogStyleClass = input(); + backdrop = input(false); + modalBoxStyleClass = input(); + closeButton = input(true); + headerText = input(); + isOpen = model(false); + + onClose = output(); + closeClick = output(); + + @ViewChild('modal') modalRef!: ElementRef; + + private initialized = signal(false); + + constructor() { + effect(() => { + if ( this.isOpen()){ + this.doOpen(); + }else{ + this.doClose(); + } + }); + } + + + ngOnInit() { + /* if (open()){ + + }*/ + } + + ngAfterViewInit() { + // console.info("dialog",this.dialog) + const modal = this.modalRef.nativeElement; + modal.addEventListener('close', () => this.onClose.emit(true) ); + this.initialized.set(true); + } + + emitClose(){ + console.info("emit close", this.onClose); + this.onClose?.emit(true) + } + + doOpen() { + if ( !this.initialized()){ + return; + } + const modal = this.modalRef.nativeElement; + if ( !modal.open ){ + modal.showModal(); + } + } + + doClose() { + if ( !this.initialized()){ + return; + } + const modal = this.modalRef.nativeElement; + if ( modal.open ){ + modal.close(); + } + } + ngOnDestroy() { + console.info("destroy"); + // const modal = this.modalRef?.nativeElement; + // if ( modal ){ + // modal.removeEventListener('close', this.emitClose); + // } + } + + closeClicked() { + console.info("close clicked"); + this.closeClick.emit(true); + } +} diff --git a/admin/projects/rschneider/ng-daisyui/src/public-api.ts b/admin/projects/rschneider/ng-daisyui/src/public-api.ts index 43bd854..29d030d 100644 --- a/admin/projects/rschneider/ng-daisyui/src/public-api.ts +++ b/admin/projects/rschneider/ng-daisyui/src/public-api.ts @@ -6,5 +6,7 @@ export * from './lib/ng-daisyui'; export * from './lib/components/button/button'; export * from './lib/components/footer/footer'; export * from './lib/components/breadcrumbs/breadcrumbs'; +export * from './lib/components/modal/modal'; +export * from './lib/components/card/card-with-centered-content-and-paddings/card-with-centered-content-and-paddings'; export * from './lib/daisy.types'; export * from './lib/layout/'; 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 c4630dc..63cfc8b 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 @@ -6,3 +6,8 @@ + + + + + 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 396bb93..dd7234c 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,4 +1,4 @@ -import { Component, ElementRef, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, effect, ElementRef, inject, OnInit, signal, ViewChild } from '@angular/core'; import { FullCalendarComponent, FullCalendarModule } from '@fullcalendar/angular'; import { CalendarOptions } from '@fullcalendar/core'; @@ -6,19 +6,30 @@ import dayGridPlugin from '@fullcalendar/daygrid'; import timeGridPlugin from '@fullcalendar/timegrid'; import listPlugin from '@fullcalendar/list'; import interactionPlugin from '@fullcalendar/interaction'; -import { appConfig } from '../../../../app.config'; +import { Modal } from '@rschneider/ng-daisyui'; +import { CreateEventForm } from '../create-event-form/create-event-form'; +import { CalendarService } from '../../services/calendar.service'; +import { addDays, subDays } from 'date-fns'; +import { EventsInRangeDTO } from '../../models/events-in-range-dto.model'; @Component({ selector: 'app-calendar-view', - imports: [FullCalendarModule], + imports: [FullCalendarModule, Modal, CreateEventForm], templateUrl: './calendar-view.html', styleUrl: './calendar-view.css', }) -export class CalendarView { +export class CalendarView implements OnInit, AfterViewInit { @ViewChild('startHour') startHour!: ElementRef; @ViewChild('calendar') calendarComponent: FullCalendarComponent | undefined; + calendarService = inject(CalendarService); + + workflow = signal("") + isOpen = signal(false); + + events = signal([]) + calendarOptions: CalendarOptions; constructor() { @@ -27,6 +38,7 @@ export class CalendarView { const end = new Date(); end.setHours(11,0,0) + this.calendarOptions = { plugins: [dayGridPlugin, timeGridPlugin, listPlugin,interactionPlugin], initialView: 'dayGridMonth', @@ -41,7 +53,6 @@ export class CalendarView { events: [ - { title: 'Meeting1 until'+end.toString(), start, end }, ], eventClick: function(info) { @@ -51,21 +62,54 @@ export class CalendarView { info.el.style.borderColor = 'red'; }, - dateClick: function(info) { - console.info('Date click on: ' , info); - const calendarApi = info.view.calendar; - const start = new Date(info.date.getTime()) - start.setHours(2,0,0) - const end = new Date(info.date.getTime()) - end.setHours(3,0,0) - calendarApi.addEvent({ - title: 'New Event', - start, - end, - }); + dateClick: (info) => { + console.info("setting day workflow"); + this.workflow.set("day"); + this.isOpen.set(true); + // console.info('Date click on: ' , info); + // const calendarApi = info.view.calendar; + // const start = new Date(info.date.getTime()) + // start.setHours(2,0,0) + // const end = new Date(info.date.getTime()) + // end.setHours(3,0,0) + // calendarApi.addEvent({ + // title: 'New Event', + // start, + // end, + // }); } }; + + effect(() => { + // this.calendarOptions.events = this.events + + }); + } + + + + ngAfterViewInit(): void { + + // this.calendarComponent?.getApi(). + + } + + ngOnInit(): void { + const start = subDays(new Date(),14) + const end = addDays(new Date(),14); + this.calendarService.getEventsInRange({ + startTime: start, + endTime: end + }).subscribe( + { + 'next': (events) => { + console.info('events',events) + } + + } + ) + } protected addEvent($event: PointerEvent) { @@ -82,4 +126,8 @@ export class CalendarView { start }) } + + closeDialog() { + this.isOpen.set(false) + } } diff --git a/admin/src/app/features/calendar/components/create-event-form/create-event-form.css b/admin/src/app/features/calendar/components/create-event-form/create-event-form.css new file mode 100644 index 0000000..e69de29 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 new file mode 100644 index 0000000..11e7a50 --- /dev/null +++ b/admin/src/app/features/calendar/components/create-event-form/create-event-form.html @@ -0,0 +1,51 @@ + +
+
+
+

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

+ +
+ +
+ + +
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+ Mégse + +
+
+
+
+
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 new file mode 100644 index 0000000..b37a9be --- /dev/null +++ b/admin/src/app/features/calendar/components/create-event-form/create-event-form.ts @@ -0,0 +1,146 @@ +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'; +import { mergeMap, switchMap, tap } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; +import { Event } from '../../../events/models/event.model'; +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'; + + +@Component({ + selector: 'app-create-event-form', + imports: [ + ReactiveFormsModule + ], + templateUrl: './create-event-form.html', + styleUrl: './create-event-form.css', +}) +export class CreateEventForm implements OnInit { + form: FormGroup; + isEditMode = false; + ready = output(); + id= input() ; + + eventTypes = signal([]) + + private numericFields = ["event_type_id"]; + + + constructor( + private fb: FormBuilder, + private route: ActivatedRoute, + private router: Router, + private eventService: EventService, + private calendarService: CalendarService, + private eventTypeService: EventTypeService + ) { + this.form = this.fb.group({ + eventTypeId: [null,Validators.required], + title: [null,Validators.required], + description: [null], + startTime: [null,Validators.required], + endTime: [null,Validators.required], + is_recurring: [null], + }); + } + + ngOnInit(): void { + of(this.id()).pipe( + tap(id => { + if (id) { + this.isEditMode = true; + }else{ + + const start = new Date(); + const end = new Date(); + 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()) { + return this.eventService.findOne(this.id()!); + } + return of(null); + }), + ).subscribe(event => { + if (event) { + this.form.patchValue(event); + } + }); + } + + onSubmit(): void { + if (this.form.invalid) { + this.form.markAllAsTouched(); + return; + } + + const payload: EventFormDTO|any = { ...this.form.value }; + + for (const field of this.numericFields) { + if (payload[field] != null && payload[field] !== '') { + payload[field] = parseFloat(payload[field]); + } + } + + let action$: Observable; + + if (this.isEditMode && this.id()) { + console.info("rong branch") + // action$ = this.calendarService.update(this.id()!, payload); + action$ = of(payload); + } else { + action$ = this.calendarService.create(payload); + } + + action$.subscribe({ + next: () => this.router.navigate(['/events']), + error: (err) => console.error('Failed to save event', err) + }); + } + + get eventType(){ + return this.form.get('eventTypeId'); + } + + get title(){ + return this.form.get('title'); + } + get description(){ + return this.form.get('description'); + } + get startTime(){ + return this.form.get('startTime'); + } + get endTime(){ + return this.form.get('endTime'); + } + + doReady(){ + this.ready.emit(); + } +} diff --git a/admin/src/app/features/calendar/models/event-form-dto.model.ts b/admin/src/app/features/calendar/models/event-form-dto.model.ts new file mode 100644 index 0000000..24f38ae --- /dev/null +++ b/admin/src/app/features/calendar/models/event-form-dto.model.ts @@ -0,0 +1,12 @@ +// dvbooking-cli/src/templates/angular/model.ts.tpl + +// Generated by the CLI +export interface EventFormDTO { + id?: number; + event_type_id: number; + title: string; + description?: string; + start_time?: Date; + end_time?: Date; + is_recurring: boolean; +} diff --git a/admin/src/app/features/calendar/models/events-in-range-dto.model.ts b/admin/src/app/features/calendar/models/events-in-range-dto.model.ts new file mode 100644 index 0000000..060e8e9 --- /dev/null +++ b/admin/src/app/features/calendar/models/events-in-range-dto.model.ts @@ -0,0 +1,22 @@ +import { Event } from '../../events/models/event.model'; +export interface EventsInRangeDTO { + startTime?: Date; + endTime?: Date; +} +export type BookingUserDto = { + id: number; + name: string; // Assuming user has a 'name' property + email: string; // Assuming user has a 'name' property +}; + +// This represents a booking with nested user info +export type BookingWithUserDto = { + user: BookingUserDto | null; + id: number; +}; + +// The final shape of a calendar event occurrence +export type CalendarEventDto = Omit & { + isModified?: boolean; + eventBookings: BookingWithUserDto[]; +}; diff --git a/admin/src/app/features/calendar/services/calendar.service.ts b/admin/src/app/features/calendar/services/calendar.service.ts new file mode 100644 index 0000000..ee08892 --- /dev/null +++ b/admin/src/app/features/calendar/services/calendar.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { ConfigurationService } from '../../../services/configuration.service'; +import { EventFormDTO } from '../models/event-form-dto.model'; +import { Event } from '../../events/models/event.model'; +import { EventsInRangeDTO } from '../models/events-in-range-dto.model'; + + +@Injectable({ + providedIn: 'root' +}) +export class CalendarService { + private readonly apiUrl: string; + + constructor( + private http: HttpClient, + private configService: ConfigurationService + ) { + this.apiUrl = `${this.configService.getApiUrl()}/calendar`; + } + /** + * get events in range + */ + public getEventsInRange(eventsInRangeDto: EventsInRangeDTO): Observable { + const params = new HttpParams() + .set('startDate', eventsInRangeDto.startTime!.toISOString()) + .set('endDate', eventsInRangeDto.endTime!.toISOString()); + return this.http.get(this.apiUrl+'', { params }); + } + + /** + * Create a new record. + */ + public create(data: EventFormDTO): Observable { + return this.http.post(this.apiUrl+'/events', data); + } + +} diff --git a/admin/src/app/features/events/components/event-form/event-form.component.html b/admin/src/app/features/events/components/event-form/event-form.component.html index 6f9de63..979a2be 100644 --- a/admin/src/app/features/events/components/event-form/event-form.component.html +++ b/admin/src/app/features/events/components/event-form/event-form.component.html @@ -1,5 +1,3 @@ - -
@@ -29,7 +27,7 @@
@@ -48,4 +46,4 @@
- \ No newline at end of file + diff --git a/server/src/calendar/calendar.module.ts b/server/src/calendar/calendar.module.ts index 15e3221..9c66b51 100644 --- a/server/src/calendar/calendar.module.ts +++ b/server/src/calendar/calendar.module.ts @@ -6,10 +6,17 @@ import { RecurrenceRule } from '../entity/recurrence-rule.entity'; import { EventException } from '../entity/event-exception.entity'; import { Event } from '../entity/event.entity'; import { Booking } from '../entity/booking.entity'; +import { EventType } from '../entity/event-type.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([RecurrenceRule, EventException, Event, Booking]), + TypeOrmModule.forFeature([ + RecurrenceRule, + EventException, + Event, + Booking, + EventType, + ]), ], controllers: [CalendarController], providers: [CalendarService], diff --git a/server/src/calendar/calendar.service.ts b/server/src/calendar/calendar.service.ts index ced1fd5..11f4d29 100644 --- a/server/src/calendar/calendar.service.ts +++ b/server/src/calendar/calendar.service.ts @@ -15,6 +15,7 @@ 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 { EventType } from '../entity/event-type.entity'; // --- Type-Safe Maps --- const frequencyMap: Record = { @@ -60,6 +61,8 @@ export class CalendarService { private readonly dataSource: DataSource, @InjectRepository(Event) private readonly eventRepository: Repository, + @InjectRepository(EventType) + private readonly eventTypeRepository: Repository, @InjectRepository(RecurrenceRule) private readonly recurrenceRuleRepository: Repository, @InjectRepository(EventException) @@ -196,9 +199,37 @@ export class CalendarService { // --- Other service methods (createEvent, etc.) remain unchanged --- async createEvent(createEventDto: CreateEventDto): Promise { + console.info('createEvent', createEventDto); const { recurrenceRule, ...eventData } = createEventDto; - const event = this.eventRepository.create(eventData); + const newEvent: Omit< + Event, + 'id' | 'bookings' | 'exceptions' | 'recurrenceRule' + > = { + ...eventData, + createdAt: new Date(), + updatedAt: new Date(), + timezone: 'Europe/Budapest', + eventType: undefined, + isRecurring: recurrenceRule ? true : false, + }; + + // check if event type exists + if (eventData.eventTypeId) { + console.info('eventTypeId found'); + const eventType = await this.eventTypeRepository.findOneBy({ + id: eventData.eventTypeId, + }); + if (!eventType) { + throw new BadRequestException( + {}, + 'Event type not found ' + eventData.eventTypeId, + ); + } + newEvent.eventType = eventType; + } + + const event = this.eventRepository.create(newEvent); const savedEvent = await this.eventRepository.save(event); if (event.isRecurring && recurrenceRule) { diff --git a/server/src/calendar/dto/create-event.dto.ts b/server/src/calendar/dto/create-event.dto.ts index 42f2bc8..e38cbac 100644 --- a/server/src/calendar/dto/create-event.dto.ts +++ b/server/src/calendar/dto/create-event.dto.ts @@ -55,9 +55,9 @@ export class CreateEventDto { @Type(() => Date) endTime: Date; - @IsNotEmpty() - @IsString() - timezone: string; // e.g., 'Europe/Berlin' + // @IsNotEmpty() + // @IsString() + // timezone: string; // e.g., 'Europe/Berlin' @IsOptional() @IsInt() @@ -71,4 +71,4 @@ export class CreateEventDto { @ValidateNested() @Type(() => RecurrenceRuleDto) recurrenceRule?: RecurrenceRuleDto; -} \ No newline at end of file +} diff --git a/server/src/entity/event.entity.ts b/server/src/entity/event.entity.ts index 16313d3..62ecaf0 100644 --- a/server/src/entity/event.entity.ts +++ b/server/src/entity/event.entity.ts @@ -23,7 +23,7 @@ export class Event { title: string; @Column({ name: 'description', type: 'text', nullable: true }) - description: string; + description?: string; @Column({ name: 'start_time', type: 'timestamptz' }) startTime: Date; @@ -45,15 +45,15 @@ export class Event { // --- Relationships --- - @Column({ name: 'event_type_id', type: 'bigint', nullable: true }) - eventTypeId: number; + // @Column({ name: 'event_type_id', type: 'bigint', nullable: true }) + // eventTypeId: number; @ManyToOne(() => EventType, (eventType) => eventType.events, { nullable: true, onDelete: 'SET NULL', // As requested for optional relationship }) @JoinColumn({ name: 'event_type_id' }) - eventType: EventType; + eventType?: EventType; @OneToOne(() => RecurrenceRule, (rule) => rule.event, { cascade: true, // Automatically save/update recurrence rule when event is saved diff --git a/server/src/main.ts b/server/src/main.ts index b5ef2cb..073fa58 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -26,6 +26,7 @@ async function bootstrap() { transformOptions: { enableImplicitConversion: true, // Allows class-transformer to convert string primitives to their target types }, + enableDebugMessages: true, }), );