feat: Implement event dashboard with activation, cancellation, editing, and booking management functionalities.
This commit is contained in:
66
.vscode/launch.json
vendored
Normal file
66
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${file}"
|
||||
},
|
||||
{
|
||||
"name": "NPM Start Admin",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}/admin",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"start"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
},
|
||||
{
|
||||
"name": "NPM Start LIB",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}/admin",
|
||||
"runtimeExecutable": "ng",
|
||||
"runtimeArgs": [
|
||||
"build",
|
||||
"@rschneider/ng-daisyui",
|
||||
"--watch"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "NPM Start REST",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}/server",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"start:dev"
|
||||
],
|
||||
"preLaunchTask": "Docker Up (Environment)",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Full Stack: Docker + App + GUI",
|
||||
"configurations": [
|
||||
"NPM Start LIB",
|
||||
"NPM Start Admin",
|
||||
"NPM Start REST"
|
||||
],
|
||||
"stopAll": true
|
||||
}
|
||||
]
|
||||
}
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"editor.fontFamily": "'FiraCode Nerd Font Mono',Menlo, Monaco, 'Courier New', monospace",
|
||||
"terminal.integrated.fontFamily": "'FiraCode Nerd Font Mono'",
|
||||
"markdown.preview.fontFamily": "'FiraCode Nerd Font Mono',-apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif"
|
||||
}
|
||||
18
.vscode/tasks.json
vendored
Normal file
18
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Docker Up (Environment)",
|
||||
"type": "shell",
|
||||
"command": "docker compose up -d",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/environment/dev"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -78,6 +78,7 @@ export interface CalendarCreateBookingDto {
|
||||
}
|
||||
|
||||
export interface CancelBookingDto {
|
||||
canceledReason: string;
|
||||
}
|
||||
|
||||
export interface UserResponseDto {
|
||||
@@ -94,6 +95,7 @@ export interface BookingResponseDto {
|
||||
canceledAt: string | null;
|
||||
createdAt: string | null;
|
||||
user: UserResponseDto;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface PaginationResponseMetaDto {
|
||||
|
||||
@@ -137,9 +137,9 @@ export class CalendarService {
|
||||
return this.httpClient.post(url, calendarCreateBookingDto, requestOptions);
|
||||
}
|
||||
|
||||
calendarControllerCancelBooking(bookingId: number, cancelBookingDto: CancelBookingDto, observe?: 'body', options?: RequestOptions<'json'>): Observable<any>;
|
||||
calendarControllerCancelBooking(bookingId: number, cancelBookingDto: CancelBookingDto, observe?: 'response', options?: RequestOptions<'json'>): Observable<HttpResponse<any>>;
|
||||
calendarControllerCancelBooking(bookingId: number, cancelBookingDto: CancelBookingDto, observe?: 'events', options?: RequestOptions<'json'>): Observable<HttpEvent<any>>;
|
||||
calendarControllerCancelBooking(bookingId: number, cancelBookingDto: CancelBookingDto, observe?: 'body', options?: RequestOptions<'json'>): Observable<CalenderControllerGetBookingResponse>;
|
||||
calendarControllerCancelBooking(bookingId: number, cancelBookingDto: CancelBookingDto, observe?: 'response', options?: RequestOptions<'json'>): Observable<HttpResponse<CalenderControllerGetBookingResponse>>;
|
||||
calendarControllerCancelBooking(bookingId: number, cancelBookingDto: CancelBookingDto, observe?: 'events', options?: RequestOptions<'json'>): Observable<HttpEvent<CalenderControllerGetBookingResponse>>;
|
||||
calendarControllerCancelBooking(bookingId: number, cancelBookingDto: CancelBookingDto, observe?: 'body' | 'events' | 'response', options?: RequestOptions<'arraybuffer' | 'blob' | 'json' | 'text'>): Observable<any> {
|
||||
const url = `${this.basePath}/api/calendar/bookings/${bookingId}/cancel`;
|
||||
|
||||
|
||||
@@ -5,26 +5,22 @@
|
||||
|
||||
@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>
|
||||
<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'">
|
||||
<app-single-event-dashboard [event]="selectedEvent()"
|
||||
(action)="setWorkFlow($event)"
|
||||
></app-single-event-dashboard>
|
||||
</rs-daisy-modal>
|
||||
<rs-daisy-modal [isOpen]="true" (closeClick)="closeDialog()" [modalBoxStyleClass]="'max-w-none w-2xl'">
|
||||
<app-single-event-dashboard [event]="selectedEvent()" (action)="setWorkFlow($event)"></app-single-event-dashboard>
|
||||
</rs-daisy-modal>
|
||||
}
|
||||
|
||||
@for (dialogDefinition of dialogs; track dialogDefinition) {
|
||||
@for (dialogDefinition of dialogs; track dialogDefinition) {
|
||||
|
||||
@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() : {}; "
|
||||
></ng-container>
|
||||
</rs-daisy-modal>
|
||||
}
|
||||
@if (dialogDefinition.isRendered()) {
|
||||
<rs-daisy-modal [isOpen]="true" (closeClick)="openDashboard()" [modalBoxStyleClass]="'max-w-none w-2xl'">
|
||||
<ng-container
|
||||
*ngComponentOutlet="dialogDefinition.component; inputs: dialogDefinition.componentInputs ? dialogDefinition.componentInputs() : {}; "></ng-container>
|
||||
</rs-daisy-modal>
|
||||
}
|
||||
}
|
||||
@@ -79,14 +79,24 @@ export class CalendarView {
|
||||
}
|
||||
});
|
||||
|
||||
this.eventBus.on(EventType.CALENDAR_VIEW_DIALOG_CLOSED)
|
||||
this.eventBus.on(EventType.CALENDAR_VIEW_CLOSE_DIALOG_AND_RELOAD)
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe({
|
||||
next: (_) => {
|
||||
this.closeDialog();
|
||||
this.calendarComponent?.getApi().refetchEvents();
|
||||
}
|
||||
})
|
||||
|
||||
this.eventBus.on(EventType.CALENDAR_VIEW_EVENT_DASHBOARD)
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe({
|
||||
next: (_) => {
|
||||
this.openDashboard();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
this.calendarOptions = {
|
||||
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
|
||||
@@ -237,16 +247,16 @@ export class CalendarView {
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next: (events) => {
|
||||
console.info('calendar events', events);
|
||||
successCallback(events);
|
||||
}
|
||||
,
|
||||
error: (error) => {
|
||||
console.error('Error fetching events', error);
|
||||
failureCallback(error);
|
||||
},
|
||||
next: (events) => {
|
||||
console.info('calendar events', events);
|
||||
successCallback(events);
|
||||
}
|
||||
,
|
||||
error: (error) => {
|
||||
console.error('Error fetching events', error);
|
||||
failureCallback(error);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -254,7 +264,9 @@ export class CalendarView {
|
||||
closeDialog() {
|
||||
this.workflow.set('NO_DIALOG');
|
||||
}
|
||||
|
||||
openDashboard() {
|
||||
this.workflow.set('event_dashboard');
|
||||
}
|
||||
/**
|
||||
* Set dashboard workflow
|
||||
* @param workflowType
|
||||
|
||||
@@ -2,37 +2,52 @@
|
||||
Foglalások
|
||||
</app-heading>
|
||||
@if (bookings.isLoading()) {
|
||||
<div>loading...</div>
|
||||
<div>loading...</div>
|
||||
} @else {
|
||||
@if (bookings.value()?.data?.length) {
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Foglalás dátuma</th>
|
||||
<th>Foglalt helyek száma</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (booking of bookings.value()?.data; track booking) {
|
||||
<tr>
|
||||
<td>{{formatDateTime(booking.createdAt)}}</td>
|
||||
<td>{{booking.reservedSeatsCount}}</td>
|
||||
<td><rs-daisy-button [variant]="'error'">
|
||||
Lemondás
|
||||
</rs-daisy-button></td>
|
||||
</tr>
|
||||
@if (bookings.value()?.data?.length) {
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Foglalás dátuma</th>
|
||||
<th>Foglalt helyek száma</th>
|
||||
<th>Státusz</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (booking of bookings.value()?.data; track booking) {
|
||||
<tr>
|
||||
<td>{{formatDateTime(booking.createdAt)}}</td>
|
||||
<td>{{booking.reservedSeatsCount}}</td>
|
||||
<td>
|
||||
@switch (booking.status){
|
||||
@case ('active'){
|
||||
<span [outerHTML]="SvgIcons.heroCheckCircle | safeHtml"></span>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if(bookings.value()?.meta?.totalPages! > 1){
|
||||
<rs-daisy-pagination [pageCount]="pageCount()" [activePage]="activePage()"
|
||||
(onPaginate)="paginate($event)"></rs-daisy-pagination>
|
||||
}
|
||||
} @else {
|
||||
Nem találtunk foglalást erre az eseményre
|
||||
}
|
||||
@case ('customer_cancelled'){
|
||||
<span [outerHTML]="SvgIcons.heroMinusCircle | safeHtml"></span>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (booking.status == 'active') {
|
||||
<rs-daisy-button [variant]="'error'" (clickEvent)="cancelBooking(booking)">
|
||||
Lemondás
|
||||
</rs-daisy-button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if(bookings.value()?.meta?.totalPages! > 1){
|
||||
<rs-daisy-pagination [pageCount]="pageCount()" [activePage]="activePage()"
|
||||
(onPaginate)="paginate($event)"></rs-daisy-pagination>
|
||||
}
|
||||
} @else {
|
||||
Nem találtunk foglalást erre az eseményre
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import { delay, of, throwError } from 'rxjs';
|
||||
import { Button, Pagination } from '@rschneider/ng-daisyui';
|
||||
import { Heading } from '../../../../../components/heading/heading';
|
||||
import { format } from 'date-fns';
|
||||
import { EventType } from '../../../../../../types';
|
||||
import { SvgIcons } from '../../../../../svg-icons';
|
||||
import { SafeHtmlPipe } from "../../../../../pipes/safe-html-pipe";
|
||||
|
||||
@Component({
|
||||
selector: 'app-single-event-booking-list',
|
||||
@@ -14,6 +17,7 @@ import { format } from 'date-fns';
|
||||
Pagination,
|
||||
Heading,
|
||||
Button,
|
||||
SafeHtmlPipe
|
||||
],
|
||||
templateUrl: './single-event-booking-list.html',
|
||||
styleUrl: './single-event-booking-list.css',
|
||||
@@ -30,57 +34,64 @@ export class SingleEventBookingList {
|
||||
pageCount = computed(() => {
|
||||
// return this.bookings.value()?.pageCount || 1;
|
||||
return 1;
|
||||
})
|
||||
});
|
||||
|
||||
bookings = rxResource(
|
||||
{
|
||||
params: () => ({
|
||||
page: this.activePage()
|
||||
page: this.activePage(),
|
||||
}),
|
||||
stream: ({params}) => {
|
||||
stream: ({ params }) => {
|
||||
|
||||
// console.info("loading resource", params);
|
||||
//
|
||||
// const allData = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"]
|
||||
//
|
||||
// let pageCount = Math.floor( allData.length / this.pageSize());
|
||||
// if ( (allData.length % this.pageSize()) > 0){
|
||||
// pageCount += 1;
|
||||
// }
|
||||
// pageCount = Math.max(pageCount ,1);
|
||||
//
|
||||
// const pageData = allData.slice( ((this.activePage()-1) * this.pageSize()),this.activePage()*this.pageSize());
|
||||
// console.info("booking page data",pageData);
|
||||
// return of({
|
||||
// items: pageData,
|
||||
// pageCount
|
||||
// }).pipe( delay(1000))
|
||||
if ( !this.event()){
|
||||
return throwError( () => new Error("no event"))
|
||||
if (!this.event()) {
|
||||
return throwError(() => new Error('no event'));
|
||||
}
|
||||
const event = this.event()!;
|
||||
return this.calendarService.calendarControllerGetBookings(
|
||||
event.id,
|
||||
new Date(event.startTime).toISOString()
|
||||
)
|
||||
new Date(event.startTime).toISOString(),
|
||||
params.page,
|
||||
this.pageSize(),
|
||||
);
|
||||
},
|
||||
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
protected paginate($event: number) {
|
||||
console.info("paginated to ", $event)
|
||||
console.info('paginated to ', $event);
|
||||
this.activePage.set($event);
|
||||
|
||||
}
|
||||
|
||||
formatDateTime( dateStr?: string|Date|null){
|
||||
if ( !dateStr ){
|
||||
return "";
|
||||
public cancelBooking(booking: BookingResponseDto) {
|
||||
this.calendarService.calendarControllerCancelBooking(
|
||||
booking.id,
|
||||
{
|
||||
canceledReason: 'customer_cancelled',
|
||||
},
|
||||
).subscribe({
|
||||
next: () => {
|
||||
// this.eventBus.emit(EventType.CALENDAR_VIEW_BOOKING_CANCELLED, '');
|
||||
this.bookings.reload();
|
||||
},
|
||||
error: () => {
|
||||
// todo: handle error
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
formatDateTime(dateStr?: string | Date | null) {
|
||||
if (!dateStr) {
|
||||
return '';
|
||||
}
|
||||
return format(new Date(dateStr),'yyyy-MM-dd HH:mm');
|
||||
return format(new Date(dateStr), 'yyyy-MM-dd HH:mm');
|
||||
}
|
||||
|
||||
protected readonly format = format;
|
||||
|
||||
protected readonly SvgIcons = SvgIcons;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,9 +43,9 @@ export class SingleEventDashboardEventActivation {
|
||||
if (eventException) {
|
||||
payload = {
|
||||
...eventException,
|
||||
originalStartTime: new Date(eventException.originalStartTime),
|
||||
originalStartTime: new Date(eventException.originalStartTime),
|
||||
newStartTime: eventException.newStartTime ? new Date(eventException.newStartTime) : undefined,
|
||||
newEndTime: eventException.newEndTime ? new Date(eventException.newEndTime) :undefined,
|
||||
newEndTime: eventException.newEndTime ? new Date(eventException.newEndTime) : undefined,
|
||||
isCancelled: !activated,
|
||||
};
|
||||
} else {
|
||||
@@ -55,7 +55,7 @@ export class SingleEventDashboardEventActivation {
|
||||
};
|
||||
}
|
||||
|
||||
this.calendarService.applyException(eventId, payload ).subscribe(
|
||||
this.calendarService.applyException(eventId, payload).subscribe(
|
||||
{
|
||||
next: () => {
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_EVENT_SAVED, 'Event saved')
|
||||
@@ -77,7 +77,7 @@ export class SingleEventDashboardEventActivation {
|
||||
}
|
||||
|
||||
protected closeDialog() {
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_DIALOG_CLOSED, 'Event saved')
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_EVENT_DASHBOARD, 'Event saved')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ export class SingleEventDashboardEventEdit {
|
||||
* proxy to ready event from form to parent
|
||||
*/
|
||||
protected triggerAction(action: ReadyType) {
|
||||
if ( action == 'save-event-success'){
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_EVENT_SAVED,'');
|
||||
}else if ( action == 'save-event-failed'){
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_DIALOG_CLOSED,'');
|
||||
}else{
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_DIALOG_CLOSED,'');
|
||||
if (action == 'save-event-success') {
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_EVENT_SAVED, '');
|
||||
} else if (action == 'save-event-failed') {
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_DIALOG_CLOSED, '');
|
||||
} else {
|
||||
this.eventBus.emit(EventType.CALENDAR_VIEW_EVENT_DASHBOARD, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,65 +28,65 @@ export class SingleEventDashboard {
|
||||
constructor() {
|
||||
|
||||
effect(() => {
|
||||
this.cards.set( [
|
||||
{
|
||||
buttonTitle: 'Szerkesztés',
|
||||
title: 'Szerkesztés',
|
||||
svgIcon: SvgIcons.heorPencilSquare,
|
||||
description: 'Az esemény módosítása',
|
||||
workflow: 'event_edit',
|
||||
},
|
||||
|
||||
this.event()?.isCancelled ?
|
||||
|
||||
this.cards.set([
|
||||
{
|
||||
buttonTitle: 'Aktiválás',
|
||||
title: 'Előfordulás aktiválása',
|
||||
svgIcon: SvgIcons.heroPlay,
|
||||
description: 'Az esemény ezen előfordulásának aktiválása',
|
||||
workflow: 'event_activate',
|
||||
} :
|
||||
{
|
||||
buttonTitle: 'Lemondás',
|
||||
title: 'Előfordulás lemondása',
|
||||
svgIcon: SvgIcons.heroXcircle,
|
||||
description: 'Az esemény ezen előfordulásának lemondása',
|
||||
workflow: 'event_cancel',
|
||||
},
|
||||
{
|
||||
buttonTitle: 'Törlés',
|
||||
title: 'Esemény törlése',
|
||||
svgIcon: SvgIcons.heroTrash,
|
||||
description: 'Az esemény törlése',
|
||||
workflow: 'event_delete',
|
||||
},
|
||||
{
|
||||
buttonTitle: 'Megnézem',
|
||||
title: 'Foglalások',
|
||||
svgIcon: SvgIcons.heroUserGroup,
|
||||
description: 'Foglalások megtekintése',
|
||||
workflow: 'booking_list',
|
||||
},
|
||||
{
|
||||
buttonTitle: 'Bejelentkezés',
|
||||
title: 'Időpont foglalás',
|
||||
svgIcon: SvgIcons.heroUserPlus,
|
||||
description: 'Időpont foglalása eseményre',
|
||||
workflow: 'booking_create',
|
||||
},
|
||||
{
|
||||
buttonTitle: 'Lemondás',
|
||||
title: 'Lemondás',
|
||||
svgIcon: SvgIcons.heroUserMinus,
|
||||
description: 'Az időpont lemondása',
|
||||
workflow: 'booking_cancel',
|
||||
},
|
||||
]);
|
||||
buttonTitle: 'Szerkesztés',
|
||||
title: 'Szerkesztés',
|
||||
svgIcon: SvgIcons.heorPencilSquare,
|
||||
description: 'Az esemény módosítása',
|
||||
workflow: 'event_edit',
|
||||
},
|
||||
|
||||
this.event()?.isCancelled ?
|
||||
|
||||
{
|
||||
buttonTitle: 'Aktiválás',
|
||||
title: 'Előfordulás aktiválása',
|
||||
svgIcon: SvgIcons.heroPlay,
|
||||
description: 'Az esemény ezen előfordulásának aktiválása',
|
||||
workflow: 'event_activate',
|
||||
} :
|
||||
{
|
||||
buttonTitle: 'Lemondás',
|
||||
title: 'Előfordulás lemondása',
|
||||
svgIcon: SvgIcons.heroXcircle,
|
||||
description: 'Az esemény ezen előfordulásának lemondása',
|
||||
workflow: 'event_cancel',
|
||||
},
|
||||
{
|
||||
buttonTitle: 'Törlés',
|
||||
title: 'Esemény törlése',
|
||||
svgIcon: SvgIcons.heroTrash,
|
||||
description: 'Az esemény törlése',
|
||||
workflow: 'event_delete',
|
||||
},
|
||||
{
|
||||
buttonTitle: 'Megnézem',
|
||||
title: 'Foglalások',
|
||||
svgIcon: SvgIcons.heroUserGroup,
|
||||
description: 'Foglalások megtekintése',
|
||||
workflow: 'booking_list',
|
||||
},
|
||||
{
|
||||
buttonTitle: 'Bejelentkezés',
|
||||
title: 'Időpont foglalás',
|
||||
svgIcon: SvgIcons.heroUserPlus,
|
||||
description: 'Időpont foglalása eseményre',
|
||||
workflow: 'booking_create',
|
||||
},
|
||||
{
|
||||
buttonTitle: 'Lemondás',
|
||||
title: 'Lemondás',
|
||||
svgIcon: SvgIcons.heroUserMinus,
|
||||
description: 'Az időpont lemondása',
|
||||
workflow: 'booking_cancel',
|
||||
},
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
onCardAction (action: WORKFLOW_TYPE) {
|
||||
if ( action ){
|
||||
onCardAction(action: WORKFLOW_TYPE) {
|
||||
if (action) {
|
||||
this.action.emit(action);
|
||||
}
|
||||
console.info("card action", action);
|
||||
|
||||
@@ -1,115 +1,108 @@
|
||||
<!-- Generated by the CLI -->
|
||||
<div class="">
|
||||
<h2 class="card-title text-3xl">
|
||||
Esemény {{ isEditMode ? 'szerkesztése' : 'létrehozása' }}
|
||||
</h2>
|
||||
<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">
|
||||
<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">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="isRecurring" class="checkbox" />
|
||||
</label></div>
|
||||
|
||||
@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>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Gyakoriság</span></label>
|
||||
<select class="select w-full"
|
||||
formControlName="frequency"
|
||||
[class.select-error]="frequency?.invalid && frequency?.touched">
|
||||
<option disabled selected>Válassz gyakoriságot</option>
|
||||
@for (frequencyOption of frequencyOptions; track frequencyOption) {
|
||||
<option [value]="frequencyOption.frequency">{{ frequencyOption.label }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Intervallum</span></label>
|
||||
<input type="number"
|
||||
formControlName="interval"
|
||||
class="input input-bordered w-full"
|
||||
[class.input-error]="interval?.invalid && interval?.touched" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Napok</span></label>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
@for (day of weekDayOptions; track day.value) {
|
||||
<label class="label cursor-pointer justify-start gap-2">
|
||||
<input type="checkbox"
|
||||
[value]="day.value"
|
||||
[checked]="isDayChecked(day.value)"
|
||||
(change)="onByDayChange($event)"
|
||||
class="checkbox" />
|
||||
<span class="label-text">{{ day.label }}</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Hónap napja</span></label>
|
||||
<input type="text"
|
||||
formControlName="byMonthDay"
|
||||
class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Vesszővel elválasztott lista (pl. 1,15,31)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Hónap</span></label>
|
||||
<input type="text"
|
||||
formControlName="byMonth"
|
||||
class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Vesszővel elválasztott lista (pl. 1,2,3)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Ismétlődés vége</span></label>
|
||||
<input type="date"
|
||||
formControlName="endDate"
|
||||
class="input input-bordered w-full" />
|
||||
</div>
|
||||
</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="card-actions justify-end mt-6">
|
||||
<a class="btn btn-ghost" (click)="doReady()">Mégse</a>
|
||||
<button type="submit" class="btn btn-primary" [disabled]="form.invalid">
|
||||
{{ isEditMode ? 'Mentés' : 'Létrezhozás' }}
|
||||
</button>
|
||||
<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="isRecurring" class="checkbox" />
|
||||
</label></div>
|
||||
|
||||
@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>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Gyakoriság</span></label>
|
||||
<select class="select w-full" formControlName="frequency"
|
||||
[class.select-error]="frequency?.invalid && frequency?.touched">
|
||||
<option disabled selected>Válassz gyakoriságot</option>
|
||||
@for (frequencyOption of frequencyOptions; track frequencyOption) {
|
||||
<option [value]="frequencyOption.frequency">{{ frequencyOption.label }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Intervallum</span></label>
|
||||
<input type="number" formControlName="interval" class="input input-bordered w-full"
|
||||
[class.input-error]="interval?.invalid && interval?.touched" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Napok</span></label>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
@for (day of weekDayOptions; track day.value) {
|
||||
<label class="label cursor-pointer justify-start gap-2">
|
||||
<input type="checkbox" [value]="day.value" [checked]="isDayChecked(day.value)"
|
||||
(change)="onByDayChange($event)" class="checkbox" />
|
||||
<span class="label-text">{{ day.label }}</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Hónap napja</span></label>
|
||||
<input type="text" formControlName="byMonthDay" class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Vesszővel elválasztott lista (pl. 1,15,31)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Hónap</span></label>
|
||||
<input type="text" formControlName="byMonth" class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Vesszővel elválasztott lista (pl. 1,2,3)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Ismétlődés vége</span></label>
|
||||
<input type="date" formControlName="endDate" class="input input-bordered w-full" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="card-actions justify-end mt-6">
|
||||
<button type="submit" class="btn btn-primary" [disabled]="form.invalid">
|
||||
{{ isEditMode ? 'Mentés' : 'Létrezhozás' }}
|
||||
</button>
|
||||
<a class="btn btn-primary" (click)="doReady()">Mégse</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -39,9 +39,24 @@ export class SvgIcons {
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z" />
|
||||
</svg>
|
||||
`
|
||||
public static heroPlay = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
public static heroPlay = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 0 1 0 1.972l-11.54 6.347a1.125 1.125 0 0 1-1.667-.986V5.653Z" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
public static heroMinusCircle = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
public static heroBadgeCircle = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 0 1-1.043 3.296 3.745 3.745 0 0 1-3.296 1.043A3.745 3.745 0 0 1 12 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 0 1-3.296-1.043 3.745 3.745 0 0 1-1.043-3.296A3.745 3.745 0 0 1 3 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 0 1 1.043-3.296 3.746 3.746 0 0 1 3.296-1.043A3.746 3.746 0 0 1 12 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 0 1 3.296 1.043 3.746 3.746 0 0 1 1.043 3.296Z" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
public static heroCheckCircle = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
export interface AppConfig{
|
||||
export interface AppConfig {
|
||||
apiUrl: string;
|
||||
apiBaseUrl: string;
|
||||
}
|
||||
@@ -19,8 +19,11 @@ export enum EventType {
|
||||
USER_LOGGED_IN = 'USER_LOGGED_IN',
|
||||
ORDER_CREATED = 'ORDER_CREATED',
|
||||
THEME_CHANGED = 'THEME_CHANGED',
|
||||
CALENDAR_VIEW_EVENT_DASHBOARD = 'CALENDAR_VIEW_EVENT_DASHBOARD',
|
||||
CALENDAR_VIEW_EVENT_SAVED = 'CALENDAR_VIEW_EVENT_SAVED',
|
||||
CALENDAR_VIEW_DIALOG_CLOSED = 'CALENDAR_VIEW_DIALOG_CLOSED'
|
||||
CALENDAR_VIEW_DIALOG_CLOSED = 'CALENDAR_VIEW_DIALOG_CLOSED',
|
||||
CALENDAR_VIEW_BOOKING_CANCELLED = 'CALENDAR_VIEW_BOOKING_CANCELLED',
|
||||
CALENDAR_VIEW_CLOSE_DIALOG_AND_RELOAD = 'CALENDAR_VIEW_CLOSE_DIALOG_AND_RELOAD'
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +33,10 @@ export interface EventMap {
|
||||
[EventType.ORDER_CREATED]: { orderId: number };
|
||||
[EventType.CALENDAR_VIEW_EVENT_SAVED]: string;
|
||||
[EventType.CALENDAR_VIEW_DIALOG_CLOSED]: string;
|
||||
[EventType.CALENDAR_VIEW_DIALOG_CLOSED]: string;
|
||||
[EventType.CALENDAR_VIEW_BOOKING_CANCELLED]: string;
|
||||
[EventType.CALENDAR_VIEW_CLOSE_DIALOG_AND_RELOAD]: string;
|
||||
[EventType.CALENDAR_VIEW_EVENT_DASHBOARD]: string;
|
||||
}
|
||||
|
||||
export interface AppEvent<T = any> {
|
||||
|
||||
8
dius.txt
Normal file
8
dius.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
NAiP
|
||||
file:///Users/rschneider/Library/Fonts/FiraCodeNerdFontMono-Light.ttf#postscript-name=FiraCodeNFM-Light
|
||||
file:///Users/rschneider/Library/Fonts/FiraCodeNerdFontMono-Regular.ttf#postscript-name=FiraCodeNFM-Reg
|
||||
file:///Users/rschneider/Library/Fonts/FiraCodeNerdFontMono-Retina.ttf#postscript-name=FiraCodeNFM-Ret
|
||||
file:///Users/rschneider/Library/Fonts/FiraCodeNerdFontMono-Medium.ttf#postscript-name=FiraCodeNFM-Med
|
||||
file:///Users/rschneider/Library/Fonts/FiraCodeNerdFontMono-SemiBold.ttf#postscript-name=FiraCodeNFM-SemBd
|
||||
file:///Users/rschneider/Library/Fonts/FiraCodeNerdFontMono-Bold.ttf#postscript-name=FiraCodeNFM-Bold
|
||||
@@ -31,7 +31,7 @@ import { CalenderControllerGetBookingResponse } from './dto/booking.response.dto
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(Role.Admin)
|
||||
export class CalendarController {
|
||||
constructor(private readonly calendarService: CalendarService) {}
|
||||
constructor(private readonly calendarService: CalendarService) { }
|
||||
|
||||
@Get()
|
||||
getCalendarEvents(@Query() getCalendarDto: GetCalendarDto) {
|
||||
@@ -110,6 +110,7 @@ export class CalendarController {
|
||||
@ApiCreatedResponse({ type: CalenderControllerGetBookingResponse }) // Removed <Booking> generic for cleaner syntax unless defined elsewhere
|
||||
// Ensure ValidationPipe is enabled with transform: true (Global or Method scoped)
|
||||
// @UsePipes(new ValidationPipe({ transform: true }))
|
||||
@Roles(Role.Admin, Role.User)
|
||||
async getBookings(
|
||||
@User() user: types.AppUser,
|
||||
@Param('eventId', ParseIntPipe) eventId: number,
|
||||
@@ -117,7 +118,7 @@ export class CalendarController {
|
||||
@Query() calendarGetBookingDto: CalendarGetBookingDto,
|
||||
) {
|
||||
return this.calendarService.getBookings(
|
||||
user.user!.userId,
|
||||
user,
|
||||
eventId,
|
||||
calendarGetBookingDto,
|
||||
);
|
||||
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
BookingResponseDto,
|
||||
CalenderControllerGetBookingResponse,
|
||||
} from './dto/booking.response.dto';
|
||||
import { AppUser } from 'src/types';
|
||||
import { Role } from '../auth/role.enum';
|
||||
|
||||
// --- Type-Safe Maps ---
|
||||
const frequencyMap: Record<string, RRule.Frequency> = {
|
||||
@@ -82,7 +84,7 @@ export class CalendarService {
|
||||
private readonly eventExceptionRepository: Repository<EventException>,
|
||||
@InjectRepository(Booking)
|
||||
private readonly bookingRepository: Repository<Booking>,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async getEventsInRange(
|
||||
startDate: Date,
|
||||
@@ -606,7 +608,7 @@ export class CalendarService {
|
||||
}
|
||||
|
||||
async getBookings(
|
||||
userId: number,
|
||||
user: AppUser,
|
||||
eventId: number,
|
||||
queryParams: CalendarGetBookingDto,
|
||||
): Promise<CalenderControllerGetBookingResponse> {
|
||||
@@ -621,6 +623,13 @@ export class CalendarService {
|
||||
['createdAt']: order!,
|
||||
},
|
||||
};
|
||||
|
||||
if (!user.user?.roles.includes(Role.Admin)) {
|
||||
findOptions.where = {
|
||||
...findOptions.where,
|
||||
userId: user.user!.userId,
|
||||
};
|
||||
}
|
||||
const paginated = limit > 0;
|
||||
const [data, totalItems] =
|
||||
await this.bookingRepository.findAndCount(findOptions);
|
||||
|
||||
@@ -32,6 +32,9 @@ export class BookingResponseDto {
|
||||
})
|
||||
user?: UserResponseDto;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
status?: string;
|
||||
|
||||
constructor(booking: Booking) {
|
||||
this.id = Number(booking.id); // Handle BigInt conversion
|
||||
this.eventId = Number(booking.eventId);
|
||||
@@ -46,6 +49,14 @@ export class BookingResponseDto {
|
||||
// Assuming User entity has firstName/lastName
|
||||
this.user = new UserResponseDto(booking.user);
|
||||
}
|
||||
this.status = 'active';
|
||||
if (booking.canceledAt) {
|
||||
if (booking.canceledReason == 'customer_cancelled') {
|
||||
this.status = 'customer_cancelled';
|
||||
} else {
|
||||
this.status = 'cancelled';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import {
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CancelBookingDto {
|
||||
|
||||
|
||||
@ApiProperty()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
|
||||
Reference in New Issue
Block a user