ae4e21e1ac
- 카테고리 트리 컨트롤러와 서비스 추가: 트리 구조를 지원하는 카테고리 값 관리 기능을 구현하였습니다. - 카테고리 트리 API 클라이언트 추가: CRUD 작업을 위한 API 클라이언트를 구현하였습니다. - 카테고리 값 관리 컴포넌트 및 설정 패널 추가: 사용자 인터페이스에서 카테고리 값을 관리할 수 있도록 트리 구조 기반의 컴포넌트를 추가하였습니다. - 관련 라우트 및 레지스트리 업데이트: 카테고리 트리 관련 라우트를 추가하고, 컴포넌트 레지스트리에 등록하였습니다. 이로 인해 카테고리 관리의 효율성이 향상되었습니다.
13 KiB
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 정상 케이스
- 좌측 테이블에서 공정 "PRC-001" 선택
- 우측 설비 선택박스에서 "MCT-01" 선택
- "설비 추가" 버튼 클릭
process_equipment테이블에 데이터 저장 확인- 우측 카드/테이블에 새 항목 표시 확인
6.2 에러 케이스
- 좌측 미선택 상태에서 버튼 클릭 → "좌측에서 항목을 선택해주세요" 메시지
- 설비 미선택 상태에서 버튼 클릭 → "설비를 선택해주세요" 메시지
- 중복 데이터 저장 시도 → "이미 등록된 설비입니다" 메시지
6.3 엣지 케이스
- 동일 설비 연속 추가 시도
- 네트워크 오류 시 재시도
- 권한 없는 사용자의 저장 시도
7. 일정
| Phase | 작업 | 예상 시간 |
|---|---|---|
| Phase 1 | 타입 정의 및 설정 UI | 1시간 |
| Phase 2 | 버튼 액션 핸들러 구현 | 1시간 |
| Phase 3 | 테스트 및 검증 | 30분 |
| 합계 | 2시간 30분 |
8. 향후 확장 가능성
- 다중 행 추가: 여러 설비를 한 번에 선택하여 추가
- 수정 모드: 기존 데이터 수정 기능
- 조건부 저장: 특정 조건 만족 시에만 저장
- 연쇄 저장: 한 번의 클릭으로 여러 테이블에 저장