# 즉시 저장(quickInsert) 버튼 액션 구현 계획서 ## 1. 개요 ### 1.1 목적 화면에서 entity 타입 선택박스로 데이터를 선택한 후, 버튼 클릭 시 특정 테이블에 즉시 INSERT하는 기능 구현 ### 1.2 사용 사례 - **공정별 설비 관리**: 좌측에서 공정 선택 → 우측에서 설비 선택 → "설비 추가" 버튼 클릭 → `process_equipment` 테이블에 즉시 저장 ### 1.3 화면 구성 예시 ``` ┌─────────────────────────────────────────────────────────────┐ │ [entity 선택박스] [버튼: quickInsert] │ │ ┌─────────────────────────────┐ ┌──────────────┐ │ │ │ MCT-01 - 머시닝센터 #1 ▼ │ │ + 설비 추가 │ │ │ └─────────────────────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 2. 기술 설계 ### 2.1 버튼 액션 타입 추가 ```typescript // types/screen-management.ts type ButtonActionType = | "save" | "cancel" | "delete" | "edit" | "add" | "search" | "reset" | "submit" | "close" | "popup" | "navigate" | "custom" | "quickInsert" // 🆕 즉시 저장 ``` ### 2.2 quickInsert 설정 구조 ```typescript interface QuickInsertColumnMapping { targetColumn: string; // 저장할 테이블의 컬럼명 sourceType: "component" | "leftPanel" | "fixed" | "currentUser"; // sourceType별 추가 설정 sourceComponentId?: string; // component: 값을 가져올 컴포넌트 ID sourceColumn?: string; // leftPanel: 좌측 선택 데이터의 컬럼명 fixedValue?: any; // fixed: 고정값 userField?: string; // currentUser: 사용자 정보 필드 (userId, userName, companyCode) } interface QuickInsertConfig { targetTable: string; // 저장할 테이블명 columnMappings: QuickInsertColumnMapping[]; // 저장 후 동작 afterInsert?: { refreshRightPanel?: boolean; // 우측 패널 새로고침 clearComponents?: string[]; // 초기화할 컴포넌트 ID 목록 showSuccessMessage?: boolean; // 성공 메시지 표시 successMessage?: string; // 커스텀 성공 메시지 }; // 중복 체크 (선택사항) duplicateCheck?: { enabled: boolean; columns: string[]; // 중복 체크할 컬럼들 errorMessage?: string; // 중복 시 에러 메시지 }; } interface ButtonComponentConfig { // 기존 설정들... actionType: ButtonActionType; // 🆕 quickInsert 전용 설정 quickInsertConfig?: QuickInsertConfig; } ``` ### 2.3 데이터 흐름 ``` 1. 사용자가 entity 선택박스에서 설비 선택 └─ equipment_code = "EQ-001" (내부값) └─ 표시: "MCT-01 - 머시닝센터 #1" 2. 사용자가 "설비 추가" 버튼 클릭 3. quickInsert 핸들러 실행 ├─ columnMappings 순회 │ ├─ equipment_code: component에서 값 가져오기 → "EQ-001" │ └─ process_code: leftPanel에서 값 가져오기 → "PRC-001" │ └─ INSERT 데이터 구성 { equipment_code: "EQ-001", process_code: "PRC-001", company_code: "COMPANY_7", // 자동 추가 writer: "wace" // 자동 추가 } 4. API 호출: POST /api/table-management/tables/process_equipment/add 5. 성공 시 ├─ 성공 메시지 표시 ├─ 우측 패널(카드/테이블) 새로고침 └─ 선택박스 초기화 ``` --- ## 3. 구현 계획 ### 3.1 Phase 1: 타입 정의 및 설정 UI | 작업 | 파일 | 설명 | |------|------|------| | 1-1 | `frontend/types/screen-management.ts` | QuickInsertConfig 타입 추가 | | 1-2 | `frontend/components/screen/config-panels/ButtonConfigPanel.tsx` | quickInsert 설정 UI 추가 | ### 3.2 Phase 2: 버튼 액션 핸들러 구현 | 작업 | 파일 | 설명 | |------|------|------| | 2-1 | `frontend/components/screen/InteractiveScreenViewerDynamic.tsx` | quickInsert 핸들러 추가 | | 2-2 | 컴포넌트 값 수집 로직 | 같은 화면의 다른 컴포넌트에서 값 가져오기 | ### 3.3 Phase 3: 테스트 및 검증 | 작업 | 설명 | |------|------| | 3-1 | 공정별 설비 화면에서 테스트 | | 3-2 | 중복 저장 방지 테스트 | | 3-3 | 에러 처리 테스트 | --- ## 4. 상세 구현 ### 4.1 ButtonConfigPanel 설정 UI ``` ┌─────────────────────────────────────────────────────────────┐ │ 버튼 액션 타입 │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 즉시 저장 (quickInsert) ▼ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ─────────────── 즉시 저장 설정 ─────────────── │ │ │ │ 대상 테이블 * │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ process_equipment ▼ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ 컬럼 매핑 [+ 추가] │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 매핑 #1 [삭제] │ │ │ │ 대상 컬럼: equipment_code │ │ │ │ 값 소스: 컴포넌트 선택 │ │ │ │ 컴포넌트: [equipment-select ▼] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 매핑 #2 [삭제] │ │ │ │ 대상 컬럼: process_code │ │ │ │ 값 소스: 좌측 패널 데이터 │ │ │ │ 소스 컬럼: process_code │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ─────────────── 저장 후 동작 ─────────────── │ │ │ │ ☑ 우측 패널 새로고침 │ │ ☑ 선택박스 초기화 │ │ ☑ 성공 메시지 표시 │ │ │ │ ─────────────── 중복 체크 (선택) ─────────────── │ │ │ │ ☐ 중복 체크 활성화 │ │ 체크 컬럼: equipment_code, process_code │ │ 에러 메시지: 이미 등록된 설비입니다. │ └─────────────────────────────────────────────────────────────┘ ``` ### 4.2 핸들러 구현 (의사 코드) ```typescript const handleQuickInsert = async (config: QuickInsertConfig) => { // 1. 컬럼 매핑에서 값 수집 const insertData: Record = {}; for (const mapping of config.columnMappings) { let value: any; switch (mapping.sourceType) { case "component": // 같은 화면의 컴포넌트에서 값 가져오기 value = getComponentValue(mapping.sourceComponentId); break; case "leftPanel": // 분할 패널 좌측 선택 데이터에서 값 가져오기 value = splitPanelContext?.selectedLeftData?.[mapping.sourceColumn]; break; case "fixed": value = mapping.fixedValue; break; case "currentUser": value = user?.[mapping.userField]; break; } if (value !== undefined && value !== null && value !== "") { insertData[mapping.targetColumn] = value; } } // 2. 필수값 검증 if (Object.keys(insertData).length === 0) { toast.error("저장할 데이터가 없습니다."); return; } // 3. 중복 체크 (설정된 경우) if (config.duplicateCheck?.enabled) { const isDuplicate = await checkDuplicate( config.targetTable, config.duplicateCheck.columns, insertData ); if (isDuplicate) { toast.error(config.duplicateCheck.errorMessage || "이미 존재하는 데이터입니다."); return; } } // 4. API 호출 try { await tableTypeApi.addTableData(config.targetTable, insertData); // 5. 성공 후 동작 if (config.afterInsert?.showSuccessMessage) { toast.success(config.afterInsert.successMessage || "저장되었습니다."); } if (config.afterInsert?.refreshRightPanel) { // 우측 패널 새로고침 트리거 onRefresh?.(); } if (config.afterInsert?.clearComponents) { // 지정된 컴포넌트 초기화 for (const componentId of config.afterInsert.clearComponents) { clearComponentValue(componentId); } } } catch (error) { toast.error("저장에 실패했습니다."); } }; ``` --- ## 5. 컴포넌트 간 통신 방안 ### 5.1 문제점 - 버튼 컴포넌트에서 같은 화면의 entity 선택박스 값을 가져와야 함 - 현재는 각 컴포넌트가 독립적으로 동작 ### 5.2 해결 방안: formData 활용 현재 `InteractiveScreenViewerDynamic`에서 `formData` 상태로 모든 입력값을 관리하고 있음. ```typescript // InteractiveScreenViewerDynamic.tsx const [localFormData, setLocalFormData] = useState>({}); // entity 선택박스에서 값 변경 시 const handleFormDataChange = (fieldName: string, value: any) => { setLocalFormData(prev => ({ ...prev, [fieldName]: value })); }; // 버튼 클릭 시 formData에서 값 가져오기 const getComponentValue = (componentId: string) => { // componentId로 컴포넌트의 columnName 찾기 const component = allComponents.find(c => c.id === componentId); if (component?.columnName) { return formData[component.columnName]; } return undefined; }; ``` --- ## 6. 테스트 시나리오 ### 6.1 정상 케이스 1. 좌측 테이블에서 공정 "PRC-001" 선택 2. 우측 설비 선택박스에서 "MCT-01" 선택 3. "설비 추가" 버튼 클릭 4. `process_equipment` 테이블에 데이터 저장 확인 5. 우측 카드/테이블에 새 항목 표시 확인 ### 6.2 에러 케이스 1. 좌측 미선택 상태에서 버튼 클릭 → "좌측에서 항목을 선택해주세요" 메시지 2. 설비 미선택 상태에서 버튼 클릭 → "설비를 선택해주세요" 메시지 3. 중복 데이터 저장 시도 → "이미 등록된 설비입니다" 메시지 ### 6.3 엣지 케이스 1. 동일 설비 연속 추가 시도 2. 네트워크 오류 시 재시도 3. 권한 없는 사용자의 저장 시도 --- ## 7. 일정 | Phase | 작업 | 예상 시간 | |-------|------|----------| | Phase 1 | 타입 정의 및 설정 UI | 1시간 | | Phase 2 | 버튼 액션 핸들러 구현 | 1시간 | | Phase 3 | 테스트 및 검증 | 30분 | | **합계** | | **2시간 30분** | --- ## 8. 향후 확장 가능성 1. **다중 행 추가**: 여러 설비를 한 번에 선택하여 추가 2. **수정 모드**: 기존 데이터 수정 기능 3. **조건부 저장**: 특정 조건 만족 시에만 저장 4. **연쇄 저장**: 한 번의 클릭으로 여러 테이블에 저장