# 선택 항목 상세입력 컴포넌트 - 완성 가이드 ## 📦 구현 완료 사항 ### ✅ 1. Zustand 스토어 생성 (modalDataStore) - 파일: `frontend/stores/modalDataStore.ts` - 기능: - 모달 간 데이터 전달 관리 - `setData()`: 데이터 저장 - `getData()`: 데이터 조회 - `clearData()`: 데이터 정리 - `updateItemData()`: 항목별 추가 데이터 업데이트 ### ✅ 2. SelectedItemsDetailInput 컴포넌트 생성 - 디렉토리: `frontend/lib/registry/components/selected-items-detail-input/` - 파일들: - `types.ts`: 타입 정의 - `SelectedItemsDetailInputComponent.tsx`: 메인 컴포넌트 - `SelectedItemsDetailInputConfigPanel.tsx`: 설정 패널 - `SelectedItemsDetailInputRenderer.tsx`: 렌더러 - `index.ts`: 컴포넌트 정의 - `README.md`: 사용 가이드 ### ✅ 3. 컴포넌트 기능 - 전달받은 원본 데이터 표시 (읽기 전용) - 각 항목별 추가 입력 필드 제공 - Grid/Table 레이아웃 및 Card 레이아웃 지원 - 6가지 입력 타입 지원 (text, number, date, select, checkbox, textarea) - 필수 입력 검증 - 항목 삭제 기능 ### ✅ 4. 설정 패널 기능 - 데이터 소스 ID 설정 - 저장 대상 테이블 선택 (검색 가능한 Combobox) - 표시할 원본 데이터 컬럼 선택 - 추가 입력 필드 정의 (필드명, 라벨, 타입, 필수 여부 등) - 레이아웃 모드 선택 (Grid/Card) - 옵션 설정 (번호 표시, 삭제 허용, 비활성화) --- ## 🚧 남은 작업 (구현 필요) ### 1. TableList에서 선택된 행 데이터를 스토어에 저장 **필요한 수정 파일:** - `frontend/lib/registry/components/table-list/TableListComponent.tsx` **구현 방법:** ```typescript import { useModalDataStore } from "@/stores/modalDataStore"; // TableList 컴포넌트 내부 const setModalData = useModalDataStore((state) => state.setData); // 선택된 행이 변경될 때마다 스토어에 저장 useEffect(() => { if (selectedRows.length > 0) { const modalDataItems = selectedRows.map((row) => ({ id: row[primaryKeyColumn] || row.id, originalData: row, additionalData: {}, })); // 컴포넌트 ID를 키로 사용하여 저장 setModalData(component.id || "default", modalDataItems); console.log("📦 [TableList] 선택된 데이터 저장:", modalDataItems); } }, [selectedRows, component.id, setModalData]); ``` **참고:** - `selectedRows`: TableList의 체크박스로 선택된 행들 - `component.id`: 컴포넌트 고유 ID - 이 ID가 SelectedItemsDetailInput의 `dataSourceId`와 일치해야 함 --- ### 2. ButtonPrimary에 'openModalWithData' 액션 타입 추가 **필요한 수정 파일:** - `frontend/lib/registry/components/button-primary/types.ts` - `frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx` - `frontend/lib/registry/components/button-primary/ButtonPrimaryConfigPanel.tsx` #### A. types.ts 수정 ```typescript export interface ButtonPrimaryConfig extends ComponentConfig { action?: { type: | "save" | "delete" | "popup" | "navigate" | "custom" | "openModalWithData"; // 🆕 새 액션 타입 // 기존 필드들... // 🆕 모달 데이터 전달용 필드 targetScreenId?: number; // 열릴 모달 화면 ID dataSourceId?: string; // 데이터를 전달할 컴포넌트 ID }; } ``` #### B. ButtonPrimaryComponent.tsx 수정 ```typescript import { useModalDataStore } from "@/stores/modalDataStore"; // 컴포넌트 내부 const modalData = useModalDataStore((state) => state.getData); // handleClick 함수 수정 const handleClick = async () => { // ... 기존 코드 ... // openModalWithData 액션 처리 if (processedConfig.action?.type === "openModalWithData") { const { targetScreenId, dataSourceId } = processedConfig.action; if (!targetScreenId) { toast.error("대상 화면이 설정되지 않았습니다."); return; } if (!dataSourceId) { toast.error("데이터 소스가 설정되지 않았습니다."); return; } // 데이터 확인 const data = modalData(dataSourceId); if (!data || data.length === 0) { toast.warning("전달할 데이터가 없습니다. 먼저 항목을 선택해주세요."); return; } console.log("📦 [ButtonPrimary] 데이터와 함께 모달 열기:", { targetScreenId, dataSourceId, dataCount: data.length, }); // 모달 열기 (기존 popup 액션과 동일) toast.success(`${data.length}개 항목을 전달합니다.`); // TODO: 실제 모달 열기 로직 (popup 액션 참고) window.open(`/screens/${targetScreenId}`, "_blank"); return; } // ... 기존 액션 처리 코드 ... }; ``` #### C. ButtonPrimaryConfigPanel.tsx 수정 설정 패널에 openModalWithData 액션 설정 UI 추가: ```typescript {config.action?.type === "openModalWithData" && (

데이터 전달 설정

{/* 대상 화면 선택 */}
{/* 화면 목록 표시 */}
{/* 데이터 소스 ID 입력 */}
updateActionConfig("dataSourceId", e.target.value) } placeholder="table-list-123" />

💡 데이터를 전달할 컴포넌트의 ID (예: TableList의 ID)

)} ``` --- ### 3. 저장 기능 구현 **방법 1: 기존 save 액션 활용** SelectedItemsDetailInput의 데이터는 자동으로 `formData`에 포함되므로, 기존 save 액션을 그대로 사용할 수 있습니다: ```typescript // formData 구조 { "selected-items-component-id": [ { id: "SALE-003", originalData: { item_code: "SALE-003", ... }, additionalData: { customer_item_code: "ABC-001", unit_price: 50, ... } }, // ... 더 많은 항목들 ] } ``` 백엔드에서 이 데이터를 받아서 각 항목을 개별 INSERT하면 됩니다. **방법 2: 전용 save 로직 추가** 더 나은 UX를 위해 전용 저장 로직을 추가할 수 있습니다: ```typescript // ButtonPrimary의 save 액션에서 if (config.action?.type === "save") { // formData에서 SelectedItemsDetailInput 데이터 찾기 const selectedItemsKey = Object.keys(formData).find( (key) => Array.isArray(formData[key]) && formData[key][0]?.originalData ); if (selectedItemsKey) { const items = formData[selectedItemsKey] as ModalDataItem[]; // 저장할 데이터 변환 const dataToSave = items.map((item) => ({ ...item.originalData, ...item.additionalData, })); // 백엔드 API 호출 const response = await apiClient.post(`/api/table-data/${targetTable}`, { data: dataToSave, batchInsert: true, }); if (response.data.success) { toast.success(`${dataToSave.length}개 항목이 저장되었습니다.`); onClose?.(); } } } ``` --- ## 🎯 통합 테스트 시나리오 ### 시나리오: 수주 등록 - 품목 상세 입력 #### 1단계: 화면 구성 **[모달 1] 품목 선택 화면 (screen_id: 100)** - TableList 컴포넌트 - ID: `item-selection-table` - multiSelect: `true` - selectedTable: `item_info` - columns: 품목코드, 품목명, 규격, 단위, 단가 - ButtonPrimary 컴포넌트 - text: "다음 (상세정보 입력)" - action.type: `openModalWithData` - action.targetScreenId: `101` (두 번째 모달) - action.dataSourceId: `item-selection-table` **[모달 2] 상세 입력 화면 (screen_id: 101)** - SelectedItemsDetailInput 컴포넌트 - ID: `selected-items-detail` - dataSourceId: `item-selection-table` - displayColumns: `["item_code", "item_name", "spec", "unit"]` - additionalFields: ```json [ { "name": "customer_item_code", "label": "거래처 품번", "type": "text" }, { "name": "customer_item_name", "label": "거래처 품명", "type": "text" }, { "name": "year", "label": "연도", "type": "select", "options": [...] }, { "name": "currency", "label": "통화", "type": "select", "options": [...] }, { "name": "unit_price", "label": "단가", "type": "number", "required": true }, { "name": "quantity", "label": "수량", "type": "number", "required": true } ] ``` - targetTable: `sales_detail` - layout: `grid` - ButtonPrimary 컴포넌트 (저장) - text: "저장" - action.type: `save` - action.targetTable: `sales_detail` #### 2단계: 테스트 절차 1. [모달 1] 품목 선택 화면 열기 2. TableList에서 3개 품목 체크박스 선택 3. "다음" 버튼 클릭 - ✅ modalDataStore에 3개 항목 저장 확인 (콘솔 로그) - ✅ 모달 2가 열림 4. [모달 2] SelectedItemsDetailInput에 3개 항목 자동 표시 확인 - ✅ 원본 데이터 (품목코드, 품목명, 규격, 단위) 표시 - ✅ 추가 입력 필드 (거래처 품번, 단가, 수량 등) 빈 상태 5. 각 항목별로 추가 정보 입력 - 거래처 품번: "ABC-001", "ABC-002", "ABC-003" - 단가: 50, 200, 3000 - 수량: 100, 50, 200 6. "저장" 버튼 클릭 - ✅ formData에 전체 데이터 포함 확인 - ✅ 백엔드 API 호출 - ✅ 저장 성공 토스트 메시지 - ✅ 모달 닫힘 #### 3단계: 데이터 검증 데이터베이스에 다음과 같이 저장되어야 합니다: ```sql SELECT * FROM sales_detail; -- 결과: -- item_code | item_name | spec | unit | customer_item_code | unit_price | quantity -- SALE-003 | 와셔 M8 | M8 | EA | ABC-001 | 50 | 100 -- SALE-005 | 육각 볼트 | M10 | EA | ABC-002 | 200 | 50 -- SIL-003 | 실리콘 | 325 | kg | ABC-003 | 3000 | 200 ``` --- ## 📚 추가 참고 자료 ### 관련 파일 위치 - 스토어: `frontend/stores/modalDataStore.ts` - 컴포넌트: `frontend/lib/registry/components/selected-items-detail-input/` - TableList: `frontend/lib/registry/components/table-list/` - ButtonPrimary: `frontend/lib/registry/components/button-primary/` ### 디버깅 팁 콘솔에서 다음 명령어로 상태 확인: ```javascript // 모달 데이터 확인 __MODAL_DATA_STORE__.getState().dataRegistry // 컴포넌트 등록 확인 __COMPONENT_REGISTRY__.get("selected-items-detail-input") // TableList 선택 상태 확인 // (TableList 컴포넌트 내부에 로그 추가 필요) ``` ### 예상 문제 및 해결 1. **데이터가 전달되지 않음** - dataSourceId가 정확히 일치하는지 확인 - modalDataStore에 데이터가 저장되었는지 콘솔 로그 확인 2. **컴포넌트가 표시되지 않음** - `frontend/lib/registry/components/index.ts`에 import 추가되었는지 확인 - 브라우저 새로고침 후 재시도 3. **저장이 안 됨** - formData에 데이터가 포함되어 있는지 확인 - 백엔드 API 응답 확인 - targetTable이 올바른지 확인 --- ## ✅ 완료 체크리스트 - [x] Zustand 스토어 생성 (modalDataStore) - [x] SelectedItemsDetailInput 컴포넌트 생성 - [x] 컴포넌트 렌더링 로직 구현 - [x] 설정 패널 구현 - [ ] TableList에서 선택된 데이터를 스토어에 저장 - [ ] ButtonPrimary에 openModalWithData 액션 추가 - [ ] 저장 기능 구현 - [ ] 통합 테스트 - [ ] 사용자 매뉴얼 작성 --- ## 🚀 다음 단계 1. TableList 컴포넌트에 modalDataStore 연동 추가 2. ButtonPrimary에 openModalWithData 액션 구현 3. 수주 등록 화면에서 실제 테스트 4. 문제 발견 시 디버깅 및 수정 5. 문서 업데이트 및 배포 **예상 소요 시간**: 2~3시간