feat(orders): Phase 2 - Frontend API 연동 완료
- actions.ts 생성: Server Actions 패턴으로 Order API 클라이언트 구현 - getOrders, getOrderById, createOrder, updateOrder, deleteOrder(s) - updateOrderStatus, getOrderStats - API snake_case → Frontend camelCase 변환 - 상태 매핑 (DRAFT→order_registered 등) - 목록 페이지(page.tsx): - SAMPLE_ORDERS 제거, API 연동 state 추가 - loadData() 함수로 API 호출 - 삭제/일괄삭제 API 연동 - 상세 페이지([id]/page.tsx): - SAMPLE_ITEMS/ORDERS 제거 - getOrderById, updateOrderStatus API 연동 - 수정 페이지([id]/edit/page.tsx): - SAMPLE_ORDER 제거 - getOrderById, updateOrder API 연동 - 등록 페이지(new/page.tsx): - createOrder API 연동
This commit is contained in:
630
src/components/orders/actions.ts
Normal file
630
src/components/orders/actions.ts
Normal file
@@ -0,0 +1,630 @@
|
||||
'use server';
|
||||
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
// ============================================================================
|
||||
// API 타입 정의
|
||||
// ============================================================================
|
||||
|
||||
interface ApiOrder {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
quote_id: number | null;
|
||||
order_no: string;
|
||||
order_type_code: string;
|
||||
status_code: string;
|
||||
category_code: string | null;
|
||||
client_id: number | null;
|
||||
client_name: string | null;
|
||||
client_contact: string | null;
|
||||
site_name: string | null;
|
||||
quantity: number;
|
||||
supply_amount: number;
|
||||
tax_amount: number;
|
||||
total_amount: number;
|
||||
discount_rate: number;
|
||||
discount_amount: number;
|
||||
delivery_date: string | null;
|
||||
delivery_method_code: string | null;
|
||||
received_at: string | null;
|
||||
memo: string | null;
|
||||
remarks: string | null;
|
||||
note: string | null;
|
||||
created_by: number | null;
|
||||
updated_by: number | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
client?: ApiClient | null;
|
||||
items?: ApiOrderItem[];
|
||||
quote?: ApiQuote | null;
|
||||
}
|
||||
|
||||
interface ApiOrderItem {
|
||||
id: number;
|
||||
order_id: number;
|
||||
item_id: number | null;
|
||||
item_name: string;
|
||||
specification: string | null;
|
||||
quantity: number;
|
||||
unit: string | null;
|
||||
unit_price: number;
|
||||
supply_amount: number;
|
||||
tax_amount: number;
|
||||
total_amount: number;
|
||||
sort_order: number;
|
||||
}
|
||||
|
||||
interface ApiClient {
|
||||
id: number;
|
||||
name: string;
|
||||
business_no?: string;
|
||||
representative?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
}
|
||||
|
||||
interface ApiQuote {
|
||||
id: number;
|
||||
quote_no: string;
|
||||
site_name: string | null;
|
||||
}
|
||||
|
||||
interface ApiOrderStats {
|
||||
total: number;
|
||||
draft: number;
|
||||
confirmed: number;
|
||||
in_progress: number;
|
||||
completed: number;
|
||||
cancelled: number;
|
||||
total_amount: number;
|
||||
confirmed_amount: number;
|
||||
}
|
||||
|
||||
interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
interface PaginatedResponse<T> {
|
||||
current_page: number;
|
||||
data: T[];
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Frontend 타입 정의
|
||||
// ============================================================================
|
||||
|
||||
// 수주 상태 타입 (API와 매핑)
|
||||
export type OrderStatus =
|
||||
| 'order_registered' // DRAFT
|
||||
| 'order_confirmed' // CONFIRMED
|
||||
| 'production_ordered' // IN_PROGRESS
|
||||
| 'in_production' // IN_PROGRESS (세부)
|
||||
| 'rework' // IN_PROGRESS (세부)
|
||||
| 'work_completed' // IN_PROGRESS (세부)
|
||||
| 'shipped' // COMPLETED
|
||||
| 'cancelled'; // CANCELLED
|
||||
|
||||
export interface Order {
|
||||
id: string;
|
||||
lotNumber: string; // order_no
|
||||
quoteNumber: string; // quote.quote_no
|
||||
quoteId?: number;
|
||||
orderDate: string; // received_at
|
||||
client: string; // client_name
|
||||
clientId?: number;
|
||||
siteName: string; // site_name
|
||||
status: OrderStatus;
|
||||
statusCode: string; // 원본 status_code
|
||||
expectedShipDate?: string; // delivery_date
|
||||
deliveryMethod?: string; // delivery_method_code
|
||||
amount: number; // total_amount
|
||||
supplyAmount: number;
|
||||
taxAmount: number;
|
||||
itemCount: number; // items.length
|
||||
hasReceivable?: boolean; // 미수 여부 (추후 구현)
|
||||
memo?: string;
|
||||
remarks?: string;
|
||||
note?: string;
|
||||
items?: OrderItem[];
|
||||
}
|
||||
|
||||
export interface OrderItem {
|
||||
id: string;
|
||||
itemId?: number;
|
||||
itemName: string;
|
||||
specification?: string;
|
||||
quantity: number;
|
||||
unit?: string;
|
||||
unitPrice: number;
|
||||
supplyAmount: number;
|
||||
taxAmount: number;
|
||||
totalAmount: number;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export interface OrderFormData {
|
||||
orderTypeCode?: string;
|
||||
categoryCode?: string;
|
||||
clientId?: number;
|
||||
clientName?: string;
|
||||
clientContact?: string;
|
||||
siteName?: string;
|
||||
supplyAmount?: number;
|
||||
taxAmount?: number;
|
||||
totalAmount?: number;
|
||||
discountRate?: number;
|
||||
discountAmount?: number;
|
||||
deliveryDate?: string;
|
||||
deliveryMethodCode?: string;
|
||||
receivedAt?: string;
|
||||
memo?: string;
|
||||
remarks?: string;
|
||||
note?: string;
|
||||
items?: OrderItemFormData[];
|
||||
}
|
||||
|
||||
export interface OrderItemFormData {
|
||||
itemId?: number;
|
||||
itemName: string;
|
||||
specification?: string;
|
||||
quantity: number;
|
||||
unit?: string;
|
||||
unitPrice: number;
|
||||
}
|
||||
|
||||
export interface OrderStats {
|
||||
total: number;
|
||||
draft: number;
|
||||
confirmed: number;
|
||||
inProgress: number;
|
||||
completed: number;
|
||||
cancelled: number;
|
||||
totalAmount: number;
|
||||
confirmedAmount: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 상태 매핑
|
||||
// ============================================================================
|
||||
|
||||
const API_TO_FRONTEND_STATUS: Record<string, OrderStatus> = {
|
||||
'DRAFT': 'order_registered',
|
||||
'CONFIRMED': 'order_confirmed',
|
||||
'IN_PROGRESS': 'production_ordered',
|
||||
'COMPLETED': 'shipped',
|
||||
'CANCELLED': 'cancelled',
|
||||
};
|
||||
|
||||
const FRONTEND_TO_API_STATUS: Record<OrderStatus, string> = {
|
||||
'order_registered': 'DRAFT',
|
||||
'order_confirmed': 'CONFIRMED',
|
||||
'production_ordered': 'IN_PROGRESS',
|
||||
'in_production': 'IN_PROGRESS',
|
||||
'rework': 'IN_PROGRESS',
|
||||
'work_completed': 'IN_PROGRESS',
|
||||
'shipped': 'COMPLETED',
|
||||
'cancelled': 'CANCELLED',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 데이터 변환 함수
|
||||
// ============================================================================
|
||||
|
||||
function transformApiToFrontend(apiData: ApiOrder): Order {
|
||||
return {
|
||||
id: String(apiData.id),
|
||||
lotNumber: apiData.order_no,
|
||||
quoteNumber: apiData.quote?.quote_no || '',
|
||||
quoteId: apiData.quote_id ?? undefined,
|
||||
orderDate: apiData.received_at || apiData.created_at.split('T')[0],
|
||||
client: apiData.client_name || apiData.client?.name || '',
|
||||
clientId: apiData.client_id ?? undefined,
|
||||
siteName: apiData.site_name || '',
|
||||
status: API_TO_FRONTEND_STATUS[apiData.status_code] || 'order_registered',
|
||||
statusCode: apiData.status_code,
|
||||
expectedShipDate: apiData.delivery_date ?? undefined,
|
||||
deliveryMethod: apiData.delivery_method_code ?? undefined,
|
||||
amount: apiData.total_amount,
|
||||
supplyAmount: apiData.supply_amount,
|
||||
taxAmount: apiData.tax_amount,
|
||||
itemCount: apiData.items?.length || 0,
|
||||
hasReceivable: false, // 추후 구현
|
||||
memo: apiData.memo ?? undefined,
|
||||
remarks: apiData.remarks ?? undefined,
|
||||
note: apiData.note ?? undefined,
|
||||
items: apiData.items?.map(transformItemApiToFrontend),
|
||||
};
|
||||
}
|
||||
|
||||
function transformItemApiToFrontend(apiItem: ApiOrderItem): OrderItem {
|
||||
return {
|
||||
id: String(apiItem.id),
|
||||
itemId: apiItem.item_id ?? undefined,
|
||||
itemName: apiItem.item_name,
|
||||
specification: apiItem.specification ?? undefined,
|
||||
quantity: apiItem.quantity,
|
||||
unit: apiItem.unit ?? undefined,
|
||||
unitPrice: apiItem.unit_price,
|
||||
supplyAmount: apiItem.supply_amount,
|
||||
taxAmount: apiItem.tax_amount,
|
||||
totalAmount: apiItem.total_amount,
|
||||
sortOrder: apiItem.sort_order,
|
||||
};
|
||||
}
|
||||
|
||||
function transformFrontendToApi(data: OrderFormData): Record<string, unknown> {
|
||||
return {
|
||||
order_type_code: data.orderTypeCode || 'ORDER',
|
||||
category_code: data.categoryCode || null,
|
||||
client_id: data.clientId || null,
|
||||
client_name: data.clientName || null,
|
||||
client_contact: data.clientContact || null,
|
||||
site_name: data.siteName || null,
|
||||
supply_amount: data.supplyAmount || 0,
|
||||
tax_amount: data.taxAmount || 0,
|
||||
total_amount: data.totalAmount || 0,
|
||||
discount_rate: data.discountRate || 0,
|
||||
discount_amount: data.discountAmount || 0,
|
||||
delivery_date: data.deliveryDate || null,
|
||||
delivery_method_code: data.deliveryMethodCode || null,
|
||||
received_at: data.receivedAt || null,
|
||||
memo: data.memo || null,
|
||||
remarks: data.remarks || null,
|
||||
note: data.note || null,
|
||||
items: data.items?.map((item) => ({
|
||||
item_id: item.itemId || null,
|
||||
item_name: item.itemName,
|
||||
specification: item.specification || null,
|
||||
quantity: item.quantity,
|
||||
unit: item.unit || null,
|
||||
unit_price: item.unitPrice,
|
||||
})) || [],
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// API 함수
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* 수주 목록 조회
|
||||
*/
|
||||
export async function getOrders(params?: {
|
||||
page?: number;
|
||||
size?: number;
|
||||
q?: string;
|
||||
status?: string;
|
||||
order_type?: string;
|
||||
client_id?: number;
|
||||
date_from?: string;
|
||||
date_to?: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
data?: { items: Order[]; total: number; page: number; totalPages: number };
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
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) {
|
||||
// Frontend status를 API status로 변환
|
||||
const apiStatus = FRONTEND_TO_API_STATUS[params.status as OrderStatus];
|
||||
if (apiStatus) searchParams.set('status', apiStatus);
|
||||
}
|
||||
if (params?.order_type) searchParams.set('order_type', params.order_type);
|
||||
if (params?.client_id) searchParams.set('client_id', String(params.client_id));
|
||||
if (params?.date_from) searchParams.set('date_from', params.date_from);
|
||||
if (params?.date_to) searchParams.set('date_to', params.date_to);
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders?${searchParams.toString()}`,
|
||||
{ method: 'GET', cache: 'no-store' }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '목록 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<PaginatedResponse<ApiOrder>> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '목록 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
items: result.data.data.map(transformApiToFrontend),
|
||||
total: result.data.total,
|
||||
page: result.data.current_page,
|
||||
totalPages: result.data.last_page,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[getOrders] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주 상세 조회
|
||||
*/
|
||||
export async function getOrderById(id: string): Promise<{
|
||||
success: boolean;
|
||||
data?: Order;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${id}`,
|
||||
{ method: 'GET', cache: 'no-store' }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<ApiOrder> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
console.error('[getOrderById] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주 생성
|
||||
*/
|
||||
export async function createOrder(data: OrderFormData): Promise<{
|
||||
success: boolean;
|
||||
data?: Order;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const apiData = transformFrontendToApi(data);
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders`,
|
||||
{ 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: ApiResponse<ApiOrder> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '등록에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
console.error('[createOrder] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주 수정
|
||||
*/
|
||||
export async function updateOrder(id: string, data: OrderFormData): Promise<{
|
||||
success: boolean;
|
||||
data?: Order;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const apiData = transformFrontendToApi(data);
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${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: ApiResponse<ApiOrder> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
console.error('[updateOrder] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주 삭제
|
||||
*/
|
||||
export async function deleteOrder(id: string): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${id}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '삭제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<string> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '삭제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('[deleteOrder] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주 상태 변경
|
||||
*/
|
||||
export async function updateOrderStatus(id: string, status: OrderStatus): Promise<{
|
||||
success: boolean;
|
||||
data?: Order;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const apiStatus = FRONTEND_TO_API_STATUS[status];
|
||||
if (!apiStatus) {
|
||||
return { success: false, error: '유효하지 않은 상태입니다.' };
|
||||
}
|
||||
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/${id}/status`,
|
||||
{ method: 'PATCH', body: JSON.stringify({ status: apiStatus }) }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '상태 변경에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<ApiOrder> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '상태 변경에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: transformApiToFrontend(result.data) };
|
||||
} catch (error) {
|
||||
console.error('[updateOrderStatus] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주 통계 조회
|
||||
*/
|
||||
export async function getOrderStats(): Promise<{
|
||||
success: boolean;
|
||||
data?: OrderStats;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/orders/stats`,
|
||||
{ method: 'GET', cache: 'no-store' }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '통계 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiResponse<ApiOrderStats> = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '통계 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
total: result.data.total,
|
||||
draft: result.data.draft,
|
||||
confirmed: result.data.confirmed,
|
||||
inProgress: result.data.in_progress,
|
||||
completed: result.data.completed,
|
||||
cancelled: result.data.cancelled,
|
||||
totalAmount: result.data.total_amount,
|
||||
confirmedAmount: result.data.confirmed_amount,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[getOrderStats] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주 일괄 삭제
|
||||
*/
|
||||
export async function deleteOrders(ids: string[]): Promise<{
|
||||
success: boolean;
|
||||
deletedCount?: number;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
// 순차적으로 삭제 (API에 bulk delete가 없으므로)
|
||||
let deletedCount = 0;
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const id of ids) {
|
||||
const result = await deleteOrder(id);
|
||||
if (result.success) {
|
||||
deletedCount++;
|
||||
} else {
|
||||
errors.push(result.error || `ID ${id} 삭제 실패`);
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedCount === 0 && errors.length > 0) {
|
||||
return { success: false, error: errors[0] };
|
||||
}
|
||||
|
||||
return { success: true, deletedCount };
|
||||
} catch (error) {
|
||||
console.error('[deleteOrders] Error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,26 @@
|
||||
/**
|
||||
* 수주 관련 컴포넌트
|
||||
* 수주 관련 컴포넌트 및 API 함수
|
||||
*/
|
||||
|
||||
// API Actions
|
||||
export {
|
||||
getOrders,
|
||||
getOrderById,
|
||||
createOrder,
|
||||
updateOrder,
|
||||
deleteOrder,
|
||||
deleteOrders,
|
||||
updateOrderStatus,
|
||||
getOrderStats,
|
||||
type Order,
|
||||
type OrderItem as OrderItemApi,
|
||||
type OrderFormData as OrderApiFormData,
|
||||
type OrderItemFormData,
|
||||
type OrderStats,
|
||||
type OrderStatus,
|
||||
} from "./actions";
|
||||
|
||||
// Components
|
||||
export { OrderRegistration, type OrderFormData } from "./OrderRegistration";
|
||||
export { QuotationSelectDialog, type QuotationForSelect, type QuotationItem } from "./QuotationSelectDialog";
|
||||
export { ItemAddDialog, type OrderItem } from "./ItemAddDialog";
|
||||
|
||||
Reference in New Issue
Block a user