Commit 63435617 by Ooh-Ao

เมนู

parent 41948ca4
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { MenuPermissionService } from '../../portal-manage/services/menu-permission.service';
@Injectable({
providedIn: 'root'
})
export class MenuPermissionGuard implements CanActivate {
constructor(
private menuPermissionService: MenuPermissionService,
private router: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
const requiredPermission = route.data['permission'] || 'view';
const menuPath = state.url;
return this.menuPermissionService.canAccessMenu(menuPath, requiredPermission).pipe(
map(hasPermission => {
if (hasPermission) {
return true;
} else {
// Redirect to unauthorized page or dashboard
this.router.navigate(['/portal-manage/unauthorized']);
return false;
}
}),
catchError(() => {
// On error, redirect to dashboard
this.router.navigate(['/portal-manage/dashboard']);
return of(false);
})
);
}
}
import { Component } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router, RouterLink, RouterModule } from '@angular/router'; import { Router, RouterLink, RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { TokenService } from '../../shared/services/token.service'; import { TokenService } from '../../shared/services/token.service';
import { MenuPermissionService } from '../services/menu-permission.service';
import { UserRoleService } from '../services/user-role.service';
interface AppModule {
id: string;
name: string;
displayName: string;
description: string;
icon: string;
path: string;
isVisible: boolean;
permissions: {
view: boolean;
create: boolean;
edit: boolean;
delete: boolean;
export: boolean;
import: boolean;
};
}
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'], styleUrls: ['./home.component.scss'],
standalone: true, standalone: true,
imports: [RouterModule] imports: [RouterModule, CommonModule]
}) })
export class HomeComponent { export class HomeComponent implements OnInit {
accessibleApps$: Observable<AppModule[]>;
userInfo$: Observable<any>;
constructor( constructor(
private router: Router, private router: Router,
private tokenService: TokenService, private tokenService: TokenService,
private menuPermissionService: MenuPermissionService,
private userRoleService: UserRoleService
) { } ) { }
ngOnInit(): void {
this.loadAccessibleApps();
this.loadUserInfo();
}
private loadAccessibleApps(): void {
this.accessibleApps$ = combineLatest([
this.menuPermissionService.getAccessibleMenus(),
this.menuPermissionService.menuPermissions$
]).pipe(
map(([accessibleMenus, allMenus]) => {
return this.getAppModules().filter(app => {
const menu = this.findMenuByPath(allMenus, app.path);
return menu && menu.isVisible && menu.permissions.view;
});
})
);
}
private loadUserInfo(): void {
// Load current user info
this.userInfo$ = this.userRoleService.getUserById('current-user').pipe(
map(user => user || {
fullName: 'ผู้ใช้ปัจจุบัน',
email: 'user@company.com',
department: 'IT',
position: 'System User'
})
);
}
private getAppModules(): AppModule[] {
return [
{
id: 'myhr-plus',
name: 'myhr-plus',
displayName: 'myHR-Plus',
description: 'ระบบจัดการทรัพยากรบุคคลขั้นสูง',
icon: './assets/images/logoallHR/myhr-plus.jpg',
path: '/myhr-plus',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'myhr-lite',
name: 'myhr-lite',
displayName: 'myHR-Lite',
description: 'ระบบจัดการทรัพยากรบุคคลพื้นฐาน',
icon: './assets/images/logoallHR/myHR-Lite-logo-new.png',
path: '/myhr-lite',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'zeeme',
name: 'zeeme',
displayName: 'Zeeme Plus',
description: 'ระบบจัดการเวลาและลงเวลา',
icon: './assets/images/logoallHR/zeemePlus.png',
path: '/zeeme',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'myface',
name: 'myface',
displayName: 'myFace',
description: 'ระบบจัดการใบหน้าและความปลอดภัย',
icon: './assets/images/logoallHR/logo_myface.png',
path: '/myface',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'mylearn',
name: 'mylearn',
displayName: 'myLearn',
description: 'ระบบจัดการการเรียนรู้และฝึกอบรม',
icon: './assets/images/logoallHR/mylearn-logo.png',
path: '/mylearn',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'myjob',
name: 'myjob',
displayName: 'myJob',
description: 'ระบบจัดการงานและโครงการ',
icon: './assets/images/logoallHR/logo_myjob.png',
path: '/myjob',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'myskill-x',
name: 'myskill-x',
displayName: 'mySkill-X',
description: 'ระบบจัดการทักษะและความสามารถ',
icon: './assets/images/logoallHR/mySkill-x.png',
path: '/myskill-x',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'permission-management',
name: 'permission-management',
displayName: 'Permission Management',
description: 'ระบบจัดการสิทธิ์และบทบาท',
icon: './assets/images/icons/widget.png',
path: '/portal-manage/permission-management',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'menu-permission-management',
name: 'menu-permission-management',
displayName: 'Menu Permission',
description: 'ระบบจัดการสิทธิ์เมนู',
icon: './assets/images/icons/menu.png',
path: '/portal-manage/menu-permission-management',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'user-role-management',
name: 'user-role-management',
displayName: 'User & Role Management',
description: 'ระบบจัดการผู้ใช้และบทบาท',
icon: './assets/images/icons/users.png',
path: '/portal-manage/user-role-management',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'meeting-booking',
name: 'meeting-booking',
displayName: 'Meeting Booking',
description: 'ระบบจองห้องประชุม',
icon: './assets/images/icons/calendar.png',
path: '/portal-manage/meeting-booking',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'company-management',
name: 'company-management',
displayName: 'Company Management',
description: 'ระบบจัดการบริษัท',
icon: './assets/images/icons/building.png',
path: '/portal-manage/company-management',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
},
{
id: 'dashboard-management',
name: 'dashboard-management',
displayName: 'Dashboard Management',
description: 'ระบบจัดการแดชบอร์ด',
icon: './assets/images/icons/dashboard.png',
path: '/portal-manage/dashboard-management',
isVisible: true,
permissions: { view: true, create: false, edit: false, delete: false, export: false, import: false }
}
];
}
private findMenuByPath(menus: any[], path: string): any {
for (const menu of menus) {
if (menu.path === path) {
return menu;
}
if (menu.children) {
const found = this.findMenuByPath(menu.children, path);
if (found) return found;
}
}
return null;
}
logout() { logout() {
// localStorage.removeItem('authToken'); // Clear the authentication token
// this.router.navigate(['/auth/login']); // Navigate to the login page
this.tokenService.signOut(); this.tokenService.signOut();
} }
checkAppToken(appmodule:string){ checkAppToken(appmodule: string) {
this.tokenService.saveAppToken(appmodule) this.tokenService.saveAppToken(appmodule);
this.router.navigate(['/'+appmodule]) this.router.navigate(['/' + appmodule]);
}
navigateToApp(app: AppModule) {
if (app.path.startsWith('/portal-manage')) {
this.router.navigate([app.path]);
} else {
this.checkAppToken(app.id);
}
}
getAppCardClass(app: AppModule): string {
const baseClass = 'card bg-white rounded-xl p-8 text-center shadow-lg transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-xl';
if (app.id.includes('management') || app.id.includes('permission')) {
return `${baseClass} hover:bg-gradient-to-br from-gray-50 to-gray-100`;
} else {
return `${baseClass} hover:bg-gradient-to-br from-blue-50 to-blue-100`;
}
} }
} }
.time-slots {
max-height: 400px;
overflow-y: auto;
.time-slot {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 4px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
&.available {
background-color: #e8f5e8;
border: 1px solid #4caf50;
color: #2e7d32;
&:hover {
background-color: #c8e6c9;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
}
&.unavailable {
background-color: #ffebee;
border: 1px solid #f44336;
color: #c62828;
cursor: not-allowed;
opacity: 0.6;
}
.time {
font-weight: 500;
}
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
}
}
.stat-card {
text-align: center;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px;
margin-bottom: 16px;
.stat-number {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 8px;
}
.stat-label {
font-size: 0.9rem;
opacity: 0.9;
}
}
.badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
&.badge-success {
background-color: #4caf50;
color: white;
}
&.badge-warning {
background-color: #ff9800;
color: white;
}
&.badge-danger {
background-color: #f44336;
color: white;
}
&.badge-info {
background-color: #2196f3;
color: white;
}
&.badge-secondary {
background-color: #6c757d;
color: white;
}
}
.table-responsive {
overflow-x: auto;
}
mat-card {
margin-bottom: 16px;
}
mat-card-header {
margin-bottom: 16px;
}
mat-form-field {
margin-bottom: 16px;
}
// Time slot grid
.time-slots {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
}
// Responsive design
@media (max-width: 768px) {
.time-slots {
grid-template-columns: 1fr;
}
.stat-card {
margin-bottom: 12px;
.stat-number {
font-size: 2rem;
}
}
}
// Form styling
form {
.row {
margin-bottom: 16px;
}
}
// Button styling
button[mat-raised-button] {
margin-right: 8px;
}
// Loading spinner
mat-spinner {
margin: 0 auto;
}
// Empty state
.text-center {
text-align: center;
}
.text-muted {
color: #6c757d;
}
// Card hover effects
mat-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transition: box-shadow 0.2s ease;
}
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatChipsModule } from '@angular/material/chips';
import { MatChipInputEvent } from '@angular/material/chips';
import { MatDialogModule, MatDialog } from '@angular/material/dialog';
import { MatTabsModule } from '@angular/material/tabs';
import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { MeetingBookingService } from '../services/meeting-booking.service';
import { MeetingRoom, MeetingBooking, BookingTimeSlot, BookingStatistics } from '../models/meeting-booking.model';
@Component({
selector: 'app-meeting-booking',
standalone: true,
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
MatCardModule,
MatButtonModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatChipsModule,
MatDialogModule,
MatTabsModule,
MatSnackBarModule,
MatProgressSpinnerModule,
TranslateModule
],
templateUrl: './meeting-booking.component.html',
styleUrls: ['./meeting-booking.component.scss']
})
export class MeetingBookingComponent implements OnInit {
rooms$: Observable<MeetingRoom[]>;
bookings$: Observable<MeetingBooking[]>;
statistics$: Observable<BookingStatistics>;
selectedDate = new Date();
selectedRoom: string = '';
timeSlots: BookingTimeSlot[] = [];
isLoading = false;
bookingForm: FormGroup;
attendees: string[] = [];
displayedColumns: string[] = ['title', 'room', 'startTime', 'endTime', 'organizer', 'status', 'actions'];
constructor(
private meetingBookingService: MeetingBookingService,
private fb: FormBuilder,
private dialog: MatDialog,
private snackBar: MatSnackBar
) {
this.rooms$ = this.meetingBookingService.rooms$;
this.bookings$ = this.meetingBookingService.bookings$;
this.bookingForm = this.fb.group({
roomId: ['', Validators.required],
title: ['', Validators.required],
description: [''],
startDateTime: ['', Validators.required],
endDateTime: ['', Validators.required],
attendees: [[]]
});
this.statistics$ = this.meetingBookingService.getBookingStatistics(
new Date(new Date().setMonth(new Date().getMonth() - 1)),
new Date()
);
}
ngOnInit(): void {
this.loadTimeSlots();
}
onDateChange(): void {
this.loadTimeSlots();
}
onRoomChange(): void {
this.loadTimeSlots();
}
loadTimeSlots(): void {
if (this.selectedRoom && this.selectedDate) {
this.isLoading = true;
this.meetingBookingService.getAvailableTimeSlots(this.selectedRoom, this.selectedDate)
.subscribe({
next: (slots) => {
this.timeSlots = slots;
this.isLoading = false;
},
error: (error) => {
console.error('Error loading time slots:', error);
this.isLoading = false;
}
});
}
}
selectTimeSlot(slot: BookingTimeSlot): void {
if (slot.isAvailable) {
const startDateTime = new Date(this.selectedDate);
const [hours, minutes] = slot.startTime.split(':').map(Number);
startDateTime.setHours(hours, minutes, 0, 0);
const endDateTime = new Date(this.selectedDate);
const [endHours, endMinutes] = slot.endTime.split(':').map(Number);
endDateTime.setHours(endHours, endMinutes, 0, 0);
this.bookingForm.patchValue({
startDateTime,
endDateTime
});
}
}
addAttendee(event: MatChipInputEvent): void {
const value = (event.value || '').trim();
if (value) {
this.attendees.push(value);
this.bookingForm.patchValue({ attendees: this.attendees });
}
event.chipInput!.clear();
}
removeAttendee(attendee: string): void {
const index = this.attendees.indexOf(attendee);
if (index >= 0) {
this.attendees.splice(index, 1);
this.bookingForm.patchValue({ attendees: this.attendees });
}
}
createBooking(): void {
if (this.bookingForm.valid) {
const formValue = this.bookingForm.value;
const booking = {
roomId: formValue.roomId,
roomName: '', // Will be filled by service
title: formValue.title,
description: formValue.description,
startDateTime: formValue.startDateTime,
endDateTime: formValue.endDateTime,
organizerId: 'current-user', // From auth service
organizerName: 'Current User', // From auth service
attendees: this.attendees.map(email => ({
userId: '',
userName: email,
email: email,
status: 'pending' as const
})),
status: 'pending' as const
};
this.meetingBookingService.createBooking(booking).subscribe({
next: () => {
this.snackBar.open('จองห้องประชุมสำเร็จ', 'ปิด', { duration: 3000 });
this.bookingForm.reset();
this.attendees = [];
this.loadTimeSlots();
},
error: (error) => {
this.snackBar.open('เกิดข้อผิดพลาดในการจอง: ' + error.message, 'ปิด', { duration: 5000 });
}
});
}
}
cancelBooking(bookingId: string): void {
this.meetingBookingService.cancelBooking(bookingId, 'Cancelled by user').subscribe({
next: () => {
this.snackBar.open('ยกเลิกการจองสำเร็จ', 'ปิด', { duration: 3000 });
this.loadTimeSlots();
},
error: (error) => {
this.snackBar.open('เกิดข้อผิดพลาดในการยกเลิก: ' + error.message, 'ปิด', { duration: 5000 });
}
});
}
getStatusColor(status: string): string {
switch (status) {
case 'confirmed': return 'success';
case 'pending': return 'warning';
case 'cancelled': return 'danger';
case 'completed': return 'info';
default: return 'secondary';
}
}
getStatusText(status: string): string {
switch (status) {
case 'confirmed': return 'ยืนยันแล้ว';
case 'pending': return 'รอดำเนินการ';
case 'cancelled': return 'ยกเลิกแล้ว';
case 'completed': return 'เสร็จสิ้น';
default: return 'ไม่ทราบสถานะ';
}
}
}
.permission-tree {
.permission-node {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #e0e0e0;
&:last-child {
border-bottom: none;
}
.permission-info {
display: flex;
align-items: center;
flex: 1;
.permission-name {
font-weight: 500;
margin-right: 8px;
}
.permission-path {
font-size: 0.875rem;
color: #666;
}
}
.permission-controls {
display: flex;
align-items: center;
gap: 16px;
.permission-toggles {
display: flex;
flex-wrap: wrap;
gap: 8px;
mat-checkbox {
font-size: 0.875rem;
}
}
}
}
}
.permission-tree mat-tree-node {
padding-left: 0;
}
.permission-tree mat-tree-node[matTreeNodePadding] {
padding-left: 0;
}
.permission-tree mat-tree-node[matTreeNodePadding] > .mat-tree-node {
padding-left: 0;
}
// Responsive design
@media (max-width: 768px) {
.permission-node {
flex-direction: column;
align-items: flex-start !important;
.permission-controls {
width: 100%;
margin-top: 8px;
justify-content: space-between;
}
}
}
// Material tree styling
.mat-tree {
background: transparent;
}
.mat-tree-node {
min-height: 40px;
}
.mat-tree-node:hover {
background-color: rgba(0, 0, 0, 0.04);
}
// Card styling
mat-card {
margin-bottom: 16px;
}
mat-card-header {
margin-bottom: 16px;
}
// Tab styling
mat-tab-group {
margin-top: 16px;
}
// Form field styling
mat-form-field {
margin-bottom: 16px;
}
// Button styling
button[mat-raised-button] {
margin-right: 8px;
}
// Info text styling
.text-info {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background-color: #e3f2fd;
border-radius: 4px;
margin-bottom: 16px;
color: #1976d2;
}
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatTreeModule, MatTreeNestedDataSource } from '@angular/material/tree';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatTabsModule } from '@angular/material/tabs';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { NestedTreeControl } from '@angular/cdk/tree';
import { MenuPermissionService } from '../services/menu-permission.service';
import { MenuHierarchy, MenuPermission } from '../models/menu-permission.model';
interface MenuNode extends MenuHierarchy {
expandable: boolean;
level: number;
}
@Component({
selector: 'app-menu-permission-management',
standalone: true,
imports: [
CommonModule,
FormsModule,
MatTreeModule,
MatIconModule,
MatButtonModule,
MatCheckboxModule,
MatSlideToggleModule,
MatCardModule,
MatTabsModule,
MatSelectModule,
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatDialogModule,
MatSnackBarModule,
TranslateModule
],
templateUrl: './menu-permission-management.component.html',
styleUrls: ['./menu-permission-management.component.scss']
})
export class MenuPermissionManagementComponent implements OnInit {
treeControl = new NestedTreeControl<MenuNode>(node => node.children);
dataSource = new MatTreeNestedDataSource<MenuNode>();
selectedRoleId: string = '';
selectedUserId: string = '';
roles: any[] = [];
users: any[] = [];
// Permission types
permissionTypes = [
{ key: 'view', label: 'ดู' },
{ key: 'create', label: 'สร้าง' },
{ key: 'edit', label: 'แก้ไข' },
{ key: 'delete', label: 'ลบ' },
{ key: 'export', label: 'ส่งออก' },
{ key: 'import', label: 'นำเข้า' }
];
constructor(private menuPermissionService: MenuPermissionService) {}
ngOnInit(): void {
this.loadMenuHierarchy();
this.loadRoles();
this.loadUsers();
}
hasChild = (_: number, node: MenuNode) => node.expandable;
loadMenuHierarchy(): void {
this.menuPermissionService.getMenuHierarchy().subscribe(menus => {
this.dataSource.data = this.convertToTreeNodes(menus);
});
}
loadRoles(): void {
// Mock data - in real app, load from API
this.roles = [
{ id: 'admin', name: 'ผู้ดูแลระบบ' },
{ id: 'manager', name: 'ผู้จัดการ' },
{ id: 'user', name: 'ผู้ใช้ทั่วไป' }
];
}
loadUsers(): void {
// Mock data - in real app, load from API
this.users = [
{ id: '1', name: 'สมชาย ใจดี', roleId: 'admin' },
{ id: '2', name: 'สมหญิง รักงาน', roleId: 'manager' },
{ id: '3', name: 'สมศักดิ์ ทำงาน', roleId: 'user' }
];
}
convertToTreeNodes(menus: MenuHierarchy[]): MenuNode[] {
return menus.map(menu => ({
...menu,
expandable: !!(menu.children && menu.children.length > 0),
level: 0,
children: menu.children ? this.convertToTreeNodes(menu.children) : undefined
}));
}
onPermissionChange(node: MenuNode, permissionType: string, isChecked: boolean): void {
if (node.permissions) {
(node.permissions as any)[permissionType] = isChecked;
}
}
onVisibilityChange(node: MenuNode, isVisible: boolean): void {
node.isVisible = isVisible;
}
saveRolePermissions(): void {
if (!this.selectedRoleId) return;
const menuPermissions = this.extractMenuPermissions(this.dataSource.data);
this.menuPermissionService.updateRoleMenuPermissions(this.selectedRoleId, menuPermissions)
.subscribe({
next: () => {
console.log('Role permissions saved successfully');
// Show success message
},
error: (error) => {
console.error('Error saving role permissions:', error);
// Show error message
}
});
}
saveUserPermissions(): void {
if (!this.selectedUserId) return;
const menuPermissions = this.extractMenuPermissions(this.dataSource.data);
this.menuPermissionService.updateUserMenuPermissions(this.selectedUserId, menuPermissions)
.subscribe({
next: () => {
console.log('User permissions saved successfully');
// Show success message
},
error: (error) => {
console.error('Error saving user permissions:', error);
// Show error message
}
});
}
private extractMenuPermissions(nodes: MenuNode[]): MenuPermission[] {
const permissions: MenuPermission[] = [];
nodes.forEach(node => {
permissions.push({
id: node.id,
menuId: node.id,
menuName: node.name,
menuPath: node.path,
parentMenuId: undefined,
icon: node.icon,
order: node.order,
isVisible: node.isVisible,
permissions: node.permissions
});
if (node.children) {
permissions.push(...this.extractMenuPermissions(node.children));
}
});
return permissions;
}
resetPermissions(): void {
this.loadMenuHierarchy();
}
}
export interface MeetingRoom {
id: string;
name: string;
capacity: number;
location: string;
floor: string;
building: string;
amenities: string[];
isActive: boolean;
imageUrl?: string;
description?: string;
}
export interface MeetingBooking {
id: string;
roomId: string;
roomName: string;
title: string;
description?: string;
startDateTime: Date;
endDateTime: Date;
organizerId: string;
organizerName: string;
attendees: MeetingAttendee[];
status: 'pending' | 'confirmed' | 'cancelled' | 'completed';
recurringPattern?: RecurringPattern;
createdAt: Date;
updatedAt: Date;
createdBy: string;
updatedBy: string;
}
export interface MeetingAttendee {
userId: string;
userName: string;
email: string;
status: 'pending' | 'accepted' | 'declined' | 'tentative';
responseDate?: Date;
}
export interface RecurringPattern {
type: 'none' | 'daily' | 'weekly' | 'monthly';
interval: number;
daysOfWeek?: number[]; // 0 = Sunday, 1 = Monday, etc.
endDate?: Date;
occurrences?: number;
}
export interface BookingTimeSlot {
startTime: string;
endTime: string;
isAvailable: boolean;
bookingId?: string;
bookingTitle?: string;
}
export interface BookingConflict {
bookingId: string;
title: string;
startDateTime: Date;
endDateTime: Date;
organizerName: string;
}
export interface BookingFilter {
roomId?: string;
startDate?: Date;
endDate?: Date;
status?: string;
organizerId?: string;
}
export interface BookingStatistics {
totalBookings: number;
confirmedBookings: number;
pendingBookings: number;
cancelledBookings: number;
mostPopularRoom: string;
averageBookingDuration: number; // in minutes
peakHours: string[];
}
export interface MenuPermission {
id: string;
menuId: string;
menuName: string;
menuPath: string;
parentMenuId?: string;
icon?: string;
order: number;
isVisible: boolean;
permissions: {
view: boolean;
create: boolean;
edit: boolean;
delete: boolean;
export: boolean;
import: boolean;
};
}
export interface RoleMenuPermission {
roleId: string;
menuPermissions: MenuPermission[];
}
export interface UserMenuPermission {
userId: string;
roleId: string;
menuPermissions: MenuPermission[];
customPermissions?: {
[menuId: string]: {
view: boolean;
create: boolean;
edit: boolean;
delete: boolean;
export: boolean;
import: boolean;
};
};
}
export interface MenuHierarchy {
id: string;
name: string;
path: string;
icon?: string;
order: number;
isVisible: boolean;
children?: MenuHierarchy[];
permissions: {
view: boolean;
create: boolean;
edit: boolean;
delete: boolean;
export: boolean;
import: boolean;
};
}
export interface User {
id: string;
username: string;
email: string;
firstName: string;
lastName: string;
fullName: string;
avatar?: string;
phone?: string;
department?: string;
position?: string;
isActive: boolean;
lastLogin?: Date;
createdAt: Date;
updatedAt: Date;
createdBy: string;
updatedBy: string;
}
export interface Role {
id: string;
name: string;
displayName: string;
description: string;
isSystem: boolean;
isActive: boolean;
permissions: string[];
createdAt: Date;
updatedAt: Date;
createdBy: string;
updatedBy: string;
}
export interface UserRole {
id: string;
userId: string;
roleId: string;
assignedBy: string;
assignedAt: Date;
expiresAt?: Date;
isActive: boolean;
}
export interface Permission {
id: string;
name: string;
displayName: string;
description: string;
category: string;
resource: string;
action: string;
isSystem: boolean;
}
export interface RolePermission {
roleId: string;
permissionId: string;
granted: boolean;
grantedBy: string;
grantedAt: Date;
}
export interface UserPermission {
userId: string;
permissionId: string;
granted: boolean;
grantedBy: string;
grantedAt: Date;
expiresAt?: Date;
}
export interface Department {
id: string;
name: string;
description?: string;
parentId?: string;
managerId?: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
export interface Position {
id: string;
name: string;
description?: string;
level: number;
departmentId?: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
export interface UserFilter {
search?: string;
departmentId?: string;
positionId?: string;
roleId?: string;
isActive?: boolean;
lastLoginFrom?: Date;
lastLoginTo?: Date;
}
export interface RoleFilter {
search?: string;
isSystem?: boolean;
isActive?: boolean;
}
export interface UserRoleAssignment {
userId: string;
userName: string;
userEmail: string;
roleId: string;
roleName: string;
assignedAt: Date;
expiresAt?: Date;
isActive: boolean;
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { map, catchError, tap } from 'rxjs/operators';
import { MenuPermission, RoleMenuPermission, UserMenuPermission, MenuHierarchy } from '../models/menu-permission.model';
@Injectable({
providedIn: 'root'
})
export class MenuPermissionService {
private menuPermissionsSubject = new BehaviorSubject<MenuHierarchy[]>([]);
public menuPermissions$ = this.menuPermissionsSubject.asObservable();
private dataUrl = 'assets/data/menu-permissions.json';
constructor(private http: HttpClient) {
this.loadMenuPermissions();
}
/**
* โหลดข้อมูลสิทธิ์เมนูจาก API หรือ Mock Data
*/
private loadMenuPermissions(): void {
this.getMenuHierarchy().subscribe({
next: (menus) => this.menuPermissionsSubject.next(menus),
error: (error) => console.error('Error loading menu permissions:', error)
});
}
/**
* ดึงข้อมูลโครงสร้างเมนูแบบลำดับชั้น
*/
getMenuHierarchy(): Observable<MenuHierarchy[]> {
return this.http.get<MenuHierarchy[]>(this.dataUrl).pipe(
catchError(() => {
// Fallback to mock data if API fails
return of(this.getMockMenuHierarchy());
})
);
}
/**
* ตรวจสอบสิทธิ์การเข้าถึงเมนู
*/
canAccessMenu(menuPath: string, permission: 'view' | 'create' | 'edit' | 'delete' | 'export' | 'import' = 'view'): Observable<boolean> {
return this.menuPermissions$.pipe(
map(menus => {
const menu = this.findMenuByPath(menus, menuPath);
return menu ? menu.permissions[permission] : false;
})
);
}
/**
* ตรวจสอบสิทธิ์การเข้าถึงเมนูหลายเมนูพร้อมกัน
*/
canAccessMenus(menuPaths: string[], permission: 'view' | 'create' | 'edit' | 'delete' | 'export' | 'import' = 'view'): Observable<{ [path: string]: boolean }> {
return this.menuPermissions$.pipe(
map(menus => {
const result: { [path: string]: boolean } = {};
menuPaths.forEach(path => {
const menu = this.findMenuByPath(menus, path);
result[path] = menu ? menu.permissions[permission] : false;
});
return result;
})
);
}
/**
* ดึงเมนูที่ผู้ใช้สามารถเข้าถึงได้
*/
getAccessibleMenus(): Observable<MenuHierarchy[]> {
return this.menuPermissions$.pipe(
map(menus => this.filterAccessibleMenus(menus))
);
}
/**
* อัปเดตสิทธิ์เมนูสำหรับบทบาท
*/
updateRoleMenuPermissions(roleId: string, menuPermissions: MenuPermission[]): Observable<any> {
const roleMenuPermission: RoleMenuPermission = {
roleId,
menuPermissions
};
// In a real app, this would make an HTTP PUT request
console.log('Updating role menu permissions:', roleMenuPermission);
return of({ success: true });
}
/**
* อัปเดตสิทธิ์เมนูสำหรับผู้ใช้เฉพาะ
*/
updateUserMenuPermissions(userId: string, menuPermissions: MenuPermission[]): Observable<any> {
const userMenuPermission: UserMenuPermission = {
userId,
roleId: '', // This would come from user data
menuPermissions
};
// In a real app, this would make an HTTP PUT request
console.log('Updating user menu permissions:', userMenuPermission);
return of({ success: true });
}
/**
* ค้นหาเมนูตาม path
*/
private findMenuByPath(menus: MenuHierarchy[], path: string): MenuHierarchy | null {
for (const menu of menus) {
if (menu.path === path) {
return menu;
}
if (menu.children) {
const found = this.findMenuByPath(menu.children, path);
if (found) return found;
}
}
return null;
}
/**
* กรองเมนูที่ผู้ใช้สามารถเข้าถึงได้
*/
private filterAccessibleMenus(menus: MenuHierarchy[]): MenuHierarchy[] {
return menus
.filter(menu => menu.isVisible && menu.permissions.view)
.map(menu => ({
...menu,
children: menu.children ? this.filterAccessibleMenus(menu.children) : undefined
}))
.filter(menu => menu.children ? menu.children.length > 0 : true);
}
/**
* Mock data สำหรับการทดสอบ
*/
private getMockMenuHierarchy(): MenuHierarchy[] {
return [
{
id: 'dashboard',
name: 'แดชบอร์ด',
path: '/portal-manage/dashboard',
icon: 'grid-alt',
order: 1,
isVisible: true,
permissions: {
view: true,
create: true,
edit: true,
delete: true,
export: true,
import: false
},
children: [
{
id: 'dashboard-management',
name: 'จัดการแดชบอร์ด',
path: '/portal-manage/dashboard/management',
order: 1,
isVisible: true,
permissions: {
view: true,
create: true,
edit: true,
delete: true,
export: true,
import: false
}
},
{
id: 'widget-warehouse',
name: 'คลังวิดเจ็ต',
path: '/portal-manage/dashboard/widget-warehouse',
order: 2,
isVisible: true,
permissions: {
view: true,
create: true,
edit: true,
delete: true,
export: true,
import: true
}
}
]
},
{
id: 'company-management',
name: 'จัดการบริษัท',
path: '/portal-manage/company-management',
icon: 'building',
order: 2,
isVisible: true,
permissions: {
view: true,
create: true,
edit: true,
delete: true,
export: true,
import: false
}
},
{
id: 'permission-management',
name: 'จัดการสิทธิ์',
path: '/portal-manage/permission-management',
icon: 'shield',
order: 3,
isVisible: true,
permissions: {
view: true,
create: true,
edit: true,
delete: true,
export: true,
import: false
}
},
{
id: 'meeting-booking',
name: 'จองห้องประชุม',
path: '/portal-manage/meeting-booking',
icon: 'calendar',
order: 4,
isVisible: true,
permissions: {
view: true,
create: true,
edit: true,
delete: true,
export: false,
import: false
}
}
];
}
}
.user-info {
.user-name {
font-weight: 500;
margin-bottom: 2px;
}
.user-email {
font-size: 0.875rem;
}
}
.badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
&.badge-success {
background-color: #4caf50;
color: white;
}
&.badge-danger {
background-color: #f44336;
color: white;
}
&.badge-primary {
background-color: #2196f3;
color: white;
}
&.badge-secondary {
background-color: #6c757d;
color: white;
}
}
.stat-card {
text-align: center;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px;
margin-bottom: 16px;
.stat-number {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 8px;
}
.stat-label {
font-size: 0.9rem;
opacity: 0.9;
}
}
.permission-category {
margin-bottom: 24px;
h5 {
color: #333;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #e0e0e0;
}
.permission-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 12px;
.permission-item {
padding: 12px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background-color: #fafafa;
mat-checkbox {
margin-bottom: 4px;
}
small {
display: block;
margin-top: 4px;
}
}
}
}
.table-responsive {
overflow-x: auto;
}
mat-card {
margin-bottom: 16px;
}
mat-card-header {
margin-bottom: 16px;
}
mat-form-field {
margin-bottom: 16px;
}
// Form styling
form {
.row {
margin-bottom: 16px;
}
}
// Button styling
button[mat-raised-button] {
margin-right: 8px;
}
// Loading spinner
mat-spinner {
margin: 0 auto;
}
// Empty state
.text-center {
text-align: center;
}
.text-muted {
color: #6c757d;
}
// Card hover effects
mat-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transition: box-shadow 0.2s ease;
}
// Responsive design
@media (max-width: 768px) {
.permission-grid {
grid-template-columns: 1fr;
}
.stat-card {
margin-bottom: 12px;
.stat-number {
font-size: 2rem;
}
}
.table-responsive {
font-size: 0.875rem;
}
}
// Material table styling
.mat-mdc-table {
width: 100%;
}
.mat-mdc-header-cell {
font-weight: 600;
color: #333;
}
.mat-mdc-cell {
padding: 8px 12px;
}
// Checkbox styling
mat-checkbox {
margin-right: 8px;
}
// Slide toggle styling
mat-slide-toggle {
margin: 8px 0;
}
// Form field styling
mat-form-field {
.mat-mdc-form-field-subscript-wrapper {
display: none;
}
}
// Tab styling
mat-tab-group {
margin-top: 16px;
}
// Card content spacing
mat-card-content {
padding: 16px;
}
// Row spacing
.row {
margin-bottom: 16px;
}
// Utility classes
.w-100 {
width: 100%;
}
.mt-3 {
margin-top: 16px;
}
.mb-3 {
margin-bottom: 16px;
}
.me-2 {
margin-right: 8px;
}
.ms-2 {
margin-left: 8px;
}
import { Directive, Input, TemplateRef, ViewContainerRef, OnInit, OnDestroy } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { MenuPermissionService } from '../../portal-manage/services/menu-permission.service';
@Directive({
selector: '[appMenuPermission]'
})
export class MenuPermissionDirective implements OnInit, OnDestroy {
@Input() appMenuPermission: string = '';
@Input() appMenuPermissionType: 'view' | 'create' | 'edit' | 'delete' | 'export' | 'import' = 'view';
private destroy$ = new Subject<void>();
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private menuPermissionService: MenuPermissionService
) {}
ngOnInit(): void {
if (this.appMenuPermission) {
this.menuPermissionService.canAccessMenu(this.appMenuPermission, this.appMenuPermissionType)
.pipe(takeUntil(this.destroy$))
.subscribe(hasPermission => {
if (hasPermission) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
});
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"rooms": [
{
"id": "1",
"name": "ห้องประชุมใหญ่",
"capacity": 20,
"location": "ชั้น 5",
"floor": "5",
"building": "อาคาร A",
"amenities": ["โปรเจคเตอร์", "ไวท์บอร์ด", "ระบบเสียง", "WiFi"],
"isActive": true,
"description": "ห้องประชุมขนาดใหญ่เหมาะสำหรับการประชุมสำคัญ"
},
{
"id": "2",
"name": "ห้องประชุมเล็ก",
"capacity": 8,
"location": "ชั้น 3",
"floor": "3",
"building": "อาคาร A",
"amenities": ["โปรเจคเตอร์", "ไวท์บอร์ด", "WiFi"],
"isActive": true,
"description": "ห้องประชุมขนาดเล็กเหมาะสำหรับการประชุมทีม"
},
{
"id": "3",
"name": "ห้องประชุม VIP",
"capacity": 12,
"location": "ชั้น 10",
"floor": "10",
"building": "อาคาร B",
"amenities": ["โปรเจคเตอร์", "ไวท์บอร์ด", "ระบบเสียง", "WiFi", "เครื่องปรับอากาศ", "โต๊ะประชุมไม้"],
"isActive": true,
"description": "ห้องประชุมระดับ VIP สำหรับการประชุมสำคัญ"
}
],
"bookings": [
{
"id": "1",
"roomId": "1",
"roomName": "ห้องประชุมใหญ่",
"title": "ประชุมทีมพัฒนา",
"description": "ประชุมรายสัปดาห์ของทีมพัฒนา",
"startDateTime": "2024-01-15T09:00:00.000Z",
"endDateTime": "2024-01-15T10:00:00.000Z",
"organizerId": "1",
"organizerName": "สมชาย ใจดี",
"attendees": [
{
"userId": "2",
"userName": "สมหญิง รักงาน",
"email": "somying@company.com",
"status": "accepted",
"responseDate": "2024-01-14T10:30:00.000Z"
},
{
"userId": "3",
"userName": "สมศักดิ์ ทำงาน",
"email": "somsak@company.com",
"status": "pending"
}
],
"status": "confirmed",
"createdAt": "2024-01-14T08:00:00.000Z",
"updatedAt": "2024-01-14T08:00:00.000Z",
"createdBy": "1",
"updatedBy": "1"
},
{
"id": "2",
"roomId": "2",
"roomName": "ห้องประชุมเล็ก",
"title": "ประชุมทีมขาย",
"description": "ประชุมรายงานผลการขายประจำเดือน",
"startDateTime": "2024-01-16T14:00:00.000Z",
"endDateTime": "2024-01-16T15:30:00.000Z",
"organizerId": "2",
"organizerName": "สมหญิง รักงาน",
"attendees": [
{
"userId": "4",
"userName": "สมพร ขายดี",
"email": "somporn@company.com",
"status": "accepted",
"responseDate": "2024-01-15T16:00:00.000Z"
}
],
"status": "pending",
"createdAt": "2024-01-15T09:00:00.000Z",
"updatedAt": "2024-01-15T09:00:00.000Z",
"createdBy": "2",
"updatedBy": "2"
}
]
}
[
{
"id": "dashboard",
"name": "แดชบอร์ด",
"path": "/portal-manage/dashboard",
"icon": "grid-alt",
"order": 1,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": true,
"export": true,
"import": false
},
"children": [
{
"id": "dashboard-management",
"name": "จัดการแดชบอร์ด",
"path": "/portal-manage/dashboard/management",
"order": 1,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": true,
"export": true,
"import": false
}
},
{
"id": "widget-warehouse",
"name": "คลังวิดเจ็ต",
"path": "/portal-manage/dashboard/widget-warehouse",
"order": 2,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": true,
"export": true,
"import": true
}
},
{
"id": "widget-linker",
"name": "เชื่อมโยงวิดเจ็ตกับชุดข้อมูล",
"path": "/portal-manage/dashboard/widget-linker",
"icon": "link",
"order": 3,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": true,
"export": false,
"import": false
}
}
]
},
{
"id": "company-management",
"name": "จัดการบริษัท",
"path": "/portal-manage/company-management",
"icon": "building",
"order": 2,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": true,
"export": true,
"import": false
}
},
{
"id": "permission-management",
"name": "จัดการสิทธิ์",
"path": "/portal-manage/permission-management",
"icon": "shield",
"order": 3,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": true,
"export": true,
"import": false
}
},
{
"id": "meeting-booking",
"name": "จองห้องประชุม",
"path": "/portal-manage/meeting-booking",
"icon": "calendar",
"order": 4,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": true,
"export": false,
"import": false
}
},
{
"id": "myhr-lite",
"name": "MyHR Lite",
"path": "/portal-manage/myhr-lite",
"icon": "user",
"order": 5,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": false,
"export": true,
"import": false
}
},
{
"id": "myhr-plus",
"name": "MyHR Plus",
"path": "/portal-manage/myhr-plus",
"icon": "user-check",
"order": 6,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": false,
"export": true,
"import": false
}
},
{
"id": "myjob",
"name": "MyJob",
"path": "/portal-manage/myjob",
"icon": "briefcase",
"order": 7,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": false,
"export": true,
"import": false
}
},
{
"id": "mylearn",
"name": "MyLearn",
"path": "/portal-manage/mylearn",
"icon": "book",
"order": 8,
"isVisible": true,
"permissions": {
"view": true,
"create": true,
"edit": true,
"delete": false,
"export": true,
"import": false
}
}
]
{
"users": [
{
"id": "1",
"username": "admin",
"email": "admin@company.com",
"firstName": "สมชาย",
"lastName": "ใจดี",
"fullName": "สมชาย ใจดี",
"phone": "081-234-5678",
"department": "IT",
"position": "System Administrator",
"isActive": true,
"lastLogin": "2024-01-15T08:30:00.000Z",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T08:30:00.000Z",
"createdBy": "system",
"updatedBy": "system"
},
{
"id": "2",
"username": "manager",
"email": "manager@company.com",
"firstName": "สมหญิง",
"lastName": "รักงาน",
"fullName": "สมหญิง รักงาน",
"phone": "081-234-5679",
"department": "HR",
"position": "HR Manager",
"isActive": true,
"lastLogin": "2024-01-14T17:00:00.000Z",
"createdAt": "2024-01-02T00:00:00.000Z",
"updatedAt": "2024-01-14T17:00:00.000Z",
"createdBy": "1",
"updatedBy": "1"
},
{
"id": "3",
"username": "user1",
"email": "user1@company.com",
"firstName": "สมศักดิ์",
"lastName": "ทำงาน",
"fullName": "สมศักดิ์ ทำงาน",
"phone": "081-234-5680",
"department": "IT",
"position": "Developer",
"isActive": true,
"lastLogin": "2024-01-14T09:00:00.000Z",
"createdAt": "2024-01-03T00:00:00.000Z",
"updatedAt": "2024-01-14T09:00:00.000Z",
"createdBy": "1",
"updatedBy": "1"
}
],
"roles": [
{
"id": "admin",
"name": "admin",
"displayName": "ผู้ดูแลระบบ",
"description": "มีสิทธิ์เข้าถึงระบบทั้งหมด",
"isSystem": true,
"isActive": true,
"permissions": ["*"],
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z",
"createdBy": "system",
"updatedBy": "system"
},
{
"id": "manager",
"name": "manager",
"displayName": "ผู้จัดการ",
"description": "สามารถจัดการทีมและดูรายงาน",
"isSystem": false,
"isActive": true,
"permissions": ["user.view", "user.edit", "report.view", "meeting.manage"],
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z",
"createdBy": "system",
"updatedBy": "system"
},
{
"id": "user",
"name": "user",
"displayName": "ผู้ใช้ทั่วไป",
"description": "สิทธิ์พื้นฐานในการใช้งานระบบ",
"isSystem": false,
"isActive": true,
"permissions": ["profile.view", "profile.edit", "meeting.book"],
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z",
"createdBy": "system",
"updatedBy": "system"
}
],
"permissions": [
{
"id": "user.view",
"name": "user.view",
"displayName": "ดูข้อมูลผู้ใช้",
"description": "สามารถดูข้อมูลผู้ใช้ได้",
"category": "User Management",
"resource": "user",
"action": "view",
"isSystem": true
},
{
"id": "user.edit",
"name": "user.edit",
"displayName": "แก้ไขข้อมูลผู้ใช้",
"description": "สามารถแก้ไขข้อมูลผู้ใช้ได้",
"category": "User Management",
"resource": "user",
"action": "edit",
"isSystem": true
},
{
"id": "user.create",
"name": "user.create",
"displayName": "สร้างผู้ใช้ใหม่",
"description": "สามารถสร้างผู้ใช้ใหม่ได้",
"category": "User Management",
"resource": "user",
"action": "create",
"isSystem": true
},
{
"id": "user.delete",
"name": "user.delete",
"displayName": "ลบผู้ใช้",
"description": "สามารถลบผู้ใช้ได้",
"category": "User Management",
"resource": "user",
"action": "delete",
"isSystem": true
},
{
"id": "role.view",
"name": "role.view",
"displayName": "ดูข้อมูลบทบาท",
"description": "สามารถดูข้อมูลบทบาทได้",
"category": "Role Management",
"resource": "role",
"action": "view",
"isSystem": true
},
{
"id": "role.edit",
"name": "role.edit",
"displayName": "แก้ไขข้อมูลบทบาท",
"description": "สามารถแก้ไขข้อมูลบทบาทได้",
"category": "Role Management",
"resource": "role",
"action": "edit",
"isSystem": true
},
{
"id": "role.create",
"name": "role.create",
"displayName": "สร้างบทบาทใหม่",
"description": "สามารถสร้างบทบาทใหม่ได้",
"category": "Role Management",
"resource": "role",
"action": "create",
"isSystem": true
},
{
"id": "role.delete",
"name": "role.delete",
"displayName": "ลบบทบาท",
"description": "สามารถลบบทบาทได้",
"category": "Role Management",
"resource": "role",
"action": "delete",
"isSystem": true
},
{
"id": "meeting.manage",
"name": "meeting.manage",
"displayName": "จัดการการประชุม",
"description": "สามารถจัดการการประชุมได้",
"category": "Meeting Management",
"resource": "meeting",
"action": "manage",
"isSystem": true
},
{
"id": "meeting.book",
"name": "meeting.book",
"displayName": "จองห้องประชุม",
"description": "สามารถจองห้องประชุมได้",
"category": "Meeting Management",
"resource": "meeting",
"action": "book",
"isSystem": true
},
{
"id": "dashboard.view",
"name": "dashboard.view",
"displayName": "ดูแดชบอร์ด",
"description": "สามารถดูแดชบอร์ดได้",
"category": "Dashboard",
"resource": "dashboard",
"action": "view",
"isSystem": true
},
{
"id": "dashboard.manage",
"name": "dashboard.manage",
"displayName": "จัดการแดชบอร์ด",
"description": "สามารถจัดการแดชบอร์ดได้",
"category": "Dashboard",
"resource": "dashboard",
"action": "manage",
"isSystem": true
}
],
"departments": [
{
"id": "1",
"name": "IT",
"description": "แผนกเทคโนโลยีสารสนเทศ",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
},
{
"id": "2",
"name": "HR",
"description": "แผนกทรัพยากรบุคคล",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
},
{
"id": "3",
"name": "Finance",
"description": "แผนกการเงิน",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
},
{
"id": "4",
"name": "Marketing",
"description": "แผนกการตลาด",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
],
"positions": [
{
"id": "1",
"name": "System Administrator",
"description": "ผู้ดูแลระบบ",
"level": 5,
"departmentId": "1",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
},
{
"id": "2",
"name": "HR Manager",
"description": "ผู้จัดการทรัพยากรบุคคล",
"level": 4,
"departmentId": "2",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
},
{
"id": "3",
"name": "Developer",
"description": "นักพัฒนา",
"level": 3,
"departmentId": "1",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
},
{
"id": "4",
"name": "HR Officer",
"description": "เจ้าหน้าที่ทรัพยากรบุคคล",
"level": 2,
"departmentId": "2",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
]
}
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
<link rel="stylesheet" href="assets/JS/pace/themes/silver/pace-theme-flash.css" /> <link rel="stylesheet" href="assets/JS/pace/themes/silver/pace-theme-flash.css" />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@100..900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@100..900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Remix Icons -->
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.0.0/fonts/remixicon.css" rel="stylesheet">
<!-- <link <!-- <link
href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700;800&display=swap" href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700;800&display=swap"
rel="stylesheet" rel="stylesheet"
......
...@@ -26,6 +26,8 @@ module.exports = { ...@@ -26,6 +26,8 @@ module.exports = {
md: "0.5rem", md: "0.5rem",
lg: "0.75rem", lg: "0.75rem",
xl: "1rem", xl: "1rem",
"2xl": "1rem",
"3xl": "1.5rem",
full: "9999px", full: "9999px",
}, },
fontFamily: { fontFamily: {
...@@ -137,6 +139,14 @@ module.exports = { ...@@ -137,6 +139,14 @@ module.exports = {
"gradient-1": "linear-gradient(102deg,transparent 41%,primary/50 0)", "gradient-1": "linear-gradient(102deg,transparent 41%,primary/50 0)",
"gradient-1": "linear-gradient(102deg,light 41%,transparent 0)", "gradient-1": "linear-gradient(102deg,light 41%,transparent 0)",
}, },
backdropBlur: {
'sm': '4px',
'md': '8px',
'lg': '12px',
'xl': '16px',
'2xl': '24px',
'3xl': '40px',
},
}, },
animation: { animation: {
projects: "particles 2s linear infinite", projects: "particles 2s linear infinite",
......
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