251127:1700 Frontend Start Build
This commit is contained in:
76
frontend/lib/api/client.ts
Normal file
76
frontend/lib/api/client.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
// File: lib/api/client.ts
|
||||
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosError } from "axios";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { getSession } from "next-auth/react";
|
||||
|
||||
// อ่านค่า Base URL จาก Environment Variable
|
||||
const baseURL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api";
|
||||
|
||||
// สร้าง Axios Instance หลัก
|
||||
const apiClient: AxiosInstance = axios.create({
|
||||
baseURL,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout: 15000, // Timeout 15 วินาที
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Request Interceptors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
apiClient.interceptors.request.use(
|
||||
async (config: InternalAxiosRequestConfig) => {
|
||||
// 1. Idempotency Key Injection
|
||||
// ป้องกันการทำรายการซ้ำสำหรับ Method ที่เปลี่ยนแปลงข้อมูล
|
||||
const method = config.method?.toLowerCase();
|
||||
if (method && ["post", "put", "delete", "patch"].includes(method)) {
|
||||
config.headers["Idempotency-Key"] = uuidv4();
|
||||
}
|
||||
|
||||
// 2. Authentication Token Injection
|
||||
// ดึง Session จาก NextAuth (ทำงานเฉพาะฝั่ง Client)
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
const session = await getSession();
|
||||
// @ts-ignore: Session type extended in types/next-auth.d.ts
|
||||
const token = session?.accessToken;
|
||||
|
||||
if (token) {
|
||||
config.headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to retrieve session token:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Response Interceptors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
if (error.response) {
|
||||
const { status } = error.response;
|
||||
|
||||
// กรณี Token หมดอายุ หรือ ไม่มีสิทธิ์
|
||||
if (status === 401) {
|
||||
console.error("Unauthorized: Please login again.");
|
||||
// สามารถเพิ่ม Logic Redirect ไปหน้า Login ได้ถ้าต้องการ
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default apiClient;
|
||||
120
frontend/lib/auth.ts
Normal file
120
frontend/lib/auth.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
// File: lib/auth.ts
|
||||
import NextAuth from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { z } from "zod";
|
||||
import type { User } from "next-auth";
|
||||
|
||||
// Schema สำหรับ Validate ข้อมูลขาเข้าอีกครั้งเพื่อความปลอดภัย
|
||||
const loginSchema = z.object({
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
});
|
||||
|
||||
export const {
|
||||
handlers: { GET, POST },
|
||||
auth,
|
||||
signIn,
|
||||
signOut,
|
||||
} = NextAuth({
|
||||
providers: [
|
||||
Credentials({
|
||||
name: "Credentials",
|
||||
credentials: {
|
||||
username: { label: "Username", type: "text" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
try {
|
||||
// 1. Validate ข้อมูลที่ส่งมาจากฟอร์ม
|
||||
const { username, password } = await loginSchema.parseAsync(credentials);
|
||||
|
||||
// อ่านค่าจาก ENV หรือใช้ Default (ต้องมั่นใจว่าชี้ไปที่ Port 3001 และมี /api)
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api";
|
||||
|
||||
console.log(`Attempting login to: ${baseUrl}/auth/login`);
|
||||
|
||||
// 2. เรียก API ไปยัง NestJS Backend
|
||||
const res = await fetch(`${baseUrl}/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
|
||||
// ถ้า Backend ตอบกลับมาว่าไม่สำเร็จ (เช่น 401, 404, 500)
|
||||
if (!res.ok) {
|
||||
const errorMsg = await res.text();
|
||||
console.error("Login failed:", errorMsg);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. รับข้อมูล JSON จาก Backend
|
||||
// โครงสร้างที่ Backend ส่งมา: { statusCode: 200, message: "...", data: { access_token: "...", user: {...} } }
|
||||
const responseJson = await res.json();
|
||||
|
||||
// เจาะเข้าไปเอาข้อมูลจริงใน .data
|
||||
const backendData = responseJson.data;
|
||||
|
||||
// ตรวจสอบว่ามี Token หรือไม่
|
||||
if (!backendData || !backendData.access_token) {
|
||||
console.error("No access token received in response data");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. Return ข้อมูล User เพื่อส่งต่อไปยัง JWT Callback
|
||||
// ต้อง Map ชื่อ Field ให้ตรงกับที่ NextAuth คาดหวัง และเก็บ Access Token
|
||||
return {
|
||||
// Map user_id จาก DB ให้เป็น id (string) ตามที่ NextAuth ต้องการ
|
||||
id: backendData.user.user_id.toString(),
|
||||
// รวมชื่อจริงนามสกุล
|
||||
name: `${backendData.user.firstName} ${backendData.user.lastName}`,
|
||||
email: backendData.user.email,
|
||||
username: backendData.user.username,
|
||||
// Role (ถ้า Backend ยังไม่ส่ง role มา อาจต้องใส่ Default หรือปรับ Backend เพิ่มเติม)
|
||||
role: backendData.user.role || "User",
|
||||
organizationId: backendData.user.primaryOrganizationId,
|
||||
// เก็บ Token ไว้ใช้งาน
|
||||
accessToken: backendData.access_token,
|
||||
} as User;
|
||||
|
||||
} catch (error) {
|
||||
console.error("Auth error:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
pages: {
|
||||
signIn: "/login", // กำหนดหน้า Login ของเราเอง
|
||||
error: "/login", // กรณีเกิด Error ให้กลับมาหน้า Login
|
||||
},
|
||||
callbacks: {
|
||||
// 1. JWT Callback: ทำงานเมื่อสร้าง Token หรืออ่าน Token
|
||||
async jwt({ token, user }) {
|
||||
// ถ้ามี user เข้ามา (คือตอน Login ครั้งแรก) ให้บันทึกข้อมูลลง Token
|
||||
if (user) {
|
||||
token.id = user.id;
|
||||
token.role = user.role;
|
||||
token.organizationId = user.organizationId;
|
||||
token.accessToken = user.accessToken;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
// 2. Session Callback: ทำงานเมื่อฝั่ง Client เรียก useSession()
|
||||
async session({ session, token }) {
|
||||
// ส่งข้อมูลจาก Token ไปให้ Client ใช้งาน
|
||||
if (token && session.user) {
|
||||
session.user.id = token.id as string;
|
||||
session.user.role = token.role as string;
|
||||
session.user.organizationId = token.organizationId as number;
|
||||
session.accessToken = token.accessToken as string;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
maxAge: 8 * 60 * 60, // 8 ชั่วโมง
|
||||
},
|
||||
secret: process.env.AUTH_SECRET,
|
||||
debug: process.env.NODE_ENV === "development",
|
||||
});
|
||||
54
frontend/lib/services/circulation.service.ts
Normal file
54
frontend/lib/services/circulation.service.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// File: lib/services/circulation.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
|
||||
// Import DTO ที่สร้างไว้
|
||||
import { CreateCirculationDto } from "@/types/dto/circulation/create-circulation.dto";
|
||||
import { SearchCirculationDto } from "@/types/dto/circulation/search-circulation.dto";
|
||||
import { UpdateCirculationRoutingDto } from "@/types/dto/circulation/update-circulation-routing.dto";
|
||||
|
||||
export const circulationService = {
|
||||
/**
|
||||
* ดึงรายการใบเวียนทั้งหมด (รองรับ Search & Filter)
|
||||
*/
|
||||
getAll: async (params?: SearchCirculationDto) => {
|
||||
// GET /circulations
|
||||
const response = await apiClient.get("/circulations", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึงรายละเอียดใบเวียนตาม ID
|
||||
*/
|
||||
getById: async (id: string | number) => {
|
||||
// GET /circulations/:id
|
||||
const response = await apiClient.get(`/circulations/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* สร้างใบเวียนใหม่ (Create Circulation)
|
||||
*/
|
||||
create: async (data: CreateCirculationDto) => {
|
||||
// POST /circulations
|
||||
const response = await apiClient.post("/circulations", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* อัปเดตสถานะการเวียน (เช่น รับทราบ / ดำเนินการเสร็จสิ้น)
|
||||
* มักจะใช้ routingId หรือ circulationId ขึ้นอยู่กับการออกแบบ API หลังบ้าน
|
||||
*/
|
||||
updateRouting: async (id: string | number, data: UpdateCirculationRoutingDto) => {
|
||||
// PATCH /circulations/:id/routing (หรือ endpoint ที่ Backend กำหนด)
|
||||
const response = await apiClient.patch(`/circulations/${id}/routing`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ลบ/ยกเลิกใบเวียน
|
||||
*/
|
||||
delete: async (id: string | number) => {
|
||||
const response = await apiClient.delete(`/circulations/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
50
frontend/lib/services/contract-drawing.service.ts
Normal file
50
frontend/lib/services/contract-drawing.service.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// File: lib/services/contract-drawing.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateContractDrawingDto,
|
||||
UpdateContractDrawingDto,
|
||||
SearchContractDrawingDto
|
||||
} from "@/types/dto/drawing/contract-drawing.dto";
|
||||
|
||||
export const contractDrawingService = {
|
||||
/**
|
||||
* ดึงรายการแบบสัญญา (Contract Drawings)
|
||||
*/
|
||||
getAll: async (params: SearchContractDrawingDto) => {
|
||||
// GET /contract-drawings?projectId=1&page=1...
|
||||
const response = await apiClient.get("/contract-drawings", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึงรายละเอียดตาม ID
|
||||
*/
|
||||
getById: async (id: string | number) => {
|
||||
const response = await apiClient.get(`/contract-drawings/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* สร้างแบบสัญญาใหม่
|
||||
*/
|
||||
create: async (data: CreateContractDrawingDto) => {
|
||||
const response = await apiClient.post("/contract-drawings", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* แก้ไขข้อมูลแบบสัญญา
|
||||
*/
|
||||
update: async (id: string | number, data: UpdateContractDrawingDto) => {
|
||||
const response = await apiClient.put(`/contract-drawings/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ลบแบบสัญญา (Soft Delete)
|
||||
*/
|
||||
delete: async (id: string | number) => {
|
||||
const response = await apiClient.delete(`/contract-drawings/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
74
frontend/lib/services/correspondence.service.ts
Normal file
74
frontend/lib/services/correspondence.service.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
// File: lib/services/correspondence.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import { SearchCorrespondenceDto } from "@/types/dto/correspondence/search-correspondence.dto";
|
||||
import { CreateCorrespondenceDto } from "@/types/dto/correspondence/create-correspondence.dto";
|
||||
// Import DTO ใหม่
|
||||
import { SubmitCorrespondenceDto } from "@/types/dto/correspondence/submit-correspondence.dto";
|
||||
import { WorkflowActionDto } from "@/types/dto/correspondence/workflow-action.dto";
|
||||
import { AddReferenceDto, RemoveReferenceDto } from "@/types/dto/correspondence/add-reference.dto";
|
||||
|
||||
export const correspondenceService = {
|
||||
// ... (getAll, getById, create, update, delete เดิมคงไว้) ...
|
||||
|
||||
getAll: async (params?: SearchCorrespondenceDto) => {
|
||||
const response = await apiClient.get("/correspondences", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getById: async (id: string | number) => {
|
||||
const response = await apiClient.get(`/correspondences/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
create: async (data: CreateCorrespondenceDto) => {
|
||||
const response = await apiClient.post("/correspondences", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
update: async (id: string | number, data: any) => {
|
||||
const response = await apiClient.put(`/correspondences/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
delete: async (id: string | number) => {
|
||||
const response = await apiClient.delete(`/correspondences/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// --- 🔥 New Methods ---
|
||||
|
||||
/**
|
||||
* ส่งเอกสาร (Submit) เพื่อเริ่ม Workflow
|
||||
*/
|
||||
submit: async (id: string | number, data: SubmitCorrespondenceDto) => {
|
||||
const response = await apiClient.post(`/correspondences/${id}/submit`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดำเนินการ Workflow (เช่น Approve, Reject) ในขั้นตอนปัจจุบัน
|
||||
*/
|
||||
processWorkflow: async (id: string | number, data: WorkflowActionDto) => {
|
||||
const response = await apiClient.post(`/correspondences/${id}/workflow`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* เพิ่มเอกสารอ้างอิง
|
||||
*/
|
||||
addReference: async (id: string | number, data: AddReferenceDto) => {
|
||||
const response = await apiClient.post(`/correspondences/${id}/references`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ลบเอกสารอ้างอิง
|
||||
*/
|
||||
removeReference: async (id: string | number, data: RemoveReferenceDto) => {
|
||||
// ใช้ DELETE method โดยส่ง body ไปด้วย (axios รองรับผ่าน config.data)
|
||||
const response = await apiClient.delete(`/correspondences/${id}/references`, {
|
||||
data: data
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
21
frontend/lib/services/index.ts
Normal file
21
frontend/lib/services/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
// File: lib/services/index.ts
|
||||
|
||||
// --- Core Master Data & Projects ---
|
||||
export * from './project.service';
|
||||
export * from './master-data.service';
|
||||
|
||||
// --- Document Modules ---
|
||||
export * from './correspondence.service';
|
||||
export * from './rfa.service';
|
||||
export * from './contract-drawing.service';
|
||||
export * from './shop-drawing.service';
|
||||
export * from './circulation.service';
|
||||
export * from './transmittal.service';
|
||||
|
||||
// --- System & Support Modules (New) ---
|
||||
export * from './user.service';
|
||||
export * from './search.service';
|
||||
export * from './notification.service';
|
||||
export * from './workflow-engine.service';
|
||||
export * from './monitoring.service';
|
||||
export * from './json-schema.service';
|
||||
75
frontend/lib/services/json-schema.service.ts
Normal file
75
frontend/lib/services/json-schema.service.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// File: lib/services/json-schema.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateJsonSchemaDto,
|
||||
UpdateJsonSchemaDto,
|
||||
SearchJsonSchemaDto
|
||||
} from "@/types/dto/json-schema/json-schema.dto";
|
||||
|
||||
export const jsonSchemaService = {
|
||||
/**
|
||||
* ดึงรายการ Schema ทั้งหมด (สำหรับ Admin จัดการ Registry)
|
||||
*/
|
||||
getAll: async (params?: SearchJsonSchemaDto) => {
|
||||
// GET /json-schemas
|
||||
const response = await apiClient.get("/json-schemas", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึงรายละเอียด Schema ตาม ID
|
||||
*/
|
||||
getById: async (id: number | string) => {
|
||||
// GET /json-schemas/:id
|
||||
const response = await apiClient.get(`/json-schemas/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึง Schema ตาม "Code" (ใช้บ่อยที่สุดสำหรับ Dynamic Form)
|
||||
*/
|
||||
getByCode: async (code: string) => {
|
||||
// GET /json-schemas/code/:code
|
||||
const response = await apiClient.get(`/json-schemas/code/${code}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* สร้าง Schema ใหม่ (Admin)
|
||||
*/
|
||||
create: async (data: CreateJsonSchemaDto) => {
|
||||
// POST /json-schemas
|
||||
const response = await apiClient.post("/json-schemas", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* อัปเดต Schema (Admin)
|
||||
*/
|
||||
update: async (id: number | string, data: UpdateJsonSchemaDto) => {
|
||||
// PUT /json-schemas/:id
|
||||
const response = await apiClient.put(`/json-schemas/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ลบ Schema (Soft Delete)
|
||||
*/
|
||||
delete: async (id: number | string) => {
|
||||
// DELETE /json-schemas/:id
|
||||
const response = await apiClient.delete(`/json-schemas/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* (Optional) ตรวจสอบความถูกต้องของข้อมูลกับ Schema ฝั่ง Server
|
||||
*/
|
||||
validate: async (code: string, data: any) => {
|
||||
// POST /json-schemas/validate
|
||||
const response = await apiClient.post(`/json-schemas/validate`, {
|
||||
schemaCode: code,
|
||||
data: data
|
||||
});
|
||||
return response.data; // { valid: true, errors: [] }
|
||||
}
|
||||
};
|
||||
84
frontend/lib/services/master-data.service.ts
Normal file
84
frontend/lib/services/master-data.service.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// File: lib/services/master-data.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
|
||||
// Import DTOs
|
||||
import { CreateTagDto, UpdateTagDto, SearchTagDto } from "@/types/dto/master/tag.dto";
|
||||
import { CreateDisciplineDto } from "@/types/dto/master/discipline.dto";
|
||||
import { CreateSubTypeDto } from "@/types/dto/master/sub-type.dto";
|
||||
import { SaveNumberFormatDto } from "@/types/dto/master/number-format.dto";
|
||||
|
||||
export const masterDataService = {
|
||||
// --- Tags Management ---
|
||||
|
||||
/** ดึงรายการ Tags ทั้งหมด (Search & Pagination) */
|
||||
getTags: async (params?: SearchTagDto) => {
|
||||
const response = await apiClient.get("/tags", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** สร้าง Tag ใหม่ */
|
||||
createTag: async (data: CreateTagDto) => {
|
||||
const response = await apiClient.post("/tags", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** แก้ไข Tag */
|
||||
updateTag: async (id: number | string, data: UpdateTagDto) => {
|
||||
const response = await apiClient.put(`/tags/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** ลบ Tag */
|
||||
deleteTag: async (id: number | string) => {
|
||||
const response = await apiClient.delete(`/tags/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// --- Disciplines Management (Admin / Req 6B) ---
|
||||
|
||||
/** ดึงรายชื่อสาขางาน (มักจะกรองตาม Contract ID) */
|
||||
getDisciplines: async (contractId?: number) => {
|
||||
const response = await apiClient.get("/disciplines", {
|
||||
params: { contractId }
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** สร้างสาขางานใหม่ */
|
||||
createDiscipline: async (data: CreateDisciplineDto) => {
|
||||
const response = await apiClient.post("/disciplines", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// --- Sub-Types Management (Admin / Req 6B) ---
|
||||
|
||||
/** ดึงรายชื่อประเภทย่อย (กรองตาม Contract และ Type) */
|
||||
getSubTypes: async (contractId?: number, typeId?: number) => {
|
||||
const response = await apiClient.get("/sub-types", {
|
||||
params: { contractId, correspondenceTypeId: typeId }
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** สร้างประเภทย่อยใหม่ */
|
||||
createSubType: async (data: CreateSubTypeDto) => {
|
||||
const response = await apiClient.post("/sub-types", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// --- Document Numbering Format (Admin Config) ---
|
||||
|
||||
/** บันทึกรูปแบบเลขที่เอกสาร */
|
||||
saveNumberFormat: async (data: SaveNumberFormatDto) => {
|
||||
const response = await apiClient.post("/document-numbering/formats", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** ดึงรูปแบบเลขที่เอกสารปัจจุบัน (เพื่อมาแก้ไข) */
|
||||
getNumberFormat: async (projectId: number, typeId: number) => {
|
||||
const response = await apiClient.get("/document-numbering/formats", {
|
||||
params: { projectId, correspondenceTypeId: typeId }
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
27
frontend/lib/services/monitoring.service.ts
Normal file
27
frontend/lib/services/monitoring.service.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// File: lib/services/monitoring.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
|
||||
export interface SetMaintenanceDto {
|
||||
enabled: boolean;
|
||||
message?: string; // ข้อความที่จะแสดงหน้าเว็บตอนปิดปรับปรุง
|
||||
}
|
||||
|
||||
export const monitoringService = {
|
||||
/** ตรวจสอบสถานะสุขภาพระบบ (Health Check) */
|
||||
getHealth: async () => {
|
||||
const response = await apiClient.get("/health");
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** ดึง Metrics การทำงาน (CPU, Memory, Request Count) */
|
||||
getMetrics: async () => {
|
||||
const response = await apiClient.get("/monitoring/metrics");
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** เปิด/ปิด Maintenance Mode */
|
||||
setMaintenanceMode: async (data: SetMaintenanceDto) => {
|
||||
const response = await apiClient.post("/monitoring/maintenance", data);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
48
frontend/lib/services/notification.service.ts
Normal file
48
frontend/lib/services/notification.service.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
// File: lib/services/notification.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
SearchNotificationDto,
|
||||
CreateNotificationDto
|
||||
} from "@/types/dto/notification/notification.dto";
|
||||
|
||||
export const notificationService = {
|
||||
/** * ดึงรายการแจ้งเตือนของผู้ใช้ปัจจุบัน
|
||||
* GET /notifications
|
||||
*/
|
||||
getMyNotifications: async (params?: SearchNotificationDto) => {
|
||||
const response = await apiClient.get("/notifications", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** * สร้างการแจ้งเตือนใหม่ (มักใช้โดย System หรือ Admin)
|
||||
* POST /notifications
|
||||
*/
|
||||
create: async (data: CreateNotificationDto) => {
|
||||
const response = await apiClient.post("/notifications", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** * อ่านแจ้งเตือน (Mark as Read)
|
||||
* PATCH /notifications/:id/read
|
||||
*/
|
||||
markAsRead: async (id: number | string) => {
|
||||
const response = await apiClient.patch(`/notifications/${id}/read`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** * อ่านทั้งหมด (Mark All as Read)
|
||||
* PATCH /notifications/read-all
|
||||
*/
|
||||
markAllAsRead: async () => {
|
||||
const response = await apiClient.patch("/notifications/read-all");
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** * ลบการแจ้งเตือน
|
||||
* DELETE /notifications/:id
|
||||
*/
|
||||
delete: async (id: number | string) => {
|
||||
const response = await apiClient.delete(`/notifications/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
63
frontend/lib/services/project.service.ts
Normal file
63
frontend/lib/services/project.service.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
// File: lib/services/project.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateProjectDto,
|
||||
UpdateProjectDto,
|
||||
SearchProjectDto
|
||||
} from "@/types/dto/project/project.dto";
|
||||
|
||||
export const projectService = {
|
||||
// --- Basic CRUD ---
|
||||
|
||||
/**
|
||||
* ดึงรายการโครงการทั้งหมด (รองรับ Search & Pagination)
|
||||
* (เดิมคือ getAllProjects แต่ปรับให้รับ params ได้)
|
||||
*/
|
||||
getAll: async (params?: SearchProjectDto) => {
|
||||
// GET /projects
|
||||
const response = await apiClient.get("/projects", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** ดึงรายละเอียดโครงการตาม ID */
|
||||
getById: async (id: string | number) => {
|
||||
const response = await apiClient.get(`/projects/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** สร้างโครงการใหม่ (Admin) */
|
||||
create: async (data: CreateProjectDto) => {
|
||||
const response = await apiClient.post("/projects", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** แก้ไขโครงการ */
|
||||
update: async (id: string | number, data: UpdateProjectDto) => {
|
||||
const response = await apiClient.put(`/projects/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** ลบโครงการ (Soft Delete) */
|
||||
delete: async (id: string | number) => {
|
||||
const response = await apiClient.delete(`/projects/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// --- Related Data / Dropdown Helpers ---
|
||||
|
||||
/** * ดึงรายชื่อองค์กรในโครงการ (สำหรับ Dropdown 'To/From')
|
||||
* GET /projects/:id/organizations
|
||||
*/
|
||||
getOrganizations: async (projectId: string | number) => {
|
||||
const response = await apiClient.get(`/projects/${projectId}/organizations`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** * ดึงรายชื่อสัญญาในโครงการ
|
||||
* GET /projects/:id/contracts
|
||||
*/
|
||||
getContracts: async (projectId: string | number) => {
|
||||
const response = await apiClient.get(`/projects/${projectId}/contracts`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
69
frontend/lib/services/rfa.service.ts
Normal file
69
frontend/lib/services/rfa.service.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
// File: lib/services/rfa.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateRfaDto,
|
||||
UpdateRfaDto,
|
||||
SearchRfaDto
|
||||
} from "@/types/dto/rfa/rfa.dto";
|
||||
|
||||
// DTO สำหรับการอนุมัติ (อาจจะย้ายไปไว้ใน folder dto/rfa/ ก็ได้ในอนาคต)
|
||||
export interface WorkflowActionDto {
|
||||
action: 'APPROVE' | 'REJECT' | 'COMMENT' | 'ACKNOWLEDGE';
|
||||
comments?: string;
|
||||
stepNumber?: number; // อาจจะไม่จำเป็นถ้า Backend เช็ค state ปัจจุบันได้เอง
|
||||
}
|
||||
|
||||
export const rfaService = {
|
||||
/**
|
||||
* ดึงรายการ RFA ทั้งหมด (รองรับ Search & Filter)
|
||||
*/
|
||||
getAll: async (params: SearchRfaDto) => {
|
||||
// GET /rfas
|
||||
const response = await apiClient.get("/rfas", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึงรายละเอียด RFA และประวัติ Workflow
|
||||
*/
|
||||
getById: async (id: string | number) => {
|
||||
// GET /rfas/:id
|
||||
const response = await apiClient.get(`/rfas/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* สร้าง RFA ใหม่
|
||||
*/
|
||||
create: async (data: CreateRfaDto) => {
|
||||
// POST /rfas
|
||||
const response = await apiClient.post("/rfas", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* แก้ไข RFA (เฉพาะสถานะ Draft)
|
||||
*/
|
||||
update: async (id: string | number, data: UpdateRfaDto) => {
|
||||
// PUT /rfas/:id
|
||||
const response = await apiClient.put(`/rfas/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดำเนินการ Workflow (อนุมัติ / ตีกลับ / ส่งต่อ)
|
||||
*/
|
||||
processWorkflow: async (id: string | number, actionData: WorkflowActionDto) => {
|
||||
// POST /rfas/:id/workflow
|
||||
const response = await apiClient.post(`/rfas/${id}/workflow`, actionData);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* (Optional) ลบ RFA (Soft Delete)
|
||||
*/
|
||||
delete: async (id: string | number) => {
|
||||
const response = await apiClient.delete(`/rfas/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
26
frontend/lib/services/search.service.ts
Normal file
26
frontend/lib/services/search.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
// File: lib/services/search.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import { SearchQueryDto } from "@/types/dto/search/search-query.dto";
|
||||
|
||||
export const searchService = {
|
||||
/**
|
||||
* ค้นหาเอกสารทั้งระบบ (Full-text Search)
|
||||
* ใช้ Elasticsearch ผ่าน Backend
|
||||
*/
|
||||
search: async (query: SearchQueryDto) => {
|
||||
// ส่ง params แบบ flat ตาม DTO
|
||||
// GET /search?q=...&type=...&projectId=...
|
||||
const response = await apiClient.get("/search", {
|
||||
params: query
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* (Optional) Re-index ข้อมูลใหม่ กรณีข้อมูลไม่ตรง (Admin Only)
|
||||
*/
|
||||
reindex: async (type?: string) => {
|
||||
const response = await apiClient.post("/search/reindex", { type });
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
41
frontend/lib/services/shop-drawing.service.ts
Normal file
41
frontend/lib/services/shop-drawing.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// File: lib/services/shop-drawing.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateShopDrawingDto,
|
||||
CreateShopDrawingRevisionDto,
|
||||
SearchShopDrawingDto
|
||||
} from "@/types/dto/drawing/shop-drawing.dto";
|
||||
|
||||
export const shopDrawingService = {
|
||||
/**
|
||||
* ดึงรายการแบบก่อสร้าง (Shop Drawings)
|
||||
*/
|
||||
getAll: async (params: SearchShopDrawingDto) => {
|
||||
const response = await apiClient.get("/shop-drawings", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึงรายละเอียดตาม ID (ควรได้ Revision History มาด้วย)
|
||||
*/
|
||||
getById: async (id: string | number) => {
|
||||
const response = await apiClient.get(`/shop-drawings/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* สร้าง Shop Drawing ใหม่ (พร้อม Revision 0)
|
||||
*/
|
||||
create: async (data: CreateShopDrawingDto) => {
|
||||
const response = await apiClient.post("/shop-drawings", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* สร้าง Revision ใหม่สำหรับ Shop Drawing เดิม
|
||||
*/
|
||||
createRevision: async (id: string | number, data: CreateShopDrawingRevisionDto) => {
|
||||
const response = await apiClient.post(`/shop-drawings/${id}/revisions`, data);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
54
frontend/lib/services/transmittal.service.ts
Normal file
54
frontend/lib/services/transmittal.service.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// File: lib/services/transmittal.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateTransmittalDto,
|
||||
UpdateTransmittalDto,
|
||||
SearchTransmittalDto
|
||||
} from "@/types/dto/transmittal/transmittal.dto";
|
||||
|
||||
export const transmittalService = {
|
||||
/**
|
||||
* ดึงรายการ Transmittal ทั้งหมด (รองรับ Search & Filter)
|
||||
*/
|
||||
getAll: async (params: SearchTransmittalDto) => {
|
||||
// GET /transmittals
|
||||
const response = await apiClient.get("/transmittals", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึงรายละเอียด Transmittal ตาม ID
|
||||
*/
|
||||
getById: async (id: string | number) => {
|
||||
// GET /transmittals/:id
|
||||
const response = await apiClient.get(`/transmittals/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* สร้างเอกสารนำส่งใหม่
|
||||
*/
|
||||
create: async (data: CreateTransmittalDto) => {
|
||||
// POST /transmittals
|
||||
const response = await apiClient.post("/transmittals", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* แก้ไขข้อมูล Transmittal (เฉพาะ Draft)
|
||||
*/
|
||||
update: async (id: string | number, data: UpdateTransmittalDto) => {
|
||||
// PUT /transmittals/:id
|
||||
const response = await apiClient.put(`/transmittals/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ลบเอกสาร (Soft Delete)
|
||||
*/
|
||||
delete: async (id: string | number) => {
|
||||
// DELETE /transmittals/:id
|
||||
const response = await apiClient.delete(`/transmittals/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
57
frontend/lib/services/user.service.ts
Normal file
57
frontend/lib/services/user.service.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
// File: lib/services/user.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateUserDto,
|
||||
UpdateUserDto,
|
||||
AssignRoleDto,
|
||||
UpdatePreferenceDto
|
||||
} from "@/types/dto/user/user.dto";
|
||||
|
||||
export const userService = {
|
||||
/** ดึงรายชื่อผู้ใช้ทั้งหมด (Admin) */
|
||||
getAll: async (params?: any) => {
|
||||
const response = await apiClient.get("/users", { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** ดึงข้อมูลผู้ใช้ตาม ID */
|
||||
getById: async (id: number | string) => {
|
||||
const response = await apiClient.get(`/users/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** สร้างผู้ใช้ใหม่ (Admin) */
|
||||
create: async (data: CreateUserDto) => {
|
||||
const response = await apiClient.post("/users", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** แก้ไขข้อมูลผู้ใช้ */
|
||||
update: async (id: number | string, data: UpdateUserDto) => {
|
||||
const response = await apiClient.put(`/users/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** แก้ไขการตั้งค่าส่วนตัว (Preferences) */
|
||||
updatePreferences: async (id: number | string, data: UpdatePreferenceDto) => {
|
||||
const response = await apiClient.put(`/users/${id}/preferences`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** * กำหนด Role ให้ผู้ใช้ (Admin)
|
||||
* หมายเหตุ: Backend DTO มี userId ใน body ด้วย แต่ API อาจจะรับ userId ใน param
|
||||
* ขึ้นอยู่กับการ Implement ของ Controller (ในที่นี้ส่งไปทั้งคู่เพื่อความชัวร์)
|
||||
*/
|
||||
assignRole: async (userId: number | string, data: Omit<AssignRoleDto, 'userId'>) => {
|
||||
// รวม userId เข้าไปใน body เพื่อให้ตรงกับ DTO Validation ฝั่ง Backend
|
||||
const payload: AssignRoleDto = { userId: Number(userId), ...data };
|
||||
const response = await apiClient.post(`/users/${userId}/roles`, payload);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/** ลบผู้ใช้ (Soft Delete) */
|
||||
delete: async (id: number | string) => {
|
||||
const response = await apiClient.delete(`/users/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
77
frontend/lib/services/workflow-engine.service.ts
Normal file
77
frontend/lib/services/workflow-engine.service.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
// File: lib/services/workflow-engine.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateWorkflowDefinitionDto,
|
||||
UpdateWorkflowDefinitionDto,
|
||||
EvaluateWorkflowDto,
|
||||
GetAvailableActionsDto
|
||||
} from "@/types/dto/workflow-engine/workflow-engine.dto";
|
||||
|
||||
export const workflowEngineService = {
|
||||
// --- Engine Execution (Low-Level) ---
|
||||
|
||||
/**
|
||||
* ตรวจสอบ Action ที่ทำได้ ณ สถานะหนึ่งๆ (ถาม Engine)
|
||||
* POST /workflow-engine/available-actions
|
||||
*/
|
||||
getAvailableActions: async (data: GetAvailableActionsDto) => {
|
||||
const response = await apiClient.post("/workflow-engine/available-actions", data);
|
||||
return response.data; // string[] e.g. ['APPROVE', 'REJECT']
|
||||
},
|
||||
|
||||
/**
|
||||
* ประมวลผล Transition (ถาม Engine ว่าถ้าทำ Action นี้แล้วจะไป State ไหน)
|
||||
* POST /workflow-engine/evaluate
|
||||
*/
|
||||
evaluate: async (data: EvaluateWorkflowDto) => {
|
||||
const response = await apiClient.post("/workflow-engine/evaluate", data);
|
||||
return response.data; // { nextState: '...', events: [...] }
|
||||
},
|
||||
|
||||
// --- Definition Management (Admin / Workflow Editor) ---
|
||||
|
||||
/**
|
||||
* ดึง Workflow Definition ทั้งหมด
|
||||
* GET /workflow-engine/definitions
|
||||
*/
|
||||
getDefinitions: async () => {
|
||||
const response = await apiClient.get("/workflow-engine/definitions");
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ดึง Workflow Definition ตาม ID
|
||||
* GET /workflow-engine/definitions/:id
|
||||
*/
|
||||
getDefinitionById: async (id: string | number) => {
|
||||
const response = await apiClient.get(`/workflow-engine/definitions/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* สร้าง Workflow Definition ใหม่
|
||||
* POST /workflow-engine/definitions
|
||||
*/
|
||||
createDefinition: async (data: CreateWorkflowDefinitionDto) => {
|
||||
const response = await apiClient.post("/workflow-engine/definitions", data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* อัปเดต Workflow Definition
|
||||
* PATCH /workflow-engine/definitions/:id
|
||||
*/
|
||||
updateDefinition: async (id: string | number, data: UpdateWorkflowDefinitionDto) => {
|
||||
const response = await apiClient.patch(`/workflow-engine/definitions/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* ลบ Workflow Definition
|
||||
* DELETE /workflow-engine/definitions/:id
|
||||
*/
|
||||
deleteDefinition: async (id: string | number) => {
|
||||
const response = await apiClient.delete(`/workflow-engine/definitions/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
29
frontend/lib/stores/draft-store.ts
Normal file
29
frontend/lib/stores/draft-store.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// File: lib/stores/draft-store.ts
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
|
||||
interface DraftState {
|
||||
drafts: Record<string, any>;
|
||||
saveDraft: (key: string, data: any) => void;
|
||||
getDraft: (key: string) => any;
|
||||
clearDraft: (key: string) => void;
|
||||
}
|
||||
|
||||
export const useDraftStore = create<DraftState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
drafts: {},
|
||||
saveDraft: (key, data) => set((state) => ({ drafts: { ...state.drafts, [key]: data } })),
|
||||
getDraft: (key) => get().drafts[key],
|
||||
clearDraft: (key) => set((state) => {
|
||||
const newDrafts = { ...state.drafts };
|
||||
delete newDrafts[key];
|
||||
return { drafts: newDrafts };
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: 'lcbp3-form-drafts',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
}
|
||||
)
|
||||
);
|
||||
27
frontend/lib/stores/ui-store.ts
Normal file
27
frontend/lib/stores/ui-store.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// File: lib/stores/ui-store.ts
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
interface UIState {
|
||||
isSidebarOpen: boolean;
|
||||
toggleSidebar: () => void;
|
||||
closeSidebar: () => void; // ✅ เพิ่มกลับมา
|
||||
openSidebar: () => void; // ✅ เพิ่มกลับมา
|
||||
}
|
||||
|
||||
export const useUIStore = create<UIState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
isSidebarOpen: true,
|
||||
|
||||
toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),
|
||||
|
||||
// ✅ เพิ่ม Implementation กลับมา
|
||||
closeSidebar: () => set({ isSidebarOpen: false }),
|
||||
openSidebar: () => set({ isSidebarOpen: true }),
|
||||
}),
|
||||
{
|
||||
name: 'sidebar-state',
|
||||
}
|
||||
)
|
||||
);
|
||||
11
frontend/lib/utils.ts
Normal file
11
frontend/lib/utils.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// File: lib/utils.ts
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
/**
|
||||
* ฟังก์ชันสำหรับรวม ClassNames โดยใช้ clsx และ tailwind-merge
|
||||
* ช่วยแก้ปัญหา class conflict ของ Tailwind
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Reference in New Issue
Block a user