feat: [outbound] 배차차량관리 목업→API 연동 전환

- mockData 제거, executePaginatedAction/executeServerAction 사용
- buildApiUrl로 쿼리 파라미터 빌드
- API 응답(snake_case) → 프론트 타입(camelCase) 변환 함수 추가
This commit is contained in:
2026-03-04 23:36:20 +09:00
parent b45c35a5e8
commit 1d3805781c

View File

@@ -1,8 +1,5 @@
/**
* 배차차량관리 서버 액션
*
* 현재: Mock 데이터 반환
* 추후: API 연동 시 serverFetch 사용
*/
'use server';
@@ -13,11 +10,9 @@ import type {
VehicleDispatchStats,
VehicleDispatchEditFormData,
} from './types';
import {
mockVehicleDispatchItems,
mockVehicleDispatchDetail,
mockVehicleDispatchStats,
} from './mockData';
import { buildApiUrl } from '@/lib/api/query-params';
import { executeServerAction } from '@/lib/api/execute-server-action';
import { executePaginatedAction } from '@/lib/api/execute-paginated-action';
// ===== 페이지네이션 타입 =====
interface PaginationMeta {
@@ -27,6 +22,59 @@ interface PaginationMeta {
total: number;
}
// ===== API 응답 → 프론트 타입 변환 =====
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function transformToListItem(data: any): VehicleDispatchItem {
const options = data.options || {};
const shipment = data.shipment || {};
return {
id: String(data.id),
dispatchNo: options.dispatch_no || `DC-${data.id}`,
shipmentNo: shipment.shipment_no || '',
lotNo: shipment.lot_no || '',
siteName: shipment.site_name || '',
orderCustomer: shipment.customer_name || '',
logisticsCompany: data.logistics_company || '',
tonnage: data.tonnage || '',
supplyAmount: options.supply_amount || 0,
vat: options.vat || 0,
totalAmount: options.total_amount || 0,
freightCostType: options.freight_cost_type || 'prepaid',
vehicleNo: data.vehicle_no || '',
driverContact: data.driver_contact || '',
writer: options.writer || '',
arrivalDateTime: data.arrival_datetime || '',
status: options.status || 'draft',
remarks: data.remarks || '',
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function transformToDetail(data: any): VehicleDispatchDetail {
const options = data.options || {};
const shipment = data.shipment || {};
return {
id: String(data.id),
dispatchNo: options.dispatch_no || `DC-${data.id}`,
shipmentNo: shipment.shipment_no || '',
lotNo: shipment.lot_no || '',
siteName: shipment.site_name || '',
orderCustomer: shipment.customer_name || '',
freightCostType: options.freight_cost_type || 'prepaid',
status: options.status || 'draft',
writer: options.writer || '',
logisticsCompany: data.logistics_company || '',
arrivalDateTime: data.arrival_datetime || '',
tonnage: data.tonnage || '',
vehicleNo: data.vehicle_no || '',
driverContact: data.driver_contact || '',
remarks: data.remarks || '',
supplyAmount: options.supply_amount || 0,
vat: options.vat || 0,
totalAmount: options.total_amount || 0,
};
}
// ===== 배차차량 목록 조회 =====
export async function getVehicleDispatches(params?: {
page?: number;
@@ -41,54 +89,18 @@ export async function getVehicleDispatches(params?: {
pagination: PaginationMeta;
error?: string;
}> {
try {
let items = [...mockVehicleDispatchItems];
// 상태 필터
if (params?.status && params.status !== 'all') {
items = items.filter((item) => item.status === params.status);
}
// 검색 필터
if (params?.search) {
const s = params.search.toLowerCase();
items = items.filter(
(item) =>
item.dispatchNo.toLowerCase().includes(s) ||
item.shipmentNo.toLowerCase().includes(s) ||
item.siteName.toLowerCase().includes(s) ||
item.orderCustomer.toLowerCase().includes(s) ||
item.vehicleNo.toLowerCase().includes(s)
);
}
// 페이지네이션
const page = params?.page || 1;
const perPage = params?.perPage || 20;
const total = items.length;
const lastPage = Math.ceil(total / perPage);
const startIndex = (page - 1) * perPage;
const paginatedItems = items.slice(startIndex, startIndex + perPage);
return {
success: true,
data: paginatedItems,
pagination: {
currentPage: page,
lastPage,
perPage,
total,
},
};
} catch (error) {
console.error('[VehicleDispatchActions] getVehicleDispatches error:', error);
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: '서버 오류가 발생했습니다.',
};
}
return executePaginatedAction({
url: buildApiUrl('/api/v1/vehicle-dispatches', {
search: params?.search,
status: params?.status !== 'all' ? params?.status : undefined,
start_date: params?.startDate,
end_date: params?.endDate,
page: params?.page,
per_page: params?.perPage,
}),
transform: transformToListItem,
errorMessage: '배차차량 목록 조회에 실패했습니다.',
});
}
// ===== 배차차량 통계 조회 =====
@@ -97,12 +109,18 @@ export async function getVehicleDispatchStats(): Promise<{
data?: VehicleDispatchStats;
error?: string;
}> {
try {
return { success: true, data: mockVehicleDispatchStats };
} catch (error) {
console.error('[VehicleDispatchActions] getVehicleDispatchStats error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
return executeServerAction<
{ prepaid_amount: number; collect_amount: number; total_amount: number },
VehicleDispatchStats
>({
url: buildApiUrl('/api/v1/vehicle-dispatches/stats'),
transform: (data) => ({
prepaidAmount: data.prepaid_amount,
collectAmount: data.collect_amount,
totalAmount: data.total_amount,
}),
errorMessage: '배차차량 통계 조회에 실패했습니다.',
});
}
// ===== 배차차량 상세 조회 =====
@@ -111,51 +129,34 @@ export async function getVehicleDispatchById(id: string): Promise<{
data?: VehicleDispatchDetail;
error?: string;
}> {
try {
// Mock: ID로 목록에서 찾아서 상세 데이터 생성
const item = mockVehicleDispatchItems.find((i) => i.id === id);
if (!item) {
// fallback으로 기본 상세 데이터 반환
return { success: true, data: { ...mockVehicleDispatchDetail, id } };
}
const detail: VehicleDispatchDetail = {
id: item.id,
dispatchNo: item.dispatchNo,
shipmentNo: item.shipmentNo,
siteName: item.siteName,
orderCustomer: item.orderCustomer,
freightCostType: item.freightCostType,
status: item.status,
writer: item.writer,
logisticsCompany: item.logisticsCompany,
arrivalDateTime: item.arrivalDateTime,
tonnage: item.tonnage,
vehicleNo: item.vehicleNo,
driverContact: item.driverContact,
remarks: item.remarks,
supplyAmount: item.supplyAmount,
vat: item.vat,
totalAmount: item.totalAmount,
};
return { success: true, data: detail };
} catch (error) {
console.error('[VehicleDispatchActions] getVehicleDispatchById error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
return executeServerAction({
url: buildApiUrl(`/api/v1/vehicle-dispatches/${id}`),
transform: transformToDetail,
errorMessage: '배차차량 상세 조회에 실패했습니다.',
});
}
// ===== 배차차량 수정 =====
export async function updateVehicleDispatch(
id: string,
_data: VehicleDispatchEditFormData
data: VehicleDispatchEditFormData
): Promise<{ success: boolean; error?: string }> {
try {
// Mock: 항상 성공 반환
return { success: true };
} catch (error) {
console.error('[VehicleDispatchActions] updateVehicleDispatch error:', error);
return { success: false, error: '서버 오류가 발생했습니다.' };
}
return executeServerAction({
url: buildApiUrl(`/api/v1/vehicle-dispatches/${id}`),
method: 'PUT',
body: {
freight_cost_type: data.freightCostType,
logistics_company: data.logisticsCompany,
arrival_datetime: data.arrivalDateTime,
tonnage: data.tonnage,
vehicle_no: data.vehicleNo,
driver_contact: data.driverContact,
remarks: data.remarks,
supply_amount: data.supplyAmount,
vat: data.vat,
total_amount: data.totalAmount,
status: undefined, // 상태는 별도로 관리
},
errorMessage: '배차차량 수정에 실패했습니다.',
});
}