Commit 1e4426e9 by sawit

หน้าจองห้องประชุม

parent 26ac6e17
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
"@syncfusion/ej2-angular-pivotview": "^31.1.17", "@syncfusion/ej2-angular-pivotview": "^31.1.17",
"@syncfusion/ej2-angular-popups": "^31.1.17", "@syncfusion/ej2-angular-popups": "^31.1.17",
"@syncfusion/ej2-angular-progressbar": "^31.1.17", "@syncfusion/ej2-angular-progressbar": "^31.1.17",
"@syncfusion/ej2-angular-schedule": "^31.1.21",
"@syncfusion/ej2-angular-treemap": "^31.1.17", "@syncfusion/ej2-angular-treemap": "^31.1.17",
"@syncfusion/ej2-base": "^31.1.17", "@syncfusion/ej2-base": "^31.1.17",
"@syncfusion/ej2-buttons": "^31.1.17", "@syncfusion/ej2-buttons": "^31.1.17",
...@@ -6334,6 +6335,17 @@ ...@@ -6334,6 +6335,17 @@
"@syncfusion/ej2-progressbar": "31.1.17" "@syncfusion/ej2-progressbar": "31.1.17"
} }
}, },
"node_modules/@syncfusion/ej2-angular-schedule": {
"version": "31.1.21",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-angular-schedule/-/ej2-angular-schedule-31.1.21.tgz",
"integrity": "sha512-mRlZmqimr980wWSu595e0s/zkIHlpV9EfvQua9EeveGvaZaf8fHoXysuL1GaUvjj4dCtEbJRrvuj3gDsaUqdlg==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-angular-base": "~31.1.17",
"@syncfusion/ej2-base": "~31.1.20",
"@syncfusion/ej2-schedule": "31.1.21"
}
},
"node_modules/@syncfusion/ej2-angular-treemap": { "node_modules/@syncfusion/ej2-angular-treemap": {
"version": "31.1.17", "version": "31.1.17",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-angular-treemap/-/ej2-angular-treemap-31.1.17.tgz", "resolved": "https://registry.npmjs.org/@syncfusion/ej2-angular-treemap/-/ej2-angular-treemap-31.1.17.tgz",
...@@ -6346,9 +6358,9 @@ ...@@ -6346,9 +6358,9 @@
} }
}, },
"node_modules/@syncfusion/ej2-base": { "node_modules/@syncfusion/ej2-base": {
"version": "31.1.17", "version": "31.1.22",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-base/-/ej2-base-31.1.17.tgz", "resolved": "https://registry.npmjs.org/@syncfusion/ej2-base/-/ej2-base-31.1.22.tgz",
"integrity": "sha512-Jl2Ls77kMfX13VwrAEow/kdDrefX3gh76Uwh943NUtEuChOXzvoe+CHecxJ+49VFRCrLYhB4Hj/7R9UOh65VCg==", "integrity": "sha512-zgNULXYUFG8Cr2ML8VcUqj8EcQSVd/eXNnD8APZdOjELJPE1ZzeNmRzCab16A/hqCZ+wAXwKmjaTC0I6xiXjSQ==",
"license": "SEE LICENSE IN license", "license": "SEE LICENSE IN license",
"dependencies": { "dependencies": {
"@syncfusion/ej2-icons": "~31.1.17" "@syncfusion/ej2-icons": "~31.1.17"
...@@ -6611,6 +6623,97 @@ ...@@ -6611,6 +6623,97 @@
"@syncfusion/ej2-svg-base": "~31.1.17" "@syncfusion/ej2-svg-base": "~31.1.17"
} }
}, },
"node_modules/@syncfusion/ej2-schedule": {
"version": "31.1.21",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-schedule/-/ej2-schedule-31.1.21.tgz",
"integrity": "sha512-OXwKKNvkZ06Y66A0yvu0lV6Xd/xafz2LokTB/bXa+0v05txzxAxbjAC95C3yDJUkRA2Mtq1cdyyETxrJX768pA==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~31.1.20",
"@syncfusion/ej2-buttons": "~31.1.21",
"@syncfusion/ej2-calendars": "~31.1.21",
"@syncfusion/ej2-data": "~31.1.17",
"@syncfusion/ej2-dropdowns": "~31.1.20",
"@syncfusion/ej2-excel-export": "~31.1.17",
"@syncfusion/ej2-inputs": "~31.1.21",
"@syncfusion/ej2-lists": "~31.1.17",
"@syncfusion/ej2-navigations": "~31.1.20",
"@syncfusion/ej2-popups": "~31.1.20"
}
},
"node_modules/@syncfusion/ej2-schedule/node_modules/@syncfusion/ej2-buttons": {
"version": "31.1.21",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-buttons/-/ej2-buttons-31.1.21.tgz",
"integrity": "sha512-NcBMK19Yn7//bwbREsdvqy3v1237AzulrphSmeS9SrKMd7L5H6apeqFe1BAkvAs/K+JPpt6A3Dv6Fhl3SkVcUg==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~31.1.20"
}
},
"node_modules/@syncfusion/ej2-schedule/node_modules/@syncfusion/ej2-calendars": {
"version": "31.1.22",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-calendars/-/ej2-calendars-31.1.22.tgz",
"integrity": "sha512-eepI70LowYogxOVQRA+pEFoHpFdqge2wyyVkz/ixhRzniVi2WwA5nN6AXJtYvS9fOMKRUThmzXXfjUdO/TP/4g==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~31.1.22",
"@syncfusion/ej2-buttons": "~31.1.21",
"@syncfusion/ej2-inputs": "~31.1.22",
"@syncfusion/ej2-lists": "~31.1.17",
"@syncfusion/ej2-popups": "~31.1.20"
}
},
"node_modules/@syncfusion/ej2-schedule/node_modules/@syncfusion/ej2-dropdowns": {
"version": "31.1.22",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-dropdowns/-/ej2-dropdowns-31.1.22.tgz",
"integrity": "sha512-UQaBh/7DudnkUAf//XOEPezc/mmmTTqdNMaKchEUlRErKOcAulY+HxDK8lsJegusxNiMz6vM8gxviiMhqW0uDg==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~31.1.22",
"@syncfusion/ej2-data": "~31.1.17",
"@syncfusion/ej2-inputs": "~31.1.22",
"@syncfusion/ej2-lists": "~31.1.17",
"@syncfusion/ej2-navigations": "~31.1.20",
"@syncfusion/ej2-notifications": "~31.1.17",
"@syncfusion/ej2-popups": "~31.1.20"
}
},
"node_modules/@syncfusion/ej2-schedule/node_modules/@syncfusion/ej2-inputs": {
"version": "31.1.22",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-inputs/-/ej2-inputs-31.1.22.tgz",
"integrity": "sha512-XkF69fWeYcDpbokpiqIgjSEyVlsG7G8qGYUslUpT0ynZZ60+tz/DWemxh7gfQicJ3QCI9dzQFxxTnzEe/aRAzw==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~31.1.22",
"@syncfusion/ej2-buttons": "~31.1.21",
"@syncfusion/ej2-popups": "~31.1.20",
"@syncfusion/ej2-splitbuttons": "~31.1.17"
}
},
"node_modules/@syncfusion/ej2-schedule/node_modules/@syncfusion/ej2-navigations": {
"version": "31.1.20",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-navigations/-/ej2-navigations-31.1.20.tgz",
"integrity": "sha512-KzJT4vHbMF9ooq8o41xwYUJX70Dr96mMEaRYJusRv/RJ+1zM/UvHhgX8n15TIFTXexH1y+KwkFvYaGchcokBgA==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~31.1.20",
"@syncfusion/ej2-buttons": "~31.1.17",
"@syncfusion/ej2-data": "~31.1.17",
"@syncfusion/ej2-inputs": "~31.1.20",
"@syncfusion/ej2-lists": "~31.1.17",
"@syncfusion/ej2-popups": "~31.1.20"
}
},
"node_modules/@syncfusion/ej2-schedule/node_modules/@syncfusion/ej2-popups": {
"version": "31.1.20",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-popups/-/ej2-popups-31.1.20.tgz",
"integrity": "sha512-GRPZWX2ZRNS73GpG8NbhC12eBNNqdMR80nhJrun1ZBVfvPTvd6y5/qvJLWg6/kYN02tM+smgyxfxfwOH4HGi3w==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~31.1.20",
"@syncfusion/ej2-buttons": "~31.1.17"
}
},
"node_modules/@syncfusion/ej2-splitbuttons": { "node_modules/@syncfusion/ej2-splitbuttons": {
"version": "31.1.17", "version": "31.1.17",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-splitbuttons/-/ej2-splitbuttons-31.1.17.tgz", "resolved": "https://registry.npmjs.org/@syncfusion/ej2-splitbuttons/-/ej2-splitbuttons-31.1.17.tgz",
......
<div class="container-fluid"> <!-- Meeting Room Booking System with Syncfusion Schedule -->
<div class="row"> <div class="meeting-booking-container">
<div class="col-12"> <!-- Header Section -->
<div class="card"> <div class="page-header">
<div class="card-header"> <div class="header-content">
<h4 class="card-title">ระบบจองห้องประชุม</h4> <div class="header-title">
<p class="card-subtitle">จองห้องประชุมและจัดการการประชุมของคุณ</p> <h1><i class="fas fa-calendar-alt"></i> ระบบจองห้องประชุม</h1>
<p>จัดการการจองห้องประชุมอย่างมีประสิทธิภาพด้วย Syncfusion Schedule</p>
</div>
<div class="header-actions">
<button mat-raised-button color="primary" (click)="showBookingForm = true">
<mat-icon>add</mat-icon>
จองห้องประชุม
</button>
<button mat-raised-button color="accent" (click)="loadSampleData()">
<mat-icon>refresh</mat-icon>
โหลดข้อมูลตัวอย่าง
</button>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Booking Form Modal -->
<div class="booking-form-overlay" *ngIf="showBookingForm" (click)="showBookingForm = false">
<div class="booking-form-container" (click)="$event.stopPropagation()">
<div class="form-header">
<h2><mat-icon>event_available</mat-icon> จองห้องประชุม</h2>
<button mat-icon-button (click)="showBookingForm = false">
<mat-icon>close</mat-icon>
</button>
</div> </div>
<div class="card-body">
<mat-tab-group>
<!-- Booking Tab -->
<mat-tab label="จองห้องประชุม">
<div class="row mt-3">
<div class="col-md-8">
<mat-card>
<mat-card-header>
<mat-card-title>ข้อมูลการจอง</mat-card-title>
</mat-card-header>
<mat-card-content>
<form [formGroup]="bookingForm" (ngSubmit)="createBooking()">
<div class="row">
<div class="col-md-6">
<mat-form-field appearance="outline" class="w-100">
<mat-label>ห้องประชุม</mat-label>
<mat-select formControlName="roomId" (selectionChange)="onRoomChange()">
<mat-option *ngFor="let room of rooms$ | async" [value]="room.id">
{{ room.name }} ({{ room.capacity }} ที่นั่ง) - {{ room.location }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-md-6">
<mat-form-field appearance="outline" class="w-100">
<mat-label>วันที่</mat-label>
<input matInput [matDatepicker]="picker"
[(ngModel)]="selectedDate"
(dateChange)="onDateChange()"
formControlName="startDateTime">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
</div>
<div class="row"> <form [formGroup]="bookingForm" (ngSubmit)="createBooking()" class="booking-form">
<div class="col-md-6"> <div class="form-row">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="form-field">
<mat-label>หัวข้อการประชุม</mat-label> <mat-label>ห้องประชุม</mat-label>
<input matInput formControlName="title" placeholder="ระบุหัวข้อการประชุม"> <mat-select formControlName="roomId" (selectionChange)="onRoomChange()">
</mat-form-field> <mat-option *ngFor="let room of rooms$ | async" [value]="room.id">
</div> {{ room.name }} ({{ room.capacity }} ที่นั่ง) - {{ room.location }}
<div class="col-md-6"> </mat-option>
<mat-form-field appearance="outline" class="w-100"> </mat-select>
<mat-label>รายละเอียด</mat-label> </mat-form-field>
<input matInput formControlName="description" placeholder="รายละเอียดเพิ่มเติม (ไม่บังคับ)">
</mat-form-field>
</div>
</div>
<div class="row"> <mat-form-field appearance="outline" class="form-field">
<div class="col-md-6"> <mat-label>หัวข้อการประชุม</mat-label>
<mat-form-field appearance="outline" class="w-100"> <input matInput formControlName="title" placeholder="ระบุหัวข้อการประชุม">
<mat-label>เวลาเริ่มต้น</mat-label> </mat-form-field>
<input matInput formControlName="startDateTime" type="datetime-local"> </div>
</mat-form-field>
</div>
<div class="col-md-6">
<mat-form-field appearance="outline" class="w-100">
<mat-label>เวลาสิ้นสุด</mat-label>
<input matInput formControlName="endDateTime" type="datetime-local">
</mat-form-field>
</div>
</div>
<div class="row"> <div class="form-row">
<div class="col-12"> <mat-form-field appearance="outline" class="form-field">
<mat-form-field appearance="outline" class="w-100"> <mat-label>วันที่เริ่มต้น</mat-label>
<mat-label>ผู้เข้าร่วมประชุม</mat-label> <input matInput [matDatepicker]="startPicker" formControlName="startDateTime">
<mat-chip-grid #chipGrid> <mat-datepicker-toggle matSuffix [for]="startPicker"></mat-datepicker-toggle>
<mat-chip-row *ngFor="let attendee of attendees" (removed)="removeAttendee(attendee)"> <mat-datepicker #startPicker></mat-datepicker>
{{ attendee }} </mat-form-field>
<button matChipRemove>
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>
<input placeholder="เพิ่มผู้เข้าร่วมประชุม"
[matChipInputFor]="chipGrid"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="addAttendee($event)">
</mat-chip-grid>
</mat-form-field>
</div>
</div>
<div class="row mt-3"> <mat-form-field appearance="outline" class="form-field">
<div class="col-12"> <mat-label>วันที่สิ้นสุด</mat-label>
<button mat-raised-button color="primary" type="submit" <input matInput [matDatepicker]="endPicker" formControlName="endDateTime">
[disabled]="!bookingForm.valid"> <mat-datepicker-toggle matSuffix [for]="endPicker"></mat-datepicker-toggle>
<mat-icon>event</mat-icon> <mat-datepicker #endPicker></mat-datepicker>
จองห้องประชุม </mat-form-field>
</button> </div>
<button mat-button type="button" (click)="bookingForm.reset()">
<mat-icon>refresh</mat-icon>
รีเซ็ต
</button>
</div>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="col-md-4"> <div class="form-row">
<mat-card> <mat-form-field appearance="outline" class="form-field">
<mat-card-header> <mat-label>เวลาเริ่มต้น</mat-label>
<mat-card-title>ช่วงเวลาที่ว่าง</mat-card-title> <input matInput type="time" formControlName="startTime">
<mat-card-subtitle *ngIf="selectedRoom">ห้อง: {{ getRoomName(selectedRoom) }}</mat-card-subtitle> </mat-form-field>
</mat-card-header>
<mat-card-content>
<div *ngIf="isLoading" class="text-center">
<mat-spinner diameter="30"></mat-spinner>
<p>กำลังโหลด...</p>
</div>
<div *ngIf="!isLoading && timeSlots.length > 0" class="time-slots"> <mat-form-field appearance="outline" class="form-field">
<div *ngFor="let slot of timeSlots" <mat-label>เวลาสิ้นสุด</mat-label>
class="time-slot" <input matInput type="time" formControlName="endTime">
[class.available]="slot.isAvailable" </mat-form-field>
[class.unavailable]="!slot.isAvailable" </div>
(click)="selectTimeSlot(slot)">
<span class="time">{{ slot.startTime }} - {{ slot.endTime }}</span>
<mat-icon *ngIf="slot.isAvailable">check_circle</mat-icon>
<mat-icon *ngIf="!slot.isAvailable">cancel</mat-icon>
</div>
</div>
<div *ngIf="!isLoading && timeSlots.length === 0" class="text-center text-muted"> <mat-form-field appearance="outline" class="form-field-full">
<mat-icon>event_busy</mat-icon> <mat-label>รายละเอียด</mat-label>
<p>ไม่มีช่วงเวลาที่ว่างในวันนี้</p> <textarea matInput formControlName="description" rows="3" placeholder="รายละเอียดเพิ่มเติม (ไม่บังคับ)"></textarea>
</div> </mat-form-field>
</mat-card-content>
</mat-card>
</div>
</div>
</mat-tab>
<!-- My Bookings Tab --> <div class="form-actions">
<mat-tab label="การจองของฉัน"> <button mat-raised-button color="primary" type="submit" [disabled]="!bookingForm.valid">
<div class="row mt-3"> <mat-icon>event</mat-icon>
<div class="col-12"> จองห้องประชุม
<mat-card> </button>
<mat-card-header> <button mat-button type="button" (click)="bookingForm.reset()">
<mat-card-title>รายการการจองของฉัน</mat-card-title> <mat-icon>refresh</mat-icon>
</mat-card-header> รีเซ็ต
<mat-card-content> </button>
<div class="table-responsive"> <button mat-button type="button" (click)="showBookingForm = false">
<table mat-table [dataSource]="(bookings$ | async) || []" class="w-100"> <mat-icon>close</mat-icon>
<ng-container matColumnDef="title"> ยกเลิก
<th mat-header-cell *matHeaderCellDef>หัวข้อ</th> </button>
<td mat-cell *matCellDef="let booking">{{ booking.title }}</td> </div>
</ng-container> </form>
</div>
</div>
<!-- Statistics Cards -->
<div class="statistics-section" *ngIf="scheduleData.length > 0">
<div class="stats-title">
<h3><mat-icon>analytics</mat-icon> สถิติการจอง</h3>
</div>
<div class="stats-cards">
<div class="stat-card">
<div class="stat-icon">
<mat-icon>event</mat-icon>
</div>
<div class="stat-content">
<div class="stat-number">{{ scheduleData.length }}</div>
<div class="stat-label">การจองทั้งหมด</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<mat-icon>check_circle</mat-icon>
</div>
<div class="stat-content">
<div class="stat-number">{{ getConfirmedCount() }}</div>
<div class="stat-label">ยืนยันแล้ว</div>
</div>
</div>
<ng-container matColumnDef="room"> <div class="stat-card">
<th mat-header-cell *matHeaderCellDef>ห้องประชุม</th> <div class="stat-icon">
<td mat-cell *matCellDef="let booking">{{ booking.roomName }}</td> <mat-icon>pending</mat-icon>
</ng-container> </div>
<div class="stat-content">
<div class="stat-number">{{ getPendingCount() }}</div>
<div class="stat-label">รอดำเนินการ</div>
</div>
</div>
<ng-container matColumnDef="startTime"> <div class="stat-card">
<th mat-header-cell *matHeaderCellDef>เวลาเริ่มต้น</th> <div class="stat-icon">
<td mat-cell *matCellDef="let booking"> <mat-icon>room</mat-icon>
{{ booking.startDateTime | date:'dd/MM/yyyy HH:mm' }} </div>
</td> <div class="stat-content">
</ng-container> <div class="stat-number">{{ getUniqueRoomsCount() }}</div>
<div class="stat-label">ห้องที่ใช้งาน</div>
</div>
</div>
</div>
</div>
<ng-container matColumnDef="endTime"> <!-- Schedule View -->
<th mat-header-cell *matHeaderCellDef>เวลาสิ้นสุด</th> <div class="schedule-section">
<td mat-cell *matCellDef="let booking"> <div class="schedule-header">
{{ booking.endDateTime | date:'dd/MM/yyyy HH:mm' }} <div class="schedule-title">
</td> <h2><mat-icon>schedule</mat-icon> ปฏิทินการจองห้องประชุม</h2>
</ng-container> <p>ดูและจัดการการจองในรูปแบบปฏิทินแบบเต็มรูปแบบ</p>
</div>
<ng-container matColumnDef="organizer"> <div class="schedule-controls">
<th mat-header-cell *matHeaderCellDef>ผู้จัด</th> <mat-form-field appearance="outline" class="control-field">
<td mat-cell *matCellDef="let booking">{{ booking.organizerName }}</td> <mat-label>กรองตามห้องประชุม</mat-label>
</ng-container> <mat-select [(value)]="selectedRoom" (selectionChange)="onRoomChange()">
<mat-option value="">ทั้งหมด</mat-option>
<mat-option *ngFor="let room of rooms$ | async" [value]="room.id">
{{ room.name }}
</mat-option>
</mat-select>
</mat-form-field>
<ng-container matColumnDef="status"> <mat-form-field appearance="outline" class="control-field">
<th mat-header-cell *matHeaderCellDef>สถานะ</th> <mat-label>มุมมอง</mat-label>
<td mat-cell *matCellDef="let booking"> <mat-select [(value)]="currentView" (selectionChange)="onViewChange($event)">
<span class="badge badge-{{ getStatusColor(booking.status) }}"> <mat-option value="Day">รายวัน</mat-option>
{{ getStatusText(booking.status) }} <mat-option value="Week">รายสัปดาห์</mat-option>
</span> <mat-option value="WorkWeek">วันทำงาน</mat-option>
</td> <mat-option value="Month">รายเดือน</mat-option>
</ng-container> <mat-option value="Agenda">วาระการประชุม</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<ng-container matColumnDef="actions"> <!-- Syncfusion Schedule Component -->
<th mat-header-cell *matHeaderCellDef>การดำเนินการ</th> <div class="schedule-wrapper">
<td mat-cell *matCellDef="let booking"> <ejs-schedule
<button mat-icon-button #scheduleObj
*ngIf="booking.status === 'pending' || booking.status === 'confirmed'" [selectedDate]="selectedDate_schedule"
(click)="cancelBooking(booking.id)" [currentView]="currentView"
matTooltip="ยกเลิกการจอง"> [eventSettings]="eventSettings"
<mat-icon>cancel</mat-icon> [showQuickInfo]="showQuickInfo"
</button> [allowDragAndDrop]="allowDragAndDrop"
<button mat-icon-button matTooltip="ดูรายละเอียด"> (eventClick)="onEventClick($event)"
<mat-icon>visibility</mat-icon> (eventCreate)="onEventCreate($event)"
</button> (eventUpdate)="onEventUpdate($event)"
</td> (eventDelete)="onEventDelete($event)"
</ng-container> (viewChange)="onViewChange($event)"
(dateChange)="onDateChange_schedule($event)"
height="700px"
width="100%"
cssClass="custom-schedule">
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <!-- Event Template -->
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> <ng-template #eventTemplate let-data>
</table> <div class="event-template">
</div> <div class="event-title">{{ data.Subject }}</div>
</mat-card-content> <div class="event-location">{{ data.Location }}</div>
</mat-card> <div class="event-time">
</div> {{ data.StartTime | date:'HH:mm' }} - {{ data.EndTime | date:'HH:mm' }}
</div> </div>
</mat-tab> </div>
</ng-template>
<!-- Statistics Tab --> <!-- Quick Info Template -->
<mat-tab label="สถิติ"> <ng-template #quickInfoTemplate let-data>
<div class="row mt-3"> <div class="quick-info-template">
<div class="col-12"> <div class="quick-info-header">
<mat-card> <h6>{{ data.Subject }}</h6>
<mat-card-header> <span class="status-badge status-{{ data.Status }}">
<mat-card-title>สถิติการจองห้องประชุม</mat-card-title> {{ getStatusText(data.Status) }}
<mat-card-subtitle>ข้อมูลสถิติการใช้งานห้องประชุม</mat-card-subtitle> </span>
</mat-card-header> </div>
<mat-card-content> <div class="quick-info-content">
<div *ngIf="statistics$ | async as stats" class="row"> <div class="info-item">
<div class="col-md-3"> <mat-icon>location_on</mat-icon>
<div class="stat-card"> <span>{{ data.Location }}</span>
<div class="stat-number">{{ stats.totalBookings }}</div> </div>
<div class="stat-label">การจองทั้งหมด</div> <div class="info-item">
</div> <mat-icon>schedule</mat-icon>
</div> <span>{{ data.StartTime | date:'dd/MM/yyyy HH:mm' }} - {{ data.EndTime | date:'dd/MM/yyyy HH:mm' }}</span>
<div class="col-md-3"> </div>
<div class="stat-card"> <div class="info-item" *ngIf="data.Description">
<div class="stat-number">{{ stats.confirmedBookings }}</div> <mat-icon>description</mat-icon>
<div class="stat-label">ยืนยันแล้ว</div> <span>{{ data.Description }}</span>
</div> </div>
</div> <div class="info-item">
<div class="col-md-3"> <mat-icon>person</mat-icon>
<div class="stat-card"> <span>{{ data.Organizer }}</span>
<div class="stat-number">{{ stats.pendingBookings }}</div> </div>
<div class="stat-label">รอดำเนินการ</div> <div class="info-item" *ngIf="data.Attendees">
</div> <mat-icon>group</mat-icon>
</div> <span>{{ data.Attendees }}</span>
<div class="col-md-3">
<div class="stat-card">
<div class="stat-number">{{ stats.averageBookingDuration | number:'1.0-0' }} นาที</div>
<div class="stat-label">ระยะเวลาเฉลี่ย</div>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
</div> </div>
</div> </div>
</mat-tab> <div class="quick-info-actions">
</mat-tab-group> <button mat-button color="primary" (click)="onEventClick(data)">
</div> <mat-icon>visibility</mat-icon>
ดูรายละเอียด
</button>
<button mat-button color="warn" *ngIf="data.Status === 'pending' || data.Status === 'confirmed'">
<mat-icon>cancel</mat-icon>
ยกเลิก
</button>
</div>
</div>
</ng-template>
</ejs-schedule>
</div> </div>
</div> </div>
</div> </div>
......
.time-slots { // Modern Meeting Room Booking System Styles
max-height: 400px; .meeting-booking-container {
overflow-y: auto; min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
font-family: 'Noto Sans Thai', sans-serif;
}
.time-slot { // Header Section
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem 0;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
.header-content {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 8px 12px; max-width: 1200px;
margin-bottom: 4px; margin: 0 auto;
border-radius: 4px; padding: 0 2rem;
cursor: pointer;
transition: all 0.2s ease;
&.available { .header-title {
background-color: #e8f5e8; h1 {
border: 1px solid #4caf50; margin: 0;
color: #2e7d32; font-size: 2.5rem;
font-weight: 700;
display: flex;
align-items: center;
gap: 1rem;
&:hover { i {
background-color: #c8e6c9; font-size: 2rem;
transform: translateY(-1px); }
box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
p {
margin: 0.5rem 0 0 0;
font-size: 1.1rem;
opacity: 0.9;
} }
} }
&.unavailable { .header-actions {
background-color: #ffebee; display: flex;
border: 1px solid #f44336; gap: 1rem;
color: #c62828;
cursor: not-allowed; button {
opacity: 0.6; padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.2);
}
}
} }
}
}
// Main Content
.main-content {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
// Booking Form Modal
.booking-form-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(5px);
.booking-form-container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
border-bottom: 1px solid #e9ecef;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 16px 16px 0 0;
.time { h2 {
font-weight: 500; margin: 0;
font-size: 1.5rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
}
button {
color: white;
}
} }
mat-icon { .booking-form {
font-size: 18px; padding: 2rem;
width: 18px;
height: 18px; .form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
}
.form-field {
width: 100%;
margin-bottom: 1rem;
}
.form-field-full {
width: 100%;
margin-bottom: 1rem;
}
.form-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #e9ecef;
button {
padding: 0.75rem 1.5rem;
font-weight: 600;
border-radius: 8px;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
}
}
}
} }
} }
} }
.stat-card { // Schedule Section
text-align: center; .schedule-section {
padding: 20px; background: white;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px;
color: white; box-shadow: 0 8px 32px rgba(0,0,0,0.1);
border-radius: 8px; overflow: hidden;
margin-bottom: 16px; margin-bottom: 2rem;
.schedule-header {
padding: 2rem;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid #dee2e6;
.schedule-title {
margin-bottom: 1.5rem;
.stat-number { h2 {
font-size: 2.5rem; margin: 0 0 0.5rem 0;
font-weight: bold; font-size: 1.8rem;
margin-bottom: 8px; font-weight: 700;
color: #495057;
display: flex;
align-items: center;
gap: 0.5rem;
}
p {
margin: 0;
color: #6c757d;
font-size: 1rem;
}
}
.schedule-controls {
display: flex;
gap: 1rem;
flex-wrap: wrap;
.control-field {
min-width: 200px;
}
}
} }
.stat-label { .schedule-wrapper {
font-size: 0.9rem; padding: 1rem;
opacity: 0.9;
} }
} }
.badge { // Statistics Section
padding: 4px 8px; .statistics-section {
border-radius: 12px; margin-bottom: 2rem;
font-size: 0.75rem; background: white;
font-weight: 500; border-radius: 16px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
padding: 2rem;
overflow: hidden;
&.badge-success { .stats-title {
background-color: #4caf50; margin-bottom: 1.5rem;
color: white;
}
&.badge-warning { h3 {
background-color: #ff9800; margin: 0;
color: white; font-size: 1.5rem;
font-weight: 700;
color: #495057;
display: flex;
align-items: center;
gap: 0.5rem;
}
} }
&.badge-danger { .stats-cards {
background-color: #f44336; display: grid;
color: white; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
} gap: 1.5rem;
&.badge-info { .stat-card {
background-color: #2196f3; background: white;
color: white; border-radius: 16px;
} padding: 2rem;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
display: flex;
align-items: center;
gap: 1rem;
transition: all 0.3s ease;
&.badge-secondary { &:hover {
background-color: #6c757d; transform: translateY(-4px);
color: white; box-shadow: 0 12px 40px rgba(0,0,0,0.15);
} }
}
.table-responsive { .stat-icon {
overflow-x: auto; width: 60px;
} height: 60px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: white;
mat-card { mat-icon {
margin-bottom: 16px; font-size: 2rem;
} width: 2rem;
height: 2rem;
}
}
mat-card-header { .stat-content {
margin-bottom: 16px; .stat-number {
} font-size: 2rem;
font-weight: 700;
color: #495057;
margin-bottom: 0.25rem;
}
mat-form-field { .stat-label {
margin-bottom: 16px; font-size: 0.9rem;
} color: #6c757d;
font-weight: 500;
}
}
// Time slot grid &:nth-child(1) .stat-icon {
.time-slots { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: grid; }
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
}
// Responsive design &:nth-child(2) .stat-icon {
@media (max-width: 768px) { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
.time-slots { }
grid-template-columns: 1fr;
}
.stat-card { &:nth-child(3) .stat-icon {
margin-bottom: 12px; background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-number { &:nth-child(4) .stat-icon {
font-size: 2rem; background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
} }
} }
} }
// Form styling // Syncfusion Schedule Custom Styling
form { ::ng-deep {
.row { .custom-schedule {
margin-bottom: 16px; .e-schedule {
border-radius: 12px;
font-family: 'Noto Sans Thai', sans-serif;
border: 1px solid #e9ecef;
}
.e-schedule .e-header-cells {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
border: none;
}
.e-schedule .e-date-header {
background-color: #f8f9fa;
color: #495057;
font-weight: 600;
}
.e-schedule .e-work-cells {
background-color: #ffffff;
border-color: #e9ecef;
}
.e-schedule .e-work-cells:hover {
background-color: #f8f9fa;
}
.e-schedule .e-current-day {
background-color: #e3f2fd !important;
}
.e-schedule .e-selected-date {
background-color: #bbdefb !important;
}
// Event styling
.e-schedule .e-appointment {
border-radius: 8px;
border: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.e-schedule .e-appointment .e-appointment-details {
padding: 6px 10px;
}
// Event templates
.event-template {
padding: 6px 10px;
font-size: 12px;
line-height: 1.3;
.event-title {
font-weight: 700;
color: #ffffff;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 13px;
}
.event-location {
color: rgba(255, 255, 255, 0.9);
font-size: 11px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
}
.event-time {
color: rgba(255, 255, 255, 0.8);
font-size: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
// Quick info template
.quick-info-template {
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
min-width: 350px;
border: 1px solid #e9ecef;
.quick-info-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #e9ecef;
h6 {
margin: 0;
color: #495057;
font-weight: 700;
font-size: 1.1rem;
}
.status-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
&.status-confirmed {
background-color: #d4edda;
color: #155724;
}
&.status-pending {
background-color: #fff3cd;
color: #856404;
}
&.status-cancelled {
background-color: #f8d7da;
color: #721c24;
}
}
}
.quick-info-content {
.info-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
color: #6c757d;
font-size: 14px;
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
color: #667eea;
}
span {
font-weight: 500;
}
}
}
.quick-info-actions {
display: flex;
gap: 8px;
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #e9ecef;
button {
padding: 8px 16px;
font-size: 0.9rem;
font-weight: 600;
border-radius: 6px;
}
}
}
// Status colors for events
.e-appointment[data-category="success"] {
background: linear-gradient(135deg, #4caf50 0%, #45a049 100%) !important;
}
.e-appointment[data-category="warning"] {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%) !important;
}
.e-appointment[data-category="danger"] {
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%) !important;
}
.e-appointment[data-category="info"] {
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%) !important;
}
.e-appointment[data-category="secondary"] {
background: linear-gradient(135deg, #6c757d 0%, #5a6268 100%) !important;
}
// Toolbar styling
.e-schedule .e-toolbar {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 2px solid #dee2e6;
padding: 12px 20px;
}
.e-schedule .e-toolbar .e-toolbar-item {
color: #495057;
font-weight: 600;
}
.e-schedule .e-toolbar .e-toolbar-item:hover {
background-color: #e9ecef;
border-radius: 6px;
}
// View switcher
.e-schedule .e-view-switcher {
.e-toolbar-item {
margin: 0 4px;
padding: 8px 16px;
border-radius: 6px;
transition: all 0.3s ease;
font-weight: 600;
&.e-active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
}
}
// Date navigator
.e-schedule .e-date-navigator {
.e-toolbar-item {
font-weight: 700;
color: #495057;
font-size: 1.1rem;
}
}
} }
} }
// Button styling // Responsive Design
button[mat-raised-button] { @media (max-width: 768px) {
margin-right: 8px; .page-header .header-content {
} flex-direction: column;
gap: 1rem;
text-align: center;
}
// Loading spinner .main-content {
mat-spinner { padding: 1rem;
margin: 0 auto; }
}
// Empty state .schedule-section .schedule-header .schedule-controls {
.text-center { flex-direction: column;
text-align: center; }
}
.text-muted { .stats-cards {
color: #6c757d; grid-template-columns: 1fr;
} }
// Card hover effects .booking-form-container {
mat-card:hover { margin: 1rem;
box-shadow: 0 4px 8px rgba(0,0,0,0.1); width: calc(100% - 2rem);
transition: box-shadow 0.2s ease; }
} }
...@@ -21,6 +21,11 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; ...@@ -21,6 +21,11 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
// Syncfusion Schedule imports
import { ScheduleModule, View, EventSettingsModel, DayService, WeekService, WorkWeekService, MonthService, AgendaService, ResizeService, DragAndDropService } from '@syncfusion/ej2-angular-schedule';
import { DateTimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns';
import { MeetingBookingService } from '../services/meeting-booking.service'; import { MeetingBookingService } from '../services/meeting-booking.service';
import { MeetingRoom, MeetingBooking, BookingTimeSlot, BookingStatistics } from '../models/meeting-booking.model'; import { MeetingRoom, MeetingBooking, BookingTimeSlot, BookingStatistics } from '../models/meeting-booking.model';
...@@ -47,7 +52,20 @@ import { MeetingRoom, MeetingBooking, BookingTimeSlot, BookingStatistics } from ...@@ -47,7 +52,20 @@ import { MeetingRoom, MeetingBooking, BookingTimeSlot, BookingStatistics } from
MatTabsModule, MatTabsModule,
MatSnackBarModule, MatSnackBarModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
TranslateModule TranslateModule,
// Syncfusion modules
ScheduleModule,
DateTimePickerModule,
DropDownListModule
],
providers: [
DayService,
WeekService,
WorkWeekService,
MonthService,
AgendaService,
ResizeService,
DragAndDropService
], ],
templateUrl: './meeting-booking.component.html', templateUrl: './meeting-booking.component.html',
styleUrls: ['./meeting-booking.component.scss'] styleUrls: ['./meeting-booking.component.scss']
...@@ -69,6 +87,19 @@ export class MeetingBookingComponent implements OnInit { ...@@ -69,6 +87,19 @@ export class MeetingBookingComponent implements OnInit {
separatorKeysCodes: number[] = [13, 188]; // ENTER and COMMA key codes separatorKeysCodes: number[] = [13, 188]; // ENTER and COMMA key codes
// Syncfusion Schedule properties
public selectedDate_schedule: Date = new Date();
public currentView: View = 'Month';
public eventSettings: EventSettingsModel = {
dataSource: []
};
public scheduleData: any[] = [];
public showQuickInfo: boolean = true;
public allowDragAndDrop: boolean = true;
// UI State properties
public showBookingForm: boolean = false;
constructor( constructor(
private meetingBookingService: MeetingBookingService, private meetingBookingService: MeetingBookingService,
private fb: FormBuilder, private fb: FormBuilder,
...@@ -95,6 +126,8 @@ export class MeetingBookingComponent implements OnInit { ...@@ -95,6 +126,8 @@ export class MeetingBookingComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.loadTimeSlots(); this.loadTimeSlots();
this.loadScheduleData();
this.loadSampleData(); // เพิ่มข้อมูลตัวอย่าง
} }
onDateChange(): void { onDateChange(): void {
...@@ -233,4 +266,167 @@ export class MeetingBookingComponent implements OnInit { ...@@ -233,4 +266,167 @@ export class MeetingBookingComponent implements OnInit {
}); });
return roomName; return roomName;
} }
// Syncfusion Schedule methods
loadScheduleData(): void {
if (this.bookings$) {
this.bookings$.subscribe(bookings => {
if (bookings && bookings.length > 0) {
this.scheduleData = bookings.map(booking => ({
Id: booking.id,
Subject: booking.title,
StartTime: new Date(booking.startDateTime),
EndTime: new Date(booking.endDateTime),
Location: booking.roomName,
Description: booking.description,
IsAllDay: false,
CategoryColor: this.getStatusColor(booking.status),
Status: booking.status,
Organizer: booking.organizerName,
Attendees: booking.attendees.map(a => a.userName).join(', ')
}));
} else {
// ถ้าไม่มีข้อมูลจาก service ให้ใช้ข้อมูลตัวอย่าง
this.loadSampleData();
}
this.eventSettings = {
dataSource: this.scheduleData
};
console.log('Schedule data loaded:', this.scheduleData);
});
} else {
// ถ้าไม่มี bookings$ observable ให้ใช้ข้อมูลตัวอย่าง
this.loadSampleData();
}
}
onEventClick(args: any): void {
console.log('Event clicked:', args);
// Handle event click - show details or edit
this.snackBar.open(`ดูรายละเอียดการจอง: ${args.event.Subject}`, 'ปิด', { duration: 3000 });
}
onEventCreate(args: any): void {
console.log('Event created:', args);
// Handle new event creation
const newEvent = args.data;
const booking = {
roomId: this.selectedRoom,
roomName: newEvent.Location || '',
title: newEvent.Subject,
description: newEvent.Description || '',
startDateTime: newEvent.StartTime,
endDateTime: newEvent.EndTime,
organizerId: 'current-user',
organizerName: 'Current User',
attendees: [],
status: 'pending' as const
};
this.meetingBookingService.createBooking(booking).subscribe({
next: () => {
this.snackBar.open('จองห้องประชุมสำเร็จ', 'ปิด', { duration: 3000 });
this.loadScheduleData();
},
error: (error) => {
this.snackBar.open('เกิดข้อผิดพลาดในการจอง: ' + error.message, 'ปิด', { duration: 5000 });
}
});
}
onEventUpdate(args: any): void {
console.log('Event updated:', args);
// Handle event update
}
onEventDelete(args: any): void {
console.log('Event deleted:', args);
// Handle event deletion
const eventId = args.data.Id;
this.cancelBooking(eventId);
}
onViewChange(args: any): void {
console.log('View changed:', args);
this.currentView = args.currentView;
}
onDateChange_schedule(args: any): void {
console.log('Date changed:', args);
this.selectedDate_schedule = args.selectedDate;
}
// ฟังก์ชันสำหรับโหลดข้อมูลตัวอย่าง
loadSampleData(): void {
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const nextWeek = new Date(today);
nextWeek.setDate(nextWeek.getDate() + 7);
const sampleData = [
{
Id: 1,
Subject: 'ประชุมทีมพัฒนา',
StartTime: new Date(today.getFullYear(), today.getMonth(), today.getDate(), 9, 0),
EndTime: new Date(today.getFullYear(), today.getMonth(), today.getDate(), 10, 30),
Location: 'ห้องประชุม A',
Description: 'ประชุมทบทวนความคืบหน้าโปรเจค',
IsAllDay: false,
CategoryColor: 'success',
Status: 'confirmed',
Organizer: 'John Doe',
Attendees: 'Jane Smith, Bob Johnson'
},
{
Id: 2,
Subject: 'การนำเสนอผลงาน',
StartTime: new Date(tomorrow.getFullYear(), tomorrow.getMonth(), tomorrow.getDate(), 14, 0),
EndTime: new Date(tomorrow.getFullYear(), tomorrow.getMonth(), tomorrow.getDate(), 15, 30),
Location: 'ห้องประชุม B',
Description: 'นำเสนอผลงานไตรมาสที่ 1',
IsAllDay: false,
CategoryColor: 'info',
Status: 'pending',
Organizer: 'Alice Brown',
Attendees: 'Charlie Wilson, Diana Lee'
},
{
Id: 3,
Subject: 'อบรมเทคโนโลยีใหม่',
StartTime: new Date(nextWeek.getFullYear(), nextWeek.getMonth(), nextWeek.getDate(), 10, 0),
EndTime: new Date(nextWeek.getFullYear(), nextWeek.getMonth(), nextWeek.getDate(), 12, 0),
Location: 'ห้องประชุม C',
Description: 'อบรม Angular 17 และ TypeScript',
IsAllDay: false,
CategoryColor: 'warning',
Status: 'confirmed',
Organizer: 'Tech Lead',
Attendees: 'ทีมพัฒนา'
}
];
this.scheduleData = sampleData;
this.eventSettings = {
dataSource: this.scheduleData
};
console.log('Sample data loaded:', this.scheduleData);
}
// Statistics methods
getConfirmedCount(): number {
return this.scheduleData.filter(booking => booking.Status === 'confirmed').length;
}
getPendingCount(): number {
return this.scheduleData.filter(booking => booking.Status === 'pending').length;
}
getUniqueRoomsCount(): number {
const uniqueRooms = new Set(this.scheduleData.map(booking => booking.Location));
return uniqueRooms.size;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment