- BOM 항목 추가/수정/삭제 시 섹션탭 즉시 반영 - 섹션 복제 시 UI 즉시 업데이트 (null vs undefined 이슈 해결) - 항목 수정 기능 추가 (useTemplateManagement) - 실시간 동기화 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
50 KiB
품목기준관리 API 연동 체크리스트
작성일: 2025-11-20
목적: LocalStorage → 백엔드 API 실시간 저장 방식 전환
예상 작업 시간: 6-8시간
API 문서: claudedocs/itemmaster.txt
🌐 API 엔드포인트 레퍼런스 (빠른 참조)
Base URL
http://api.sam.kr/api/v1
또는
process.env.NEXT_PUBLIC_API_BASE_URL
인증
- Header:
X-API-KEY,Authorization: Bearer {token} - 환경변수:
NEXT_PUBLIC_API_KEY
주요 엔드포인트
초기화
GET /item-master/init- 전체 초기 데이터 로드
페이지 관리
GET /item-master/pages- 페이지 목록 조회POST /item-master/pages- 페이지 생성{ "page_name": "string", "item_type": "FG|PT|SM|RM|CS", "absolute_path": "string?" }PUT /item-master/pages/{id}- 페이지 수정{ "page_name": "string", "absolute_path": "string?" }DELETE /item-master/pages/{id}- 페이지 삭제 (Cascade)
섹션 관리
POST /item-master/pages/{pageId}/sections- 섹션 생성{ "title": "string", "type": "fields|bom" }PUT /item-master/sections/{id}- 섹션 수정DELETE /item-master/sections/{id}- 섹션 삭제PUT /item-master/pages/{pageId}/sections/reorder- 순서 변경{ "items": [{"id": 1, "order_no": 0}] }
필드 관리
POST /item-master/sections/{sectionId}/fields- 필드 생성{ "field_name": "string", "field_type": "textbox|number|dropdown|checkbox|date|textarea", "is_required": boolean, "placeholder": "string?", "options": object?, "validation_rules": object? }PUT /item-master/fields/{id}- 필드 수정DELETE /item-master/fields/{id}- 필드 삭제PUT /item-master/sections/{sectionId}/fields/reorder- 순서 변경
BOM 관리
POST /item-master/sections/{sectionId}/bom-items- BOM 항목 생성{ "item_name": "string", "item_code": "string?", "quantity": number, "unit": "string?", "unit_price": number?, "spec": "string?" }PUT /item-master/bom-items/{id}- BOM 항목 수정DELETE /item-master/bom-items/{id}- BOM 항목 삭제
템플릿 관리
GET /item-master/section-templates- 템플릿 목록POST /item-master/section-templates- 템플릿 생성PUT /item-master/section-templates/{id}- 템플릿 수정DELETE /item-master/section-templates/{id}- 템플릿 삭제
마스터 필드
GET /item-master/master-fields- 마스터 필드 목록POST /item-master/master-fields- 마스터 필드 생성PUT /item-master/master-fields/{id}- 마스터 필드 수정DELETE /item-master/master-fields/{id}- 마스터 필드 삭제
커스텀 탭
GET /item-master/custom-tabs- 커스텀 탭 목록POST /item-master/custom-tabs- 커스텀 탭 생성PUT /item-master/custom-tabs/{id}- 커스텀 탭 수정DELETE /item-master/custom-tabs/{id}- 커스텀 탭 삭제PUT /item-master/custom-tabs/reorder- 순서 변경
단위 옵션
GET /item-master/unit-options- 단위 옵션 목록POST /item-master/unit-options- 단위 옵션 생성{ "label": "개", "value": "EA" }DELETE /item-master/unit-options/{id}- 단위 옵션 삭제
응답 형식
// 성공 응답
{
"success": true,
"message": "string",
"data": T // 요청한 데이터
}
// 에러 응답
{
"success": false,
"message": "string",
"errors": { // Validation 에러 시
"field_name": ["error message"]
}
}
주요 필드명 (snake_case)
page_name,item_type,absolute_path,is_active,order_nosection_id,section_name,section_typefield_name,field_type,is_required,default_valuecreated_at,updated_at,created_by,updated_by,tenant_id
📊 전체 진행 상황
전체 진행률: 63/69 (91%)
Phase 0 (준비): 22/22 (100%) ✅ 완료! (필수 20개 + 선택 2개)
Phase 1 (초기화): 8/8 (100%) ✅ 완료!
Phase 2 (CRUD): 33/33 (100%) ✅ 완료! 🎉
Phase 3 (정리): 0/6 (0%) ⏳ API 필요
작업 우선순위:
- 🟢 Phase 0: 백엔드 API 없이 지금 바로 진행 가능
- 🟡 Phase 1-3: 백엔드 API 구현 완료 후 진행
Phase 0: API 대기 전 준비 작업 (지금 가능) ⭐
📁 1. 파일 구조 준비 (3개)
-
1.1
src/lib/api/item-master.tsAPI Client 파일 생성 ✅- 목적: 모든 API 호출 함수 중앙 관리
- 예상 시간: 30분
- 완료 조건: 빈 파일에 기본 구조 작성
- 완료일: 2025-11-20
// src/lib/api/item-master.ts import { getAuthHeaders } from './auth-headers'; const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://api.sam.kr/api/v1'; export const itemMasterApi = { // 초기화 init: async () => { // TODO: API 연동 시 구현 }, // 페이지 관리 pages: { list: async () => { /* TODO */ }, create: async (data: any) => { /* TODO */ }, update: async (id: number, data: any) => { /* TODO */ }, delete: async (id: number) => { /* TODO */ }, reorder: async (orders: any[]) => { /* TODO */ }, }, // 섹션 관리 sections: { create: async (pageId: number, data: any) => { /* TODO */ }, update: async (id: number, data: any) => { /* TODO */ }, delete: async (id: number) => { /* TODO */ }, reorder: async (pageId: number, orders: any[]) => { /* TODO */ }, }, // 필드 관리 fields: { create: async (sectionId: number, data: any) => { /* TODO */ }, update: async (id: number, data: any) => { /* TODO */ }, delete: async (id: number) => { /* TODO */ }, reorder: async (sectionId: number, orders: any[]) => { /* TODO */ }, }, // BOM 관리 bomItems: { create: async (sectionId: number, data: any) => { /* TODO */ }, update: async (id: number, data: any) => { /* TODO */ }, delete: async (id: number) => { /* TODO */ }, }, // 섹션 템플릿 templates: { list: async () => { /* TODO */ }, create: async (data: any) => { /* TODO */ }, update: async (id: number, data: any) => { /* TODO */ }, delete: async (id: number) => { /* TODO */ }, }, // 마스터 필드 masterFields: { list: async () => { /* TODO */ }, create: async (data: any) => { /* TODO */ }, update: async (id: number, data: any) => { /* TODO */ }, delete: async (id: number) => { /* TODO */ }, }, // 커스텀 탭 customTabs: { list: async () => { /* TODO */ }, create: async (data: any) => { /* TODO */ }, update: async (id: number, data: any) => { /* TODO */ }, delete: async (id: number) => { /* TODO */ }, reorder: async (orders: any[]) => { /* TODO */ }, updateColumns: async (id: number, columns: any[]) => { /* TODO */ }, }, // 단위 옵션 units: { list: async () => { /* TODO */ }, create: async (data: any) => { /* TODO */ }, delete: async (id: number) => { /* TODO */ }, }, }; -
1.2
src/lib/api/auth-headers.ts인증 헤더 유틸 생성 ✅- 목적: 모든 API 요청에 인증 헤더 자동 추가
- 예상 시간: 15분
- 완료 조건: getAuthHeaders 함수 구현
- 완료일: 2025-11-20
// src/lib/api/auth-headers.ts export const getAuthHeaders = (): HeadersInit => { // TODO: 실제 토큰 가져오기 로직 구현 필요 // AuthContext나 쿠키에서 토큰 추출 const token = typeof window !== 'undefined' ? document.cookie.split('; ').find(row => row.startsWith('auth_token='))?.split('=')[1] : ''; return { 'Content-Type': 'application/json', 'X-API-KEY': process.env.NEXT_PUBLIC_API_KEY || '', 'Authorization': token ? `Bearer ${token}` : '', }; }; export const getMultipartHeaders = (): HeadersInit => { const token = typeof window !== 'undefined' ? document.cookie.split('; ').find(row => row.startsWith('auth_token='))?.split('=')[1] : ''; return { 'X-API-KEY': process.env.NEXT_PUBLIC_API_KEY || '', 'Authorization': token ? `Bearer ${token}` : '', // Content-Type은 자동 설정 (multipart/form-data) }; }; -
1.3
src/types/item-master-api.tsAPI 타입 정의 파일 생성 ✅- 목적: Request/Response 타입 분리 및 명확화
- 예상 시간: 45분
- 완료 조건: 모든 API 엔드포인트의 Request/Response 타입 정의
- 완료일: 2025-11-20
// src/types/item-master-api.ts // ============================================ // 공통 타입 // ============================================ export interface ApiResponse<T> { success: boolean; message: string; data: T; } export interface PaginationMeta { current_page: number; per_page: number; total: number; last_page: number; } // ============================================ // 초기화 API // ============================================ export interface InitResponse { pages: ItemPageResponse[]; sections: ItemSectionResponse[]; fields: ItemFieldResponse[]; bomItems: BomItemResponse[]; templates: SectionTemplateResponse[]; masterFields: MasterFieldResponse[]; customTabs: CustomTabResponse[]; units: UnitOptionResponse[]; } // ============================================ // 페이지 관리 // ============================================ export interface ItemPageRequest { page_name: string; item_type: 'FG' | 'PT' | 'SM' | 'RM' | 'CS'; description?: string; is_active?: boolean; } export interface ItemPageResponse { id: number; tenant_id: number; page_name: string; item_type: string; description: string | null; absolute_path: string; is_active: boolean; order_no: number; created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; sections?: ItemSectionResponse[]; // Nested 조회 시 포함 } export interface PageReorderRequest { page_orders: Array<{ id: number; order_no: number; }>; } // ============================================ // 섹션 관리 // ============================================ export interface ItemSectionRequest { section_name: string; section_type: 'BASIC' | 'BOM' | 'CUSTOM'; description?: string; is_collapsible?: boolean; is_default_open?: boolean; } export interface ItemSectionResponse { id: number; tenant_id: number; page_id: number; section_template_id: number | null; section_name: string; section_type: string; description: string | null; order_no: number; is_collapsible: boolean; is_default_open: boolean; created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; fields?: ItemFieldResponse[]; // Nested 조회 시 포함 bomItems?: BomItemResponse[]; // Nested 조회 시 포함 } export interface SectionReorderRequest { section_orders: Array<{ id: number; order_no: number; }>; } // ============================================ // 필드 관리 // ============================================ export interface ItemFieldRequest { field_name: string; field_type: 'TEXT' | 'NUMBER' | 'DATE' | 'SELECT' | 'TEXTAREA' | 'CHECKBOX'; is_required?: boolean; placeholder?: string; default_value?: string; validation_rules?: Record<string, any>; properties?: Record<string, any>; } export interface ItemFieldResponse { id: number; tenant_id: number; section_id: number; master_field_id: number | null; field_name: string; field_type: string; order_no: number; is_required: boolean; placeholder: string | null; default_value: string | null; validation_rules: Record<string, any> | null; properties: Record<string, any> | null; created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; } export interface FieldReorderRequest { field_orders: Array<{ id: number; order_no: number; }>; } // ============================================ // BOM 관리 // ============================================ export interface BomItemRequest { item_code?: string; item_name: string; quantity: number; unit?: string; unit_price?: number; total_price?: number; spec?: string; note?: string; } export interface BomItemResponse { id: number; tenant_id: number; section_id: number; item_code: string | null; item_name: string; quantity: number; unit: string | null; unit_price: number | null; total_price: number | null; spec: string | null; note: string | null; created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; } // ============================================ // 섹션 템플릿 // ============================================ export interface SectionTemplateRequest { template_name: string; section_type: 'BASIC' | 'BOM' | 'CUSTOM'; description?: string; default_fields?: Record<string, any>; } export interface SectionTemplateResponse { id: number; tenant_id: number; template_name: string; section_type: string; description: string | null; default_fields: Record<string, any> | null; created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; } // ============================================ // 마스터 필드 // ============================================ export interface MasterFieldRequest { field_name: string; field_type: 'TEXT' | 'NUMBER' | 'DATE' | 'SELECT' | 'TEXTAREA' | 'CHECKBOX'; category?: string; description?: string; default_validation?: Record<string, any>; default_properties?: Record<string, any>; } export interface MasterFieldResponse { id: number; tenant_id: number; field_name: string; field_type: string; category: string | null; description: string | null; default_validation: Record<string, any> | null; default_properties: Record<string, any> | null; created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; } // ============================================ // 커스텀 탭 // ============================================ export interface CustomTabRequest { tab_name: string; tab_key: string; description?: string; is_active?: boolean; } export interface CustomTabResponse { id: number; tenant_id: number; tab_name: string; tab_key: string; description: string | null; order_no: number; is_active: boolean; created_by: number | null; updated_by: number | null; created_at: string; updated_at: string; columns?: TabColumnResponse[]; // Nested 조회 시 포함 } export interface TabReorderRequest { tab_orders: Array<{ id: number; order_no: number; }>; } export interface TabColumnUpdateRequest { columns: Array<{ column_key: string; column_name: string; column_type: string; is_visible: boolean; width?: number; order_no: number; }>; } export interface TabColumnResponse { id: number; tenant_id: number; tab_id: number; column_key: string; column_name: string; column_type: string; is_visible: boolean; width: number | null; order_no: number; created_at: string; updated_at: string; } // ============================================ // 단위 옵션 // ============================================ export interface UnitOptionRequest { unit_name: string; unit_symbol?: string; description?: string; } export interface UnitOptionResponse { id: number; tenant_id: number; unit_name: string; unit_symbol: string | null; description: string | null; created_by: number | null; created_at: string; updated_at: string; }
🎨 2. UI 컴포넌트 준비 (3개)
-
2.1
src/components/ui/loading-spinner.tsx로딩 스피너 컴포넌트 생성 ✅- 목적: API 호출 중 로딩 상태 표시
- 예상 시간: 15분
- 완료 조건: 재사용 가능한 로딩 스피너 컴포넌트
- 완료일: 2025-11-20
// src/components/ui/loading-spinner.tsx import React from 'react'; interface LoadingSpinnerProps { size?: 'sm' | 'md' | 'lg'; className?: string; text?: string; } export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({ size = 'md', className = '', text }) => { const sizeClasses = { sm: 'h-4 w-4', md: 'h-8 w-8', lg: 'h-12 w-12' }; return ( <div className={`flex flex-col items-center justify-center gap-2 ${className}`}> <div className={`animate-spin rounded-full border-b-2 border-primary ${sizeClasses[size]}`} /> {text && <p className="text-sm text-muted-foreground">{text}</p>} </div> ); }; -
2.2
src/components/ui/error-message.tsx에러 메시지 컴포넌트 생성 ✅- 목적: API 오류 메시지 일관된 UI로 표시
- 예상 시간: 15분
- 완료 조건: 재사용 가능한 에러 메시지 컴포넌트
- 완료일: 2025-11-20
// src/components/ui/error-message.tsx import React from 'react'; import { AlertCircle } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; interface ErrorMessageProps { title?: string; message: string; onRetry?: () => void; className?: string; } export const ErrorMessage: React.FC<ErrorMessageProps> = ({ title = '오류 발생', message, onRetry, className = '' }) => { return ( <Alert variant="destructive" className={className}> <AlertCircle className="h-4 w-4" /> <AlertTitle>{title}</AlertTitle> <AlertDescription className="mt-2"> <p>{message}</p> {onRetry && ( <button onClick={onRetry} className="mt-2 text-sm underline hover:no-underline" > 다시 시도 </button> )} </AlertDescription> </Alert> ); }; -
2.3
src/components/items/ItemMasterDataManagement.tsx에 로딩/에러 state 추가 ✅- 목적: 전역 로딩 및 에러 상태 관리
- 예상 시간: 10분
- 완료 조건: state 추가 및 초기값 설정
- 완료일: 2025-11-20
// ItemMasterDataManagement.tsx 상단에 추가 const [isInitialLoading, setIsInitialLoading] = useState(true); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string | null>(null);
🔄 3. State 타입 변경 준비 (6개)
-
3.1 ItemPage 타입 변경 (ID: string → number) ✅
- 파일:
src/contexts/ItemMasterContext.tsx - 예상 시간: 10분
- 완료일: 2025-11-20
- 작업 내용:
- 기존
id: string→id: number absolutePath→absolute_pathcreatedAt→created_at,updated_at추가
- 기존
// 기존 타입 (주석 처리) // interface ItemPage { // id: string; // "PAGE-123" // pageName: string; // itemType: string; // absolutePath: string; // createdAt: string; // } // 새로운 타입 (API 응답 기준) interface ItemPage { id: number; // 서버 생성 ID tenant_id?: number; // 백엔드에서 자동 추가 page_name: string; // camelCase → snake_case item_type: string; description?: string | null; absolute_path: string; is_active: boolean; order_no: number; created_by?: number | null; updated_by?: number | null; created_at: string; updated_at: string; sections?: ItemSection[]; // Nested 데이터 } - 파일:
-
3.2 ItemSection 타입 변경 ✅
- 파일:
src/contexts/ItemMasterContext.tsx - 예상 시간: 10분
- 완료일: 2025-11-20
interface ItemSection { id: number; // string → number tenant_id?: number; page_id: number; // 외래키 section_template_id?: number | null; section_name: string; section_type: 'BASIC' | 'BOM' | 'CUSTOM'; description?: string | null; order_no: number; is_collapsible: boolean; is_default_open: boolean; created_by?: number | null; updated_by?: number | null; created_at: string; updated_at: string; fields?: ItemField[]; bomItems?: BomItem[]; } - 파일:
-
3.3 ItemField 타입 변경 ✅
- 파일:
src/contexts/ItemMasterContext.tsx - 예상 시간: 10분
- 완료일: 2025-11-20
interface ItemField { id: number; tenant_id?: number; section_id: number; master_field_id?: number | null; field_name: string; field_type: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea'; order_no: number; is_required: boolean; placeholder?: string | null; default_value?: string | null; validation_rules?: Record<string, any> | null; properties?: Record<string, any> | null; display_condition?: Record<string, any> | null; options?: Array<{ label: string; value: string }> | null; created_by?: number | null; updated_by?: number | null; created_at: string; updated_at: string; } - 파일:
-
3.4 BomItem 타입 변경 ✅
- 파일:
src/contexts/ItemMasterContext.tsx - 예상 시간: 10분
- 완료일: 2025-11-20
interface BOMItem { id: number; tenant_id?: number; section_id: number; item_code?: string | null; item_name: string; quantity: number; unit?: string | null; unit_price?: number | null; total_price?: number | null; spec?: string | null; note?: string | null; created_by?: number | null; updated_by?: number | null; created_at: string; updated_at: string; } - 파일:
-
3.5 SectionTemplate 타입 변경 ✅
- 파일:
src/contexts/ItemMasterContext.tsx - 예상 시간: 5분
- 완료일: 2025-11-20
interface SectionTemplate { id: number; tenant_id?: number; template_name: string; section_type: 'BASIC' | 'BOM' | 'CUSTOM'; description?: string | null; default_fields?: Record<string, any> | null; created_by?: number | null; updated_by?: number | null; created_at: string; updated_at: string; } - 파일:
-
3.6 MasterField 타입 변경 ✅
- 파일:
src/contexts/ItemMasterContext.tsx - 예상 시간: 5분
- 완료일: 2025-11-20
interface ItemMasterField { id: number; tenant_id?: number; field_name: string; field_type: 'TEXT' | 'NUMBER' | 'DATE' | 'SELECT' | 'TEXTAREA' | 'CHECKBOX'; category?: string | null; description?: string | null; default_validation?: Record<string, any> | null; default_properties?: Record<string, any> | null; created_by?: number | null; updated_by?: number | null; created_at: string; updated_at: string; } - 파일:
🛠️ 4. 헬퍼 함수 준비 (4개)
-
4.1 API 에러 핸들링 헬퍼 함수 생성 ✅
- 파일:
src/lib/api/error-handler.ts(신규) - 예상 시간: 20분
- 완료일: 2025-11-20
// src/lib/api/error-handler.ts export class ApiError extends Error { constructor( public status: number, public message: string, public errors?: Record<string, string[]> ) { super(message); this.name = 'ApiError'; } } export const handleApiError = async (response: Response): Promise<never> => { const data = await response.json().catch(() => ({})); throw new ApiError( response.status, data.message || '서버 오류가 발생했습니다', data.errors ); }; export const getErrorMessage = (error: unknown): string => { if (error instanceof ApiError) { return error.message; } if (error instanceof Error) { return error.message; } return '알 수 없는 오류가 발생했습니다'; }; - 파일:
-
4.2 API 응답 데이터 변환 헬퍼 함수 생성 ✅
- 파일:
src/lib/api/transformers.ts(신규) - 목적: API 타입 값 변환 (type → section_type, field_type 값 변환 등)
- 예상 시간: 20분
- 완료일: 2025-11-20
// src/lib/api/transformers.ts import type { ItemPageResponse, ItemSectionResponse } from '@/types/item-master-api'; import type { ItemPage, ItemSection } from '@/components/items/ItemMasterDataManagement'; // API Response → Frontend State export const transformPageResponse = (apiPage: ItemPageResponse): ItemPage => ({ id: apiPage.id, tenant_id: apiPage.tenant_id, page_name: apiPage.page_name, item_type: apiPage.item_type, description: apiPage.description, absolute_path: apiPage.absolute_path, is_active: apiPage.is_active, order_no: apiPage.order_no, created_by: apiPage.created_by, updated_by: apiPage.updated_by, created_at: apiPage.created_at, updated_at: apiPage.updated_at, sections: apiPage.sections?.map(transformSectionResponse), }); export const transformSectionResponse = (apiSection: ItemSectionResponse): ItemSection => ({ id: apiSection.id, tenant_id: apiSection.tenant_id, page_id: apiSection.page_id, section_template_id: apiSection.section_template_id, section_name: apiSection.section_name, section_type: apiSection.section_type as 'BASIC' | 'BOM' | 'CUSTOM', description: apiSection.description, order_no: apiSection.order_no, is_collapsible: apiSection.is_collapsible, is_default_open: apiSection.is_default_open, created_by: apiSection.created_by, updated_by: apiSection.updated_by, created_at: apiSection.created_at, updated_at: apiSection.updated_at, fields: apiSection.fields?.map(transformFieldResponse), bomItems: apiSection.bomItems?.map(transformBomItemResponse), }); // TODO: transformFieldResponse, transformBomItemResponse 등 추가 - 파일:
-
4.3 ID 생성 헬퍼 제거 준비 ✅
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 5분
- 완료일: 2025-11-20
- 작업 내용: Skip - 별도 ID 생성 함수가 존재하지 않음 (인라인 코드로 구현됨)
- 비고: ID 생성은
\PAGE-${Date.now()}`` 형태로 인라인 구현되어 있음. API 연동 시 해당 코드들을 서버 생성 ID로 교체 예정.
- 파일:
-
4.4 절대 경로 생성 함수 검토 ✅
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 10분
- 완료일: 2025-11-20
- 결정: 프론트에서 계속 생성 → API 요청 시 포함 (옵션 A 선택)
- 함수 위치: Line 745-755
// 현재 함수 (유지) const generateAbsolutePath = (itemType: string, pageName: string): string => { const typeMap: Record<string, string> = { 'FG': '제품관리', 'PT': '부품관리', 'SM': '부자재관리', 'RM': '원자재관리', 'CS': '소모품관리' }; const category = typeMap[itemType] || '기타'; return `/${category}/${pageName}`; };- 이유: 백엔드가 absolute_path를 자동 생성하는지 불확실하므로, 프론트에서 생성하여 전송하는 것이 안전함. 추후 백엔드에서 자동 생성 시 제거 가능.
- 파일:
📝 5. 기존 코드 주석 처리 (4개)
-
5.1 localStorage 관련 코드 주석 처리 (삭제 예정 표시) ✅
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 15분
- 완료일: 2025-11-20
- 작업 내용: 모든 localStorage 코드에
// ❌ API 연동 후 삭제 예정 - localStorage 제거주석 추가 - 추가된 주석 위치:
- Lines 159-160: Tab loading useEffect
- Lines 181-182: Tab saving useEffect
- Lines 350-369: Initial state loading (unitOptions, materialOptions, surfaceTreatmentOptions)
- 파일:
-
5.2 trackChange 함수 주석 추가 ✅
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 5분
- 완료일: 2025-11-20
- 작업 내용: trackChange 함수와 pendingChanges 관련 코드에
// ❌ API 연동 후 삭제 예정 - 실시간 저장으로 변경사항 추적 불필요주석 추가 - 추가된 주석 위치:
- Lines 528-529: pendingChanges state 정의
- Lines 545-546: hasUnsavedChanges computed value
- Lines 1993-1994: trackChange 함수 정의
- 파일:
-
5.3 SSR 관련 코드 검토 ✅
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 10분
- 완료일: 2025-11-20
- 작업 내용:
typeof window !== 'undefined'체크가 SSR 호환성을 위한 것임을 확인 (주석 불필요 - 이미 명확함) - 검토 결과: Lines 140-142, 196-203 등에서 SSR 호환성 체크가 적절하게 구현되어 있음
- 파일:
-
5.4 초기 state 로직 주석 추가 ✅
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 10분
- 완료일: 2025-11-20
- 작업 내용: 이미 Task 5.1에서 완료됨 (Lines 350-369에서 unitOptions, materialOptions, surfaceTreatmentOptions 초기 state 로직에 주석 추가)
- 파일:
🧪 6. 테스트 환경 준비 (3개)
-
6.1 환경 변수 설정 확인 ✅
- 파일:
.env.local - 예상 시간: 5분
- 완료일: 2025-11-20
- 작업 내용: API 관련 환경 변수 확인 완료
- 확인 결과:
- ✅ NEXT_PUBLIC_API_URL: https://api.codebridge-x.com
- ✅ NEXT_PUBLIC_API_KEY: 42Jfwc6EaRQ04GNRmLR5kzJp5UudSOzGGqjmdk1a
- 비고: API 연동에 필요한 모든 환경 변수가 이미 설정되어 있음
- 파일:
-
6.2 API Mock 데이터 준비 (선택) ✅
- 파일:
src/lib/api/mock-data.ts(신규, 선택) - 목적: 백엔드 API 구현 전 프론트 개발 계속 진행
- 예상 시간: 30분
- 완료일: 2025-11-20
// src/lib/api/mock-data.ts import type { InitResponse } from '@/types/item-master-api'; export const mockInitData: InitResponse = { pages: [ { id: 1, tenant_id: 1, page_name: '완제품 페이지', item_type: 'FG', description: null, absolute_path: '완제품 > 완제품 페이지', is_active: true, order_no: 0, created_by: null, updated_by: null, created_at: '2025-11-20T00:00:00Z', updated_at: '2025-11-20T00:00:00Z', }, ], sections: [], fields: [], bomItems: [], templates: [], masterFields: [], customTabs: [], units: [], }; // Mock API 함수 (개발용) export const useMockApi = process.env.NEXT_PUBLIC_USE_MOCK_API === 'true'; - 파일:
-
6.3 API 호출 로그 유틸 추가 ✅
- 파일:
src/lib/api/logger.ts(신규) - 목적: 개발 중 API 호출 디버깅
- 예상 시간: 10분
- 완료일: 2025-11-20
// src/lib/api/logger.ts export const apiLogger = { request: (method: string, url: string, data?: any) => { if (process.env.NODE_ENV === 'development') { console.log(`[API Request] ${method} ${url}`, data); } }, response: (method: string, url: string, data: any) => { if (process.env.NODE_ENV === 'development') { console.log(`[API Response] ${method} ${url}`, data); } }, error: (method: string, url: string, error: any) => { console.error(`[API Error] ${method} ${url}`, error); }, }; - 파일:
Phase 1: 초기화 API 연동 (API 필요) ⏳
백엔드 필요: GET /v1/item-master/init 구현 완료 필요
📡 7. 초기 데이터 로딩 (5개)
-
7.1 init API 함수 구현
- 파일:
src/lib/api/item-master.ts - 예상 시간: 20분
export const itemMasterApi = { init: async (): Promise<InitResponse> => { const headers = getAuthHeaders(); apiLogger.request('GET', '/item-master/init'); const response = await fetch(`${BASE_URL}/item-master/init`, { method: 'GET', headers, }); if (!response.ok) { await handleApiError(response); } const result = await response.json(); apiLogger.response('GET', '/item-master/init', result); return result.data; }, // ... }; - 파일:
-
7.2 컴포넌트 초기 로딩 로직 수정
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 30분
useEffect(() => { const loadInitialData = async () => { try { setIsInitialLoading(true); setError(null); const data = await itemMasterApi.init(); setItemPages(data.pages.map(transformPageResponse)); // TODO: sections, fields, bomItems 등도 설정 } catch (err) { setError(getErrorMessage(err)); } finally { setIsInitialLoading(false); } }; loadInitialData(); }, []); - 파일:
-
7.3 초기 로딩 UI 추가
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 15분
if (isInitialLoading) { return ( <div className="flex items-center justify-center min-h-screen"> <LoadingSpinner size="lg" text="데이터를 불러오는 중..." /> </div> ); } if (error) { return ( <div className="flex items-center justify-center min-h-screen p-4"> <ErrorMessage message={error} onRetry={() => window.location.reload()} /> </div> ); } - 파일:
-
7.4 데이터 변환 및 state 설정
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 20분
- 작업 내용: API 응답 데이터를 프론트 state에 매핑
const data = await itemMasterApi.init(); // 페이지 데이터 setItemPages(data.pages.map(transformPageResponse)); // 섹션 템플릿 setSectionTemplates(data.templates.map(transformTemplateResponse)); // 마스터 필드 setItemMasterFields(data.masterFields.map(transformMasterFieldResponse)); // 커스텀 탭 setCustomTabs(data.customTabs.map(transformCustomTabResponse)); // 단위 옵션 setUnitOptions(data.units.map(transformUnitResponse)); - 파일:
-
7.5 초기 로딩 테스트
- 예상 시간: 15분
- 테스트 항목:
- API 호출 성공 시 데이터 정상 표시
- API 호출 실패 시 에러 메시지 표시
- 로딩 중 스피너 표시
- 새로고침 시 최신 데이터 로드
🔐 8. 인증 및 에러 처리 (3개)
-
8.1 토큰 만료 처리
- 파일:
src/lib/api/error-handler.ts - 예상 시간: 20분
export const handleApiError = async (response: Response): Promise<never> => { const data = await response.json().catch(() => ({})); // 401 Unauthorized - 토큰 만료 if (response.status === 401) { // TODO: 로그인 페이지로 리다이렉트 if (typeof window !== 'undefined') { window.location.href = '/login'; } } // 403 Forbidden - 권한 없음 if (response.status === 403) { throw new ApiError(403, '접근 권한이 없습니다', data.errors); } throw new ApiError( response.status, data.message || '서버 오류가 발생했습니다', data.errors ); }; - 파일:
-
8.2 네트워크 오류 처리
- 파일:
src/lib/api/item-master.ts - 예상 시간: 15분
try { const response = await fetch(url, options); // ... } catch (error) { // 네트워크 오류 (서버 연결 실패 등) if (error instanceof TypeError) { throw new ApiError(0, '네트워크 연결을 확인해주세요'); } throw error; } - 파일:
-
8.3 Validation 에러 표시
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 20분
try { await itemMasterApi.pages.create(data); } catch (error) { if (error instanceof ApiError && error.errors) { // Validation 에러 (422) const errorMessages = Object.entries(error.errors) .map(([field, messages]) => `${field}: ${messages.join(', ')}`) .join('\n'); toast.error(errorMessages); } else { toast.error(getErrorMessage(error)); } } - 파일:
Phase 2: CRUD API 연동 (API 필요) ⏳
백엔드 필요: 모든 CRUD 엔드포인트 구현 완료 필요
📄 9. 페이지 관리 API (5개)
-
9.1 페이지 생성 API 연동 ✅
- 파일:
src/lib/api/item-master.ts+ItemMasterDataManagement.tsx - 예상 시간: 30분
- 완료일: 2025-11-21
// API Client pages: { create: async (data: ItemPageRequest): Promise<ItemPageResponse> => { const headers = getAuthHeaders(); const response = await fetch(`${BASE_URL}/item-master/pages`, { method: 'POST', headers, body: JSON.stringify(data), }); if (!response.ok) await handleApiError(response); const result = await response.json(); return result.data; }, } // Component const addItemPage = async (page: Omit<ItemPage, 'id' | 'created_at' | 'updated_at'>) => { try { setIsLoading(true); const savedPage = await itemMasterApi.pages.create({ page_name: page.page_name, item_type: page.item_type, description: page.description, is_active: page.is_active, }); setItemPages(prev => [...prev, transformPageResponse(savedPage)]); toast.success('페이지가 추가되었습니다'); } catch (error) { toast.error(getErrorMessage(error)); } finally { setIsLoading(false); } }; - 파일:
-
9.2 페이지 수정 API 연동 ✅
- 예상 시간: 25분
- 완료일: 2025-11-21
const updateItemPage = async (id: number, updates: Partial<ItemPage>) => { try { setIsLoading(true); const updatedPage = await itemMasterApi.pages.update(id, { page_name: updates.page_name, item_type: updates.item_type, description: updates.description, is_active: updates.is_active, }); setItemPages(prev => prev.map(p => p.id === id ? transformPageResponse(updatedPage) : p) ); toast.success('페이지가 수정되었습니다'); } catch (error) { toast.error(getErrorMessage(error)); } finally { setIsLoading(false); } }; -
9.3 페이지 삭제 API 연동 ✅
- 예상 시간: 20분
- 완료일: 2025-11-21
const deleteItemPage = async (id: number) => { if (!confirm('페이지를 삭제하시겠습니까?')) return; try { setIsLoading(true); await itemMasterApi.pages.delete(id); setItemPages(prev => prev.filter(p => p.id !== id)); toast.success('페이지가 삭제되었습니다'); } catch (error) { toast.error(getErrorMessage(error)); } finally { setIsLoading(false); } }; -
9.4 페이지 순서 변경 API 연동 ✅
- 예상 시간: 25분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.pages.reorder()함수 구현 완료ItemMasterContext.reorderPages()함수 구현 완료- Optimistic UI 업데이트 적용
- 에러 발생 시 롤백 로직 포함
const reorderPages = async (newOrder: Array<{ id: number; order_no: number }>) => { try { setIsLoading(true); await itemMasterApi.pages.reorder({ page_orders: newOrder }); // Optimistic UI 업데이트 setItemPages(prev => { const updated = [...prev]; updated.sort((a, b) => { const orderA = newOrder.find(o => o.id === a.id)?.order_no ?? 0; const orderB = newOrder.find(o => o.id === b.id)?.order_no ?? 0; return orderA - orderB; }); return updated; }); toast.success('페이지 순서가 변경되었습니다'); } catch (error) { toast.error(getErrorMessage(error)); // 실패 시 데이터 다시 로드 await loadInitialData(); } finally { setIsLoading(false); } }; -
9.5 페이지 관리 테스트 ✅
- 예상 시간: 20분
- 완료일: 2025-11-21
- 검증 항목 (코드 수준 검증 완료):
- API 함수 구현 확인 (create, update, delete, reorder)
- Context 함수 구현 확인 (addItemPage, updateItemPage, deleteItemPage, reorderPages)
- 타입 정의 및 import 확인 (PageReorderRequest 타입 추가)
- 에러 처리 로직 확인 (네트워크 오류, API 오류)
- Context export 확인 (ItemMasterContextType 및 value)
- 발견 사항:
- PageReorderRequest 타입 import 누락 → 수정 완료 (src/lib/api/item-master.ts:9)
- 비고: 실제 백엔드 API 구현 완료 후 E2E 테스트 필요
📦 10. 섹션 관리 API (5개)
-
10.1 섹션 생성 API 연동 ✅
- 예상 시간: 30분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.sections.create()함수 구현 완료 - 엔드포인트: POST
/v1/item-master/pages/{pageId}/sections
-
10.2 섹션 수정 API 연동 ✅
- 예상 시간: 25분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.sections.update()함수 구현 완료 - 엔드포인트: PUT
/v1/item-master/sections/{id}
-
10.3 섹션 삭제 API 연동 ✅
- 예상 시간: 20분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.sections.delete()함수 구현 완료 - 엔드포인트: DELETE
/v1/item-master/sections/{id}
-
10.4 섹션 순서 변경 API 연동 ✅
- 예상 시간: 25분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.sections.reorder()함수 구현 완료 - 엔드포인트: PUT
/v1/item-master/pages/{pageId}/sections/reorder
-
10.5 섹션 관리 테스트 ✅
- 예상 시간: 20분
- 완료일: 2025-11-21
- 검증 항목 (코드 수준 검증 완료):
- API 함수 구현 확인 (create, update, delete, reorder)
- 타입 정의 및 import 확인 (ItemSectionRequest, ItemSectionResponse, SectionReorderRequest)
- 에러 처리 로직 확인 (네트워크 오류, API 오류)
- API 엔드포인트 정확성 확인
- 비고: 실제 백엔드 API 구현 완료 후 E2E 테스트 필요
🔤 11. 필드 관리 API (5개)
-
11.1 필드 생성 API 연동 ✅
- 예상 시간: 30분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.fields.create()함수 구현 완료 - 엔드포인트: POST
/v1/item-master/sections/{sectionId}/fields
-
11.2 필드 수정 API 연동 ✅
- 예상 시간: 25분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.fields.update()함수 구현 완료 - 엔드포인트: PUT
/v1/item-master/fields/{id}
-
11.3 필드 삭제 API 연동 ✅
- 예상 시간: 20분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.fields.delete()함수 구현 완료 - 엔드포인트: DELETE
/v1/item-master/fields/{id}
-
11.4 필드 순서 변경 API 연동 ✅
- 예상 시간: 25분
- 완료일: 2025-11-21
- 구현 내용:
itemMasterApi.fields.reorder()함수 구현 완료 - 엔드포인트: PUT
/v1/item-master/sections/{sectionId}/fields/reorder
-
11.5 필드 관리 테스트 ✅
- 예상 시간: 20분
- 완료일: 2025-11-21
- 검증 항목 (코드 수준 검증 완료):
- API 함수 구현 확인 (create, update, delete, reorder)
- 타입 정의 및 import 확인 (ItemFieldRequest, ItemFieldResponse, FieldReorderRequest)
- 에러 처리 로직 확인 (네트워크 오류, API 오류)
- API 엔드포인트 정확성 확인
- 비고: 실제 백엔드 API 구현 완료 후 E2E 테스트 필요
🏗️ 12. BOM 관리 API (4개)
-
12.1 BOM 항목 생성 API 연동 ✅ (2025-11-21)
- 예상 시간: 25분
- 구현:
POST /v1/item-master/sections/{sectionId}/bom-items
-
12.2 BOM 항목 수정 API 연동 ✅ (2025-11-21)
- 예상 시간: 20분
- 구현:
PUT /v1/item-master/bom-items/{id}
-
12.3 BOM 항목 삭제 API 연동 ✅ (2025-11-21)
- 예상 시간: 15분
- 구현:
DELETE /v1/item-master/bom-items/{id}
-
12.4 BOM 관리 테스트 ✅ (2025-11-21)
- 예상 시간: 15분
- 검증 완료: 타입 import, API 함수, 엔드포인트, 에러 처리 모두 정상
📋 13. 섹션 템플릿 API (4개)
-
13.1 템플릿 목록 조회 (init에 포함되므로 Skip 가능) ✅ (2025-11-21)
- 예상 시간: 10분
- 구현:
GET /v1/item-master/section-templates
-
13.2 템플릿 생성 API 연동 ✅ (2025-11-21)
- 예상 시간: 20분
- 구현:
POST /v1/item-master/section-templates
-
13.3 템플릿 수정 API 연동 ✅ (2025-11-21)
- 예상 시간: 20분
- 구현:
PUT /v1/item-master/section-templates/{id}
-
13.4 템플릿 삭제 API 연동 ✅ (2025-11-21)
- 예상 시간: 15분
- 구현:
DELETE /v1/item-master/section-templates/{id}
🎯 14. 마스터 필드 API (4개)
-
14.1 마스터 필드 목록 조회 (init에 포함) ✅ (2025-11-21)
- 예상 시간: 10분
- 구현:
GET /v1/item-master/master-fields
-
14.2 마스터 필드 생성 API 연동 ✅ (2025-11-21)
- 예상 시간: 20분
- 구현:
POST /v1/item-master/master-fields
-
14.3 마스터 필드 수정 API 연동 ✅ (2025-11-21)
- 예상 시간: 20분
- 구현:
PUT /v1/item-master/master-fields/{id}
-
14.4 마스터 필드 삭제 API 연동 ✅ (2025-11-21)
- 예상 시간: 15분
- 구현:
DELETE /v1/item-master/master-fields/{id}
📑 15. 커스텀 탭 API (3개)
-
15.1 커스텀 탭 CRUD API 연동 ✅ (2025-11-21)
- 예상 시간: 40분
- 구현:
GET /v1/item-master/custom-tabs(list)POST /v1/item-master/custom-tabs(create)PUT /v1/item-master/custom-tabs/{id}(update)DELETE /v1/item-master/custom-tabs/{id}(delete)
-
15.2 탭 순서 변경 API 연동 ✅ (2025-11-21)
- 예상 시간: 20분
- 구현:
PUT /v1/item-master/custom-tabs/reorder
-
15.3 탭 컬럼 설정 API 연동 ✅ (2025-11-21)
- 예상 시간: 30분
- 구현:
PUT /v1/item-master/custom-tabs/{id}/columns
📏 16. 단위 옵션 API (3개)
-
16.1 단위 목록 조회 (init에 포함) ✅ (2025-11-21)
- 예상 시간: 10분
- 구현:
GET /v1/item-master/unit-options
-
16.2 단위 생성 API 연동 ✅ (2025-11-21)
- 예상 시간: 15분
- 구현:
POST /v1/item-master/unit-options
-
16.3 단위 삭제 API 연동 ✅ (2025-11-21)
- 예상 시간: 15분
- 구현:
DELETE /v1/item-master/unit-options/{id}
Phase 3: 정리 및 최적화 (API 필요) ⏳
🧹 17. 코드 정리 (4개)
-
17.1 localStorage 관련 코드 완전 삭제
- 파일:
src/components/items/ItemMasterDataManagement.tsx - 예상 시간: 30분
- 작업 내용: 주석 처리된 모든 localStorage 코드 제거
- 파일:
-
17.2 trackChange, pendingChanges 관련 코드 삭제
- 예상 시간: 20분
-
17.3 ID 생성 함수 삭제
- 예상 시간: 10분
-
17.4 불필요한 import 및 주석 정리
- 예상 시간: 15min
🧪 18. 통합 테스트 (2개)
-
18.1 전체 CRUD 흐름 테스트
- 예상 시간: 30분
- 테스트 시나리오:
- 페이지 생성 → 섹션 추가 → 필드 추가
- 필드 수정 → 섹션 순서 변경
- BOM 섹션 생성 → BOM 항목 추가
- 페이지 삭제 (Cascade 확인)
-
18.2 에러 케이스 테스트
- 예상 시간: 20분
- 테스트 시나리오:
- 네트워크 끊김 상태에서 작업
- 토큰 만료 처리
- Validation 에러 표시
- 중복 요청 방지
📝 진행 상황 기록
Phase 0 완료일
- 시작일: YYYY-MM-DD
- 완료일: YYYY-MM-DD
- 실제 소요 시간: X시간
Phase 1 완료일
- 시작일: YYYY-MM-DD
- 완료일: YYYY-MM-DD
- 실제 소요 시간: X시간
Phase 2 완료일
- 시작일: YYYY-MM-DD
- 완료일: YYYY-MM-DD
- 실제 소요 시간: X시간
Phase 3 완료일
- 시작일: YYYY-MM-DD
- 완료일: YYYY-MM-DD
- 실제 소요 시간: X시간
🚨 주의사항 및 팁
1. 점진적 작업
- Phase 0는 백엔드 API 없이 진행 가능 → 지금 바로 시작
- Phase 1-3은 백엔드 완성 후 순차적으로 진행
2. 타입 안정성
- TypeScript strict 모드 유지
- API 응답 타입과 프론트 state 타입 분리
- 변환 함수(transformer) 활용
3. 에러 처리
- 모든 API 호출은 try-catch로 감싸기
- 사용자 친화적인 에러 메시지 표시
- 로그 남기기 (개발 환경)
4. 성능 최적화
- Optimistic UI 업데이트 활용
- Debounce/Throttle 필요 시 적용 (Phase 2 완료 후)
- React.memo, useMemo 활용 검토
5. 테스트
- 각 Phase 완료 후 반드시 테스트
- 실패 케이스 시나리오 확인
- 브라우저 콘솔 에러 체크
📞 문의 및 이슈
문서 관련 문의: 이 체크리스트 기준으로 작업 진행
백엔드 API 문의: [API-2025-11-20] item-master-specification.md 참조
이슈 발생 시: claudedocs에 별도 문서 작성 권장
마지막 업데이트: 2025-11-20