add calendarview event creation
This commit is contained in:
parent
008b644bb1
commit
6b975dadac
11
admin/package-lock.json
generated
11
admin/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
<div class="card bg-base-100 w-96 shadow-sm" [class]="cardClass()">
|
||||
@if (imageSrc()) {
|
||||
<figure class="px-10 pt-10">
|
||||
<img
|
||||
[src]="imageSrc()"
|
||||
[alt]="imageAlt()"
|
||||
class="rounded-xl"
|
||||
[class]="imageClass()"
|
||||
/>
|
||||
</figure>
|
||||
}
|
||||
<div class="card-body items-center text-center">
|
||||
@if (cardTitle()) {
|
||||
<h2 class="card-title">{{ cardTitle() }}</h2>
|
||||
}
|
||||
@if (cardText()) {
|
||||
<p>A card component has a figure, a body part, and inside body there are title and actions parts</p>
|
||||
}
|
||||
<ng-content></ng-content>
|
||||
@if (cardActionText()) {
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-primary">{{ cardActionText() }}</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@ -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<string | null>(null);
|
||||
imageAlt = input<string | null>(null);
|
||||
imageClass = input<string | null>(null);
|
||||
cardTitle = input<string | null>(null);
|
||||
cardText = input<string | null>(null);
|
||||
cardActionText = input<string | null>(null);
|
||||
cardActionClick = output<void>();
|
||||
cardClass = signal<string | null>(null);
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
{{ isOpen() }}
|
||||
<dialog #modal class="modal" [class]="dialogStyleClass()">
|
||||
<div class="modal-box" [class]="modalBoxStyleClass()">
|
||||
<button (click)="closeClicked()" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
@if (headerText()) {
|
||||
<h3 class="text-lg font-bold">{{ headerText() }}</h3>
|
||||
}
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
@if (backdrop()) {
|
||||
<form (click)="closeClicked()" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
}
|
||||
</dialog>
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Modal } from './modal';
|
||||
|
||||
describe('Modal', () => {
|
||||
let component: Modal;
|
||||
let fixture: ComponentFixture<Modal>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Modal]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Modal);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -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<string>();
|
||||
backdrop = input<boolean>(false);
|
||||
modalBoxStyleClass = input<string>();
|
||||
closeButton = input<boolean>(true);
|
||||
headerText = input<string>();
|
||||
isOpen = model<boolean>(false);
|
||||
|
||||
onClose = output<boolean>();
|
||||
closeClick = output<boolean>();
|
||||
|
||||
@ViewChild('modal') modalRef!: ElementRef<HTMLDialogElement>;
|
||||
|
||||
private initialized = signal<boolean>(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);
|
||||
}
|
||||
}
|
||||
@ -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/';
|
||||
|
||||
@ -6,3 +6,8 @@
|
||||
</div>
|
||||
<full-calendar #calendar [options]="calendarOptions"></full-calendar>
|
||||
</div>
|
||||
|
||||
<rs-daisy-modal [isOpen]="isOpen()" (closeClick)="closeDialog()" >
|
||||
<app-create-event-form (ready)="closeDialog()"></app-create-event-form>
|
||||
</rs-daisy-modal>
|
||||
|
||||
|
||||
@ -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<string>("")
|
||||
isOpen = signal<boolean>(false);
|
||||
|
||||
events = signal<EventsInRangeDTO[]>([])
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
<!-- Generated by the CLI -->
|
||||
<div class="p-4 md:p-8">
|
||||
<div class="card bg-base-100 shadow-xl max-w-2xl mx-auto">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-3xl">
|
||||
Esemény {{ isEditMode ? 'szerkesztése' : 'létrehozása' }}
|
||||
</h2>
|
||||
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="space-y-4 mt-4">
|
||||
|
||||
<div class="form-control"><label class="label"><span class="label-text">Megnevezés</span></label>
|
||||
<input [class.input-error]="title?.invalid && title?.touched" type="text" formControlName="title" class="input input-bordered w-full" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-control"><label class="label"><span class="label-text">Esemény típus</span></label>
|
||||
<select class="select w-full"
|
||||
formControlName="eventTypeId"
|
||||
[class.input-error]="eventType?.invalid && eventType?.touched"
|
||||
>
|
||||
<option disabled selected>Pick a color</option>
|
||||
@for (eventType of eventTypes(); track eventType.id) {
|
||||
<option [value]="eventType.id">{{ eventType.name }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-control"><label class="label"><span class="label-text">Leírás</span></label>
|
||||
<input [class.input-error]="description?.invalid && description?.touched" type="text" formControlName="description" class="input input-bordered w-full" /></div>
|
||||
|
||||
<div class="form-control"><label class="label"><span class="label-text">Kezdő időpont</span></label>
|
||||
<input [class.input-error]="startTime?.invalid && startTime?.touched" type="datetime-local" formControlName="startTime" class="input input-bordered w-full" /></div>
|
||||
|
||||
<div class="form-control"><label class="label"><span class="label-text">Befejezési időpont</span></label>
|
||||
<input [class.input-error]="endTime?.invalid && endTime?.touched" type="datetime-local" formControlName="endTime" class="input input-bordered w-full" /></div>
|
||||
|
||||
<div class="form-control"><label class="label cursor-pointer justify-start gap-4">
|
||||
<span class="label-text">Ismétlődő</span>
|
||||
<input type="checkbox" formControlName="is_recurring" class="checkbox" />
|
||||
</label></div>
|
||||
|
||||
<div class="card-actions justify-end mt-6">
|
||||
<a routerLink="/events" class="btn btn-ghost">Mégse</a>
|
||||
<button type="submit" class="btn btn-primary" [disabled]="form.invalid">
|
||||
{{ isEditMode ? 'Mentés' : 'Létrezhozás' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -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<void>();
|
||||
id= input<number | null>() ;
|
||||
|
||||
eventTypes = signal<EventType[]>([])
|
||||
|
||||
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<Event>;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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<Event, 'bookings'> & {
|
||||
isModified?: boolean;
|
||||
eventBookings: BookingWithUserDto[];
|
||||
};
|
||||
39
admin/src/app/features/calendar/services/calendar.service.ts
Normal file
39
admin/src/app/features/calendar/services/calendar.service.ts
Normal file
@ -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<EventsInRangeDTO[]> {
|
||||
const params = new HttpParams()
|
||||
.set('startDate', eventsInRangeDto.startTime!.toISOString())
|
||||
.set('endDate', eventsInRangeDto.endTime!.toISOString());
|
||||
return this.http.get<EventsInRangeDTO[]>(this.apiUrl+'', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new record.
|
||||
*/
|
||||
public create(data: EventFormDTO): Observable<Event> {
|
||||
return this.http.post<Event>(this.apiUrl+'/events', data);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,3 @@
|
||||
<!-- dvbooking-cli/src/templates/angular/form.component.html.tpl -->
|
||||
|
||||
<!-- Generated by the CLI -->
|
||||
<div class="p-4 md:p-8">
|
||||
<div class="card bg-base-100 shadow-xl max-w-2xl mx-auto">
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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<string, RRule.Frequency> = {
|
||||
@ -60,6 +61,8 @@ export class CalendarService {
|
||||
private readonly dataSource: DataSource,
|
||||
@InjectRepository(Event)
|
||||
private readonly eventRepository: Repository<Event>,
|
||||
@InjectRepository(EventType)
|
||||
private readonly eventTypeRepository: Repository<EventType>,
|
||||
@InjectRepository(RecurrenceRule)
|
||||
private readonly recurrenceRuleRepository: Repository<RecurrenceRule>,
|
||||
@InjectRepository(EventException)
|
||||
@ -196,9 +199,37 @@ export class CalendarService {
|
||||
// --- Other service methods (createEvent, etc.) remain unchanged ---
|
||||
|
||||
async createEvent(createEventDto: CreateEventDto): Promise<Event> {
|
||||
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) {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -26,6 +26,7 @@ async function bootstrap() {
|
||||
transformOptions: {
|
||||
enableImplicitConversion: true, // Allows class-transformer to convert string primitives to their target types
|
||||
},
|
||||
enableDebugMessages: true,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user