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:
@@ -18,11 +18,10 @@ const USE_MOCK_DATA = false;
|
||||
|
||||
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { PaginatedApiResponse } from '@/lib/api/types';
|
||||
import { buildApiUrl } from '@/lib/api/query-params';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { executeServerAction } from '@/lib/api/execute-server-action';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
import { executePaginatedAction } from '@/lib/api/execute-paginated-action';
|
||||
|
||||
import type {
|
||||
ReceivingItem,
|
||||
@@ -361,8 +360,6 @@ interface ReceivingApiData {
|
||||
has_inspection_template?: boolean;
|
||||
}
|
||||
|
||||
type ReceivingApiPaginatedResponse = PaginatedApiResponse<ReceivingApiData>;
|
||||
|
||||
interface ReceivingApiStatsResponse {
|
||||
receiving_pending_count: number;
|
||||
shipping_count: number;
|
||||
@@ -513,14 +510,6 @@ function transformProcessDataToApi(
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 페이지네이션 타입 =====
|
||||
interface PaginationMeta {
|
||||
currentPage: number;
|
||||
lastPage: number;
|
||||
perPage: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
// ===== 입고 목록 조회 =====
|
||||
export async function getReceivings(params?: {
|
||||
page?: number;
|
||||
@@ -529,13 +518,7 @@ export async function getReceivings(params?: {
|
||||
endDate?: string;
|
||||
status?: string;
|
||||
search?: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
data: ReceivingItem[];
|
||||
pagination: PaginationMeta;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
}) {
|
||||
// ===== 목데이터 모드 =====
|
||||
if (USE_MOCK_DATA) {
|
||||
let filteredData = [...MOCK_RECEIVING_LIST];
|
||||
@@ -565,7 +548,7 @@ export async function getReceivings(params?: {
|
||||
const paginatedData = filteredData.slice(startIndex, startIndex + perPage);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
success: true as const,
|
||||
data: paginatedData,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
@@ -576,29 +559,18 @@ export async function getReceivings(params?: {
|
||||
};
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
if (params?.perPage) searchParams.set('per_page', String(params.perPage));
|
||||
if (params?.startDate) searchParams.set('start_date', params.startDate);
|
||||
if (params?.endDate) searchParams.set('end_date', params.endDate);
|
||||
if (params?.status && params.status !== 'all') searchParams.set('status', params.status);
|
||||
if (params?.search) searchParams.set('search', params.search);
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const emptyPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
|
||||
const result = await executeServerAction<ReceivingApiPaginatedResponse>({
|
||||
url: `${API_URL}/api/v1/receivings${queryString ? `?${queryString}` : ''}`,
|
||||
return executePaginatedAction<ReceivingApiData, ReceivingItem>({
|
||||
url: buildApiUrl('/api/v1/receivings', {
|
||||
page: params?.page,
|
||||
per_page: params?.perPage,
|
||||
start_date: params?.startDate,
|
||||
end_date: params?.endDate,
|
||||
status: params?.status && params.status !== 'all' ? params.status : undefined,
|
||||
search: params?.search,
|
||||
}),
|
||||
transform: transformApiToListItem,
|
||||
errorMessage: '입고 목록 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, data: [], pagination: emptyPagination, __authError: true };
|
||||
if (!result.success || !result.data) return { success: false, data: [], pagination: emptyPagination, error: result.error };
|
||||
|
||||
const pd = result.data;
|
||||
return {
|
||||
success: true,
|
||||
data: (pd.data || []).map(transformApiToListItem),
|
||||
pagination: { currentPage: pd.current_page, lastPage: pd.last_page, perPage: pd.per_page, total: pd.total },
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 입고 통계 조회 =====
|
||||
@@ -611,7 +583,7 @@ export async function getReceivingStats(): Promise<{
|
||||
if (USE_MOCK_DATA) return { success: true, data: MOCK_RECEIVING_STATS };
|
||||
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/stats`,
|
||||
url: buildApiUrl('/api/v1/receivings/stats'),
|
||||
transform: (data: ReceivingApiStatsResponse) => transformApiToStats(data),
|
||||
errorMessage: '입고 통계 조회에 실패했습니다.',
|
||||
});
|
||||
@@ -632,7 +604,7 @@ export async function getReceivingById(id: string): Promise<{
|
||||
}
|
||||
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${id}`,
|
||||
url: buildApiUrl(`/api/v1/receivings/${id}`),
|
||||
transform: (data: ReceivingApiData) => transformApiToDetail(data),
|
||||
errorMessage: '입고 조회에 실패했습니다.',
|
||||
});
|
||||
@@ -646,7 +618,7 @@ export async function createReceiving(
|
||||
): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> {
|
||||
const apiData = transformFrontendToApi(data);
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings`,
|
||||
url: buildApiUrl('/api/v1/receivings'),
|
||||
method: 'POST',
|
||||
body: apiData,
|
||||
transform: (d: ReceivingApiData) => transformApiToDetail(d),
|
||||
@@ -663,7 +635,7 @@ export async function updateReceiving(
|
||||
): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> {
|
||||
const apiData = transformFrontendToApi(data);
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${id}`,
|
||||
url: buildApiUrl(`/api/v1/receivings/${id}`),
|
||||
method: 'PUT',
|
||||
body: apiData,
|
||||
transform: (d: ReceivingApiData) => transformApiToDetail(d),
|
||||
@@ -678,7 +650,7 @@ export async function deleteReceiving(
|
||||
id: string
|
||||
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${id}`,
|
||||
url: buildApiUrl(`/api/v1/receivings/${id}`),
|
||||
method: 'DELETE',
|
||||
errorMessage: '입고 삭제에 실패했습니다.',
|
||||
});
|
||||
@@ -693,7 +665,7 @@ export async function processReceiving(
|
||||
): Promise<{ success: boolean; data?: ReceivingDetail; error?: string; __authError?: boolean }> {
|
||||
const apiData = transformProcessDataToApi(data);
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${id}/process`,
|
||||
url: buildApiUrl(`/api/v1/receivings/${id}/process`),
|
||||
method: 'POST',
|
||||
body: apiData,
|
||||
transform: (d: ReceivingApiData) => transformApiToDetail(d),
|
||||
@@ -739,13 +711,9 @@ export async function searchItems(query?: string): Promise<{
|
||||
return { success: true, data: filtered };
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
if (query) searchParams.set('search', query);
|
||||
searchParams.set('per_page', '50');
|
||||
|
||||
interface ItemApiData { data: Array<Record<string, string>> }
|
||||
const result = await executeServerAction<ItemApiData, ItemOption[]>({
|
||||
url: `${API_URL}/api/v1/items?${searchParams.toString()}`,
|
||||
url: buildApiUrl('/api/v1/items', { search: query, per_page: 50 }),
|
||||
transform: (d) => (d.data || []).map((item) => ({
|
||||
value: item.item_code,
|
||||
label: item.item_code,
|
||||
@@ -786,13 +754,9 @@ export async function searchSuppliers(query?: string): Promise<{
|
||||
return { success: true, data: filtered };
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
if (query) searchParams.set('search', query);
|
||||
searchParams.set('per_page', '50');
|
||||
|
||||
interface SupplierApiData { data: Array<Record<string, string>> }
|
||||
const result = await executeServerAction<SupplierApiData, SupplierOption[]>({
|
||||
url: `${API_URL}/api/v1/suppliers?${searchParams.toString()}`,
|
||||
url: buildApiUrl('/api/v1/suppliers', { search: query, per_page: 50 }),
|
||||
transform: (d) => (d.data || []).map((s) => ({
|
||||
value: s.name,
|
||||
label: s.name,
|
||||
@@ -1041,11 +1005,10 @@ export async function checkInspectionTemplate(itemId?: number): Promise<{
|
||||
}
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append('category', 'incoming_inspection');
|
||||
searchParams.append('item_id', String(itemId));
|
||||
|
||||
const url = `${API_URL}/api/v1/documents/resolve?${searchParams.toString()}`;
|
||||
const url = buildApiUrl('/api/v1/documents/resolve', {
|
||||
category: 'incoming_inspection',
|
||||
item_id: itemId,
|
||||
});
|
||||
|
||||
const { response, error } = await serverFetch(url, { method: 'GET' });
|
||||
|
||||
@@ -1240,12 +1203,11 @@ export async function getInspectionTemplate(params: {
|
||||
return { success: false, error: '품목 ID가 필요합니다.' };
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('category', 'incoming_inspection');
|
||||
searchParams.set('item_id', String(params.itemId));
|
||||
|
||||
const result = await executeServerAction<DocumentResolveResponse>({
|
||||
url: `${API_URL}/api/v1/documents/resolve?${searchParams.toString()}`,
|
||||
url: buildApiUrl('/api/v1/documents/resolve', {
|
||||
category: 'incoming_inspection',
|
||||
item_id: params.itemId,
|
||||
}),
|
||||
errorMessage: '검사 템플릿 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
@@ -1860,7 +1822,7 @@ export async function uploadInspectionFiles(files: File[]): Promise<{
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await fetch(
|
||||
`${API_URL}/api/v1/files/upload`,
|
||||
buildApiUrl('/api/v1/files/upload'),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -1881,7 +1843,7 @@ export async function uploadInspectionFiles(files: File[]): Promise<{
|
||||
uploadedFiles.push({
|
||||
id: result.data.id,
|
||||
name: result.data.display_name || file.name,
|
||||
url: `${API_URL}/api/v1/files/${result.data.id}/download`,
|
||||
url: buildApiUrl(`/api/v1/files/${result.data.id}/download`),
|
||||
size: result.data.file_size,
|
||||
});
|
||||
}
|
||||
@@ -1911,7 +1873,7 @@ export async function saveInspectionData(params: {
|
||||
}> {
|
||||
// Step 1: POST /v1/documents/upsert - 검사 데이터 저장
|
||||
const docResult = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/documents/upsert`,
|
||||
url: buildApiUrl('/api/v1/documents/upsert'),
|
||||
method: 'POST',
|
||||
body: {
|
||||
template_id: params.templateId,
|
||||
@@ -1931,7 +1893,7 @@ export async function saveInspectionData(params: {
|
||||
const inspectionResultLabel = params.inspectionResult === 'pass' ? '합격' : params.inspectionResult === 'fail' ? '불합격' : null;
|
||||
|
||||
const recResult = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/receivings/${params.receivingId}`,
|
||||
url: buildApiUrl(`/api/v1/receivings/${params.receivingId}`),
|
||||
method: 'PUT',
|
||||
body: {
|
||||
status: 'receiving_pending',
|
||||
|
||||
Reference in New Issue
Block a user