/** * 품목 관리 API 클라이언트 * * Laravel 백엔드와 통신하는 API 함수들 * Next.js 15 Server Components와 Client Components 모두에서 사용 가능 */ import type { ItemMaster, CreateItemData, UpdateItemData, FetchItemsParams, ApiResponse, PaginatedResponse, BOMLine, } from '@/types/item'; // ===== 환경 변수 ===== const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'; // ===== 유틸리티 함수 ===== /** * 인증 토큰 가져오기 * TODO: 실제 인증 구현에 맞게 수정 필요 */ function getAuthToken(): string | null { // Server Component에서는 쿠키에서 자동으로 // Client Component에서는 localStorage 또는 쿠키에서 if (typeof window !== 'undefined') { return localStorage.getItem('auth_token'); } return null; } /** * Fetch 옵션 생성 */ function createFetchOptions(options: RequestInit = {}): RequestInit { const token = getAuthToken(); const headers: Record = { 'Content-Type': 'application/json', ...(options.headers as Record), }; if (token) { headers['Authorization'] = `Bearer ${token}`; } return { ...options, headers, credentials: 'include', // 쿠키 포함 }; } /** * API 에러 처리 */ async function handleApiResponse(response: Response): Promise { if (!response.ok) { const error = await response.json().catch(() => ({ message: 'API 요청 실패', })); throw new Error(error.message || `HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); return data; } // ===== 품목 CRUD ===== /** * 품목 목록 조회 * * @example * // Server Component에서 * const items = await fetchItems({ itemType: 'FG' }); * * // Client Component에서 * const [items, setItems] = useState([]); * useEffect(() => { * fetchItems().then(setItems); * }, []); */ export async function fetchItems( params?: FetchItemsParams ): Promise { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { queryParams.append(key, String(value)); } }); } const url = `${API_URL}/api/items${queryParams.toString() ? `?${queryParams}` : ''}`; const response = await fetch(url, createFetchOptions()); const data = await handleApiResponse>(response); return data.data; } /** * 품목 목록 조회 (페이지네이션) */ export async function fetchItemsPaginated( params?: FetchItemsParams ): Promise> { const queryParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { queryParams.append(key, String(value)); } }); } const url = `${API_URL}/api/items/paginated${queryParams.toString() ? `?${queryParams}` : ''}`; const response = await fetch(url, createFetchOptions()); const data = await handleApiResponse>>(response); return data.data; } /** * 품목 상세 조회 * * @param itemCode - 품목 코드 (예: "KD-FG-001") * * @example * const item = await fetchItemByCode('KD-FG-001'); */ export async function fetchItemByCode(itemCode: string): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}`, createFetchOptions() ); const data = await handleApiResponse>(response); return data.data; } /** * 품목 등록 * * @example * const newItem = await createItem({ * itemCode: 'KD-FG-001', * itemName: '스크린 제품', * itemType: 'FG', * unit: 'EA', * }); */ export async function createItem( itemData: CreateItemData ): Promise { const response = await fetch( `${API_URL}/api/items`, createFetchOptions({ method: 'POST', body: JSON.stringify(itemData), }) ); const data = await handleApiResponse>(response); return data.data; } /** * 품목 수정 * * @param itemCode - 품목 코드 * @param updates - 수정할 필드들 * * @example * const updatedItem = await updateItem('KD-FG-001', { * itemName: '스크린 제품 (수정)', * salesPrice: 150000, * }); */ export async function updateItem( itemCode: string, updates: UpdateItemData ): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}`, createFetchOptions({ method: 'PUT', body: JSON.stringify(updates), }) ); const data = await handleApiResponse>(response); return data.data; } /** * 품목 삭제 * * @param itemCode - 품목 코드 * * @example * await deleteItem('KD-FG-001'); */ export async function deleteItem(itemCode: string): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}`, createFetchOptions({ method: 'DELETE', }) ); await handleApiResponse>(response); } // ===== BOM (자재명세서) 관리 ===== /** * BOM 목록 조회 * * @param itemCode - 상위 품목 코드 */ export async function fetchBOM(itemCode: string): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}/bom`, createFetchOptions() ); const data = await handleApiResponse>(response); return data.data; } /** * BOM 계층구조 조회 (트리 형태) * * @param itemCode - 상위 품목 코드 */ export async function fetchBOMTree(itemCode: string): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}/bom/tree`, createFetchOptions() ); const data = await handleApiResponse>(response); return data.data; } /** * BOM 라인 추가 * * @param itemCode - 상위 품목 코드 * @param bomLine - BOM 라인 데이터 */ export async function addBOMLine( itemCode: string, bomLine: Omit ): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}/bom`, createFetchOptions({ method: 'POST', body: JSON.stringify(bomLine), }) ); const data = await handleApiResponse>(response); return data.data; } /** * BOM 라인 수정 * * @param itemCode - 상위 품목 코드 * @param lineId - BOM 라인 ID * @param updates - 수정할 필드들 */ export async function updateBOMLine( itemCode: string, lineId: string, updates: Partial ): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}/bom/${lineId}`, createFetchOptions({ method: 'PUT', body: JSON.stringify(updates), }) ); const data = await handleApiResponse>(response); return data.data; } /** * BOM 라인 삭제 * * @param itemCode - 상위 품목 코드 * @param lineId - BOM 라인 ID */ export async function deleteBOMLine( itemCode: string, lineId: string ): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}/bom/${lineId}`, createFetchOptions({ method: 'DELETE', }) ); await handleApiResponse>(response); } // ===== 파일 업로드 ===== /** * 파일 업로드 (시방서, 인정서, 전개도 등) * * @param itemCode - 품목 코드 * @param file - 업로드할 파일 * @param fileType - 파일 유형 (specification, certification, bending_diagram) */ export async function uploadFile( itemCode: string, file: File, fileType: 'specification' | 'certification' | 'bending_diagram' ): Promise<{ url: string; filename: string }> { const formData = new FormData(); formData.append('file', file); formData.append('type', fileType); const token = getAuthToken(); const headers: Record = {}; if (token) { headers['Authorization'] = `Bearer ${token}`; } const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}/files`, { method: 'POST', headers, body: formData, credentials: 'include', } ); const data = await handleApiResponse>(response); return data.data; } /** * 파일 삭제 * * @param itemCode - 품목 코드 * @param fileType - 파일 유형 */ export async function deleteFile( itemCode: string, fileType: 'specification' | 'certification' | 'bending_diagram' ): Promise { const response = await fetch( `${API_URL}/api/items/${encodeURIComponent(itemCode)}/files/${fileType}`, createFetchOptions({ method: 'DELETE', }) ); await handleApiResponse>(response); } // ===== 검색 및 필터 ===== /** * 품목 코드로 검색 (자동완성용) * * @param query - 검색어 * @param limit - 최대 결과 개수 */ export async function searchItemCodes( query: string, limit: number = 10 ): Promise { const response = await fetch( `${API_URL}/api/items/search/codes?q=${encodeURIComponent(query)}&limit=${limit}`, createFetchOptions() ); const data = await handleApiResponse>(response); return data.data; } /** * 품목명으로 검색 (자동완성용) * * @param query - 검색어 * @param limit - 최대 결과 개수 */ export async function searchItemNames( query: string, limit: number = 10 ): Promise> { const response = await fetch( `${API_URL}/api/items/search/names?q=${encodeURIComponent(query)}&limit=${limit}`, createFetchOptions() ); const data = await handleApiResponse>>(response); return data.data; } // ===== 유틸리티 ===== /** * 품목 코드 중복 체크 * * @param itemCode - 체크할 품목 코드 * @returns 사용 가능 여부 (true: 사용 가능, false: 중복) */ export async function checkItemCodeAvailability( itemCode: string ): Promise { const response = await fetch( `${API_URL}/api/items/check/${encodeURIComponent(itemCode)}`, createFetchOptions() ); const data = await handleApiResponse>(response); return data.data.available; } /** * 다음 품목 코드 생성 (서버에서 자동 생성) * * @param itemType - 품목 유형 * @returns 생성된 품목 코드 (예: "KD-FG-001") */ export async function generateItemCode( itemType: string ): Promise { const response = await fetch( `${API_URL}/api/items/generate-code?itemType=${itemType}`, createFetchOptions() ); const data = await handleApiResponse>(response); return data.data.itemCode; } /** * Next.js revalidation 트리거 * Server Component에서 데이터 변경 시 호출 */ export async function revalidateItems(): Promise { if (typeof window === 'undefined') { // Server Component에서만 실행 const { revalidatePath } = await import('next/cache'); revalidatePath('/items'); } }