feat: add Thai date selector, HTML prototypes, and SQL script

- Replace xlsx with exceljs in construction calculator; switch date input to Thai Buddhist Era selectors
- Add disbursement plan preview HTML and schedule table prototype
- Add sync_budget_come_from SQL script
- Add budget type select menu design plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nut.ไปเรื่อย
2026-06-24 10:13:05 +07:00
parent 2b01bec262
commit 2fbb0b3d51
5 changed files with 1414 additions and 44 deletions
@@ -0,0 +1,456 @@
# Budget Type Select Menu Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** แยก 3 ปุ่ม dropdown (งบลงทุน / งบเงินอุดหนุน / งบรายจ่ายอื่น) ออกจาก search component ไปเป็นหน้าเลือกประเภทงบประมาณใหม่ที่ route `app/request-budget/select` ออกแบบตาม design ที่กำหนด
**Architecture:** สร้าง `BudgetTypeSelectComponent` ใหม่เป็น child route ใน `RequestBudgetModule` ที่ path `select` — component นี้เป็น presenter ล้วนๆ ไม่มี API call, ทำหน้าที่แสดง 3 card พร้อม sub-item แล้ว navigate ไปยัง route เดิมที่ 3 ปุ่มเดิมชี้อยู่ จากนั้นแทนที่ 3 ปุ่มใน search component ด้วยปุ่มเดียว
**Tech Stack:** Angular 13+, Angular Material (mat-button, mat-icon), Tailwind CSS, TypeScript
---
## File Structure
| Action | Path |
|--------|------|
| **Create** | `rmutr-web/src/app/feature/budget-request/request-budget/presenter/budget-type-select/budget-type-select.component.ts` |
| **Create** | `rmutr-web/src/app/feature/budget-request/request-budget/presenter/budget-type-select/budget-type-select.component.html` |
| **Create** | `rmutr-web/src/app/feature/budget-request/request-budget/presenter/budget-type-select/budget-type-select.component.scss` |
| **Modify** | `rmutr-web/src/app/feature/budget-request/request-budget/request-budget.module.ts` |
| **Modify** | `rmutr-web/src/app/feature/budget-request/request-budget/presenter/search/request-budget-search/request-budget-search.component.html` (lines 86118) |
| **Modify** | `rmutr-web/src/app/feature/budget-request/request-budget/presenter/search/request-budget-search/request-budget-search.component.ts` |
---
### Task 1: Create BudgetTypeSelectComponent (TypeScript)
**Files:**
- Create: `rmutr-web/src/app/feature/budget-request/request-budget/presenter/budget-type-select/budget-type-select.component.ts`
- [ ] **Step 1: สร้างไฟล์ component TypeScript**
```typescript
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Router } from '@angular/router';
import { RequestPlanUidService } from 'src/app/core/service/request-budget/request-plan-uid.service';
@Component({
selector: 'app-budget-type-select',
templateUrl: './budget-type-select.component.html',
styleUrls: ['./budget-type-select.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BudgetTypeSelectComponent {
constructor(
private router: Router,
private requestPlanUidSV: RequestPlanUidService,
) {}
goBack() {
this.router.navigate(['app/request-budget']);
}
// งบลงทุน
add_invest_asset() {
this.router.navigate(['app/durable-articles-manage']);
}
add_invest() {
this.router.navigate(['app/invest-construct-manage']);
}
// งบเงินอุดหนุน
add_request_actualize() {
this.router.navigate(['app/request-actualize']);
}
add() {
this.router.navigate(['app/menu-icon']);
}
addrequest7() {
this.requestPlanUidSV.request_budget_type$.next('944e7cec-d666-447f-8809-bb13d21f2ca4');
this.router.navigate(['app/request-budget-list']);
}
// งบรายจ่ายอื่น
add_request_other_expenses() {
this.router.navigate(['app/request-other-expenses/add']);
}
add2() {
this.router.navigate(['app/menu-icon-2']);
}
}
```
- [ ] **Step 2: สร้างไฟล์ SCSS (ว่างไว้ก่อน)**
สร้างไฟล์ `budget-type-select.component.scss` เนื้อหาว่างเปล่า (ใช้ Tailwind ทั้งหมด)
```scss
```
---
### Task 2: Create BudgetTypeSelectComponent (HTML Template)
**Files:**
- Create: `rmutr-web/src/app/feature/budget-request/request-budget/presenter/budget-type-select/budget-type-select.component.html`
Design จากรูปภาพ:
- พื้นหลังสีครีม `bg-gray-50`
- Header มีปุ่ม back + title + subtitle
- Section title + คำอธิบาย + hint ขวา
- 3 card แบบ flex row: งบลงทุน(แดง), งบเงินอุดหนุน(น้ำเงิน), งบรายจ่ายอื่น(ม่วง)
- แต่ละ card: icon + ชื่อ + คำอธิบาย + sub-item list
- [ ] **Step 3: สร้าง HTML template**
```html
<div class="min-h-screen bg-gray-50 p-8">
<!-- Header -->
<div class="flex items-start gap-4 mb-8">
<button mat-icon-button class="border border-gray-300 rounded-lg bg-white" (click)="goBack()">
<mat-icon>chevron_left</mat-icon>
</button>
<div>
<h1 class="text-2xl font-bold text-gray-900">จัดทำคำของบประมาณแผ่นดิน</h1>
<p class="text-sm text-gray-500 mt-1">คำของบประมาณแผ่นดิน</p>
</div>
</div>
<!-- Section Title Row -->
<div class="flex items-start justify-between mb-6">
<div>
<h2 class="text-lg font-semibold text-gray-800">เลือกประเภทคำของบประมาณ</h2>
<p class="text-sm text-gray-500 mt-1">เลือกหมวดงบและประเภทย่อยเพื่อเริ่มจัดทำรายการคำของบประมาณ</p>
</div>
<div class="flex items-center gap-1 text-sm text-gray-500">
<mat-icon class="text-blue-400 text-base leading-none">auto_awesome</mat-icon>
<span>เลือกได้ในที่เดียว ไม่ต้องเปิดเมนูซ้อน</span>
</div>
</div>
<!-- Cards Row -->
<div class="flex gap-5">
<!-- Card 1: งบลงทุน (red) -->
<div class="flex-1 bg-white rounded-2xl border-2 border-red-200 p-5 flex flex-col gap-4">
<!-- Card Header -->
<div class="flex items-center gap-3">
<div class="w-14 h-14 rounded-xl bg-red-50 flex items-center justify-center flex-shrink-0">
<mat-icon class="text-red-400" style="font-size:28px;width:28px;height:28px;">domain</mat-icon>
</div>
<div>
<h3 class="font-semibold text-gray-900 text-base">งบลงทุน</h3>
<p class="text-xs text-gray-400 mt-0.5">ครุภัณฑ์ · ที่ดินและสิ่งก่อสร้าง</p>
</div>
</div>
<!-- Sub-items label -->
<p class="text-xs text-gray-400 font-medium -mb-1">เลือกประเภทย่อย</p>
<!-- Sub-item: ค่าครุภัณฑ์ -->
<button class="w-full flex items-center justify-between bg-gray-50 hover:bg-red-50 border border-gray-200 hover:border-red-300 rounded-xl px-4 py-3 transition-colors cursor-pointer text-left"
(click)="add_invest_asset()">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-red-100 flex items-center justify-center">
<mat-icon class="text-red-400 text-base" style="font-size:18px;width:18px;height:18px;">build</mat-icon>
</div>
<span class="text-sm font-medium text-gray-700">ค่าครุภัณฑ์</span>
</div>
<mat-icon class="text-gray-400 text-base">arrow_forward</mat-icon>
</button>
<!-- Sub-item: ค่าที่ดินและสิ่งก่อสร้าง -->
<button class="w-full flex items-center justify-between bg-gray-50 hover:bg-red-50 border border-gray-200 hover:border-red-300 rounded-xl px-4 py-3 transition-colors cursor-pointer text-left"
(click)="add_invest()">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-red-100 flex items-center justify-center">
<mat-icon class="text-red-400 text-base" style="font-size:18px;width:18px;height:18px;">domain</mat-icon>
</div>
<span class="text-sm font-medium text-gray-700">ค่าที่ดินและสิ่งก่อสร้าง</span>
</div>
<mat-icon class="text-gray-400 text-base">arrow_forward</mat-icon>
</button>
</div>
<!-- Card 2: งบเงินอุดหนุน (blue) -->
<div class="flex-1 bg-white rounded-2xl border-2 border-blue-400 p-5 flex flex-col gap-4">
<!-- Card Header -->
<div class="flex items-center gap-3">
<div class="w-14 h-14 rounded-xl bg-blue-50 flex items-center justify-center flex-shrink-0">
<mat-icon class="text-blue-400" style="font-size:28px;width:28px;height:28px;">pool</mat-icon>
</div>
<div>
<h3 class="font-semibold text-gray-900 text-base">งบเงินอุดหนุน</h3>
<p class="text-xs text-gray-400 mt-0.5">ดำเนินงาน · โครงการ · อื่น ๆ</p>
</div>
</div>
<!-- Sub-items label -->
<p class="text-xs text-gray-400 font-medium -mb-1">เลือกประเภทย่อย</p>
<!-- Sub-item: ค่าใช้จ่ายดำเนินการ -->
<button class="w-full flex items-center justify-between bg-gray-50 hover:bg-blue-50 border border-gray-200 hover:border-blue-300 rounded-xl px-4 py-3 transition-colors cursor-pointer text-left"
(click)="add_request_actualize()">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-blue-100 flex items-center justify-center">
<mat-icon class="text-blue-400 text-base" style="font-size:18px;width:18px;height:18px;">settings</mat-icon>
</div>
<span class="text-sm font-medium text-gray-700">ค่าใช้จ่ายดำเนินการ</span>
</div>
<mat-icon class="text-gray-400 text-base">arrow_forward</mat-icon>
</button>
<!-- Sub-item: ค่าใช้จ่ายโครงการ -->
<button class="w-full flex items-center justify-between bg-blue-500 border border-blue-500 rounded-xl px-4 py-3 transition-colors cursor-pointer text-left"
(click)="add()">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-blue-400 flex items-center justify-center">
<mat-icon class="text-white text-base" style="font-size:18px;width:18px;height:18px;">check</mat-icon>
</div>
<span class="text-sm font-medium text-white">ค่าใช้จ่ายโครงการ</span>
</div>
<mat-icon class="text-white text-base">arrow_forward</mat-icon>
</button>
<!-- Sub-item: ค่าใช้จ่ายอื่น ๆ -->
<button class="w-full flex items-center justify-between bg-gray-50 hover:bg-blue-50 border border-gray-200 hover:border-blue-300 rounded-xl px-4 py-3 transition-colors cursor-pointer text-left"
(click)="addrequest7()">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-blue-100 flex items-center justify-center">
<mat-icon class="text-blue-400 text-base" style="font-size:18px;width:18px;height:18px;">description</mat-icon>
</div>
<span class="text-sm font-medium text-gray-700">ค่าใช้จ่ายอื่น ๆ</span>
</div>
<mat-icon class="text-gray-400 text-base">arrow_forward</mat-icon>
</button>
</div>
<!-- Card 3: งบรายจ่ายอื่น (purple) -->
<div class="flex-1 bg-white rounded-2xl border-2 border-purple-200 p-5 flex flex-col gap-4">
<!-- Card Header -->
<div class="flex items-center gap-3">
<div class="w-14 h-14 rounded-xl bg-purple-50 flex items-center justify-center flex-shrink-0">
<mat-icon class="text-purple-400" style="font-size:28px;width:28px;height:28px;">card_giftcard</mat-icon>
</div>
<div>
<h3 class="font-semibold text-gray-900 text-base">งบรายจ่ายอื่น</h3>
<p class="text-xs text-gray-400 mt-0.5">ดำเนินงาน · โครงการ</p>
</div>
</div>
<!-- Sub-items label -->
<p class="text-xs text-gray-400 font-medium -mb-1">เลือกประเภทย่อย</p>
<!-- Sub-item: ค่าใช้จ่ายดำเนินการ -->
<button class="w-full flex items-center justify-between bg-gray-50 hover:bg-purple-50 border border-gray-200 hover:border-purple-300 rounded-xl px-4 py-3 transition-colors cursor-pointer text-left"
(click)="add_request_other_expenses()">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-purple-100 flex items-center justify-center">
<mat-icon class="text-purple-400 text-base" style="font-size:18px;width:18px;height:18px;">settings</mat-icon>
</div>
<span class="text-sm font-medium text-gray-700">ค่าใช้จ่ายดำเนินการ</span>
</div>
<mat-icon class="text-gray-400 text-base">arrow_forward</mat-icon>
</button>
<!-- Sub-item: ค่าใช้จ่ายโครงการ -->
<button class="w-full flex items-center justify-between bg-gray-50 hover:bg-purple-50 border border-gray-200 hover:border-purple-300 rounded-xl px-4 py-3 transition-colors cursor-pointer text-left"
(click)="add2()">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-purple-100 flex items-center justify-center">
<mat-icon class="text-purple-400 text-base" style="font-size:18px;width:18px;height:18px;">layers</mat-icon>
</div>
<span class="text-sm font-medium text-gray-700">ค่าใช้จ่ายโครงการ</span>
</div>
<mat-icon class="text-gray-400 text-base">arrow_forward</mat-icon>
</button>
</div>
</div>
</div>
```
---
### Task 3: Register Component in Module + Add Child Route
**Files:**
- Modify: `rmutr-web/src/app/feature/budget-request/request-budget/request-budget.module.ts`
- [ ] **Step 4: เพิ่ม import + declaration + route**
เปิดไฟล์ `request-budget.module.ts` แล้วแก้ไขดังนี้:
```typescript
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RequestBudgetContainer } from './container/request-budget/request-budget.container';
import { RequestBudgetListComponent } from './presenter/list/request-budget-list/request-budget-list.component';
import { RequestBudgetRouterComponent } from './router/request-budget-router/request-budget-router.component';
import { Routes, RouterModule } from '@angular/router';
import { SharedModule } from 'src/app/shared/shared.module';
import { RequestBudgetSearchComponent } from './presenter/search/request-budget-search/request-budget-search.component';
import { BudgetTypeSelectComponent } from './presenter/budget-type-select/budget-type-select.component'; // NEW
import { TotalFootShowPipe } from './presenter/pipe/total-foot-table-show.pipe';
import { TotalInvestShowPipe } from './presenter/pipe/total-invest-show.pipe copy';
import { TotalRequestTopicShowPipe } from './presenter/pipe/total-request-topic-show.pipe2';
import { TotalPlanInvestShowPipe } from './presenter/pipe/total-plan-invest-table-show.pipe';
import { TotalPlanRequestShowPipe } from './presenter/pipe/total-plan-request-table-show.pipe';
import { TotalPlanShowPipe } from './presenter/pipe/total-plan-table-show.pipe';
import { TotalBudgetShowPipe } from './presenter/pipe/total-budget-show.pipe';
import { TotalProgressPipe } from './presenter/pipe/total-progress.pipe copy';
import { TotalHeadInvestShowPipe } from './presenter/pipe/total-head-invest-table-show.pipe';
const routes: Routes = [
{
path: '',
component: RequestBudgetRouterComponent,
children: [
{
path: '',
component: RequestBudgetContainer
},
{
path: 'select', // NEW — หน้าเลือกประเภทงบ
component: BudgetTypeSelectComponent
},
]
}
]
@NgModule({
declarations: [
RequestBudgetContainer,
RequestBudgetRouterComponent,
RequestBudgetListComponent,
RequestBudgetSearchComponent,
BudgetTypeSelectComponent, // NEW
TotalFootShowPipe,
TotalInvestShowPipe,
TotalRequestTopicShowPipe,
TotalPlanRequestShowPipe,
TotalPlanInvestShowPipe,
TotalPlanShowPipe,
TotalBudgetShowPipe,
TotalProgressPipe,
TotalHeadInvestShowPipe
],
imports: [
CommonModule,
SharedModule,
RouterModule.forChild(routes),
]
})
export class RequestBudgetModule { }
```
---
### Task 4: Replace 3 Buttons in Search Component
**Files:**
- Modify: `rmutr-web/src/app/feature/budget-request/request-budget/presenter/search/request-budget-search/request-budget-search.component.html` (lines 86118)
- Modify: `rmutr-web/src/app/feature/budget-request/request-budget/presenter/search/request-budget-search/request-budget-search.component.ts`
- [ ] **Step 5: แทนที่ 3 ปุ่มใน search component HTML**
แทนที่บรรทัด 86–118 ใน `request-budget-search.component.html`:
**เดิม (ลบออก):**
```html
<div style="display: flex;justify-content: flex-end" *ngIf="keyUrl == 1">
<!-- <button mat-raised-button class="buttonColorRed" (click)="add_invest()">งบลงทุน</button>&nbsp;&nbsp; -->
<button mat-raised-button class="buttonColorReject" [matMenuTriggerFor]="menuinvest">งบลงทุน
<span class="material-icons" style="color:white;">
keyboard_arrow_down
</span>
</button>
<mat-menu #menuinvest="matMenu" class="bg-white" >
<button mat-menu-item (click)="add_invest_asset()">ค่าครุภัณฑ์</button>
<button mat-menu-item (click)="add_invest()"> ค่าที่ดินและสิ่งก่อสร้าง </button>
</mat-menu>&nbsp;&nbsp;
<button mat-raised-button class="buttonColorBlue" [matMenuTriggerFor]="menu">งบเงินอุดหนุน
<span class="material-icons" style="color:white;">
keyboard_arrow_down
</span>
</button>
<mat-menu #menu="matMenu" class="bg-white">
<button mat-menu-item (click)="add_request_actualize()">ค่าใช้จ่ายดำเนินการ</button>
<button mat-menu-item (click)="add()"> ค่าใช้จ่ายโครงการ </button>
<button mat-menu-item (click)="addrequest7()"> ค่าใช้จ่ายอื่น ๆ </button>
</mat-menu>
&nbsp;&nbsp;
<button mat-raised-button class="buttonColorPurple" [matMenuTriggerFor]="menu2">งบรายจ่ายอื่น
<span class="material-icons" style="color:white;">
keyboard_arrow_down
</span>
</button>
<mat-menu #menu2="matMenu" class="bg-white">
<button mat-menu-item (click)="add_request_other_expenses()">ค่าใช้จ่ายดำเนินการ</button>
<button mat-menu-item (click)="add2()"> ค่าใช้จ่ายโครงการ </button>
<!-- <button mat-menu-item (click)="addrequest7()"> ค่าใช้จ่ายอื่น ๆ </button> -->
</mat-menu>
</div>
```
**ใหม่ (ใส่แทน):**
```html
<div style="display: flex;justify-content: flex-end" *ngIf="keyUrl == 1">
<button mat-raised-button class="buttonColorBlue" (click)="openBudgetTypeSelect()">
<span class="material-icons" style="color:white;margin-right:4px;">add</span>
จัดทำคำของบประมาณ
</button>
</div>
```
- [ ] **Step 6: เพิ่ม method ใน search component TS**
เปิดไฟล์ `request-budget-search.component.ts` แล้วเพิ่ม method `openBudgetTypeSelect()` ต่อท้าย method `add_request_other_expenses()` (ก่อนปิด class):
```typescript
openBudgetTypeSelect() {
this.router.navigate(['app/request-budget/select']);
}
```
> **หมายเหตุ:** methods เดิม (add, add2, add_invest_asset, add_invest, addrequest7, add_request_actualize, add_request_other_expenses) ไม่ต้องลบออก เผื่อยังมีที่อื่นเรียกใช้
- [ ] **Step 7: ตรวจสอบ Angular dev server compile ไม่มี error**
เปิด terminal แล้วดู log ที่ `/tmp/rmutr-web.log` — ต้องไม่มีบรรทัดที่ขึ้นต้นด้วย `ERROR`
- [ ] **Step 8: เปิด browser ที่ `http://localhost:4200/app/request-budget`**
ตรวจสอบ:
1. ปุ่ม "จัดทำคำของบประมาณ" ปรากฏแทนที่ 3 ปุ่มเดิม
2. กดปุ่มแล้ว navigate ไปที่ `/app/request-budget/select`
3. หน้า select แสดง 3 card ครบถ้วน (งบลงทุน, งบเงินอุดหนุน, งบรายจ่ายอื่น)
4. กด back arrow กลับมาหน้า list ได้
5. กด sub-item ใดก็ navigate ไปยัง route ที่ถูกต้อง
- [ ] **Step 9: Commit**
```bash
git add rmutr-web/src/app/feature/budget-request/request-budget/presenter/budget-type-select/
git add rmutr-web/src/app/feature/budget-request/request-budget/request-budget.module.ts
git add rmutr-web/src/app/feature/budget-request/request-budget/presenter/search/request-budget-search/request-budget-search.component.html
git add rmutr-web/src/app/feature/budget-request/request-budget/presenter/search/request-budget-search/request-budget-search.component.ts
git commit -m "feat: add budget-type-select menu page, replace 3 dropdown buttons"
```
---
## Self-Review Checklist
- [x] **Spec coverage:** 3 ปุ่มถูกแยกออก ✓ | หน้าใหม่ออกแบบตาม design image ✓ | back button ✓ | sub-item navigation ครบ 7 รายการ ✓
- [x] **No placeholders:** ทุก step มี code จริง ✓
- [x] **Type consistency:** `BudgetTypeSelectComponent` ใช้ชื่อเดียวกันในทุก task ✓ | route path `select` ตรงกัน ✓ | method names ตรงกับ `RequestPlanUidService`
- [x] **`addrequest7` ใช้ `RequestPlanUidService`** — inject ใน Task 1 และใช้ใน method ✓
- [x] **`RequestBudgetRouterComponent` มี `<router-outlet>`** — child route `select` จะทำงานได้ ✓