-login guard
All checks were successful
Build Docker Image / Preparing Dependecies (push) Successful in 4s

-caching
-budget
This commit is contained in:
2025-11-19 18:30:35 +07:00
parent 213fd197ef
commit 15308ababa
25 changed files with 667 additions and 479 deletions

View File

@@ -37,12 +37,10 @@
} }
], ],
"styles": [ "styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"node_modules/@fortawesome/fontawesome-free/css/all.min.css", "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
"src/styles.css" "src/styles.css"
], ],
"scripts": [ "scripts": [
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
] ]
}, },
"configurations": { "configurations": {
@@ -116,12 +114,10 @@
} }
], ],
"styles": [ "styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"node_modules/@fortawesome/fontawesome-free/css/all.min.css", "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
"src/styles.css" "src/styles.css"
], ],
"scripts": [ "scripts": [
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
] ]
} }
} }

View File

@@ -26,7 +26,6 @@
"@fortawesome/free-regular-svg-icons": "^7.1.0", "@fortawesome/free-regular-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0",
"@tailwindcss/postcss": "^4.1.17", "@tailwindcss/postcss": "^4.1.17",
"bootstrap": "^5.3.8",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
@@ -7030,17 +7029,6 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.3", "version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
@@ -9432,25 +9420,6 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/bootstrap": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bplist-parser": { "node_modules/bplist-parser": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz",

View File

@@ -58,7 +58,6 @@
"@fortawesome/free-regular-svg-icons": "^7.1.0", "@fortawesome/free-regular-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0",
"@tailwindcss/postcss": "^4.1.17", "@tailwindcss/postcss": "^4.1.17",
"bootstrap": "^5.3.8",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",

View File

@@ -3,10 +3,11 @@ import { RouterModule, Routes } from '@angular/router';
import { SidebarContentComponent } from './content/sidebar-content/sidebar-content.component'; import { SidebarContentComponent } from './content/sidebar-content/sidebar-content.component';
import { LicensePrivacyTermsComponent } from './component/license-privacy-terms/license-privacy-terms.component'; import { LicensePrivacyTermsComponent } from './component/license-privacy-terms/license-privacy-terms.component';
import { authGuard } from './services/auth.guard'; import { authGuard } from './services/auth.guard';
import { loginGuard } from './services/login.guard';
const routes: Routes = [ const routes: Routes = [
{ path: 'login', loadChildren: () => import('./controls/login-control/login-control.module').then(m => m.LoginControlModule) }, { path: 'login', loadChildren: () => import('./controls/login-control/login-control.module').then(m => m.LoginControlModule), canActivate: [loginGuard] },
{ path: 'license', component: LicensePrivacyTermsComponent}, { path: 'license', component: LicensePrivacyTermsComponent},

View File

@@ -13,7 +13,7 @@ import { SidebarContentComponent } from './content/sidebar-content/sidebar-conte
import { SidebarComponent } from './component/sidebar/sidebar.component'; import { SidebarComponent } from './component/sidebar/sidebar.component';
// import { ReactiveFormsModule } from '@angular/forms'; // import { ReactiveFormsModule } from '@angular/forms';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { LicensePrivacyTermsComponent } from './component/license-privacy-terms/license-privacy-terms.component'; import { LicensePrivacyTermsComponent } from './component/license-privacy-terms/license-privacy-terms.component';
// import { MainDashboardContentComponent } from './content/main-dashboard-content/main-dashboard-content.component'; // import { MainDashboardContentComponent } from './content/main-dashboard-content/main-dashboard-content.component';
// import { MainDashboardComponent } from './component/main-dashboard/main-dashboard.component'; // import { MainDashboardComponent } from './component/main-dashboard/main-dashboard.component';
@@ -23,6 +23,7 @@ import { LicensePrivacyTermsComponent } from './component/license-privacy-terms/
import { provideCharts, withDefaultRegisterables } from 'ng2-charts'; import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
import { CachingInterceptor } from './services/caching.interceptor';
// import { BudgetAproval } from './component/budget-aproval/budget-aproval'; // import { BudgetAproval } from './component/budget-aproval/budget-aproval';
// import { AccDateFormatPipe } from './pipe/dtmtodatetime.pipe'; // import { AccDateFormatPipe } from './pipe/dtmtodatetime.pipe';
// import { DtmtodatetimePipe } from './dtmtodatetime.pipe'; // import { DtmtodatetimePipe } from './dtmtodatetime.pipe';
@@ -61,7 +62,10 @@ import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
exports: [ exports: [
// AccDateFormatPipe // AccDateFormatPipe
], ],
providers: [provideCharts(withDefaultRegisterables())], providers: [
provideCharts(withDefaultRegisterables()),
{ provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true }
],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule { } export class AppModule { }

View File

@@ -1,9 +1,10 @@
<div class="p-6"> <div class="p-6 space-y-10">
<!-- Header --> <!-- Header -->
<div class="flex justify-between items-center mb-4 "> <div class="flex justify-between items-center mb-4 ">
<h2 class="text-xl font-semibold"> <h2 class="text-xl font-semibold">
รายการงบประมาณของโครงการ: {{ project?.name }} รายการงบประมาณของโครงการ: ระบบจัดการน้ำดื่ม
<!-- {{ project?.name }} -->
</h2> </h2>
<div class="text-gray-600 text-sm"> <div class="text-gray-600 text-sm">
@@ -13,20 +14,19 @@
<!-- Add New Budget Item --> <!-- Add New Budget Item -->
<form [formGroup]="addItemForm" class="bg-gray-50 border rounded-xl p-4 mb-6"> <!-- <form [formGroup]="addItemForm" class="bg-gray-50 border rounded-xl p-4 mb-6">
<h3 class="font-semibold mb-3 text-gray-700">เพิ่มรายการงบประมาณ</h3> <h3 class="font-semibold mb-3 text-gray-700">เพิ่มรายการงบประมาณ</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4"> <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<!-- หมวดงบ --> หมวดงบ
<div> <div>
<label class="text-sm text-gray-600">หมวดงบประมาณ</label> <label class="text-sm text-gray-600">หมวดงบประมาณ</label>
<select <select
formControlName="category" formControlName="category"
class="w-full px-4 py-2 border rounded-xl bg-white mt-1 class="w-full px-4 py-2 border rounded-xl bg-white mt-1
focus:ring-2 focus:ring-blue-200 focus:border-blue-300" focus:ring-2 focus:ring-blue-200 focus:border-blue-300" >
>
<option value="">-- เลือกหมวดงบ --</option> <option value="">-- เลือกหมวดงบ --</option>
@for (item of budgetCategoriesDrop.expense; track item.dtlcod) { @for (item of budgetCategoriesDrop.expense; track item.dtlcod) {
@@ -34,7 +34,7 @@
} }
</select> </select>
<!-- error --> error
<div <div
*ngIf="addItemForm.controls['category'].invalid && addItemForm.controls['category'].touched" *ngIf="addItemForm.controls['category'].invalid && addItemForm.controls['category'].touched"
class="text-red-500 text-xs mt-1" class="text-red-500 text-xs mt-1"
@@ -43,9 +43,9 @@
</div> </div>
</div> </div>
<!-- รายการ --> รายการ
<div> <div>
<!-- <label class="text-sm text-gray-600">ชื่อรายการ</label> <label class="text-sm text-gray-600">ชื่อรายการ</label>
<select <select
formControlName="name" formControlName="name"
class="w-full px-4 py-2 border rounded-xl bg-white mt-1 class="w-full px-4 py-2 border rounded-xl bg-white mt-1
@@ -56,7 +56,7 @@
<option *ngFor="let it of masterItems" [value]="it.name"> <option *ngFor="let it of masterItems" [value]="it.name">
{{ it.name }} {{ it.name }}
</option> </option>
</select> --> </select>
<div <div
*ngIf="addItemForm.controls['name'].invalid && addItemForm.controls['name'].touched" *ngIf="addItemForm.controls['name'].invalid && addItemForm.controls['name'].touched"
@@ -66,7 +66,7 @@
</div> </div>
</div> </div>
<!-- จำนวน --> จำนวน
<div> <div>
<label class="text-sm text-gray-600">จำนวน</label> <label class="text-sm text-gray-600">จำนวน</label>
<input <input
@@ -84,15 +84,14 @@
</div> </div>
</div> </div>
<!-- ราคา --> ราคา
<div> <div>
<label class="text-sm text-gray-600">ราคา</label> <label class="text-sm text-gray-600">ราคา</label>
<input <input
type="number" type="number"
formControlName="price" formControlName="price"
class="w-full px-4 py-2 border rounded-xl bg-white mt-1 class="w-full px-4 py-2 border rounded-xl bg-white mt-1
focus:ring-2 focus:ring-blue-200 focus:border-blue-300" focus:ring-2 focus:ring-blue-200 focus:border-blue-300"/>
/>
<div <div
*ngIf="addItemForm.controls['price'].invalid && addItemForm.controls['price'].touched" *ngIf="addItemForm.controls['price'].invalid && addItemForm.controls['price'].touched"
@@ -104,7 +103,7 @@
</div> </div>
<!-- Add button --> Add button
<button <button
type="button" type="button"
(click)="addBudgetItem()" (click)="addBudgetItem()"
@@ -113,12 +112,83 @@
เพิ่มเข้าตาราง เพิ่มเข้าตาราง
</button> </button>
</form> </form> -->
<div class="bg-white rounded-lg shadow-md border border-gray-200 ">
<div class="flex justify-between items-center p-4 border-b bg-gray-50 rounded-t-lg" (click)="toggleFormCollapse()">
<h3 class="text-base font-semibold text-gray-700">เพิ่มรายการงบประมาณสำหรับโครงการ</h3>
<button class="text-gray-400 hover:text-gray-600 focus:outline-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"
[ngClass]="{'rotate-180': !isFormExpanded}">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
@if(!isFormExpanded){
<div class="p-4 space-y-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label for="borrower_id" class="block text-sm font-medium text-gray-700 mb-1">หมวดงบประมาณ</label>
<select name="" id="" class="w-full p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500 shadow-sm">
<option value="">-- เลือกหมวดงบ --</option>
@for (item of budgetCategoriesDrop.expense; track item.dtlcod) {
<option [value]="item.dtlcod">{{ item.dtlnam }}</option>
}
</select>
</div>
<div>
<label for="first_name" class="block text-sm font-medium text-gray-700 mb-1">จำนวนเงิน</label>
<input type="text" id="first_name" name="first_name" class="w-full p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500 shadow-sm" placeholder="">
</div>
<!--
<div>
<label for="last_name" class="block text-sm font-medium text-gray-700 mb-1">นามสกุล</label>
<input type="text" id="last_name" name="last_name" class="w-full p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500 shadow-sm" placeholder="">
</div> -->
</div>
<div class="flex justify-end pt-4 space-x-2">
<!-- <button class="flex items-center px-4 py-2 text-sm font-medium text-white bg-gray-500 rounded-md shadow-sm hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
รีเซ็ต
</button> -->
<!-- <button class="flex items-center px-4 py-2 text-sm font-medium text-white bg-blue-500 rounded-md shadow-sm hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
ค้นหา
</button> -->
<!-- <button class="flex items-center px-4 py-2 text-sm font-medium text-white bg-green-500 rounded-md shadow-sm hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0011.414 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3 3v-6" />
</svg>
ส่งออก Excel
</button> -->
<button class="flex items-center px-4 py-2 text-sm font-medium text-white bg-green-500 rounded-md shadow-sm hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0011.414 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3 3v-6" />
</svg>
เพิ่มเข้าสู่ตาราง
</button>
</div>
</div>
}
</div>
<!-- Table --> <!-- Table -->
<div class="overflow-x-auto bg-white shadow rounded-xl border"> <!-- <div class="overflow-x-auto bg-white shadow rounded-xl border">
<table class="min-w-full border-collapse text-sm"> <table class="min-w-full border-collapse text-sm">
<thead class="bg-gray-100 border-b"> <thead class="bg-gray-100 border-b">
@@ -183,6 +253,82 @@
</tbody> </tbody>
</table> </table>
</div> -->
<div class="overflow-x-auto bg-white border border-gray-200 rounded-2xl shadow-sm">
<table class="min-w-full text-left border-collapse">
<thead class="bg-gray-100 border-b border-gray-200 text-gray-700 text-sm">
<tr>
<th class="py-3 px-4 font-semibold">ลำดับ</th>
<th class="py-3 px-4 font-semibold">รหัสโครงการ</th>
<th class="py-3 px-4 font-semibold">ชื่อโครงการ</th>
<th class="py-3 px-4 font-semibold">ผู้รับผิดชอบ</th>
<th class="py-3 px-4 font-semibold">งบที่ขออนุมัติ</th>
<th class="py-3 px-4 font-semibold">หมวดงบ</th>
<th class="py-3 px-4 font-semibold">จำนวนที่อนุมัติ</th>
<th class="py-3 px-4 font-semibold text-center">สถานะ</th>
<th class="py-3 px-4 font-semibold text-center">ดำเนินการ</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let p of projects; let i = index"
class="border-b border-gray-100 hover:bg-blue-50/20 transition">
<td class="py-4 px-4 text-gray-700">{{ i + 1 }}</td>
<td class="py-4 px-4 font-medium text-gray-700"> {{ p.code }}</td>
<td class="py-4 px-4 text-gray-800 font-semibold leading-tight">{{ p.name }}</td>
<td class="py-4 px-4 text-gray-700">{{ p.owner }}</td>
<td class="py-4 px-4 text-blue-700 font-bold whitespace-nowrap"> {{ p.budget | number:'1.0-0' }} บาท</td>
<td class="py-4 px-4 w-64">
<!-- <select class="w-full px-4 py-2.5 border border-gray-300 rounded-xl bg-white
focus:outline-none focus:ring-2 focus:ring-blue-200 focus:border-blue-300
text-sm transition">
<option value="">ไม่เลือก</option>
@for (item of budgetCategoriesDrop.expense; track item.dtlcod) {
<option [value]="item.dtlcod">
{{ item.dtlnam }}
</option>
}
</select> -->
{{ p.bdgnam }}
</td>
<td class="py-4 px-4 w-40">
<!-- <input type="number" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl bg-white focus:outline-none focus:ring-2 focus:ring-blue-200 focus:border-blue-300 text-sm transition"/> -->
{{ p.acp }} บาท
</td>
<td class="py-4 px-4 text-center">
<span
class="px-3 py-1.5 rounded-full text-xs font-semibold inline-flex items-center gap-1
shadow-sm border"
[ngClass]="{
'bg-yellow-50 text-yellow-700 border-yellow-200': p.status === 'WAIT',
'bg-green-50 text-green-700 border-green-200': p.status === 'APPROVED',
'bg-red-50 text-red-700 border-red-200': p.status === 'REJECTED'
}"
>
<ng-container *ngIf="p.status === 'APPROVED'">อนุมัติแล้ว</ng-container>
<ng-container *ngIf="p.status === 'WAIT'">รออนุมัติ</ng-container>
<ng-container *ngIf="p.status === 'REJECTED'">ไม่อนุมัติ</ng-container>
</span>
</td>
<td class="py-4 px-4 text-center space-x-2 whitespace-nowrap">
<button
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-xl text-sm
shadow-sm font-medium transition"
>
จัดสรรงบประมาณ
</button>
</td>
</tr>
</tbody>
</table>
<!-- endtable -->
</div> </div>

View File

@@ -16,6 +16,7 @@ export class BudgetAproval {
projectCode: any; projectCode: any;
project: any; project: any;
addItemForm!: FormGroup; addItemForm!: FormGroup;
isFormExpanded: boolean = false;
budgetItems = [ budgetItems = [
{ code: 'ITEM001', name: 'เอกซ์เรย์', qty: 1, price: 1000 }, { code: 'ITEM001', name: 'เอกซ์เรย์', qty: 1, price: 1000 },
@@ -23,6 +24,12 @@ export class BudgetAproval {
{ code: 'ITEM003', name: 'ตรวจพื้นฐาน', qty: 1, price: 1000 }, { code: 'ITEM003', name: 'ตรวจพื้นฐาน', qty: 1, price: 1000 },
]; ];
projects = [
{ code: 'PRJ001', name: 'ระบบจัดการน้ำดื่ม', owner: 'นาย A', budget: 20000, status: 'WAIT', acp: 0, bdgnam: 'ยังไมจัดสรร' },
{ code: 'PRJ002', name: 'ปรับปรุงอาคาร B', owner: 'นางสาว B', budget: 45000, status: 'WAIT', acp: 0, bdgnam: 'ยังไมจัดสรร'},
{ code: 'PRJ003', name: 'ซื้อคอมพิวเตอร์', owner: 'นาย C', budget: 30000, status: 'APPROVED', acp: 20000, bdgnam: 'งบดำเนินงาน' }
];
budgetCategoriesDrop = { budgetCategoriesDrop = {
expense: [ expense: [
{ dtlcod: 'BDG001', dtlnam: 'เงินรายได้' }, { dtlcod: 'BDG001', dtlnam: 'เงินรายได้' },
@@ -77,6 +84,10 @@ setupForm() {
}); });
} }
toggleFormCollapse(): void {
this.isFormExpanded = !this.isFormExpanded;
}
addBudgetItem() { addBudgetItem() {
if (this.addItemForm.invalid) { if (this.addItemForm.invalid) {
this.addItemForm.markAllAsTouched(); this.addItemForm.markAllAsTouched();

View File

@@ -8,12 +8,11 @@
--radius: 8px; --radius: 8px;
--shadow: 0 10px 30px rgba(11,26,43,0.08); --shadow: 0 10px 30px rgba(11,26,43,0.08);
--glass: rgba(255,255,255,0.6); --glass: rgba(255,255,255,0.6);
--success-color: #10b981; /* Green for success/confirm */
} }
/* Page layout */ /* Page layout (unchanged) */
.login-widget { .login-widget {
/* Fill the viewport and center the card. Do not let the page itself
scroll; the card gets an internal max-height instead. */
min-height: 100vh; min-height: 100vh;
height: 100vh; height: 100vh;
display: flex; display: flex;
@@ -24,8 +23,7 @@
color: var(--text); color: var(--text);
} }
/* Card (unchanged) */
/* Card */
.login-widget .card{ .login-widget .card{
width: 380px; width: 380px;
max-width: calc(100% - 40px); max-width: calc(100% - 40px);
@@ -37,16 +35,14 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 14px; gap: 14px;
/* Constrain the card so it never forces the page to scroll. If content
grows, the card will scroll internally. */
max-height: calc(100vh - 56px); max-height: calc(100vh - 56px);
overflow: auto; overflow: auto;
} }
/* Modal/backdrop styles */ /* Modal/backdrop styles (unchanged) */
.login-backdrop{ .login-backdrop{
position: fixed; position: fixed;
inset: 0; /* top:0; right:0; bottom:0; left:0; */ inset: 0;
background: rgba(0,0,0,0.38); background: rgba(0,0,0,0.38);
display: flex; display: flex;
align-items: center; align-items: center;
@@ -54,43 +50,19 @@
z-index: 1040; z-index: 1040;
padding: 24px; padding: 24px;
} }
.login-modal{ width: 480px; max-width: 480px; } .login-modal{ width: 480px; max-width: 480px; }
.modal-card{ .modal-card{
border-radius: 12px; border-radius: 12px;
padding: 0; /* card children control internal padding */ padding: 0;
overflow: hidden; overflow: hidden;
box-shadow: 0 20px 50px rgba(2,6,23,0.4); box-shadow: 0 20px 50px rgba(2,6,23,0.4);
} }
/* Slightly larger brand area inside modal */ /* Brand area (unchanged) */
.modal-card .brand{ padding: 18px; }
/* Make the primary button pill-shaped and slightly larger */
button.primary{
color: #000;
border-radius: 999px;
padding: 10px 18px;
font-size: 15px;
}
/* Make biometric and other action buttons visually lighter */
.biometric{
border-radius: 999px;
padding: 8px 12px;
}
/* On small screens reduce modal padding and width to avoid overflow */
@media (max-width: 420px){
.login-backdrop{ padding: 12px; }
.login-modal{ max-width: 100%; }
.modal-card .brand{ padding: 12px; }
}
/* Brand area */
.brand{ .brand{
text-align: center; text-align: center;
padding: 18px;
padding-bottom: 4px; padding-bottom: 4px;
border-bottom: 1px solid #eef2f5; border-bottom: 1px solid #eef2f5;
} }
@@ -116,16 +88,13 @@ button.primary{
/* Form area */ /* Form area */
.form{ .form{
/* keep compact spacing inside the card */
/* width: 410px; */
margin-top: 6px; margin-top: 6px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
padding: 6px 0 2px; padding: 6px 22px 22px 22px;
} }
/* Field label wrapper (unchanged) */
/* Field label wrapper */
.field{ .field{
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -135,11 +104,11 @@ button.primary{
font-size: 13px; font-size: 13px;
color: var(--muted); color: var(--muted);
} }
/* Inputs (class 'input-field' added to HTML) */
/* Inputs */
input[type="email"], input[type="email"],
input[type="password"], input[type="password"],
input[type="text"]{ input[type="text"],
.input-field { /* เพิ่ม class input-field เพื่อให้สไตล์ถูกใช้กับ input ที่กำหนด */
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 10px 12px; padding: 10px 12px;
@@ -163,31 +132,30 @@ input:focus{
transform: translateZ(0); transform: translateZ(0);
} }
/* Checkbox / stay signed */
.stay-signed{
display: inline-flex;
gap: 8px;
align-items: center;
font-size: 13px;
color: var(--muted);
}
.stay-signed input[type="checkbox"]{
width: 16px;
height: 16px;
accent-color: var(--primary);
}
/* Actions row */ /* Actions row */
.actions{ .actions{
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
gap: 12px; gap: 12px;
margin-top: 4px; margin-top: 4px;
flex-direction: row-reverse;
} }
/* Custom class for justify-end when using flex-row-reverse */
.actions.justify-end-custom {
justify-content: flex-start;
}
/* Custom class for lift hover effect (used for 'เปิด Modal' button) */
.hover-lift:hover {
transform: translateY(-2.5px);
transition: transform .2s ease;
}
/* PRIMARY BUTTON - แก้ไขสีข้อความให้เป็นสีดำ */
button.primary{ button.primary{
background: linear-gradient(180deg, var(--primary) 0%, var(--primary-600) 100%); background: linear-gradient(180deg, var(--primary) 0%, var(--primary-600) 100%);
color: #000000; color: #000000; /* ⬅️ แก้ไขเป็นสีดำตามคำขอ */
border: none; border: none;
padding: 10px 14px; padding: 10px 14px;
border-radius: 6px; border-radius: 6px;
@@ -197,6 +165,7 @@ button.primary{
box-shadow: 0 6px 18px rgba(0,120,212,0.12); box-shadow: 0 6px 18px rgba(0,120,212,0.12);
transition: transform .06s ease, box-shadow .12s ease, opacity .12s ease; transition: transform .06s ease, box-shadow .12s ease, opacity .12s ease;
} }
button.primary:hover:not(:disabled){ button.primary:hover:not(:disabled){
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 10px 24px rgba(0,120,212,0.14); box-shadow: 0 10px 24px rgba(0,120,212,0.14);
@@ -207,45 +176,26 @@ button.primary:active{
button.primary:disabled{ button.primary:disabled{
opacity: 0.55; opacity: 0.55;
cursor: not-allowed; cursor: not-allowed;
color: #000000; /* ข้อความ disabled ก็เป็นสีดำ */
box-shadow: none; box-shadow: none;
} }
/* Alternative options */ /* Secondary Button Style (สำหรับปุ่ม 'เปิด Modal', 'ส่งอีกครั้ง') */
.alt-options{ .primary.secondary-button {
display: flex;
align-items: center;
gap: 12px;
margin-top: 6px;
flex-wrap: wrap;
}
.biometric{
display: inline-flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
background: transparent; background: transparent;
color: var(--primary); color: var(--primary);
border: 1px solid rgba(0,120,212,0.14); border: 1px solid var(--primary);
border-radius: 6px; box-shadow: none;
cursor: pointer; transition: background-color .14s ease;
font-weight: 600;
font-size: 13px;
} }
.biometric svg{ display: block; opacity: .95; } .primary.secondary-button:hover {
.biometric:hover{ background: rgba(0, 120, 212, 0.05);
background: rgba(0,120,212,0.04); transform: none;
box-shadow: none;
} }
/* Help link */
.help-link{
margin-left: auto;
font-size: 13px;
color: var(--primary);
text-decoration: none;
}
.help-link:hover{ text-decoration: underline; }
/* Footer */ /* Footer (unchanged) */
.footer{ .footer{
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -262,13 +212,9 @@ button.primary:disabled{
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 600;
} }
.footer a:hover{ text-decoration: underline; }
.divider{ color: #d0d6db; }
/* Focus styles for keyboard users */ /* Focus styles (unchanged) */
:focus{ :focus{ outline: none; }
outline: none;
}
:focus-visible{ :focus-visible{
outline: 3px solid rgba(0,120,212,0.12); outline: 3px solid rgba(0,120,212,0.12);
outline-offset: 2px; outline-offset: 2px;
@@ -277,14 +223,13 @@ button.primary:disabled{
/* Small screens */ /* Small screens */
@media (max-width:420px){ @media (max-width:420px){
.login-backdrop{ padding: 12px; }
.login-modal{ max-width: 100%; }
.modal-card .brand{ padding: 12px; }
.login-widget .card{ .login-widget .card{
padding: 18px; padding: 18px;
width: 100%; width: 100%;
} }
.brand h1{ font-size: 18px; } .brand h1{ font-size: 18px; }
.brand .subtitle{
font-family: "Kanit";
font-weight: 1000;
font-style: normal; }
.biometric span, .primary{ font-size: 13px; } .biometric span, .primary{ font-size: 13px; }
} }

View File

@@ -1,52 +1,38 @@
<div class="login-backdrop"> <div class="login-backdrop">
<div class="login-modal d-flex align-items-center justify-content-center "> <div class="login-modal d-flex align-items-center justify-content-center bg-white rounded-2xl">
<div class="card modal-card" role="dialog" aria-labelledby="signin-title" aria-modal="true"> <div class="card modal-card" role="dialog" aria-labelledby="signin-title" aria-modal="true">
<div class="brand"> <div class="brand">
<!-- <img src="assets/logo.png" alt="Company logo" class="logo" /> -->
<img src="logo.png" alt="Company logo" class="logo mb-2"/> <img src="logo.png" alt="Company logo" class="logo mb-2"/>
<h1 id="signin-title" class='kanit-bold'>ลืมรหัสผ่าน</h1> <h1 id="signin-title" class="kanit-bold">ลืมรหัสผ่าน</h1>
<p class="subtitle">โปรดกรอก Email ของท่าน</p> <p class="subtitle">โปรดกรอก Email ของท่าน</p>
</div> </div>
<form [formGroup]="forgotFrm" class="form px-3 pb-3 login-mobile">
<form [formGroup]="forgotFrm" class="form">
<label class="field"> <label class="field">
<span class="label-text">อีเมล์</span> <span class="label-text">อีเมล์</span>
<input type="email" formControlName="email" class="px-2 " id="englishInput" autocomplete="username" placeholder="nuttakit@gmail.com" aria-label="Email address" required /> <input type="email" formControlName="email" class="input-field" id="englishInput" autocomplete="username" placeholder="nuttakit@gmail.com" aria-label="Email address" required />
</label> </label>
@if (isSendOtp === true) { @if (isSendOtp === true) {
<label class="field"> <label class="field">
<span class="label-text">รหัสยืนยัน OTP</span> <span class="label-text">รหัสยืนยัน OTP</span>
<input type="email" formControlName="otp" autocomplete="otp" placeholder="123456" alt required/> <input type="text" formControlName="otp" class="input-field" autocomplete="one-time-code" placeholder="123456" alt required/>
</label> </label>
} }
<!-- <div class="justify-end flex"> -->
<!-- <label class="stay-signed"> <div class="actions justify-end-custom mt-4">
<input type="checkbox" formControlName="remember" />
<span>จดจำรหัสผ่าน</span> <div class="flex-row hover-lift">
</label> --> <button class="primary secondary-button" type="button" (click)="isModalOpen = true">
<div class="flex flex-row gap-2 mt-4 justify-end">
<div class="flex-row hover:-translate-y-2.5 transform transition-all">
<button class="bg-[linear-gradient(180deg,var(--primary)_0%,var(--primary-600)_100%)]
text-black
border-0
px-3.5 py-2.5
rounded-md
font-semibold
cursor-pointer
text-[14px]
shadow-[0_6px_18px_var(--color-blue-500)]
transition
ease-in-out
duration-100
hover:scale-[1.02]
active:opacity-90" (click)="isModalOpen = true">
เปิด Modal เปิด Modal
</button> </button>
</div> </div>
@if (isSendOtp === false) { @if (isSendOtp === false) {
<div class="flex justify-end"> <div class="flex justify-end">
@if (isLoading === true) { @if (isLoading === true) {
<button type="submit" class="primary cursor-progress!" disabled> <button type="submit" class="primary" disabled>
กำลังส่ง... กำลังส่ง...
</button> </button>
} @else { } @else {
@@ -57,7 +43,7 @@
</div> </div>
} @else if(isSendOtp === true) { } @else if(isSendOtp === true) {
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button type="button" class="primary" (click)="onSubmin()"> <button type="button" class="primary secondary-button" (click)="onSubmin()">
{{ 'ส่งอีกครั้ง' }} {{ 'ส่งอีกครั้ง' }}
</button> </button>
<button type="submit" class="primary" (click)="onVerifySubmit()"> <button type="submit" class="primary" (click)="onVerifySubmit()">
@@ -66,17 +52,13 @@
</div> </div>
} }
</div> </div>
<!-- <button mat-raised-button color="primary" [disabled]="isLoading">
{{ isLoading ? 'กำลังส่ง...' : 'ส่งรหัส OTP' }}
</button> -->
<!-- } -->
<!-- </div> -->
</form> </form>
@if(isModalOpen){ @if(isModalOpen){
<div class="fixed inset-0 flex items-center justify-center z-50 bg-black bg-opacity-50" [formGroup]="forgotFrm"> <div class="fixed inset-0 flex items-center justify-center z-50 bg-black bg-opacity-50" [formGroup]="forgotFrm">
<div class="bg-white p-6 rounded-lg shadow-lg max-w-sm w-fit"> <div class="bg-white p-6 rounded-lg shadow-xl max-w-sm w-fit modal-reset-password">
<h2 class="text-xl font-bold mb-4">เปลี่ยนรหัสผ่าน</h2> <h2 class="text-xl font-bold mb-4 text-center">เปลี่ยนรหัสผ่าน</h2>
<hr class="w-full h-1 bg-gray-300 rounded-sm shadow-neutral-400 md:my-1"> <hr class="w-full h-px bg-gray-200 border-0 my-3">
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">รหัสผ่านใหม่</label> <label class="block text-sm font-medium text-gray-700 mb-1">รหัสผ่านใหม่</label>
<input type="password" id="newPassword" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" formControlName="newPassword" placeholder="กรอกรหัสผ่านใหม่"> <input type="password" id="newPassword" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" formControlName="newPassword" placeholder="กรอกรหัสผ่านใหม่">
@@ -85,15 +67,14 @@
<label for="confirmPassword" class="block text-sm font-medium text-gray-700 mb-1">ยืนยันรหัสผ่านใหม่</label> <label for="confirmPassword" class="block text-sm font-medium text-gray-700 mb-1">ยืนยันรหัสผ่านใหม่</label>
<input type="password" id="confirmPassword" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" formControlName="confirmPassword" placeholder="กรอกยืนยันรหัสผ่านใหม่"> <input type="password" id="confirmPassword" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" formControlName="confirmPassword" placeholder="กรอกยืนยันรหัสผ่านใหม่">
@if ( this.forgotFrm.get('confirmPassword')!.touched && this.forgotFrm.get('newPassword')?.value !== this.forgotFrm.get('confirmPassword')?.value ){ @if ( this.forgotFrm.get('confirmPassword')!.touched && this.forgotFrm.get('newPassword')?.value !== this.forgotFrm.get('confirmPassword')?.value ){
<span class="text-red-600 md">รหัสผ่านไม่ตรงกัน</span> <span class="text-red-600 text-xs mt-1 block">รหัสผ่านไม่ตรงกัน</span>
} }
</div> </div>
<!-- <hr class="w-full h-[] bg-gray-100 border-0 rounded-sm md:my-1 dark:bg-gray-700"> --> <div class="flex justify-end gap-2 mt-5">
<div class="flex justify-end gap-2"> <button class="bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors hover:bg-gray-500" (click)="isModalOpen = false">
<button class="bg-red-500 text-white px-4 py-2 rounded" (click)="isModalOpen = false">
ปิด ปิด
</button> </button>
<button class="bg-green-500 text-white px-4 py-2 rounded shadow-[0_1px_18px_var(--color-green-300)] hover:-translate-y-1.5 hover:shadow-[0_6px_18px_var(--color-green-500)] transition-all duration-500 ease-in-out" (click)="onSetNewPassword()"> <button class="bg-green-600 text-white px-4 py-2 rounded-lg font-semibold shadow-md transition-all duration-300 ease-in-out hover:bg-green-700 hover:shadow-lg" (click)="onSetNewPassword()">
ยืนยัน ยืนยัน
</button> </button>
</div> </div>

View File

@@ -12,8 +12,6 @@
/* Page layout */ /* Page layout */
.login-widget { .login-widget {
/* Fill the viewport and center the card. Do not let the page itself
scroll; the card gets an internal max-height instead. */
min-height: 100vh; min-height: 100vh;
height: 100vh; height: 100vh;
display: flex; display: flex;
@@ -37,8 +35,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 14px; gap: 14px;
/* Constrain the card so it never forces the page to scroll. If content
grows, the card will scroll internally. */
max-height: calc(100vh - 56px); max-height: calc(100vh - 56px);
overflow: auto; overflow: auto;
} }
@@ -46,7 +42,7 @@
/* Modal/backdrop styles */ /* Modal/backdrop styles */
.login-backdrop{ .login-backdrop{
position: fixed; position: fixed;
inset: 0; /* top:0; right:0; bottom:0; left:0; */ inset: 0;
background: rgba(0,0,0,0.38); background: rgba(0,0,0,0.38);
display: flex; display: flex;
align-items: center; align-items: center;
@@ -64,33 +60,10 @@
box-shadow: 0 20px 50px rgba(2,6,23,0.4); box-shadow: 0 20px 50px rgba(2,6,23,0.4);
} }
/* Slightly larger brand area inside modal */
.modal-card .brand{ padding: 18px; }
/* Make the primary button pill-shaped and slightly larger */
button.primary{
color: #000;
border-radius: 999px;
padding: 10px 18px;
font-size: 15px;
}
/* Make biometric and other action buttons visually lighter */
.biometric{
border-radius: 999px;
padding: 8px 12px;
}
/* On small screens reduce modal padding and width to avoid overflow */
@media (max-width: 420px){
.login-backdrop{ padding: 12px; }
.login-modal{ max-width: 100%; }
.modal-card .brand{ padding: 12px; }
}
/* Brand area */ /* Brand area */
.brand{ .brand{
text-align: center; text-align: center;
padding: 18px; /* Use padding from modal-card .brand */
padding-bottom: 4px; padding-bottom: 4px;
border-bottom: 1px solid #eef2f5; border-bottom: 1px solid #eef2f5;
} }
@@ -116,12 +89,11 @@ button.primary{
/* Form area */ /* Form area */
.form{ .form{
/* keep compact spacing inside the card */
margin-top: 6px; margin-top: 6px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
padding: 6px 0 2px; padding: 6px 22px 2px 22px; /* Adjusted padding to match card padding */
} }
/* Field label wrapper */ /* Field label wrapper */
@@ -185,8 +157,9 @@ input:focus{
margin-top: 4px; margin-top: 4px;
} }
button.primary{ button.primary{
background: linear-gradient(180deg, var(--primary) 0%, var(--primary-600) 100%); /* ⭐️ แก้ไขตรงนี้: เปลี่ยนสีข้อความเป็นสีดำตามคำขอ */
color: #000000; color: #000000;
background: linear-gradient(180deg, var(--primary) 0%, var(--primary-600) 100%);
border: none; border: none;
padding: 10px 14px; padding: 10px 14px;
border-radius: 6px; border-radius: 6px;
@@ -196,6 +169,7 @@ button.primary{
box-shadow: 0 6px 18px rgba(0,120,212,0.12); box-shadow: 0 6px 18px rgba(0,120,212,0.12);
transition: transform .06s ease, box-shadow .12s ease, opacity .12s ease; transition: transform .06s ease, box-shadow .12s ease, opacity .12s ease;
} }
button.primary:hover:not(:disabled){ button.primary:hover:not(:disabled){
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 10px 24px rgba(0,120,212,0.14); box-shadow: 0 10px 24px rgba(0,120,212,0.14);
@@ -206,6 +180,7 @@ button.primary:active{
button.primary:disabled{ button.primary:disabled{
opacity: 0.55; opacity: 0.55;
cursor: not-allowed; cursor: not-allowed;
color: #000000; /* ข้อความ Disabled ก็ยังเป็นสีดำ */
box-shadow: none; box-shadow: none;
} }
@@ -230,6 +205,7 @@ button.primary:disabled{
font-weight: 600; font-weight: 600;
font-size: 13px; font-size: 13px;
} }
.biometric svg{ display: block; opacity: .95; } .biometric svg{ display: block; opacity: .95; }
.biometric:hover{ .biometric:hover{
background: rgba(0,120,212,0.04); background: rgba(0,120,212,0.04);
@@ -276,14 +252,14 @@ button.primary:disabled{
/* Small screens */ /* Small screens */
@media (max-width:420px){ @media (max-width:420px){
.login-backdrop{ padding: 12px; }
.login-modal{ max-width: 100%; }
.modal-card .brand{ padding: 12px; }
.login-widget .card{ .login-widget .card{
padding: 18px; padding: 18px;
width: 100%; width: 100%;
} }
.brand h1{ font-size: 18px; } .brand h1{ font-size: 18px; }
.brand .subtitle{
font-family: "Kanit";
font-weight: 1000;
font-style: normal; }
.biometric span, .primary{ font-size: 13px; } .biometric span, .primary{ font-size: 13px; }
} }

View File

@@ -1,55 +1,55 @@
<!-- Modal-like backdrop that covers the viewport -->
<div class="login-backdrop"> <div class="login-backdrop">
<div class="login-modal d-flex align-items-center justify-content-center"> <div class="login-modal d-flex align-items-center justify-content-center bg-white rounded-2xl">
<div class="card modal-card" role="dialog" aria-labelledby="signin-title" aria-modal="true"> <div class="card modal-card" role="dialog" aria-labelledby="signin-title" aria-modal="true">
<div class="brand"> <div class="brand">
<!-- <img src="assets/logo.png" alt="Company logo" class="logo" /> --> <img src="/logo.png" alt="Company logo" class="logo"/>
<img src="/logo.png" alt="Company logo" class="logo mb-2"/> <h1 id="signin-title" class="kanit-bold">เข้าสู่ระบบ</h1>
<h1 id="signin-title" class='kanit-bold'>เข้าสู่ระบบ</h1>
<p class="subtitle">บัญชีโปรแกรมจัดการแผนงานงบประมาณของท่าน</p> <p class="subtitle">บัญชีโปรแกรมจัดการแผนงานงบประมาณของท่าน</p>
</div> </div>
<form [formGroup]="loginForm" (ngSubmit)="signIn()" class="form px-3 pb-3">
<form [formGroup]="loginForm" (ngSubmit)="signIn()" class="form">
<label class="field"> <label class="field">
<span class="label-text">อีเมล์</span> <span class="label-text">อีเมล์</span>
<input type="email" formControlName="username" autocomplete="username" placeholder="nuttakit@gmail.com" required /> <input type="email" formControlName="username" autocomplete="username" placeholder="nuttakit@gmail.com" required class="input-field" />
</label> </label>
<label class="field"> <label class="field mt-3">
<span class="label-text">รหัสผ่าน</span> <span class="label-text">รหัสผ่าน</span>
<input type="password" formControlName="password" autocomplete="current-password" required /> <input type="password" formControlName="password" autocomplete="current-password" required class="input-field" />
</label> </label>
<div class="actions"> <div class="actions d-flex justify-content-between align-items-center mt-4">
<label class="stay-signed"> <label class="stay-signed">
<input type="checkbox" formControlName="remember" /> <input type="checkbox" formControlName="remember" />
<span>จดจำรหัสผ่าน</span> <span>จดจำรหัสผ่าน</span>
</label> </label>
<!-- <fa-icon [icon]="faCoffee" /> --> <button type="submit" class="primary login-button"
<button type="submit" class="primary" [disabled]="!(loginForm.get('username')?.valid && loginForm.get('password')?.value)"> [disabled]="!(loginForm.get('username')?.valid && loginForm.get('password')?.value)">
เข้าสู่ระบบ เข้าสู่ระบบ
</button> </button>
</div> </div>
<div class="alt-options"> <div class="alt-options mt-4 text-center">
<button type="button" class="biometric" (click)="useBiometric()"> <button type="button" class="biometric btn-icon" (click)="useBiometric()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" class="icon-svg">
<path d="M12 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" fill="currentColor" opacity=".9"/> <path d="M12 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" fill="currentColor" opacity=".9"/>
<path d="M6.2 10.9A6 6 0 0 1 12 6a6 6 0 0 1 5.8 4.9M12 22v-2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M6.2 10.9A6 6 0 0 1 12 6a6 6 0 0 1 5.8 4.9M12 22v-2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12a8 8 0 0 1 16 0v1" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M4 12a8 8 0 0 1 16 0v1" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
<span>เข้าสู่ระบบด้วย Windows Hello / Touch ID</span> <span>เข้าสู่ระบบด้วย Windows Hello / Touch ID</span>
</button> </button>
<a class="help-link mt-2" href="#" (click)="$event.preventDefault(); forgotPassword()">ลืมรหัส ใช่ หรือ ไม่?</a>
<a class="help-link" href="#" (click)="$event.preventDefault(); forgotPassword()">ลืมรหัส ใช่ หรือ ไม่?</a>
</div> </div>
<div class="footer"> <div class="footer mt-5 text-center">
<a href="#" (click)="$event.preventDefault(); createAccount()">สร้างบัญชี</a> <a href="#" (click)="$event.preventDefault(); createAccount()">สร้างบัญชี</a>
<span class="divider"></span> <span class="divider mx-2"></span>
<a href="#" (click)="$event.preventDefault(); privacy()">Privacy & terms</a> <a href="#" (click)="$event.preventDefault(); privacy()">Privacy & terms</a>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
<section class="dashboard"> <section class="dashboard">
<header class="dashboard__hero"> <!-- <header class="dashboard__hero">
<div class="hero__text"> <div class="hero__text">
<p class="eyebrow">ภาพรวมบัญชี</p> <p class="eyebrow">ภาพรวมบัญชี</p>
<h1>ยินดีต้อนรับกลับ, {{ ownerName }}</h1> <h1>ยินดีต้อนรับกลับ, {{ ownerName }}</h1>
@@ -9,11 +9,11 @@
</div> </div>
<div class="hero__actions"> <div class="hero__actions">
<button class="btn btn--primary">สร้างรายงานด่วน</button> <button class="btn btn--primary">สร้างรายงานด่วน</button>
<!-- <button class="btn btn--ghost">อัปโหลดใบเสร็จ</button> --> <button class="btn btn--ghost">อัปโหลดใบเสร็จ</button>
</div> </div>
</header> </header> -->
<section class="dashboard__periods"> <!-- <section class="dashboard__periods">
<article class="period-card" *ngFor="let summary of periodSummaries"> <article class="period-card" *ngFor="let summary of periodSummaries">
<header class="period-card__header"> <header class="period-card__header">
<span class="period-card__badge" [ngClass]="'period-card__badge--' + summary.badge"> <span class="period-card__badge" [ngClass]="'period-card__badge--' + summary.badge">
@@ -39,9 +39,9 @@
<span class="trend-chip">แนวโน้ม {{ summary.trend }}</span> <span class="trend-chip">แนวโน้ม {{ summary.trend }}</span>
</footer> </footer>
</article> </article>
</section> </section> -->
<section class="dashboard__stats"> <!-- <section class="dashboard__stats">
<article class="stat-card" *ngFor="let card of kpiCards"> <article class="stat-card" *ngFor="let card of kpiCards">
<div class="stat-card__icon" [ngClass]="'accent-' + card.accent"></div> <div class="stat-card__icon" [ngClass]="'accent-' + card.accent"></div>
<div class="stat-card__body"> <div class="stat-card__body">
@@ -50,8 +50,8 @@
<p class="stat-card__trend">{{ card.trend }} · {{ card.context }}</p> <p class="stat-card__trend">{{ card.trend }} · {{ card.context }}</p>
</div> </div>
</article> </article>
</section> </section> -->
<!--
<section class="ledger-grid"> <section class="ledger-grid">
<article class="panel quick-log"> <article class="panel quick-log">
<div class="panel__header"> <div class="panel__header">
@@ -70,7 +70,7 @@
</label> </label>
<label> <label>
<span>วันที่</span> <span>วันที่</span>
<!-- <input type="text" disabled placeholder="10/04/2025 เวลา 12:00"/> --> <input type="text" disabled placeholder="10/04/2025 เวลา 12:00"/>
<input type="datetime-local"/> <input type="datetime-local"/>
</label> </label>
@@ -125,7 +125,7 @@
<span>ยอดเงิน</span> <span>ยอดเงิน</span>
<span>บันทึก</span> <span>บันทึก</span>
</div> </div>
<!-- @for (idx of myActData; track i; let i = $index) { @for (idx of myActData; track i; let i = $index) {
<div class="ledger-row"> <div class="ledger-row">
<div class="ledger-main"> <div class="ledger-main">
<span class="pill" [ngClass]="idx.acttyp === 'i' ? 'pill--income' : 'pill--expense'"> <span class="pill" [ngClass]="idx.acttyp === 'i' ? 'pill--income' : 'pill--expense'">
@@ -146,7 +146,7 @@
<span class="ledger-note">{{ idx.note }}</span> <span class="ledger-note">{{ idx.note }}</span>
</div> </div>
} --> }
@for (idx of myActData; track idx.actseq; let i = $index) { @for (idx of myActData; track idx.actseq; let i = $index) {
<div class="ledger-row"> <div class="ledger-row">
@@ -172,7 +172,7 @@
} }
</div> </div>
</article> </article>
</section> </section> -->
<section class="dashboard__grid"> <section class="dashboard__grid">
<!-- <article class="panel panel--main"> <!-- <article class="panel panel--main">
@@ -238,7 +238,7 @@
</div> </div>
</article> </article>
<article class="panel alerts-panel"> <!-- <article class="panel alerts-panel">
<div class="panel__header"> <div class="panel__header">
<div> <div>
<h2>การแจ้งเตือนสำคัญ</h2> <h2>การแจ้งเตือนสำคัญ</h2>
@@ -252,9 +252,9 @@
</div> </div>
<span class="alert__tag">{{ alert.tag }}</span> <span class="alert__tag">{{ alert.tag }}</span>
</div> </div>
</article> </article> -->
<article class="panel tasks-panel"> <!-- <article class="panel tasks-panel">
<div class="panel__header"> <div class="panel__header">
<div> <div>
<h2>รายการยอดค้างจ่าย</h2> <h2>รายการยอดค้างจ่าย</h2>
@@ -271,7 +271,7 @@
<span class="task__badge">{{ task.priority }}</span> <span class="task__badge">{{ task.priority }}</span>
</li> </li>
</ul> </ul>
</article> </article> -->
</section> </section>
</section> </section>

View File

@@ -22,52 +22,52 @@
<main class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-6"> <main class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-6">
<div class="md:col-span-2 "> <div class="md:col-span-2 ">
<div class="bg-white p-6 rounded-xl shadow-lg"> <div class="bg-white p-6 rounded-xl shadow-lg h-88">
<h2 class="text-lg font-medium text-gray-700 mb-4">เมนู</h2> <h2 class="text-lg font-medium text-gray-700 mb-4">เมนู</h2>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer"> <button class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m4 2h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m4 2h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" />
</svg> </svg>
<span class="text-gray-700">การเบิกงวดงบ</span> <span class="text-gray-700">การเบิกงวดงบ</span>
</div> </button>
<div class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer"> <button class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg> </svg>
<span class="text-gray-700">สัญญา / ข้อตกลง</span> <span class="text-gray-700">สัญญา / ข้อตกลง</span>
</div> </button>
<div class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer"> <button class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer">
<!-- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <!-- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.102a9 9 0 11-12.728 0m12.728 0a9 9 0 00-12.728 0M3 12a9 9 0 1118 0" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.102a9 9 0 11-12.728 0m12.728 0a9 9 0 00-12.728 0M3 12a9 9 0 1118 0" />
</svg> --> </svg> -->
<img src="stamp_0.png" alt="" class="h-6 w-6 text-gray-500 opacity-50"> <img src="stamp_0.png" alt="" class="h-6 w-6 text-gray-500 opacity-50">
<span class="text-gray-700">การอนุมัติโครงการ</span> <span class="text-gray-700">การอนุมัติโครงการ</span>
</div> </button>
<div class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer"> <button class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
<span class="text-gray-700">เอกสารฟอร์มวิทยาลัย</span> <span class="text-gray-700">เอกสารฟอร์มวิทยาลัย</span>
</div> </button>
<div class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer"> <button class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer">
<!-- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <!-- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.992 9.992 0 0112 21c-3.142 0-6.186-1.042-8.625-2.999M21 12a9.992 9.992 0 00-3-6.945M3 12a9.992 9.992 0 013-6.945M12 15a3 3 0 100-6 3 3 0 000 6z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.992 9.992 0 0112 21c-3.142 0-6.186-1.042-8.625-2.999M21 12a9.992 9.992 0 00-3-6.945M3 12a9.992 9.992 0 013-6.945M12 15a3 3 0 100-6 3 3 0 000 6z" />
</svg> --> </svg> -->
<img src="chart-simple.png" alt="" class="h-6 w-6 text-gray-500 opacity-50"> <img src="chart-simple.png" alt="" class="h-6 w-6 text-gray-500 opacity-50">
<span class="text-gray-700">จัดสรรงบประมาณ</span> <span class="text-gray-700">จัดสรรงบประมาณ</span>
</div> </button>
<div class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer"> <button class="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition duration-150 cursor-pointer" (click)="navigate('main/report')">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg> </svg>
<span class="text-gray-700">วิเคราะห์ & รายงาน</span> <span class="text-gray-700">วิเคราะห์ & รายงาน</span>
</div> </button>
</div> </div>
</div> </div>
</div> </div>
@@ -75,22 +75,50 @@
<div> <div>
<div class="bg-white p-6 rounded-xl shadow-lg"> <div class="bg-white p-6 rounded-xl shadow-lg">
<h2 class="text-lg font-medium text-gray-700 mb-4">กล่องแจ้งเตือน</h2> <h2 class="text-lg font-medium text-gray-700 mb-4">กล่องแจ้งเตือน</h2>
<div class="space-y-3"> <div class="space-y-4">
<div class="h-4 bg-gray-200 rounded w-11/12"></div>
<div class="h-4 bg-gray-200 rounded w-10/12"></div> <div class="flex items-start space-x-3 p-3 bg-red-50 rounded-lg border-l-4 border-red-500 hover:bg-red-100 transition duration-150 cursor-pointer">
<div class="h-4 bg-gray-200 rounded w-9/12"></div> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mt-0.5 text-red-600 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div>
<p class="text-sm font-medium text-gray-800">มี **โครงการ 3 รายการ** รออนุมัติ</p>
<p class="text-xs text-red-600 mt-0.5">ครบกำหนด: วันนี้, 15:00 น.</p>
</div>
</div>
<div class="flex items-start space-x-3 p-3 bg-blue-50 rounded-lg border-l-4 border-blue-500 hover:bg-blue-100 transition duration-150 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mt-0.5 text-blue-600 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<p class="text-sm font-medium text-gray-800">เอกสารฟอร์มวิทยาลัย **ฉบับใหม่** พร้อมดาวน์โหลดแล้ว</p>
<p class="text-xs text-gray-500 mt-0.5">ประกาศเมื่อ: 17 พ.ย. 2568</p>
</div>
</div>
<div class="flex items-start space-x-3 p-3 bg-green-50 rounded-lg border-l-4 border-green-500 hover:bg-green-100 transition duration-150 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mt-0.5 text-green-600 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.102a9 9 0 11-12.728 0m12.728 0a9 9 0 00-12.728 0" />
</svg>
<div>
<p class="text-sm font-medium text-gray-800">การเปิดงบประมาณ **ไตรมาส 4** ได้รับการอนุมัติแล้ว</p>
<p class="text-xs text-gray-500 mt-0.5">สำเร็จเมื่อ: 15 พ.ย. 2568</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</main> </main>
<section class="mt-6"> <!-- <section class="mt-6">
<div class="bg-white p-6 rounded-xl shadow-lg flex items-center space-x-6"> <div class="bg-white p-6 rounded-xl shadow-lg flex items-center space-x-6">
<div class="shrink-0"> <div class="shrink-0">
<div class="h-24 w-24 rounded-full bg-white flex items-center justify-center overflow-hidden"> <div class="h-24 w-24 rounded-full bg-white flex items-center justify-center overflow-hidden">
<img src="logo.png" alt=""> <img src="logo.png" alt="">
<!-- <span class="text-center text-white font-bold text-sm leading-tight">วิทยาลัยเทคนิคตรัง</span> --> <span class="text-center text-white font-bold text-sm leading-tight">วิทยาลัยเทคนิคตรัง</span>
</div> </div>
</div> </div>
@@ -100,6 +128,103 @@
<div class="h-4 bg-red-100 rounded w-64"></div> <div class="h-4 bg-red-100 rounded w-64"></div>
<div class="h-4 bg-red-100 rounded w-56"></div> <div class="h-4 bg-red-100 rounded w-56"></div>
</div> </div>
<table>
<tbody>
<td>sdsd</td>
<td>sdasds</td>
</tbody>
</table>
</div>
</div>
</section> -->
<section class="mt-6">
<div class="bg-white p-6 rounded-xl shadow-lg flex items-start space-x-6">
<div class="shrink-0">
<div class="h-24 w-24 rounded-full bg-white flex items-center justify-center overflow-hidden border border-gray-200">
<img src="logo.png" alt="Trang Technical College Logo" class="h-full w-full object-cover">
</div>
</div>
<div class="grow">
<h2 class="text-xl font-semibold text-gray-800 mb-3">อัพเดตซอฟต์แวร์ล่าสุด</h2>
<div class="flex text-sm font-medium text-gray-500 border-b pb-1 mb-2">
<span class="w-2/5 pr-2">รายการ</span>
<span class="w-1/5">ประเภท</span>
<span class="w-1/5 text-right">เวอร์ชัน</span>
<span class="w-1/5 pl-2">สถานะ</span>
</div>
<div class="flex items-center py-2 border-b last:border-b-0 hover:bg-gray-50 transition duration-100">
<div class="w-2/5 flex items-center space-x-3 pr-2">
<div>
<p class="text-sm font-medium text-gray-900">แก้ไขบั๊ก (Patch Fix)</p>
<p class="text-xs text-gray-500">18 พ.ย. 2568</p>
</div>
</div>
<span class="w-1/5">
<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-green-100 text-green-800">
ปรับปรุง
</span>
</span>
<span class="w-1/5 text-sm font-medium text-right text-green-600">
1.0.1
</span>
<span class="w-1/5 text-sm text-gray-600 pl-2 truncate">
เสร็จสมบูรณ์
</span>
</div>
<div class="flex items-center py-2 border-b last:border-b-0 hover:bg-gray-50 transition duration-100">
<div class="w-2/5 flex items-center space-x-3 pr-2">
<div>
<p class="text-sm font-medium text-gray-900">อัปเดตระบบหลัก (Major Update)</p>
<p class="text-xs text-gray-500">25 ต.ค. 2568</p>
</div>
</div>
<span class="w-1/5">
<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-red-100 text-red-800">
คุณสมบัติใหม่
</span>
</span>
<span class="w-1/5 text-sm font-medium text-right text-red-600">
2.0.0
</span>
<span class="w-1/5 text-sm text-gray-600 pl-2 truncate">
รอตรวจสอบ
</span>
</div>
<div class="flex items-center py-2 last:border-b-0 hover:bg-gray-50 transition duration-100">
<div class="w-2/5 flex items-center space-x-3 pr-2">
<div>
<p class="text-sm font-medium text-gray-900">แก้ไขช่องโหว่ความปลอดภัย</p>
<p class="text-xs text-gray-500">5 พ.ย. 2568</p>
</div>
</div>
<span class="w-1/5">
<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-green-100 text-green-800">
ปรับปรุง
</span>
</span>
<span class="w-1/5 text-sm font-medium text-right text-green-600">
1.0.0
</span>
<span class="w-1/5 text-sm text-gray-600 pl-2 truncate">
เสร็จสมบูรณ์
</span>
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-main-landing', selector: 'app-main-landing',
@@ -9,10 +10,15 @@ import { Component, OnInit } from '@angular/core';
export class MainLandingComponent implements OnInit { export class MainLandingComponent implements OnInit {
constructor( constructor(
private router: Router
) {} ) {}
ngOnInit() { ngOnInit() {
}
} }
navigate(path: string) {
this.router.navigate([path]);
}
}

View File

@@ -1,7 +1,9 @@
:host { :host {
display: block; display: block;
padding: 2rem clamp(1.25rem, 4vw, 3rem) 3rem; padding: 2rem clamp(1.25rem, 4vw, 3rem) 3rem;
background: radial-gradient(120% 120% at 0% 0%, #f6f8ff 0%, #eef5ff 55%, #ffffff 100%); /* background: radial-gradient(120% 120% at 0% 0%, #f6f8ff 0%, #eef5ff 55%, #ffffff 100%); */
/* background: white; */
background-color: var(--color-gray-100);
min-height: 100%; min-height: 100%;
} }

View File

@@ -2,7 +2,7 @@
<!-- Summary Cards --> <!-- Summary Cards -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6 mb-8"> <div class="grid grid-cols-1 sm:grid-cols-3 gap-6 mb-8">
<!--
<div class="bg-white border border-gray-200 rounded-2xl p-5 shadow-sm hover:shadow-md transition"> <div class="bg-white border border-gray-200 rounded-2xl p-5 shadow-sm hover:shadow-md transition">
<div class="text-gray-500 text-sm">งบทั้งหมด</div> <div class="text-gray-500 text-sm">งบทั้งหมด</div>
<div class="text-3xl font-bold text-gray-800 mt-1"> <div class="text-3xl font-bold text-gray-800 mt-1">
@@ -25,7 +25,7 @@
> >
{{ remainingBudget | number:'1.0-2' }} บาท {{ remainingBudget | number:'1.0-2' }} บาท
</div> </div>
</div> </div> -->
</div> </div>
@@ -38,6 +38,8 @@
</button> </button>
</div> </div>
<!-- Table --> <!-- Table -->
<div class="overflow-x-auto bg-white border border-gray-200 rounded-2xl shadow-sm"> <div class="overflow-x-auto bg-white border border-gray-200 rounded-2xl shadow-sm">
<table class="min-w-full text-left border-collapse"> <table class="min-w-full text-left border-collapse">
@@ -56,51 +58,30 @@
</thead> </thead>
<tbody> <tbody>
<tr <tr *ngFor="let p of projects; let i = index"class="border-b border-gray-100 hover:bg-blue-50/20 transition">
*ngFor="let p of projects; let i = index"
class="border-b border-gray-100 hover:bg-blue-50/20 transition"
>
<td class="py-4 px-4 text-gray-700">{{ i + 1 }}</td> <td class="py-4 px-4 text-gray-700">{{ i + 1 }}</td>
<td class="py-4 px-4 font-medium text-gray-700"> {{ p.code }}</td>
<td class="py-4 px-4 font-medium text-gray-700"> <td class="py-4 px-4 text-gray-800 font-semibold leading-tight">{{ p.name }}</td>
{{ p.code }} <td class="py-4 px-4 text-gray-700">{{ p.owner }}</td>
</td> <td class="py-4 px-4 text-blue-700 font-bold whitespace-nowrap"> {{ p.budget | number:'1.0-0' }} บาท</td>
<td class="py-4 px-4 text-gray-800 font-semibold leading-tight">
{{ p.name }}
</td>
<td class="py-4 px-4 text-gray-700">
{{ p.owner }}
</td>
<td class="py-4 px-4 text-blue-700 font-bold whitespace-nowrap">
{{ p.budget | number:'1.0-0' }} บาท
</td>
<td class="py-4 px-4 w-64"> <td class="py-4 px-4 w-64">
<select <!-- <select class="w-full px-4 py-2.5 border border-gray-300 rounded-xl bg-white
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl bg-white
focus:outline-none focus:ring-2 focus:ring-blue-200 focus:border-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-200 focus:border-blue-300
text-sm transition" text-sm transition">
>
<option value="">ไม่เลือก</option> <option value="">ไม่เลือก</option>
@for (item of budgetCategoriesDrop.expense; track item.dtlcod) { @for (item of budgetCategoriesDrop.expense; track item.dtlcod) {
<option [value]="item.dtlcod"> <option [value]="item.dtlcod">
{{ item.dtlnam }} {{ item.dtlnam }}
</option> </option>
} }
</select> </select> -->
{{ p.bdgnam }}
</td> </td>
<td class="py-4 px-4 w-40"> <td class="py-4 px-4 w-40">
<input <!-- <input type="number" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl bg-white focus:outline-none focus:ring-2 focus:ring-blue-200 focus:border-blue-300 text-sm transition"/> -->
type="number" {{ p.acp }} บาท
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl bg-white
focus:outline-none focus:ring-2 focus:ring-blue-200 focus:border-blue-300
text-sm transition"
/>
</td> </td>
<td class="py-4 px-4 text-center"> <td class="py-4 px-4 text-center">
@@ -124,21 +105,19 @@
<button <button
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-xl text-sm class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-xl text-sm
shadow-sm font-medium transition" shadow-sm font-medium transition"
(click)="openBudgetDetail(p)" (click)="openBudgetDetail(p)">
> จัดสรรงบประมาณ
จัดการงบประมาณ
</button> </button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<!-- endtable -->
</div> </div>
<!-- Pagination <!-- Pagination
<div class="flex justify-end items-center gap-3 mt-5"> <div class="flex justify-end items-center gap-3 mt-5">
<button class="px-4 py-2 border border-gray-300 rounded-xl bg-white hover:bg-gray-100"> <button class="px-4 py-2 border border-gray-300 rounded-xl bg-white hover:bg-gray-100">

View File

@@ -27,9 +27,9 @@ export class MainManagerComponent implements OnInit {
projects = [ projects = [
{ code: 'PRJ001', name: 'ระบบจัดการน้ำดื่ม', owner: 'นาย A', budget: 20000, status: 'WAIT' }, { code: 'PRJ001', name: 'ระบบจัดการน้ำดื่ม', owner: 'นาย A', budget: 20000, status: 'WAIT', acp: 0, bdgnam: 'ยังไมจัดสรร' },
{ code: 'PRJ002', name: 'ปรับปรุงอาคาร B', owner: 'นางสาว B', budget: 45000, status: 'WAIT' }, { code: 'PRJ002', name: 'ปรับปรุงอาคาร B', owner: 'นางสาว B', budget: 45000, status: 'WAIT', acp: 0, bdgnam: 'ยังไมจัดสรร'},
{ code: 'PRJ003', name: 'ซื้อคอมพิวเตอร์', owner: 'นาย C', budget: 30000, status: 'APPROVED' } { code: 'PRJ003', name: 'ซื้อคอมพิวเตอร์', owner: 'นาย C', budget: 30000, status: 'APPROVED', acp: 20000, bdgnam: 'งบดำเนินงาน' }
]; ];
approveProject(p: any) { approveProject(p: any) {

View File

@@ -3,7 +3,7 @@
<div> <div>
<p class="eyebrow">สรุปรายงาน</p> <p class="eyebrow">สรุปรายงาน</p>
<h1>รายงานรายรับรายจ่าย</h1> <h1>รายงานรายรับรายจ่าย</h1>
<p class="muted">ช่วงวันที่ {{ reportRange.start }} - {{ reportRange.end }}</p> <!-- <p class="muted">ช่วงวันที่ {{ reportRange.start }} - {{ reportRange.end }}</p> -->
</div> </div>
<div class="report__actions"> <div class="report__actions">
<button class="btn btn--ghost">ส่งออกเป็น Excel</button> <button class="btn btn--ghost">ส่งออกเป็น Excel</button>
@@ -11,21 +11,21 @@
</div> </div>
</header> </header>
<section class="summary-grid"> <!-- <section class="summary-grid">
<article class="summary-card" *ngFor="let card of summaryCards"> <article class="summary-card" *ngFor="let card of summaryCards">
<p class="summary-card__label">{{ card.label }}</p> <p class="summary-card__label">{{ card.label }}</p>
<h2>{{ card.value }}</h2> <h2>{{ card.value }}</h2>
<p class="summary-card__detail">{{ card.detail }}</p> <p class="summary-card__detail">{{ card.detail }}</p>
<span class="summary-card__tone" [ngClass]="'tone-' + card.tone"></span> <span class="summary-card__tone" [ngClass]="'tone-' + card.tone"></span>
</article> </article>
</section> </section> -->
<section class="report__content"> <section class="report__content">
<article class="panel"> <article class="panel">
<div class="panel__header"> <div class="panel__header">
<div> <div>
<h2>สมุดรายวั</h2> <h2>รายงา</h2>
<p>บันทึกรายรับรายจ่ายทั้งหมดในช่วงเวลา</p> <!-- <p>บันทึกรายรับรายจ่ายทั้งหมดในช่วงเวลา</p> -->
</div> </div>
<button class="btn btn--compact btn--ghost">กรองข้อมูล</button> <button class="btn btn--compact btn--ghost">กรองข้อมูล</button>
</div> </div>
@@ -85,7 +85,7 @@
<div> <div>
<p class="eyebrow">Print Preview</p> <p class="eyebrow">Print Preview</p>
<h2>รายงานรายรับรายจ่าย</h2> <h2>รายงานรายรับรายจ่าย</h2>
<p class="muted">ช่วงวันที่ {{ reportRange.start }} - {{ reportRange.end }}</p> <!-- <p class="muted">ช่วงวันที่ {{ reportRange.start }} - {{ reportRange.end }}</p> -->
</div> </div>
<div class="preview-modal__actions"> <div class="preview-modal__actions">
<button class="btn btn--ghost" (click)="closePreview()">ปิด</button> <button class="btn btn--ghost" (click)="closePreview()">ปิด</button>

View File

@@ -9,9 +9,9 @@
</button> </button>
<div class="flex items-center gap-3 p-5"> <div class="flex items-center gap-3 p-5">
<h3 *ngIf="isOpen" class="text-2xl font-bold transition-all duration-300"> <div *ngIf="isOpen" class="text-2xl font-bold transition-all duration-300 flex justify-center pl-4">
<img src="logo.png" alt=""> <img src="logo.png" alt="" class="w-35">
</h3> </div>
<!-- <div class="h-24 w-24 rounded-full bg-white flex items-center justify-center overflow-hidden"> <!-- <div class="h-24 w-24 rounded-full bg-white flex items-center justify-center overflow-hidden">
@@ -38,7 +38,7 @@
(click)="navigate('/main/dashboard')"> (click)="navigate('/main/dashboard')">
<i class="fas fa-tachometer-alt text-xl group-hover:scale-110 transition-transform"></i> <i class="fas fa-tachometer-alt text-xl group-hover:scale-110 transition-transform"></i>
@if(isOpen){ @if(isOpen){
<span class="text-lg font-medium">Dashboard</span> <span class="text-lg font-medium">แดชบอร์ด</span>
} }
</li> </li>
@@ -47,7 +47,7 @@
(click)="navigate('/main/manager')"> (click)="navigate('/main/manager')">
<i class="fas fa-user-circle text-xl group-hover:scale-110 transition-transform"></i> <i class="fas fa-user-circle text-xl group-hover:scale-110 transition-transform"></i>
@if(isOpen){ @if(isOpen){
<span class="text-lg font-medium">Manager</span> <span class="text-lg font-medium">การจัดการ</span>
} }
</li> </li>
@@ -56,7 +56,7 @@
(click)="navigate('/main/report')"> (click)="navigate('/main/report')">
<i class="fas fa-chart-bar text-xl group-hover:scale-110 transition-transform"></i> <i class="fas fa-chart-bar text-xl group-hover:scale-110 transition-transform"></i>
@if(isOpen){ @if(isOpen){
<span class="text-lg font-medium">Report</span> <span class="text-lg font-medium">รายงาน</span>
} }
</li> </li>
@@ -67,7 +67,7 @@
(click)="logout()"> (click)="logout()">
<i class="fas fa-sign-out-alt text-xl group-hover:scale-110 transition-transform"></i> <i class="fas fa-sign-out-alt text-xl group-hover:scale-110 transition-transform"></i>
@if(isOpen){ @if(isOpen){
<span class="text-lg font-medium text-red-200">Logout</span> <span class="text-lg font-medium text-red-200">ลงชื่อออก</span>
} }
</li> </li>
</ul> </ul>

View File

@@ -4,8 +4,8 @@ export const CACHEABLE_URLS = {
// e.g., '/api/data' // e.g., '/api/data'
], ],
POST: [ POST: [
'/api/web/accountingSetup', '/api/web/accountingSetup'
'/api/nigga' // '/api/web/accountingSum'
// Add POST URIs here that you want to cache // Add POST URIs here that you want to cache
// e.g., '/api/search' // e.g., '/api/search'
] ]

View File

@@ -57,7 +57,7 @@ export class LoginContentComponent implements OnInit {
if (result.code === '200' && result.data?.token) { if (result.code === '200' && result.data?.token) {
this.generalService.trowApi(result); this.generalService.trowApi(result);
localStorage.setItem('access_token', result.data.token); localStorage.setItem('access_token', result.data.token);
this.router.navigate(['main/dashboard']); this.router.navigate(['main']);
} else { } else {
const errorMessage = result.message_th || result.message || 'Sign-in failed.'; const errorMessage = result.message_th || result.message || 'Sign-in failed.';
if (this.loginPageComponent) { if (this.loginPageComponent) {

View File

@@ -10,9 +10,9 @@ import { MainLandingComponent } from '../../component/main-landing/main-landing.
const routes: Routes = [ const routes: Routes = [
{ path: 'landing', component: MainLandingComponent },
{ path: 'dashboard', component: MainDashboardContentComponent }, { path: 'dashboard', component: MainDashboardContentComponent },
{ path: 'report', component: MainReportComponent }, { path: 'report', component: MainReportComponent },
{ path: 'landing', component: MainLandingComponent },
{ path: 'manager', component: MainManagerContentComponent }, { path: 'manager', component: MainManagerContentComponent },
{ {
path: 'manager', path: 'manager',

View File

@@ -13,6 +13,7 @@ export class CachingInterceptor implements HttpInterceptor {
constructor(private cache: CachingService) {} constructor(private cache: CachingService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('Caching interceptor:', req.urlWithParams, 'isCacheable:', this.isCacheable(req));
if (!this.isCacheable(req)) { if (!this.isCacheable(req)) {
return next.handle(req); return next.handle(req);
} }
@@ -32,12 +33,22 @@ export class CachingInterceptor implements HttpInterceptor {
} }
private isCacheable(req: HttpRequest<any>): boolean { private isCacheable(req: HttpRequest<any>): boolean {
const checkUrl = (methodUrls: string[]) => {
return methodUrls.some(url => {
// Regex to match the configured URL, and ensure it's not just a partial match of a larger URL segment.
// e.g. /api/user should match /api/user, /api/user/123, /api/user?query=1
// but not /api/user-roles
const regex = new RegExp(url + '($|/|\\?)');
return regex.test(req.urlWithParams);
});
};
if (req.method === 'GET') { if (req.method === 'GET') {
return CACHEABLE_URLS.GET.some(url => req.urlWithParams.includes(url)); return checkUrl(CACHEABLE_URLS.GET);
} }
if (req.method === 'POST') { if (req.method === 'POST') {
return CACHEABLE_URLS.POST.some(url => req.urlWithParams.includes(url)); return checkUrl(CACHEABLE_URLS.POST);
} }
return false; return false;

View File

@@ -0,0 +1,30 @@
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { jwtDecode } from 'jwt-decode';
export const loginGuard: CanActivateFn = (route, state) => {
const router = inject(Router);
const accessToken = localStorage.getItem('access_token');
if (accessToken) {
try {
const decodedToken: any = jwtDecode(accessToken);
const currentTime = Date.now() / 1000;
if (decodedToken.exp < currentTime) {
// Token expired
localStorage.removeItem('access_token');
return true;
}
router.navigate(['/main']);
return false;
} catch (error) {
// Error decoding token
localStorage.removeItem('access_token');
return true;
}
} else {
return true;
}
};

View File

@@ -224,6 +224,13 @@ body {
/* Custom-Table */ /* Custom-Table */
.ledger-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 1.5rem;
}
.ledger-table { .ledger-table {
display: flex; display: flex;
flex-direction: column; flex-direction: column;