-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
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'">
<div class="h-full bg-white border-r border-gray-200 flex flex-col shadow-sm transition-all duration-300">
<button
(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">
<i class="fas" [ngClass]="isOpen ? 'fa-angle-left' : 'fa-angle-right'"></i>
</button>
<!-- ส่วน Logo เดิม (เอาออก หรือ Comment ไว้) -->
<!--
<div class="h-16 flex items-center justify-center border-b border-gray-100 shrink-0">
...
</div>
-->
<div class="flex items-center gap-3 p-5">
<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">
<!-- User Profile Summary (ปรับให้เป็นส่วนบนสุดแทน หรือเอาออกถ้าจะย้ายไป Navbar) -->
<!-- ถ้าจะเอาออกด้วย ให้ลบส่วนนี้ทิ้งครับ แต่ถ้ายังอยากเก็บไว้ ก็ปล่อยไว้ -->
<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>
<!-- 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">
<img src="logo.png" alt="">
<span class="text-center text-white font-bold text-sm leading-tight">วิทยาลัยเทคนิคตรัง</span>
</div> -->
</div>
<!-- Dashboard -->
<a routerLink="/main" routerLinkActive="bg-red-50 text-red-600 shadow-sm ring-1 ring-red-100"
[routerLinkActiveOptions]="{exact: true}"
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 ? '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">
<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/landing')">
<i class="text-xl group-hover:scale-110 transition-transform"><img src="icons8-home-384.png" alt="" class="h-6 grayscale"></i>
@if(isOpen){
<span class="text-lg font-medium">หน้าหลัก</span>
}
</li>
<!-- Projects -->
<a routerLink="/main/project" 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 ? 'Projects' : ''">
<div class="w-6 flex justify-center shrink-0">
<i class="fas fa-folder text-lg group-hover:scale-110 transition-transform"></i>
</div>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Projects</span>
</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
hover:bg-amber-800 hover:shadow-lg transition-all duration-300 ease-in-out"
(click)="navigate('/main/dashboard')">
<i class="fas fa-tachometer-alt text-xl group-hover:scale-110 transition-transform"></i>
@if(isOpen){
<span class="text-lg font-medium">แดชบอร์ด</span>
}
</li>
<!-- Users -->
<a routerLink="/main/manager" 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 ? 'Users' : ''">
<div class="w-6 flex justify-center shrink-0">
<i class="fas fa-users-cog text-lg group-hover:scale-110 transition-transform"></i>
</div>
<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
hover:bg-amber-800 hover:shadow-lg transition-all duration-300 ease-in-out"
(click)="navigate('/main/manager')">
<i class="fas fa-user-circle text-xl group-hover:scale-110 transition-transform"></i>
@if(isOpen){
<span class="text-lg font-medium">การจัดการ</span>
}
</li>
</nav>
<!-- Footer / Logout -->
<div class="p-4 border-t border-gray-200 bg-gray-50 shrink-0">
<button (click)="logout()"
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">
<i class="fas fa-sign-out-alt"></i>
<span *ngIf="!isCollapsed">Logout</span>
</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
*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 { trigger, state, style, transition, animate } from '@angular/animations';
import { JwtService } from '../../services/jwt.service';
@Component({
selector: 'app-sidebar',
standalone: false,
templateUrl: './sidebar.component.html',
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')
])
])
]
styleUrls: ['./sidebar.component.css']
})
export class SidebarComponent implements OnInit {
isOpen = true; // ขยายไหม
isMobile = false; // ตรวจอุปกรณ์
showOverlay = false; // สำหรับ mobile overlay
@Input() isCollapsed: boolean = false; // รับค่าสถานะย่อ/ขยาย
constructor(
private router: Router,
private jwtService: JwtService
) {}
userData: any = {
name: 'Nuttakit',
role: 'Admin',
avatar: ''
};
ngOnInit() {
this.checkDevice();
window.addEventListener('resize', () => this.checkDevice());
}
constructor(private router: Router) { }
@HostListener('window:resize')
checkDevice() {
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]);
ngOnInit(): void {
// โหลด User Data ถ้ามี
}
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>
</div>
<!-- <p>หมดอายุใน: {{ countdown }}</p> -->

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,62 @@
<div class="flex h-screen overflow-hidden">
<!-- Sidebar (เฉพาะ main) -->
<app-sidebar></app-sidebar>
<app-token-timer></app-token-timer>
<div class="flex h-screen bg-gray-100 overflow-hidden relative">
<!-- Mobile Sidebar Backdrop (Overlay) -->
<!-- แสดงเฉพาะเมื่อเปิด 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>

View File

@@ -1,11 +1,19 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-sidebar-content',
standalone: false,
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;
}
}
/* ✅ 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 {
border-radius: 8px !important;
backdrop-filter: blur(6px);