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

@@ -2,6 +2,7 @@
import { executeServerAction } from '@/lib/api/execute-server-action';
import { buildApiUrl } from '@/lib/api/query-params';
import type { PaginatedApiResponse } from '@/lib/api/types';
import type { Process, ProcessFormData, ClassificationRule, IndividualItem, ProcessStep } from '@/types/process';
@@ -222,8 +223,6 @@ function transformFrontendToApi(data: ProcessFormData): Record<string, unknown>
// API 함수
// ============================================================================
const API_URL = process.env.NEXT_PUBLIC_API_URL;
/**
* 공정 목록 조회
*/
@@ -234,15 +233,14 @@ export async function getProcessList(params?: {
status?: string;
process_type?: string;
}): Promise<{ success: boolean; data?: { items: Process[]; total: number; page: number; totalPages: number }; error?: string; __authError?: boolean }> {
const searchParams = new URLSearchParams();
if (params?.page) searchParams.set('page', String(params.page));
if (params?.size) searchParams.set('size', String(params.size));
if (params?.q) searchParams.set('q', params.q);
if (params?.status) searchParams.set('status', params.status);
if (params?.process_type) searchParams.set('process_type', params.process_type);
const result = await executeServerAction<PaginatedApiResponse<ApiProcess>>({
url: `${API_URL}/api/v1/processes?${searchParams.toString()}`,
url: buildApiUrl('/api/v1/processes', {
page: params?.page,
size: params?.size,
q: params?.q,
status: params?.status,
process_type: params?.process_type,
}),
errorMessage: '공정 목록 조회에 실패했습니다.',
});
if (result.__authError) return { success: false, __authError: true };
@@ -263,7 +261,7 @@ export async function getProcessList(params?: {
*/
export async function getProcessById(id: string): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${id}`,
url: buildApiUrl(`/api/v1/processes/${id}`),
transform: (data: ApiProcess) => transformApiToFrontend(data),
errorMessage: '공정 조회에 실패했습니다.',
});
@@ -276,7 +274,7 @@ export async function getProcessById(id: string): Promise<{ success: boolean; da
*/
export async function createProcess(data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes`,
url: buildApiUrl('/api/v1/processes'),
method: 'POST',
body: transformFrontendToApi(data),
transform: (d: ApiProcess) => transformApiToFrontend(d),
@@ -291,7 +289,7 @@ export async function createProcess(data: ProcessFormData): Promise<{ success: b
*/
export async function updateProcess(id: string, data: ProcessFormData): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${id}`,
url: buildApiUrl(`/api/v1/processes/${id}`),
method: 'PUT',
body: transformFrontendToApi(data),
transform: (d: ApiProcess) => transformApiToFrontend(d),
@@ -306,7 +304,7 @@ export async function updateProcess(id: string, data: ProcessFormData): Promise<
*/
export async function removeProcessItem(processId: string, remainingItemIds: number[]): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${processId}`,
url: buildApiUrl(`/api/v1/processes/${processId}`),
method: 'PUT',
body: { item_ids: remainingItemIds },
transform: (d: ApiProcess) => transformApiToFrontend(d),
@@ -321,7 +319,7 @@ export async function removeProcessItem(processId: string, remainingItemIds: num
*/
export async function deleteProcess(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${id}`,
url: buildApiUrl(`/api/v1/processes/${id}`),
method: 'DELETE',
errorMessage: '공정 삭제에 실패했습니다.',
});
@@ -334,7 +332,7 @@ export async function deleteProcess(id: string): Promise<{ success: boolean; err
*/
export async function deleteProcesses(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string; __authError?: boolean }> {
const result = await executeServerAction<{ deleted_count: number }>({
url: `${API_URL}/api/v1/processes`,
url: buildApiUrl('/api/v1/processes'),
method: 'DELETE',
body: { ids: ids.map((id) => parseInt(id, 10)) },
errorMessage: '공정 일괄 삭제에 실패했습니다.',
@@ -349,7 +347,7 @@ export async function deleteProcesses(ids: string[]): Promise<{ success: boolean
*/
export async function toggleProcessActive(id: string): Promise<{ success: boolean; data?: Process; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${id}/toggle`,
url: buildApiUrl(`/api/v1/processes/${id}/toggle`),
method: 'PATCH',
transform: (d: ApiProcess) => transformApiToFrontend(d),
errorMessage: '공정 상태 변경에 실패했습니다.',
@@ -365,7 +363,7 @@ export async function reorderProcesses(
processes: { id: string; order: number }[]
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/reorder`,
url: buildApiUrl('/api/v1/processes/reorder'),
method: 'PATCH',
body: {
items: processes.map((p) => ({
@@ -390,7 +388,7 @@ export async function getProcessOptions(): Promise<{
}> {
interface ApiOptionItem { id: number; process_code: string; process_name: string; process_type: string; department: string }
const result = await executeServerAction<ApiOptionItem[]>({
url: `${API_URL}/api/v1/processes/options`,
url: buildApiUrl('/api/v1/processes/options'),
errorMessage: '공정 옵션 조회에 실패했습니다.',
});
if (result.__authError) return { success: false, __authError: true };
@@ -418,7 +416,7 @@ export async function getProcessStats(): Promise<{
}> {
interface ApiStats { total: number; active: number; inactive: number; by_type: Record<string, number> }
const result = await executeServerAction<ApiStats>({
url: `${API_URL}/api/v1/processes/stats`,
url: buildApiUrl('/api/v1/processes/stats'),
errorMessage: '공정 통계 조회에 실패했습니다.',
});
if (result.__authError) return { success: false, __authError: true };
@@ -456,7 +454,7 @@ export async function getDepartmentOptions(): Promise<DepartmentOption[]> {
];
interface DeptResponse { data: Array<{ id: number; name: string }> }
const result = await executeServerAction<DeptResponse>({
url: `${API_URL}/api/v1/departments`,
url: buildApiUrl('/api/v1/departments'),
errorMessage: '부서 목록 조회에 실패했습니다.',
});
if (!result.success || !result.data?.data) return defaultOptions;
@@ -507,15 +505,14 @@ interface GetItemListParams {
* - 파라미터에 processName, processCategory 필터 추가 필요 (공정/구분 필터링용)
*/
export async function getItemList(params?: GetItemListParams): Promise<ItemOption[]> {
const searchParams = new URLSearchParams();
searchParams.set('size', String(params?.size || 1000));
if (params?.q) searchParams.set('q', params.q);
if (params?.itemType) searchParams.set('item_type', params.itemType);
if (params?.excludeProcessId) searchParams.set('exclude_process_id', params.excludeProcessId);
interface ItemListResponse { data: Array<{ id: number; name: string; item_code?: string; item_type?: string; item_type_name?: string }> }
const result = await executeServerAction<ItemListResponse>({
url: `${API_URL}/api/v1/items?${searchParams.toString()}`,
url: buildApiUrl('/api/v1/items', {
size: params?.size || 1000,
q: params?.q,
item_type: params?.itemType,
exclude_process_id: params?.excludeProcessId,
}),
errorMessage: '품목 목록 조회에 실패했습니다.',
});
if (!result.success || !result.data?.data) return [];
@@ -534,7 +531,7 @@ export async function getItemList(params?: GetItemListParams): Promise<ItemOptio
*/
export async function getItemTypeOptions(): Promise<Array<{ value: string; label: string }>> {
const result = await executeServerAction<Array<{ code: string; name: string }>>({
url: `${API_URL}/api/v1/settings/common/item_type`,
url: buildApiUrl('/api/v1/settings/common/item_type'),
errorMessage: '품목 유형 옵션 조회에 실패했습니다.',
});
if (!result.success || !result.data) return [];
@@ -561,7 +558,7 @@ export async function getDocumentTemplates(): Promise<{
}> {
interface ApiTemplateItem { id: number; name: string; category: string }
const result = await executeServerAction<{ data: ApiTemplateItem[] }>({
url: `${API_URL}/api/v1/document-templates?is_active=1&per_page=100`,
url: buildApiUrl('/api/v1/document-templates', { is_active: 1, per_page: 100 }),
errorMessage: '문서 양식 목록 조회에 실패했습니다.',
});
if (!result.success || !result.data?.data) return { success: false, error: result.error };
@@ -621,7 +618,7 @@ export async function getProcessSteps(processId: string): Promise<{
error?: string;
}> {
const result = await executeServerAction<ApiProcessStep[]>({
url: `${API_URL}/api/v1/processes/${processId}/steps`,
url: buildApiUrl(`/api/v1/processes/${processId}/steps`),
errorMessage: '공정 단계 목록 조회에 실패했습니다.',
});
if (!result.success || !result.data) return { success: false, error: result.error };
@@ -637,7 +634,7 @@ export async function getProcessStepById(processId: string, stepId: string): Pro
error?: string;
}> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${processId}/steps/${stepId}`,
url: buildApiUrl(`/api/v1/processes/${processId}/steps/${stepId}`),
transform: (d: ApiProcessStep) => transformStepApiToFrontend(d),
errorMessage: '공정 단계 조회에 실패했습니다.',
});
@@ -652,7 +649,7 @@ export async function createProcessStep(
data: Omit<ProcessStep, 'id'>
): Promise<{ success: boolean; data?: ProcessStep; error?: string }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${processId}/steps`,
url: buildApiUrl(`/api/v1/processes/${processId}/steps`),
method: 'POST',
body: {
step_name: data.stepName,
@@ -689,7 +686,7 @@ export async function updateProcessStep(
if (data.completionType !== undefined) apiData.completion_type = data.completionType || null;
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${processId}/steps/${stepId}`,
url: buildApiUrl(`/api/v1/processes/${processId}/steps/${stepId}`),
method: 'PUT',
body: apiData,
transform: (d: ApiProcessStep) => transformStepApiToFrontend(d),
@@ -706,7 +703,7 @@ export async function deleteProcessStep(
stepId: string
): Promise<{ success: boolean; error?: string }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/processes/${processId}/steps/${stepId}`,
url: buildApiUrl(`/api/v1/processes/${processId}/steps/${stepId}`),
method: 'DELETE',
errorMessage: '공정 단계 삭제에 실패했습니다.',
});
@@ -721,7 +718,7 @@ export async function reorderProcessSteps(
steps: { id: string; order: number }[]
): Promise<{ success: boolean; data?: ProcessStep[]; error?: string }> {
const result = await executeServerAction<ApiProcessStep[]>({
url: `${API_URL}/api/v1/processes/${processId}/steps/reorder`,
url: buildApiUrl(`/api/v1/processes/${processId}/steps/reorder`),
method: 'PATCH',
body: {
items: steps.map((s) => ({