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

@@ -19,7 +19,8 @@
import { executeServerAction } from '@/lib/api/execute-server-action';
import type { PaginatedApiResponse } from '@/lib/api/types';
import { executePaginatedAction } from '@/lib/api/execute-paginated-action';
import { buildApiUrl } from '@/lib/api/query-params';
import type {
ShipmentItem,
ShipmentDetail,
@@ -112,8 +113,6 @@ interface ShipmentItemApiData {
remarks?: string;
}
type ShipmentApiPaginatedResponse = PaginatedApiResponse<ShipmentApiData>;
interface ShipmentApiStatsResponse {
today_shipment_count: number;
scheduled_count: number;
@@ -294,60 +293,36 @@ function transformEditFormToApi(
}
// ===== 페이지네이션 타입 =====
interface PaginationMeta {
currentPage: number;
lastPage: number;
perPage: number;
total: number;
}
const API_URL = process.env.NEXT_PUBLIC_API_URL;
// ===== 출고 목록 조회 =====
export async function getShipments(params?: {
page?: number; perPage?: number; search?: string; status?: string;
priority?: string; deliveryMethod?: string; scheduledFrom?: string; scheduledTo?: string;
canShip?: boolean; depositConfirmed?: boolean; sortBy?: string; sortDir?: string;
}): Promise<{ success: boolean; data: ShipmentItem[]; pagination: PaginationMeta; error?: string; __authError?: boolean }> {
const emptyPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
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?.search) searchParams.set('search', params.search);
if (params?.status && params.status !== 'all') searchParams.set('status', params.status);
if (params?.priority && params.priority !== 'all') searchParams.set('priority', params.priority);
if (params?.deliveryMethod && params.deliveryMethod !== 'all') searchParams.set('delivery_method', params.deliveryMethod);
if (params?.scheduledFrom) searchParams.set('scheduled_from', params.scheduledFrom);
if (params?.scheduledTo) searchParams.set('scheduled_to', params.scheduledTo);
if (params?.canShip !== undefined) searchParams.set('can_ship', String(params.canShip));
if (params?.depositConfirmed !== undefined) searchParams.set('deposit_confirmed', String(params.depositConfirmed));
if (params?.sortBy) searchParams.set('sort_by', params.sortBy);
if (params?.sortDir) searchParams.set('sort_dir', params.sortDir);
const queryString = searchParams.toString();
const result = await executeServerAction<ShipmentApiPaginatedResponse>({
url: `${API_URL}/api/v1/shipments${queryString ? `?${queryString}` : ''}`,
}) {
return executePaginatedAction<ShipmentApiData, ShipmentItem>({
url: buildApiUrl('/api/v1/shipments', {
page: params?.page,
per_page: params?.perPage,
search: params?.search,
status: params?.status !== 'all' ? params?.status : undefined,
priority: params?.priority !== 'all' ? params?.priority : undefined,
delivery_method: params?.deliveryMethod !== 'all' ? params?.deliveryMethod : undefined,
scheduled_from: params?.scheduledFrom,
scheduled_to: params?.scheduledTo,
can_ship: params?.canShip,
deposit_confirmed: params?.depositConfirmed,
sort_by: params?.sortBy,
sort_dir: params?.sortDir,
}),
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 };
return {
success: true,
data: (result.data.data || []).map(transformApiToListItem),
pagination: {
currentPage: result.data.current_page, lastPage: result.data.last_page,
perPage: result.data.per_page, total: result.data.total,
},
};
}
// ===== 출고 통계 조회 =====
export async function getShipmentStats(): Promise<{ success: boolean; data?: ShipmentStats; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/shipments/stats`,
url: buildApiUrl('/api/v1/shipments/stats'),
transform: (data: ShipmentApiStatsResponse & { total_count?: number }) => transformApiToStats(data),
errorMessage: '출고 통계 조회에 실패했습니다.',
});
@@ -358,7 +333,7 @@ export async function getShipmentStats(): Promise<{ success: boolean; data?: Shi
// ===== 상태별 통계 조회 (탭용) =====
export async function getShipmentStatsByStatus(): Promise<{ success: boolean; data?: ShipmentStatusStats; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/shipments/stats-by-status`,
url: buildApiUrl('/api/v1/shipments/stats-by-status'),
transform: (data: ShipmentApiStatsByStatusResponse) => transformApiToStatsByStatus(data),
errorMessage: '상태별 통계 조회에 실패했습니다.',
});
@@ -369,7 +344,7 @@ export async function getShipmentStatsByStatus(): Promise<{ success: boolean; da
// ===== 출고 상세 조회 =====
export async function getShipmentById(id: string): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/shipments/${id}`,
url: buildApiUrl(`/api/v1/shipments/${id}`),
transform: (data: ShipmentApiData) => transformApiToDetail(data),
errorMessage: '출고 조회에 실패했습니다.',
});
@@ -383,7 +358,7 @@ export async function createShipment(
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
const apiData = transformCreateFormToApi(data);
const result = await executeServerAction({
url: `${API_URL}/api/v1/shipments`,
url: buildApiUrl('/api/v1/shipments'),
method: 'POST',
body: apiData,
transform: (d: ShipmentApiData) => transformApiToDetail(d),
@@ -399,7 +374,7 @@ export async function updateShipment(
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
const apiData = transformEditFormToApi(data);
const result = await executeServerAction({
url: `${API_URL}/api/v1/shipments/${id}`,
url: buildApiUrl(`/api/v1/shipments/${id}`),
method: 'PUT',
body: apiData,
transform: (d: ShipmentApiData) => transformApiToDetail(d),
@@ -426,7 +401,7 @@ export async function updateShipmentStatus(
if (additionalData?.confirmedArrival) apiData.confirmed_arrival = additionalData.confirmedArrival;
const result = await executeServerAction({
url: `${API_URL}/api/v1/shipments/${id}/status`,
url: buildApiUrl(`/api/v1/shipments/${id}/status`),
method: 'PATCH',
body: apiData,
transform: (d: ShipmentApiData) => transformApiToDetail(d),
@@ -439,7 +414,7 @@ export async function updateShipmentStatus(
// ===== 출고 삭제 =====
export async function deleteShipment(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
const result = await executeServerAction({
url: `${API_URL}/api/v1/shipments/${id}`,
url: buildApiUrl(`/api/v1/shipments/${id}`),
method: 'DELETE',
errorMessage: '출고 삭제에 실패했습니다.',
});
@@ -450,7 +425,7 @@ export async function deleteShipment(id: string): Promise<{ success: boolean; er
// ===== LOT 옵션 조회 =====
export async function getLotOptions(): Promise<{ success: boolean; data: LotOption[]; error?: string; __authError?: boolean }> {
const result = await executeServerAction<LotOption[]>({
url: `${API_URL}/api/v1/shipments/options/lots`,
url: buildApiUrl('/api/v1/shipments/options/lots'),
errorMessage: 'LOT 옵션 조회에 실패했습니다.',
});
if (result.__authError) return { success: false, data: [], __authError: true };
@@ -460,7 +435,7 @@ export async function getLotOptions(): Promise<{ success: boolean; data: LotOpti
// ===== 물류사 옵션 조회 =====
export async function getLogisticsOptions(): Promise<{ success: boolean; data: LogisticsOption[]; error?: string; __authError?: boolean }> {
const result = await executeServerAction<LogisticsOption[]>({
url: `${API_URL}/api/v1/shipments/options/logistics`,
url: buildApiUrl('/api/v1/shipments/options/logistics'),
errorMessage: '물류사 옵션 조회에 실패했습니다.',
});
if (result.__authError) return { success: false, data: [], __authError: true };
@@ -470,7 +445,7 @@ export async function getLogisticsOptions(): Promise<{ success: boolean; data: L
// ===== 차량 톤수 옵션 조회 =====
export async function getVehicleTonnageOptions(): Promise<{ success: boolean; data: VehicleTonnageOption[]; error?: string; __authError?: boolean }> {
const result = await executeServerAction<VehicleTonnageOption[]>({
url: `${API_URL}/api/v1/shipments/options/vehicle-tonnage`,
url: buildApiUrl('/api/v1/shipments/options/vehicle-tonnage'),
errorMessage: '차량 톤수 옵션 조회에 실패했습니다.',
});
if (result.__authError) return { success: false, data: [], __authError: true };