-ux
All checks were successful
Build Docker Image / Build Docker Image (push) Successful in 6m34s
Build Docker Image / Restart Docker Compose (push) Successful in 0s

-ui
ใหม่ ทั้งระบบ (ai little retouch)
This commit is contained in:
x2Skyz
2025-11-28 20:39:48 +07:00
parent 4db7ddaba2
commit 11018ae49d
9 changed files with 205 additions and 194 deletions

View File

@@ -1,88 +1,84 @@
<div <div class="h-full bg-white border-r border-gray-200 flex flex-col shadow-sm transition-all duration-300">
class="h-screen bg-linear-to-b from-amber-950 to-amber-900 text-gray-100 shadow-2xl flex flex-col relative transition-all duration-300 ease-in-out"
[@sidebarState]="isOpen ? 'expanded' : 'collapsed'">
<button <!-- ส่วน Logo เดิม (เอาออก หรือ Comment ไว้) -->
(click)="toggleSidebar()" <!--
class="absolute -right-3 top-6 bg-amber-700 hover:bg-amber-600 text-white rounded-full p-2 shadow-md transition-all duration-300"> <div class="h-16 flex items-center justify-center border-b border-gray-100 shrink-0">
<i class="fas" [ngClass]="isOpen ? 'fa-angle-left' : 'fa-angle-right'"></i> ...
</button> </div>
-->
<div class="flex items-center gap-3 p-5"> <!-- User Profile Summary (ปรับให้เป็นส่วนบนสุดแทน หรือเอาออกถ้าจะย้ายไป Navbar) -->
<div *ngIf="isOpen" class="text-2xl font-bold transition-all duration-300 flex justify-center pl-4"> <!-- ถ้าจะเอาออกด้วย ให้ลบส่วนนี้ทิ้งครับ แต่ถ้ายังอยากเก็บไว้ ก็ปล่อยไว้ -->
<img src="logo.png" alt="" class="w-35"> <div *ngIf="!isCollapsed" class="p-4 border-b border-gray-50 bg-gray-50/50 shrink-0 transition-all duration-300">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center text-red-500 font-bold border border-red-200 shadow-sm shrink-0">
<span *ngIf="!userData?.avatar">{{ (userData?.name?.charAt(0) || 'U') | uppercase }}</span>
</div>
<div class="overflow-hidden">
<p class="text-sm font-semibold text-gray-700 truncate">{{ userData?.name || 'User Name' }}</p>
<p class="text-xs text-gray-500 truncate">{{ userData?.role || 'Administrator' }}</p>
</div>
</div>
</div> </div>
<!-- Navigation Menu (เพิ่ม padding-top ถ้าเอา Header ออก) -->
<nav class="flex-1 overflow-y-auto py-4 px-2 space-y-1 custom-scrollbar pt-6">
<!-- <div class="h-24 w-24 rounded-full bg-white flex items-center justify-center overflow-hidden"> <!-- Dashboard -->
<img src="logo.png" alt=""> <a routerLink="/main" routerLinkActive="bg-red-50 text-red-600 shadow-sm ring-1 ring-red-100"
<span class="text-center text-white font-bold text-sm leading-tight">วิทยาลัยเทคนิคตรัง</span> [routerLinkActiveOptions]="{exact: true}"
</div> --> class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-all group"
</div> [title]="isCollapsed ? 'Dashboard' : ''">
<div class="w-6 flex justify-center shrink-0">
<i class="fas fa-th-large text-lg group-hover:scale-110 transition-transform"></i>
</div>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Dashboard</span>
</a>
<hr class="border-t border-amber-700 mx-4 my-4 opacity-70"> <!-- Section Header -->
<div *ngIf="!isCollapsed" class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider whitespace-nowrap">
Management
</div>
<div *ngIf="isCollapsed" class="h-4"></div>
<ul class="flex flex-col gap-2 px-2 grow"> <!-- Projects -->
<li class="group flex items-center gap-3 p-3 rounded-lg cursor-pointer <a routerLink="/main/project" routerLinkActive="bg-red-50 text-red-600 shadow-sm ring-1 ring-red-100"
hover:bg-amber-800 hover:shadow-lg transition-all duration-300 ease-in-out" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-all group"
(click)="navigate('/main/landing')"> [title]="isCollapsed ? 'Projects' : ''">
<i class="text-xl group-hover:scale-110 transition-transform"><img src="icons8-home-384.png" alt="" class="h-6 grayscale"></i> <div class="w-6 flex justify-center shrink-0">
@if(isOpen){ <i class="fas fa-folder text-lg group-hover:scale-110 transition-transform"></i>
<span class="text-lg font-medium">หน้าหลัก</span> </div>
} <span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Projects</span>
</li> </a>
<!-- Budgets -->
<a routerLink="/main/budget" routerLinkActive="bg-red-50 text-red-600 shadow-sm ring-1 ring-red-100"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-all group"
[title]="isCollapsed ? 'Budgets' : ''">
<div class="w-6 flex justify-center shrink-0">
<i class="fas fa-file-invoice-dollar text-lg group-hover:scale-110 transition-transform"></i>
</div>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Budgets</span>
</a>
<li class="group flex items-center gap-3 p-3 rounded-lg cursor-pointer <!-- Users -->
hover:bg-amber-800 hover:shadow-lg transition-all duration-300 ease-in-out" <a routerLink="/main/manager" routerLinkActive="bg-red-50 text-red-600 shadow-sm ring-1 ring-red-100"
(click)="navigate('/main/dashboard')"> class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-all group"
<i class="fas fa-tachometer-alt text-xl group-hover:scale-110 transition-transform"></i> [title]="isCollapsed ? 'Users' : ''">
@if(isOpen){ <div class="w-6 flex justify-center shrink-0">
<span class="text-lg font-medium">แดชบอร์ด</span> <i class="fas fa-users-cog text-lg group-hover:scale-110 transition-transform"></i>
} </div>
</li> <span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Users</span>
</a>
<li class="group flex items-center gap-3 p-3 rounded-lg cursor-pointer </nav>
hover:bg-amber-800 hover:shadow-lg transition-all duration-300 ease-in-out" <!-- Footer / Logout -->
(click)="navigate('/main/manager')"> <div class="p-4 border-t border-gray-200 bg-gray-50 shrink-0">
<i class="fas fa-user-circle text-xl group-hover:scale-110 transition-transform"></i> <button (click)="logout()"
@if(isOpen){ class="flex items-center justify-center gap-2 w-full px-4 py-2 text-sm font-medium text-white bg-red-500 rounded-lg hover:bg-red-600 transition-colors shadow-sm hover:shadow">
<span class="text-lg font-medium">การจัดการ</span> <i class="fas fa-sign-out-alt"></i>
} <span *ngIf="!isCollapsed">Logout</span>
</li> </button>
</div>
<li class="group flex items-center gap-3 p-3 rounded-lg cursor-pointer
hover:bg-amber-800 hover:shadow-lg transition-all duration-300 ease-in-out"
(click)="navigate('/main/report')">
<i class="fas fa-chart-bar text-xl group-hover:scale-110 transition-transform"></i>
@if(isOpen){
<span class="text-lg font-medium">รายงาน</span>
}
</li>
<li class="group flex items-center gap-3 p-3 rounded-lg cursor-pointer mt-auto
hover:bg-red-700 hover:shadow-lg transition-all duration-300 ease-in-out"
(click)="logout()">
<i class="fas fa-sign-out-alt text-xl group-hover:scale-110 transition-transform"></i>
@if(isOpen){
<span class="text-lg font-medium text-red-200">ลงชื่อออก</span>
}
</li>
</ul>
</div> </div>
<div
*ngIf="isMobile && showOverlay"
class="fixed inset-0 bg-black bg-opacity-50 z-40 transition-opacity duration-300"
(click)="toggleSidebar()">
</div>
<!--
<div class="flex-1 bg-gray-100 text-gray-900 overflow-y-auto">
<router-outlet></router-outlet>
</div> -->

View File

@@ -1,69 +1,29 @@
import { Component, HostListener, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { JwtService } from '../../services/jwt.service';
@Component({ @Component({
selector: 'app-sidebar', selector: 'app-sidebar',
standalone: false, standalone: false,
templateUrl: './sidebar.component.html', templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.css'], styleUrls: ['./sidebar.component.css']
animations: [
trigger('sidebarState', [
state('expanded', style({
width: '220px',
opacity: 1
})),
state('collapsed', style({
width: '70px',
opacity: 0.95
})),
transition('expanded <=> collapsed', [
animate('300ms ease-in-out')
])
])
]
}) })
export class SidebarComponent implements OnInit { export class SidebarComponent implements OnInit {
isOpen = true; // ขยายไหม @Input() isCollapsed: boolean = false; // รับค่าสถานะย่อ/ขยาย
isMobile = false; // ตรวจอุปกรณ์
showOverlay = false; // สำหรับ mobile overlay
constructor( userData: any = {
private router: Router, name: 'Nuttakit',
private jwtService: JwtService role: 'Admin',
) {} avatar: ''
};
ngOnInit() { constructor(private router: Router) { }
this.checkDevice();
window.addEventListener('resize', () => this.checkDevice());
}
@HostListener('window:resize') ngOnInit(): void {
checkDevice() { // โหลด User Data ถ้ามี
this.isMobile = window.innerWidth <= 768;
if (this.isMobile) {
this.isOpen = false; // ซ่อน sidebar ตอนเข้า mobile
} else {
this.isOpen = true; // เปิดไว้ตลอดใน desktop
this.showOverlay = false;
}
}
toggleSidebar() {
if (this.isMobile) {
this.showOverlay = !this.showOverlay;
this.isOpen = this.showOverlay;
} else {
this.isOpen = !this.isOpen;
}
}
navigate(path: string) {
this.router.navigate([path]);
} }
logout() { logout() {
this.jwtService.logout(); localStorage.removeItem('access_token');
this.router.navigate(['/login']);
} }
} }

View File

@@ -1,3 +1 @@
<div *ngIf="countdown$ | async as countdown" class="token-timer"> <!-- <p>หมดอายุใน: {{ countdown }}</p> -->
<p>หมดอายุใน: {{ countdown }}</p>
</div>

View File

@@ -1,52 +1,45 @@
<div class="h-screen bg-gray-100 flex flex-col"> <div class="h-screen bg-gray-100 flex flex-col">
<!-- Header Section --> <header class="flex justify-between items-center py-4 px-6">
<header class="flex justify-between items-center py-4 px-6 bg-white shadow-sm border-b border-gray-200 z-10">
@if (mode == 'default') { @if (mode == 'default') {
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2"> <div class="flex flex-col md:flex-row justify-between items-end mb-6 gap-4">
<i class="fas fa-folder text-red-500"></i> โครงการทั้งหมด <div>
</h1> <h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
<span class="w-2 h-8 bg-red-900 rounded-full"></span> รายการโครงการทั้งหมด
</h1>
<p class="text-gray-500 mt-1 text-sm pl-4">ติดตาม สถานะโครงการ และ เสนอโครงการ</p>
</div>
</div>
<!-- <h1 class="text-3xl font-bold text-gray-800">โครงการทั้งหมด</h1> -->
} @else if (mode == 'add') { } @else if (mode == 'add') {
<div class="flex items-center gap-3"> <div class="flex flex-col md:flex-row justify-between items-end gap-4">
<!-- ปุ่มย้อนกลับ --> <div>
<button (click)="onCancelProject()" <h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
class="w-10 h-10 rounded-full flex items-center justify-center hover:bg-gray-100 text-gray-500 transition-colors hover:text-red-500" <button (click)="onCancelProject()"
title="ย้อนกลับ"> class="group flex items-center justify-center w-8 h-8 rounded-full hover:bg-gray-100 transition-all mr-1"
<i class="fas fa-arrow-left text-lg"></i> title="ย้อนกลับ">
</button> <i class="fas fa-arrow-left text-gray-400 group-hover:text-red-900 text-lg"></i>
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2"> </button>
<i class="fas fa-plus-circle text-red-500"></i> เพิ่มโครงการ <div class="h-6 w-px bg-gray-200 mr-2"></div>
</h1>
</div> <span class="w-2 h-8 bg-red-900 rounded-full"></span> เพิ่มโครงการ
} @else if (mode == 'edit') { </h1>
<div class="flex items-center gap-3"> <p class="text-gray-500 mt-1 text-sm pl-14">กรอกรายละเอียดเพื่อขออนุมัติงบประมาณ</p>
<!-- ปุ่มย้อนกลับ --> </div>
<button (click)="onCancelProject()" </div>
class="w-10 h-10 rounded-full flex items-center justify-center hover:bg-gray-100 text-gray-500 transition-colors hover:text-red-500" }
title="ย้อนกลับ">
<i class="fas fa-arrow-left text-lg"></i>
</button>
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
<i class="fas fa-edit text-red-500"></i> แก้ไขโครงการ
</h1>
</div>
}
</header> </header>
<!-- Content Area (Scrollable) --> <div class="grow overflow-y-auto px-6 pb-6">
<div class="grow overflow-y-auto px-6 pb-6 pt-6">
@if ( mode == 'default') { @if ( mode == 'default') {
<!-- รายการโครงการ (Smart Component ย่อย หรือ Dumb Component ก็ได้) -->
<app-main-project></app-main-project> <app-main-project></app-main-project>
} @else if ( mode == 'add') { } @else if ( mode == 'add') {
<!-- หน้าเพิ่มโครงการ (Dumb Component) --> <app-main-project-add
<!-- เชื่อม Event save/cancel กลับมาที่ Parent --> (save)="onSaveProject($event)"
<app-main-project-add (cancel)="onCancelProject()">
(save)="onSaveProject($event)" </app-main-project-add>
(cancel)="onCancelProject()">
</app-main-project-add>
} }
</div> </div>

View File

@@ -67,6 +67,6 @@ export class MainProjectContent implements OnInit {
// รับ Event (cancel) จากลูก // รับ Event (cancel) จากลูก
onCancelProject(): void { onCancelProject(): void {
this.router.navigate(['/main/project']); this.router.navigate(['/main/project']);
} }
} }

View File

@@ -1,11 +1,6 @@
.layout { /* เพิ่ม transition ให้ความกว้าง sidebar */
display: flex; .transition-all {
height: 100vh; transition-property: all;
width: 100%; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
} transition-duration: 300ms;
.main-container {
flex: 1;
background: #f5f5f5;
overflow-y: auto;
padding: 20px;
} }

View File

@@ -1,10 +1,62 @@
<div class="flex h-screen overflow-hidden"> <div class="flex h-screen bg-gray-100 overflow-hidden relative">
<!-- Sidebar (เฉพาะ main) -->
<app-sidebar></app-sidebar> <!-- Mobile Sidebar Backdrop (Overlay) -->
<app-token-timer></app-token-timer> <!-- แสดงเฉพาะเมื่อเปิด Sidebar บนมือถือ -->
<div *ngIf="!isSidebarCollapsed"
class="fixed inset-0 bg-black/50 z-30 md:hidden transition-opacity duration-300"
(click)="toggleSidebar()">
</div>
<!-- Sidebar Area -->
<!--
Mobile: fixed, z-40, transition transform เพื่อให้เลื่อนเข้า-ออกได้
Desktop: static, hidden md:block เหมือนเดิม แต่ปรับ logic การซ่อน
-->
<div [ngClass]="{
'translate-x-0': !isSidebarCollapsed,
'-translate-x-full': isSidebarCollapsed,
'w-64': true
}"
class="fixed inset-y-0 left-0 z-40 bg-white transition-transform duration-300 ease-in-out md:translate-x-0 md:static md:shrink-0"
[class.md:w-20]="isSidebarCollapsed"
[class.md:w-64]="!isSidebarCollapsed">
<!-- ส่งค่า isCollapsed ให้ลูก (บนมือถือถ้าเปิดคือไม่ Collapsed, บน Desktop ตาม State) -->
<app-sidebar [isCollapsed]="isSidebarCollapsed"></app-sidebar>
</div>
<!-- Main Content Area -->
<div class="flex-1 flex flex-col h-screen overflow-hidden relative w-full transition-all duration-300">
<!-- Global Navbar / Top Bar -->
<header class="bg-white shadow-sm h-16 flex items-center px-4 justify-between z-20 shrink-0 border-b border-gray-200">
<div class="flex items-center gap-4">
<!-- Toggle Button -->
<button (click)="toggleSidebar()"
class="p-2 rounded-lg hover:bg-gray-100 text-gray-600 focus:outline-none transition-colors">
<i class="fas" [ngClass]="isSidebarCollapsed ? 'fa-indent' : 'fa-outdent'"></i>
</button>
<!-- เพิ่ม Logo ตรงนี้แทน -->
<div class="flex items-center gap-2 border-l border-gray-200 pl-4">
<img src="logo.png" alt="Logo" class="w-8 h-8 object-contain">
<span class="text-xl font-bold text-gray-800 tracking-tight">Trang Technical Collage</span>
</div>
</div>
<!-- Right Side Navbar Items -->
<div class="flex items-center gap-3">
</div>
</header>
<!-- Router Outlet Container -->
<main class="flex-1 overflow-hidden relative bg-gray-50">
<router-outlet></router-outlet>
</main>
</div>
<!-- Content -->
<div class="flex-1 overflow-y-auto bg-gray-50 text-gray-900">
<router-outlet></router-outlet>
</div>
</div> </div>

View File

@@ -1,11 +1,19 @@
import { Component } from '@angular/core'; import { Component, OnInit } from '@angular/core';
@Component({ @Component({
selector: 'app-sidebar-content', selector: 'app-sidebar-content',
standalone: false, standalone: false,
templateUrl: './sidebar-content.component.html', templateUrl: './sidebar-content.component.html',
styleUrl: './sidebar-content.component.css' styleUrls: ['./sidebar-content.component.css']
}) })
export class SidebarContentComponent { export class SidebarContentComponent implements OnInit {
isSidebarCollapsed = false;
constructor() {}
ngOnInit(): void {}
toggleSidebar() {
this.isSidebarCollapsed = !this.isSidebarCollapsed;
}
} }

View File

@@ -39,7 +39,16 @@ html, body, app-root {
transition: transform 0.3s ease-in-out; transition: transform 0.3s ease-in-out;
} }
} }
/* ✅ Toast Custom Style */
input::placeholder{
color: #9aa3ad;
}
input:focus{
border-color: var(--primary);
box-shadow: 0 6px 20px rgba(0,120,212,0.10);
transform: translateZ(0);
}
.ngx-toastr { .ngx-toastr {
border-radius: 8px !important; border-radius: 8px !important;
backdrop-filter: blur(6px); backdrop-filter: blur(6px);