2025-12-16 14:38:03 +09:00
|
|
|
# 즉시 저장(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<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` 상태로 모든 입력값을 관리하고 있음.
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// 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. **연쇄 저장**: 한 번의 클릭으로 여러 테이블에 저장
|
|
|
|
|
|
2025-12-17 17:41:29 +09:00
|
|
|
|
2025-12-18 16:35:55 +09:00
|
|
|
|
2025-12-23 09:31:18 +09:00
|
|
|
|
2026-01-06 10:27:54 +09:00
|
|
|
|
2026-01-13 18:28:11 +09:00
|
|
|
|