-downloads
All checks were successful
Build Docker Image / Build Docker Image (push) Successful in 7m6s
Build Docker Image / Restart Docker Compose (push) Successful in 0s

-subscribe project
This commit is contained in:
x2Skyz
2025-11-30 23:47:29 +07:00
parent 07d49d87cf
commit 40e682e5d8
10 changed files with 193 additions and 77 deletions

View File

@@ -90,13 +90,25 @@
</td>
<td class="py-4 px-6 text-center">
<button (click)="openBudgetDetail(idx)"
class="text-red-900 hover:text-white border border-red-900 hover:bg-red-900 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-xs px-4 py-2 text-center transition-all duration-200 shadow-sm flex items-center justify-center gap-1 mx-auto whitespace-nowrap">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
จัดสรรงบ
</button>
<div class="flex items-center justify-center gap-2">
<button (click)="OnDocumentDownload(idx.prjseq ?? 0)"
class="text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-4 focus:outline-none focus:ring-gray-100 font-medium rounded-lg text-xs px-3 py-2 text-center transition-all duration-200 shadow-sm flex items-center justify-center gap-1 whitespace-nowrap" title="ดาวน์โหลดเอกสารโครงการ">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
<span>เอกสาร</span>
</button>
<button (click)="openBudgetDetail(idx)"
class="text-red-900 hover:text-white border border-red-900 hover:bg-red-900 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-xs px-3 py-2 text-center transition-all duration-200 shadow-sm flex items-center justify-center gap-1 whitespace-nowrap">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
จัดสรรงบ
</button>
</div>
</td>
</tr>
@@ -118,7 +130,7 @@
<div class="bg-gray-50 px-6 py-4 border-t border-gray-200 flex justify-between items-center text-xs text-gray-500">
<div>แสดง {{ myPrjMst.length }} รายการ</div>
</div>
</div>
</div>
</div>

View File

@@ -12,6 +12,7 @@ import { Router } from '@angular/router';
styleUrl: './main-manager.component.css'
})
export class MainManagerComponent implements OnInit {
@Output() documentDownload = new EventEmitter<any>();
mode: string = 'i';
@@ -60,6 +61,10 @@ export class MainManagerComponent implements OnInit {
}
});
}
OnDocumentDownload(prjseq: number){
this.documentDownload.emit(prjseq);
}
setupFormControl(){
}

View File

@@ -40,13 +40,13 @@
'text-red-600': item.prjcomstt === 'CN'
}">
@if(item.prjcomstt === 'UAC'){ รออนุมัติ }
@else if(item.prjcomstt === 'BAP'){ อนุมัติแล้ว }
@else if(item.prjcomstt === 'BAP'){ จัดสรรงบประมาณแล้ว }
@else if(item.prjcomstt === 'CN'){ ไม่อนุมัติ }
@else { {{ item.prjcomstt }} }
</td>
<td class="py-3 px-4 text-center flex flex-row justify-center gap-2">
<button class="px-3 py-1 text-xs font-semibold text-white bg-red-900 rounded-md hover:bg-red-950">แก้ไข</button>
<button class="px-3 py-1 text-xs font-semibold text-white bg-red-900 rounded-md hover:bg-red-950">ติดตาม</button>
<button class="px-3 py-1 text-xs font-semibold text-white rounded-md transition-colors" [ngClass]="isSubscribed(item) ? 'bg-gray-500 hover:bg-gray-600' : 'bg-red-900 hover:bg-red-950'" (click)="onSubscribePrj(item)">{{ isSubscribed(item) ? 'ยกเลิก' : 'ติดตาม' }}</button>
</td>
</tr>
} @empty {
@@ -58,68 +58,86 @@
</table>
</div>
</div>
<!-- ความคืบหน้าโครงการ -->
@for (idx of subscribeData; track $index) {
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-200">
<h2 class="text-xl font-semibold text-gray-700 mb-6">ความคืบหน้าโครงการ: {{ idx.prjnam }}</h2>
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-200">
<h2 class="text-xl font-semibold text-gray-700 mb-6">ความคืบหน้าโครงการ: โครงการปรับปรุงอาคาร</h2>
<div class="space-y-4">
<div class="space-y-4">
<div class="flex items-start">
<div class="flex flex-col items-center mr-4">
<div class="w-8 h-8 rounded-full bg-green-500 text-white flex items-center justify-center text-sm font-bold shadow-md">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
<div class="w-px h-16 bg-green-500"></div> </div>
<div>
<h3 class="font-semibold text-green-700">1. ยื่นคำขอโครงการ</h3>
<p class="text-sm text-gray-500">วันที่: {{ (idx.prjacpdtm ?? '-') | dtmtodatetime}}</p>
<p class="text-sm text-gray-700 mt-1">เอกสารและรายละเอียดโครงการครบถ้วน</p>
</div>
</div>
<div class="flex items-start">
<div class="flex items-start">
<div class="flex flex-col items-center mr-4">
<div class="w-8 h-8 rounded-full bg-green-500 text-white flex items-center justify-center text-sm font-bold shadow-md">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
<div class="w-px h-16 bg-green-500"></div> </div>
<div>
<h3 class="font-semibold text-green-700">1. ยื่นคำขอโครงการ</h3>
<p class="text-sm text-gray-500">วันที่: 10/11/2568</p>
<p class="text-sm text-gray-700 mt-1">เอกสารและรายละเอียดโครงการครบถ้วน</p>
</div>
</div>
<div class="flex items-start">
<div class="flex flex-col items-center mr-4">
<div class="w-8 h-8 rounded-full bg-blue-500 text-white flex items-center justify-center text-sm font-bold border-2 border-blue-700 shadow-xl">
<div [ngClass]="idx.prjcomstt == 'BAP' ? 'bg-green-500 border-green-700' : 'bg-blue-500 border-blue-700'"
class="w-8 h-8 rounded-full text-white flex items-center justify-center text-sm font-bold border-2 shadow-xl">
2
</div>
<div class="w-px h-16 bg-gray-300"></div> </div>
<div class="w-px h-16 bg-gray-300"></div>
</div>
<div>
<h3 class="font-bold text-blue-700">2. พิจารณางบประมาณ</h3>
<p class="text-sm text-gray-500">กำหนดแล้วเสร็จ: 25/11/2568</p>
<p class="text-sm text-gray-700 mt-1 font-medium">รอฝ่ายบัญชีและงบประมาณอนุมัติยอดจัดสรร</p>
<h3 class="font-bold" [ngClass]="idx.prjcomstt == 'BAP' ? 'text-green-700' : 'text-blue-700'">
2. พิจารณางบประมาณ
</h3>
<p class="text-sm text-gray-500">กำหนดแล้วเสร็จ: {{ (idx.trnacpdtm ?? '-') | dtmtodatetime }}</p>
@if(idx.prjcomstt === 'BAP') {
<p class="text-sm text-green-600 mt-1 font-medium">ได้รับอนุมัติงบประมาณเรียบร้อยแล้ว</p>
} @else if(idx.prjcomstt === 'CN') {
<p class="text-sm text-red-600 mt-1 font-medium">โครงการไม่ผ่านการอนุมัติ</p>
} @else {
<p class="text-sm text-gray-700 mt-1 font-medium">รอฝ่ายแผนและงบประมาณอนุมัติยอดจัดสรร</p>
}
<button class="mt-2 text-xs text-blue-500 hover:text-blue-700">ดูรายละเอียดการเงิน</button>
</div>
</div>
<div class="flex items-start">
<div class="flex flex-col items-center mr-4">
<div class="w-8 h-8 rounded-full bg-gray-300 text-gray-700 flex items-center justify-center text-sm font-bold">
3
</div>
<div class="w-px h-16 bg-gray-300"></div> </div>
<div>
<h3 class="font-semibold text-gray-500">3. อนุมัติโดยผู้บริหาร</h3>
<p class="text-sm text-gray-400">ยังไม่ระบุวันที่</p>
<p class="text-sm text-gray-400 mt-1">รอขั้นตอนก่อนหน้าเสร็จสิ้น</p>
</div>
</div>
<div class="flex items-start">
<div class="flex flex-col items-center mr-4">
<div class="w-8 h-8 rounded-full bg-gray-300 text-gray-700 flex items-center justify-center text-sm font-bold">
3
</div>
<div class="w-px h-16 bg-gray-300"></div> </div>
<div>
<h3 class="font-semibold text-gray-500">3. อนุมัติโดยผู้บริหาร</h3>
<p class="text-sm text-gray-400">ยังไม่ระบุวันที่</p>
<p class="text-sm text-gray-400 mt-1">รอขั้นตอนก่อนหน้าเสร็จสิ้น</p>
</div>
</div>
<div class="flex items-start">
<div class="flex flex-col items-center mr-4">
<div class="w-8 h-8 rounded-full bg-gray-300 text-gray-700 flex items-center justify-center text-sm font-bold">
4
</div>
</div>
<div>
<h3 class="font-semibold text-gray-500">4. จัดสรรงบประมาณสำเร็จ</h3>
<p class="text-sm text-gray-400">ยังไม่ระบุวันที่</p>
</div>
</div>
<div class="flex items-start">
<div class="flex flex-col items-center mr-4">
<div class="w-8 h-8 rounded-full bg-gray-300 text-gray-700 flex items-center justify-center text-sm font-bold">
4
</div>
</div>
<div>
<h3 class="font-semibold text-gray-500">4. จัดสรรงบประมาณสำเร็จ</h3>
<p class="text-sm text-gray-400">ยังไม่ระบุวันที่</p>
</div>
</div>
</div>
</div>
</div>
</div>
} @empty {
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-200">
<p class="text-center text-gray-500">กรุณาเลือกโครงการที่จะแสดง</p>
</div>
}
</div>
<div class="lg:col-span-1 space-y-6">

View File

@@ -17,6 +17,7 @@ import { ProjectStateService } from '../../services/state/project-state.service'
export class MainProject implements OnInit {
myPrjMst: IPrjMst[] = []; // ตัวแปรเก็บข้อมูลโครงการ
subscribeData: IPrjMst[] = [];
constructor(
private generalService: GeneralService,
@@ -54,6 +55,23 @@ export class MainProject implements OnInit {
// });
// }
onSubscribePrj(item: any) {
// 1. เช็คว่า item นี้ เป็นตัวเดิมที่ติดตามอยู่แล้วหรือไม่?
const isAlreadySubscribed = this.subscribeData.some(sub => sub.prjseq === item.prjseq);
if (isAlreadySubscribed) {
// กรณีเดิม: ถ้ากดซ้ำตัวเดิม -> ให้ยกเลิกการติดตาม (เคลียร์ Array)
this.subscribeData = [];
} else {
// กรณีใหม่: ถ้ากดตัวใหม่ -> ให้แทนที่ตัวเก่าทันที (มีแค่ตัวเดียวเสมอ)
this.subscribeData = [item];
}
}
// Helper Function: เช็คสถานะเพื่อเปลี่ยนสีปุ่ม
isSubscribed(item: any): boolean {
return this.subscribeData.some(sub => sub.prjseq === item.prjseq);
}
navigate(path: string) {
this.router.navigate([path]);
}

View File

@@ -32,7 +32,7 @@
<div class="w-6 flex justify-center shrink-0">
<i class="fas fa-th-large text-lg group-hover:scale-110 transition-transform"></i>
</div>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Dashboard</span>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">หน้าหลัก</span>
</a>
<!-- Section Header -->
@@ -48,18 +48,18 @@
<div class="w-6 flex justify-center shrink-0">
<i class="fas fa-folder text-lg group-hover:scale-110 transition-transform"></i>
</div>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Projects</span>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">โครงการของฉัน</span>
</a>
<!-- Budgets -->
<a routerLink="/main/budget" routerLinkActive="bg-red-50 text-red-600 shadow-sm ring-1 ring-red-100"
<!-- <a routerLink="/main/budget" routerLinkActive="bg-red-50 text-red-600 shadow-sm ring-1 ring-red-100"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-gray-600 hover:bg-gray-50 hover:text-gray-900 transition-all group"
[title]="isCollapsed ? 'Budgets' : ''">
<div class="w-6 flex justify-center shrink-0">
<i class="fas fa-file-invoice-dollar text-lg group-hover:scale-110 transition-transform"></i>
</div>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Budgets</span>
</a>
</a> -->
<!-- Users -->
<a routerLink="/main/manager" routerLinkActive="bg-red-50 text-red-600 shadow-sm ring-1 ring-red-100"
@@ -68,7 +68,7 @@
<div class="w-6 flex justify-center shrink-0">
<i class="fas fa-users-cog text-lg group-hover:scale-110 transition-transform"></i>
</div>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">Users</span>
<span *ngIf="!isCollapsed" class="font-medium whitespace-nowrap">จัดสรรงบประมาณ</span>
</a>
</nav>

View File

@@ -1,2 +1,2 @@
<!-- <app-main-dashboard></app-main-dashboard> -->
<app-main-manager></app-main-manager>
<app-main-manager (documentDownload)="OnDownloadPrjDoc($event)"></app-main-manager>

View File

@@ -62,7 +62,46 @@ export class MainManagerContentComponent implements OnInit {
});
}
OnDownloadPrjDoc(value: any): void {
let uri = `/api/ttc/projectdownload`;
let param = {
prjseq: value
}
// isBlob = true
this.generalService.getRequest(uri, param, true).subscribe({
next: (blob: Blob) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `เอกสาร_โครงการ_${value}.zip`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
const mockResult = {
code: '200',
message: 'Download Successful',
message_th: 'ดาวน์โหลดเอกสารสำเร็จเรียบร้อย'
};
// this.generalService.trowApi(mockResult);
},
error: (error: any) => {
if (error.status === 404) {
// แจ้งเตือนเฉพาะเจาะจง
this.generalService.trowApi({
code: '404',
message: 'No documents found',
message_th: 'ไม่พบเอกสารแนบของโครงการนี้'
});
} else {
// Error อื่นๆ (500, etc.)
this.generalService.trowApi(error);
}
}
});
}
// OnSetupDashboard(value: any, setupFirst: boolean): void {
// const uri = '/api/web/accountingSetup';

View File

@@ -8,6 +8,7 @@ export interface IPrjMst {
prjacpbdg?: number;
prjcomstt?: string;
prjacpdtm?: string;
trnacpdtm?: number;
}
export interface ITrnmst {

View File

@@ -12,9 +12,9 @@ export class AccDateFormatPipe implements PipeTransform {
const str = value.toString();
if (str.length !== 12) return str;
const dd = str.slice(0, 2);
const mm = str.slice(2, 4);
const yyyy = str.slice(4, 8);
const dd = str.slice(6, 8);
const mm = str.slice(4, 6);
const yyyy = str.slice(0, 4);
const hh = str.slice(8, 10);
const min = str.slice(10, 12);

View File

@@ -1,6 +1,6 @@
import { ToastrService } from 'ngx-toastr';
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { environment } from '../../environments/environment';
@@ -18,15 +18,13 @@ export class GeneralService {
private toastr: ToastrService
) {}
// ✅ แก้ไข 1: รับ parameter เพื่อกำหนดว่าต้องใส่ Content-Type: application/json หรือไม่
private getHttpOptions(isJson: boolean = true) {
private getHttpOptions(isJson: boolean = true) {
const token = localStorage.getItem('access_token');
let headersConfig: any = {
...(token ? { 'Authorization': `Bearer ${token}` } : {})
};
// ถ้าเป็น JSON ให้ใส่ Header แต่ถ้าเป็น FormData ห้ามใส่ (Browser จะจัดการเอง)
if (isJson) {
headersConfig['Content-Type'] = 'application/json';
}
@@ -35,6 +33,7 @@ export class GeneralService {
return { headers };
}
// Helper: wrap body ให้มี request ครอบเสมอ (ใช้เฉพาะ JSON)
private wrapRequestBody(body: any): any {
if (body && body.request) {
@@ -101,14 +100,38 @@ export class GeneralService {
// });
// }
// GET Request (เหมือนเดิม)
getRequest(uri: string): Observable<any> {
getRequest(uri: string, params?: any, isBlob: boolean = false): Observable<any> {
const fullUrl = `${this.baseUrl}${uri}`;
return this.http.get(fullUrl, this.getHttpOptions(true)).pipe( // default true
// 1. แปลง Object เป็น HttpParams
let httpParams = new HttpParams();
if (params) {
Object.keys(params).forEach(key => {
if (params[key] !== null && params[key] !== undefined) {
httpParams = httpParams.set(key, params[key]);
}
});
}
// 2. รวม Headers และ Config
const options: any = {
...this.getHttpOptions(true),
params: httpParams,
responseType: isBlob ? 'blob' : 'json' // ✅ สลับ Type ตามค่า isBlob
};
return this.http.get(fullUrl, options).pipe(
map((res: any) => res),
catchError((error: any) => {
console.error(`❌ [${isBlob ? 'Download' : 'GET'} Request Error]:`, error);
// กรณี Download (Blob) ให้ส่ง Error เดิมกลับไปเลย (เพื่อให้ Component ไป parse Blob เป็น JSON Error เอง)
if (isBlob) {
return throwError(() => error);
}
// กรณี JSON ปกติ แปลง Error ตาม Pattern
const response = error?.error;
console.error('❌ [GET Request Error]:', error);
return throwError(() => ({
status: error.status,
code: response?.code ?? '500',
@@ -169,7 +192,7 @@ export class GeneralService {
'ngx-toastr success-toast bg-white bg-opacity-90 text-green-700 shadow-lg'
});
} else {
this.toastr.error(`${msgTh || msg}`,'error',{
this.toastr.error(`${msgTh || msg}`,`${msg}`,{
positionClass: 'toast-top-right',
timeOut: 3500,
progressBar: true,