refactor(WEB): 전체 actions.ts에 공통 API 유틸 적용

- buildApiUrl / executePaginatedAction 패턴으로 전환 (40+ actions 파일)
- 직접 URLSearchParams 조립 → buildApiUrl 유틸 사용
- 수동 페이지네이션 메타 변환 → executePaginatedAction 자동 처리
- HandoverReportDocumentModal, OrderDocumentModal 개선
- 급여관리 SalaryManagement 코드 개선
- CLAUDE.md Server Action 공통 유틸 규칙 정리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-12 20:59:59 +09:00
parent 31be9d4a25
commit cbb38d48b9
51 changed files with 1050 additions and 1405 deletions

View File

@@ -14,6 +14,7 @@
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { cookies } from 'next/headers';
import { executeServerAction } from '@/lib/api/execute-server-action';
import { buildApiUrl } from '@/lib/api/query-params';
import type {
ExpenseEstimateItem,
ApprovalPerson,
@@ -125,8 +126,6 @@ function transformEmployee(employee: EmployeeApiData): ApprovalPerson {
// API 함수
// ============================================
const API_URL = process.env.NEXT_PUBLIC_API_URL;
/**
* 파일 업로드
* @param files 업로드할 파일 배열
@@ -202,11 +201,8 @@ export async function getExpenseEstimateItems(yearMonth?: string): Promise<{
accountBalance: number;
finalDifference: number;
} | null> {
const searchParams = new URLSearchParams();
if (yearMonth) searchParams.set('year_month', yearMonth);
const result = await executeServerAction<ExpenseEstimateApiResponse>({
url: `${API_URL}/api/v1/reports/expense-estimate?${searchParams.toString()}`,
url: buildApiUrl('/api/v1/reports/expense-estimate', { year_month: yearMonth }),
errorMessage: '비용견적서 조회에 실패했습니다.',
});
if (!result.success || !result.data) return null;
@@ -222,12 +218,8 @@ export async function getExpenseEstimateItems(yearMonth?: string): Promise<{
* 직원 목록 조회 (결재선/참조 선택용)
*/
export async function getEmployees(search?: string): Promise<ApprovalPerson[]> {
const searchParams = new URLSearchParams();
searchParams.set('per_page', '100');
if (search) searchParams.set('search', search);
const result = await executeServerAction<{ data: EmployeeApiData[] }>({
url: `${API_URL}/api/v1/employees?${searchParams.toString()}`,
url: buildApiUrl('/api/v1/employees', { per_page: 100, search }),
errorMessage: '직원 목록 조회에 실패했습니다.',
});
if (!result.success || !result.data?.data) return [];
@@ -276,7 +268,7 @@ export async function createApproval(formData: DocumentFormData): Promise<{
};
const result = await executeServerAction<ApprovalCreateResponse>({
url: `${API_URL}/api/v1/approvals`,
url: buildApiUrl('/api/v1/approvals'),
method: 'POST',
body: requestBody,
errorMessage: '문서 저장에 실패했습니다.',
@@ -293,7 +285,7 @@ export async function submitApproval(id: number): Promise<{
error?: string;
}> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/approvals/${id}/submit`,
url: buildApiUrl(`/api/v1/approvals/${id}/submit`),
method: 'POST',
body: {},
errorMessage: '문서 상신에 실패했습니다.',
@@ -349,7 +341,7 @@ export async function getApprovalById(id: number): Promise<{
}> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = await executeServerAction<any>({
url: `${API_URL}/api/v1/approvals/${id}`,
url: buildApiUrl(`/api/v1/approvals/${id}`),
errorMessage: '문서 조회에 실패했습니다.',
});
if (!result.success || !result.data) return { success: false, error: result.error };
@@ -397,7 +389,7 @@ export async function updateApproval(id: number, formData: DocumentFormData): Pr
};
const result = await executeServerAction<ApprovalCreateResponse>({
url: `${API_URL}/api/v1/approvals/${id}`,
url: buildApiUrl(`/api/v1/approvals/${id}`),
method: 'PATCH',
body: requestBody,
errorMessage: '문서 수정에 실패했습니다.',
@@ -449,7 +441,7 @@ export async function deleteApproval(id: number): Promise<{
error?: string;
}> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/approvals/${id}`,
url: buildApiUrl(`/api/v1/approvals/${id}`),
method: 'DELETE',
errorMessage: '문서 삭제에 실패했습니다.',
});