refactor(WEB): Server Action 공통화 및 보안 강화
- executeServerAction 공통 유틸 도입으로 actions.ts 대폭 간소화 (50+개 파일) - sanitize 유틸 추가 (XSS 방지) - middleware CSP 헤더 추가 및 Open Redirect 방지 - 프록시 라우트 로깅 개발환경 한정으로 변경 - 프로덕션 불필요 console.log 제거 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,8 +18,7 @@
|
||||
'use server';
|
||||
|
||||
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import { executeServerAction } from '@/lib/api/execute-server-action';
|
||||
import type {
|
||||
ShipmentItem,
|
||||
ShipmentDetail,
|
||||
@@ -308,523 +307,177 @@ interface PaginationMeta {
|
||||
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;
|
||||
}> {
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
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);
|
||||
|
||||
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}` : ''}`,
|
||||
errorMessage: '출고 목록 조회에 실패했습니다.',
|
||||
});
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments${queryString ? `?${queryString}` : ''}`;
|
||||
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 };
|
||||
|
||||
console.log('[ShipmentActions] GET shipments:', url);
|
||||
|
||||
const { response, error } = await serverFetch(url, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[ShipmentActions] GET shipments error:', response?.status);
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
||||
error: `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
||||
error: result.message || '출고 목록 조회에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const paginatedData: ShipmentApiPaginatedResponse = result.data || {
|
||||
data: [],
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
const shipments = (paginatedData.data || []).map(transformApiToListItem);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: shipments,
|
||||
pagination: {
|
||||
currentPage: paginatedData.current_page,
|
||||
lastPage: paginatedData.last_page,
|
||||
perPage: paginatedData.per_page,
|
||||
total: paginatedData.total,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getShipments error:', error);
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
||||
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;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/stats`,
|
||||
{
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[ShipmentActions] GET stats error:', response?.status);
|
||||
return { success: false, error: `API 오류: ${response?.status}` };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
return { success: false, error: result.message || '출고 통계 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToStats(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getShipmentStats error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
export async function getShipmentStats(): Promise<{ success: boolean; data?: ShipmentStats; error?: string; __authError?: boolean }> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/shipments/stats`,
|
||||
transform: (data: ShipmentApiStatsResponse & { total_count?: number }) => transformApiToStats(data),
|
||||
errorMessage: '출고 통계 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 상태별 통계 조회 (탭용) =====
|
||||
export async function getShipmentStatsByStatus(): Promise<{
|
||||
success: boolean;
|
||||
data?: ShipmentStatusStats;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/stats-by-status`,
|
||||
{
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[ShipmentActions] GET stats-by-status error:', response?.status);
|
||||
return { success: false, error: `API 오류: ${response?.status}` };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
return { success: false, error: result.message || '상태별 통계 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToStatsByStatus(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getShipmentStatsByStatus error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
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`,
|
||||
transform: (data: ShipmentApiStatsByStatusResponse) => transformApiToStatsByStatus(data),
|
||||
errorMessage: '상태별 통계 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 출고 상세 조회 =====
|
||||
export async function getShipmentById(id: string): Promise<{
|
||||
success: boolean;
|
||||
data?: ShipmentDetail;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/${id}`,
|
||||
{
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.error('[ShipmentActions] GET shipment error:', response?.status);
|
||||
return { success: false, error: `API 오류: ${response?.status}` };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
return { success: false, error: result.message || '출고 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getShipmentById error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
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}`,
|
||||
transform: (data: ShipmentApiData) => transformApiToDetail(data),
|
||||
errorMessage: '출고 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 출고 등록 =====
|
||||
export async function createShipment(
|
||||
data: ShipmentCreateFormData
|
||||
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const apiData = transformCreateFormToApi(data);
|
||||
console.log('[ShipmentActions] POST shipment request:', apiData);
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '출고 등록에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[ShipmentActions] POST shipment response:', result);
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '출고 등록에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] createShipment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const apiData = transformCreateFormToApi(data);
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/shipments`,
|
||||
method: 'POST',
|
||||
body: apiData,
|
||||
transform: (d: ShipmentApiData) => transformApiToDetail(d),
|
||||
errorMessage: '출고 등록에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 출고 수정 =====
|
||||
export async function updateShipment(
|
||||
id: string,
|
||||
data: Partial<ShipmentEditFormData>
|
||||
id: string, data: Partial<ShipmentEditFormData>
|
||||
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const apiData = transformEditFormToApi(data);
|
||||
console.log('[ShipmentActions] PUT shipment request:', apiData);
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '출고 수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[ShipmentActions] PUT shipment response:', result);
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '출고 수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] updateShipment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const apiData = transformEditFormToApi(data);
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/shipments/${id}`,
|
||||
method: 'PUT',
|
||||
body: apiData,
|
||||
transform: (d: ShipmentApiData) => transformApiToDetail(d),
|
||||
errorMessage: '출고 수정에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 출고 상태 변경 =====
|
||||
export async function updateShipmentStatus(
|
||||
id: string,
|
||||
status: ShipmentStatus,
|
||||
id: string, status: ShipmentStatus,
|
||||
additionalData?: {
|
||||
loadingTime?: string;
|
||||
loadingCompletedAt?: string;
|
||||
vehicleNo?: string;
|
||||
driverName?: string;
|
||||
driverContact?: string;
|
||||
confirmedArrival?: string;
|
||||
loadingTime?: string; loadingCompletedAt?: string; vehicleNo?: string;
|
||||
driverName?: string; driverContact?: string; confirmedArrival?: string;
|
||||
}
|
||||
): Promise<{ success: boolean; data?: ShipmentDetail; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const apiData: Record<string, unknown> = { status };
|
||||
if (additionalData?.loadingTime) apiData.loading_time = additionalData.loadingTime;
|
||||
if (additionalData?.loadingCompletedAt) apiData.loading_completed_at = additionalData.loadingCompletedAt;
|
||||
if (additionalData?.vehicleNo) apiData.vehicle_no = additionalData.vehicleNo;
|
||||
if (additionalData?.driverName) apiData.driver_name = additionalData.driverName;
|
||||
if (additionalData?.driverContact) apiData.driver_contact = additionalData.driverContact;
|
||||
if (additionalData?.confirmedArrival) apiData.confirmed_arrival = additionalData.confirmedArrival;
|
||||
const apiData: Record<string, unknown> = { status };
|
||||
if (additionalData?.loadingTime) apiData.loading_time = additionalData.loadingTime;
|
||||
if (additionalData?.loadingCompletedAt) apiData.loading_completed_at = additionalData.loadingCompletedAt;
|
||||
if (additionalData?.vehicleNo) apiData.vehicle_no = additionalData.vehicleNo;
|
||||
if (additionalData?.driverName) apiData.driver_name = additionalData.driverName;
|
||||
if (additionalData?.driverContact) apiData.driver_contact = additionalData.driverContact;
|
||||
if (additionalData?.confirmedArrival) apiData.confirmed_arrival = additionalData.confirmedArrival;
|
||||
|
||||
console.log('[ShipmentActions] PATCH status request:', apiData);
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/${id}/status`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '상태 변경에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[ShipmentActions] PATCH status response:', result);
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '상태 변경에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToDetail(result.data) };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] updateShipmentStatus error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/shipments/${id}/status`,
|
||||
method: 'PATCH',
|
||||
body: apiData,
|
||||
transform: (d: ShipmentApiData) => transformApiToDetail(d),
|
||||
errorMessage: '상태 변경에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, data: result.data, error: result.error };
|
||||
}
|
||||
|
||||
// ===== 출고 삭제 =====
|
||||
export async function deleteShipment(
|
||||
id: string
|
||||
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/${id}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '출고 삭제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[ShipmentActions] DELETE shipment response:', result);
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '출고 삭제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] deleteShipment error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
export async function deleteShipment(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
||||
const result = await executeServerAction({
|
||||
url: `${API_URL}/api/v1/shipments/${id}`,
|
||||
method: 'DELETE',
|
||||
errorMessage: '출고 삭제에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, __authError: true };
|
||||
return { success: result.success, error: result.error };
|
||||
}
|
||||
|
||||
// ===== LOT 옵션 조회 =====
|
||||
export async function getLotOptions(): Promise<{
|
||||
success: boolean;
|
||||
data: LotOption[];
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/options/lots`,
|
||||
{
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, data: [], error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[ShipmentActions] GET lot options error:', response?.status);
|
||||
return { success: false, data: [], error: `API 오류: ${response?.status}` };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, data: [], error: result.message || 'LOT 옵션 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: result.data || [] };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getLotOptions error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
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`,
|
||||
errorMessage: 'LOT 옵션 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, data: [], __authError: true };
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
|
||||
// ===== 물류사 옵션 조회 =====
|
||||
export async function getLogisticsOptions(): Promise<{
|
||||
success: boolean;
|
||||
data: LogisticsOption[];
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/options/logistics`,
|
||||
{
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, data: [], error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[ShipmentActions] GET logistics options error:', response?.status);
|
||||
return { success: false, data: [], error: `API 오류: ${response?.status}` };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, data: [], error: result.message || '물류사 옵션 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: result.data || [] };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getLogisticsOptions error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
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`,
|
||||
errorMessage: '물류사 옵션 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, data: [], __authError: true };
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
|
||||
// ===== 차량 톤수 옵션 조회 =====
|
||||
export async function getVehicleTonnageOptions(): Promise<{
|
||||
success: boolean;
|
||||
data: VehicleTonnageOption[];
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/shipments/options/vehicle-tonnage`,
|
||||
{
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, data: [], error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[ShipmentActions] GET vehicle tonnage options error:', response?.status);
|
||||
return { success: false, data: [], error: `API 오류: ${response?.status}` };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
return { success: false, data: [], error: result.message || '차량 톤수 옵션 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: result.data || [] };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[ShipmentActions] getVehicleTonnageOptions error:', error);
|
||||
return { success: false, data: [], error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
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`,
|
||||
errorMessage: '차량 톤수 옵션 조회에 실패했습니다.',
|
||||
});
|
||||
if (result.__authError) return { success: false, data: [], __authError: true };
|
||||
return { success: result.success, data: result.data || [], error: result.error };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user