ERP-node/docs/즉시저장_버튼_액션_구현_계획서.md

13 KiB

즉시 저장(quickInsert) 버튼 액션 구현 계획서

1. 개요

1.1 목적

화면에서 entity 타입 선택박스로 데이터를 선택한 후, 버튼 클릭 시 특정 테이블에 즉시 INSERT하는 기능 구현

1.2 사용 사례

  • 공정별 설비 관리: 좌측에서 공정 선택 → 우측에서 설비 선택 → "설비 추가" 버튼 클릭 → process_equipment 테이블에 즉시 저장

1.3 화면 구성 예시

┌─────────────────────────────────────────────────────────────┐
│  [entity 선택박스]              [버튼: quickInsert]         │
│  ┌─────────────────────────────┐  ┌──────────────┐         │
│  │ MCT-01 - 머시닝센터 #1   ▼ │  │ + 설비 추가  │         │
│  └─────────────────────────────┘  └──────────────┘         │
└─────────────────────────────────────────────────────────────┘

2. 기술 설계

2.1 버튼 액션 타입 추가

// types/screen-management.ts
type ButtonActionType = 
  | "save"
  | "cancel"
  | "delete"
  | "edit"
  | "add"
  | "search"
  | "reset"
  | "submit"
  | "close"
  | "popup"
  | "navigate"
  | "custom"
  | "quickInsert"  // 🆕 즉시 저장

2.2 quickInsert 설정 구조

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 핸들러 구현 (의사 코드)

const handleQuickInsert = async (config: QuickInsertConfig) => {
  // 1. 컬럼 매핑에서 값 수집
  const insertData: Record<string, any> = {};
  
  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 상태로 모든 입력값을 관리하고 있음.

// InteractiveScreenViewerDynamic.tsx
const [localFormData, setLocalFormData] = useState<Record<string, any>>({});

// 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. 연쇄 저장: 한 번의 클릭으로 여러 테이블에 저장