리피터 케이블 설정 구현

This commit is contained in:
kjs
2026-01-15 15:17:52 +09:00
parent bed7f5f5c4
commit e168753d87
8 changed files with 893 additions and 116 deletions

View File

@@ -13,15 +13,238 @@ alwaysApply: false
## 목차
1. [엔티티 조인 컬럼 활용 (필수)](#1-엔티티-조인-컬럼-활용-필수)
2. [폼 데이터 관리](#2-폼-데이터-관리)
3. [다국어 지원](#3-다국어-지원)
4. [컬럼 설정 패널 구현](#4-컬럼-설정-패널-구현)
5. [체크리스트](#5-체크리스트)
1. [컴포넌트별 테이블 설정 (핵심 원칙)](#1-컴포넌트별-테이블-설정-핵심-원칙)
2. [엔티티 조인 컬럼 활용 (필수)](#2-엔티티-조인-컬럼-활용-필수)
3. [폼 데이터 관리](#3-폼-데이터-관리)
4. [다국어 지원](#4-다국어-지원)
5. [컬럼 설정 패널 구현](#5-컬럼-설정-패널-구현)
6. [체크리스트](#6-체크리스트)
---
## 1. 엔티티 조인 컬럼 활용 (필수)
## 1. 컴포넌트별 테이블 설정 (핵심 원칙)
### 핵심 원칙
**하나의 화면에서 여러 테이블을 다룰 수 있습니다.**
화면 생성 시 "메인 테이블"을 필수로 지정하지 않으며, 컴포넌트별로 사용할 테이블을 지정할 수 있습니다.
### 왜 필요한가?
일반적인 ERP 화면에서는 여러 테이블이 동시에 필요합니다:
| 예시: 입고 화면 | 테이블 | 용도 |
| --------------- | ----------------------- | ------------------------------- |
| 메인 폼 | `receiving_mng` | 입고 마스터 정보 입력/저장 |
| 조회 리스트 | `purchase_order_detail` | 발주 상세 목록 조회 (읽기 전용) |
| 입력 리피터 | `receiving_detail` | 입고 상세 항목 입력/저장 |
### 컴포넌트 설정 패턴
#### 1. 테이블 리스트 (조회용)
```typescript
interface TableListConfig {
// 조회용 테이블 (화면 메인 테이블과 다를 수 있음)
customTableName?: string; // 사용할 테이블명
useCustomTable?: boolean; // true: customTableName 사용
isReadOnly?: boolean; // true: 조회만, 저장 안 함
}
```
#### 2. 리피터 (입력/저장용)
```typescript
interface UnifiedRepeaterConfig {
// 저장 대상 테이블 (화면 메인 테이블과 다를 수 있음)
mainTableName?: string; // 저장할 테이블명
useCustomTable?: boolean; // true: mainTableName 사용
// FK 자동 연결 (마스터-디테일 관계)
foreignKeyColumn?: string; // 이 테이블의 FK 컬럼 (예: receiving_id)
foreignKeySourceColumn?: string; // 마스터 테이블의 PK 컬럼 (예: id)
}
```
### 저장 테이블 설정 UI 표준
리피터 등 저장 기능이 있는 컴포넌트의 ConfigPanel에서:
```tsx
// 1. 테이블 선택 Combobox
<Popover open={tableComboboxOpen} onOpenChange={setTableComboboxOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="w-full justify-between"
>
{selectedTableName || "테이블 선택..."}
<ChevronsUpDown className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0">
<Command>
<CommandInput placeholder="테이블 검색..." />
<CommandList>
{/* 그룹 1: 현재 화면 테이블 (기본) */}
<CommandGroup heading="기본">
<CommandItem value={currentTableName}>
<Database className="h-3 w-3 text-blue-500" />
{currentTableName}
</CommandItem>
</CommandGroup>
{/* 그룹 2: 연관 테이블 (FK 자동 설정) */}
{relatedTables.length > 0 && (
<CommandGroup heading="연관 테이블 (FK 자동 설정)">
{relatedTables.map((table) => (
<CommandItem key={table.tableName} value={table.tableName}>
<Link2 className="h-3 w-3 text-green-500" />
{table.tableName}
<span className="text-xs text-muted-foreground ml-auto">
FK: {table.foreignKeyColumn}
</span>
</CommandItem>
))}
</CommandGroup>
)}
{/* 그룹 3: 전체 테이블 */}
<CommandGroup heading="전체 테이블">
{allTables.map((table) => (
<CommandItem key={table.tableName} value={table.tableName}>
{table.displayName || table.tableName}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>;
// 2. 연관 테이블 선택 시 FK/PK 자동 설정
const handleSaveTableSelect = (tableName: string) => {
const relation = relatedTables.find((r) => r.tableName === tableName);
if (relation) {
// 엔티티 관계에서 자동으로 FK/PK 가져옴
updateConfig({
useCustomTable: true,
mainTableName: tableName,
foreignKeyColumn: relation.foreignKeyColumn,
foreignKeySourceColumn: relation.referenceColumn,
});
} else {
// 연관 테이블이 아니면 수동 입력 필요
updateConfig({
useCustomTable: true,
mainTableName: tableName,
foreignKeyColumn: undefined,
foreignKeySourceColumn: undefined,
});
}
};
```
### 연관 테이블 조회 API
엔티티 관계에서 현재 테이블을 참조하는 테이블 목록을 조회합니다:
```typescript
// API 호출
const response = await apiClient.get(
`/api/table-management/columns/${currentTableName}/referenced-by`
);
// 응답
{
success: true,
data: [
{
tableName: "receiving_detail", // 참조하는 테이블
columnName: "receiving_id", // FK 컬럼
referenceColumn: "id", // 참조되는 컬럼 (PK)
},
// ...
]
}
```
### FK 자동 연결 동작
마스터 저장 후 디테일 저장 시 FK가 자동으로 설정됩니다:
```typescript
// 1. 마스터 저장 이벤트 발생 (ButtonConfigPanel에서)
window.dispatchEvent(
new CustomEvent("repeaterSave", {
detail: {
masterRecordId: savedId, // 마스터 테이블에 저장된 ID
tableName: "receiving_mng",
mainFormData: formData,
},
})
);
// 2. 리피터에서 이벤트 수신 및 FK 설정
useEffect(() => {
const handleSaveEvent = (event: CustomEvent) => {
const { masterRecordId } = event.detail;
if (config.foreignKeyColumn && masterRecordId) {
// 모든 행에 FK 값 자동 설정
const updatedRows = rows.map((row) => ({
...row,
[config.foreignKeyColumn]: masterRecordId,
}));
// 저장 실행
saveRows(updatedRows);
}
};
window.addEventListener("repeaterSave", handleSaveEvent);
return () => window.removeEventListener("repeaterSave", handleSaveEvent);
}, [config.foreignKeyColumn, rows]);
```
### 저장 테이블 변경 시 컬럼 자동 로드
저장 테이블이 변경되면 해당 테이블의 컬럼이 자동으로 로드됩니다:
```typescript
// 저장 테이블 또는 화면 테이블 기준으로 컬럼 로드
const targetTableForColumns =
config.useCustomTable && config.mainTableName
? config.mainTableName
: currentTableName;
useEffect(() => {
const loadColumns = async () => {
if (!targetTableForColumns) return;
const columnData = await tableTypeApi.getColumns(targetTableForColumns);
setCurrentTableColumns(columnData);
};
loadColumns();
}, [targetTableForColumns]);
```
### 요약
| 상황 | 처리 방법 |
| ------------------------------------- | ----------------------------------- |
| 화면과 같은 테이블에 저장 | `useCustomTable: false` (기본값) |
| 다른 테이블에 저장 + 엔티티 관계 있음 | 연관 테이블 선택 → FK/PK 자동 설정 |
| 다른 테이블에 저장 + 엔티티 관계 없음 | 전체 테이블에서 선택 → FK 수동 입력 |
| 조회만 (저장 안 함) | `isReadOnly: true` 설정 |
---
## 2. 엔티티 조인 컬럼 활용 (필수)
### 핵심 원칙
@@ -283,7 +506,7 @@ const getEntityJoinValue = (item: any, columnName: string): any => {
---
## 2. 폼 데이터 관리
## 3. 폼 데이터 관리
### 통합 폼 시스템 (UnifiedFormContext)
@@ -368,7 +591,7 @@ const handleChange = useCallback(
---
## 3. 다국어 지원
## 4. 다국어 지원
### 타입 정의 시 다국어 필드 추가
@@ -534,7 +757,7 @@ if (comp.componentType === "my-new-component") {
---
## 4. 컬럼 설정 패널 구현
## 5. 컬럼 설정 패널 구현
### 필수 구조
@@ -639,10 +862,19 @@ export const MyComponentConfigPanel: React.FC<ConfigPanelProps> = ({
---
## 5. 체크리스트
## 6. 체크리스트
새 컴포넌트 개발 시 다음 항목을 확인하세요:
### 컴포넌트별 테이블 설정 (핵심)
- [ ] 화면 메인 테이블과 다른 테이블을 사용할 수 있는지 확인
- [ ] `useCustomTable`, `mainTableName` (또는 `customTableName`) 설정 지원
- [ ] 연관 테이블 선택 시 FK/PK 자동 설정 (`/api/table-management/columns/:tableName/referenced-by` API 활용)
- [ ] 저장 테이블 변경 시 해당 테이블의 컬럼 자동 로드
- [ ] 테이블 선택 UI는 Combobox 형태로 그룹별 표시 (기본/연관/전체)
- [ ] FK 자동 연결: `repeaterSave` 이벤트에서 `masterRecordId` 수신 및 적용
### 엔티티 조인 (필수)
- [ ] `entityJoinApi.getEntityJoinColumns()` 호출하여 조인 컬럼 로드