UnifiedRepeater 및 관련 컴포넌트에서 마스터 레코드 ID와 커스텀 테이블 설정 기능을 추가했습니다. 데이터 저장 시 마스터 레코드 ID를 포함하여 FK 자동 연결을 지원하며, 커스텀 테이블 사용 여부에 따라 저장 대상을 설정할 수 있도록 개선했습니다.
This commit is contained in:
parent
321c52a1f8
commit
bed7f5f5c4
|
|
@ -542,10 +542,17 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
const response = await dynamicFormApi.saveData(saveData);
|
||||
|
||||
if (response.success) {
|
||||
const masterRecordId = response.data?.id || formData.id;
|
||||
|
||||
// 리피터 데이터 저장 이벤트 발생 (UnifiedRepeater 컴포넌트가 리스닝)
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: { parentId: response.data?.id || formData.id },
|
||||
detail: {
|
||||
parentId: masterRecordId,
|
||||
masterRecordId, // 🆕 마스터 레코드 ID (FK 자동 연결용)
|
||||
mainFormData: formData,
|
||||
tableName: screenInfo.tableName,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -812,26 +812,34 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
|||
<SelectValue placeholder="버튼 액션 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{/* 핵심 액션 */}
|
||||
<SelectItem value="save">저장</SelectItem>
|
||||
<SelectItem value="delete">삭제</SelectItem>
|
||||
<SelectItem value="edit">편집</SelectItem>
|
||||
<SelectItem value="copy">복사 (품목코드 초기화)</SelectItem>
|
||||
<SelectItem value="navigate">페이지 이동</SelectItem>
|
||||
<SelectItem value="transferData">데이터 전달</SelectItem>
|
||||
<SelectItem value="modal">모달 열기</SelectItem>
|
||||
<SelectItem value="openRelatedModal">연관 데이터 버튼 모달 열기</SelectItem>
|
||||
<SelectItem value="openModalWithData" className="text-muted-foreground">
|
||||
(deprecated) 데이터 전달 + 모달 열기
|
||||
</SelectItem>
|
||||
<SelectItem value="quickInsert">즉시 저장</SelectItem>
|
||||
<SelectItem value="control">제어 흐름</SelectItem>
|
||||
<SelectItem value="view_table_history">테이블 이력 보기</SelectItem>
|
||||
<SelectItem value="transferData">데이터 전달</SelectItem>
|
||||
|
||||
{/* 엑셀 관련 */}
|
||||
<SelectItem value="excel_download">엑셀 다운로드</SelectItem>
|
||||
<SelectItem value="excel_upload">엑셀 업로드</SelectItem>
|
||||
|
||||
{/* 고급 기능 */}
|
||||
<SelectItem value="quickInsert">즉시 저장</SelectItem>
|
||||
<SelectItem value="control">제어 흐름</SelectItem>
|
||||
|
||||
{/* 특수 기능 (필요 시 사용) */}
|
||||
<SelectItem value="barcode_scan">바코드 스캔</SelectItem>
|
||||
<SelectItem value="code_merge">코드 병합</SelectItem>
|
||||
{/* <SelectItem value="empty_vehicle">공차등록</SelectItem> */}
|
||||
<SelectItem value="operation_control">운행알림 및 종료</SelectItem>
|
||||
|
||||
{/* 🔒 숨김 처리 - 기존 시스템 호환성 유지, UI에서만 숨김
|
||||
<SelectItem value="copy">복사 (품목코드 초기화)</SelectItem>
|
||||
<SelectItem value="openRelatedModal">연관 데이터 버튼 모달 열기</SelectItem>
|
||||
<SelectItem value="openModalWithData">(deprecated) 데이터 전달 + 모달 열기</SelectItem>
|
||||
<SelectItem value="view_table_history">테이블 이력 보기</SelectItem>
|
||||
<SelectItem value="code_merge">코드 병합</SelectItem>
|
||||
<SelectItem value="empty_vehicle">공차등록</SelectItem>
|
||||
*/}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -104,14 +104,30 @@ export const UnifiedRepeater: React.FC<UnifiedRepeaterProps> = ({
|
|||
// 저장 이벤트 리스너
|
||||
useEffect(() => {
|
||||
const handleSaveEvent = async (event: CustomEvent) => {
|
||||
const tableName = config.dataSource?.tableName;
|
||||
// 🆕 mainTableName이 설정된 경우 우선 사용, 없으면 dataSource.tableName 사용
|
||||
const tableName = config.useCustomTable && config.mainTableName
|
||||
? config.mainTableName
|
||||
: config.dataSource?.tableName;
|
||||
const eventParentId = event.detail?.parentId;
|
||||
const mainFormData = event.detail?.mainFormData;
|
||||
|
||||
// 🆕 마스터 테이블에서 생성된 ID (FK 연결용)
|
||||
const masterRecordId = event.detail?.masterRecordId || mainFormData?.id;
|
||||
|
||||
if (!tableName || data.length === 0) {
|
||||
console.log("📋 UnifiedRepeater 저장 스킵:", { tableName, dataLength: data.length });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("📋 UnifiedRepeater 저장 시작:", {
|
||||
tableName,
|
||||
useCustomTable: config.useCustomTable,
|
||||
mainTableName: config.mainTableName,
|
||||
foreignKeyColumn: config.foreignKeyColumn,
|
||||
masterRecordId,
|
||||
dataLength: data.length
|
||||
});
|
||||
|
||||
try {
|
||||
// 테이블 유효 컬럼 조회
|
||||
let validColumns: Set<string> = new Set();
|
||||
|
|
@ -130,12 +146,25 @@ export const UnifiedRepeater: React.FC<UnifiedRepeaterProps> = ({
|
|||
// 내부 필드 제거
|
||||
const cleanRow = Object.fromEntries(Object.entries(row).filter(([key]) => !key.startsWith("_")));
|
||||
|
||||
// 메인 폼 데이터 병합
|
||||
const { id: _mainId, ...mainFormDataWithoutId } = mainFormData || {};
|
||||
const mergedData = {
|
||||
...mainFormDataWithoutId,
|
||||
...cleanRow,
|
||||
};
|
||||
// 메인 폼 데이터 병합 (커스텀 테이블 사용 시에는 메인 폼 데이터 병합 안함)
|
||||
let mergedData: Record<string, any>;
|
||||
if (config.useCustomTable && config.mainTableName) {
|
||||
// 커스텀 테이블: 리피터 데이터만 저장
|
||||
mergedData = { ...cleanRow };
|
||||
|
||||
// 🆕 FK 자동 연결
|
||||
if (config.foreignKeyColumn && masterRecordId) {
|
||||
mergedData[config.foreignKeyColumn] = masterRecordId;
|
||||
console.log(`📎 FK 자동 연결: ${config.foreignKeyColumn} = ${masterRecordId}`);
|
||||
}
|
||||
} else {
|
||||
// 기존 방식: 메인 폼 데이터 병합
|
||||
const { id: _mainId, ...mainFormDataWithoutId } = mainFormData || {};
|
||||
mergedData = {
|
||||
...mainFormDataWithoutId,
|
||||
...cleanRow,
|
||||
};
|
||||
}
|
||||
|
||||
// 유효하지 않은 컬럼 제거
|
||||
const filteredData: Record<string, any> = {};
|
||||
|
|
@ -148,9 +177,9 @@ export const UnifiedRepeater: React.FC<UnifiedRepeaterProps> = ({
|
|||
await apiClient.post(`/table-management/tables/${tableName}/add`, filteredData);
|
||||
}
|
||||
|
||||
console.log("UnifiedRepeater 저장 완료:", data.length, "건");
|
||||
console.log("✅ UnifiedRepeater 저장 완료:", data.length, "건 →", tableName);
|
||||
} catch (error) {
|
||||
console.error("UnifiedRepeater 저장 실패:", error);
|
||||
console.error("❌ UnifiedRepeater 저장 실패:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -159,7 +188,7 @@ export const UnifiedRepeater: React.FC<UnifiedRepeaterProps> = ({
|
|||
return () => {
|
||||
window.removeEventListener("repeaterSave" as any, handleSaveEvent);
|
||||
};
|
||||
}, [data, config.dataSource?.tableName, parentId]);
|
||||
}, [data, config.dataSource?.tableName, config.useCustomTable, config.mainTableName, config.foreignKeyColumn, parentId]);
|
||||
|
||||
// 현재 테이블 컬럼 정보 로드
|
||||
useEffect(() => {
|
||||
|
|
@ -631,6 +660,107 @@ export const UnifiedRepeater: React.FC<UnifiedRepeaterProps> = ({
|
|||
};
|
||||
}, [config.fieldName]);
|
||||
|
||||
// 🆕 데이터 전달 이벤트 리스너 (transferData 버튼 액션용)
|
||||
useEffect(() => {
|
||||
// componentDataTransfer: 특정 컴포넌트 ID로 데이터 전달
|
||||
const handleComponentDataTransfer = async (event: Event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
const { targetComponentId, data: transferData, mappingRules, mode } = customEvent.detail || {};
|
||||
|
||||
// 이 컴포넌트가 대상인지 확인
|
||||
if (targetComponentId !== parentId && targetComponentId !== config.fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("📥 [UnifiedRepeater] componentDataTransfer 수신:", {
|
||||
targetComponentId,
|
||||
dataCount: transferData?.length,
|
||||
mode,
|
||||
myId: parentId,
|
||||
});
|
||||
|
||||
if (!transferData || transferData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 데이터 매핑 처리
|
||||
const mappedData = transferData.map((item: any, index: number) => {
|
||||
const newRow: any = { _id: `transfer_${Date.now()}_${index}` };
|
||||
|
||||
if (mappingRules && mappingRules.length > 0) {
|
||||
// 매핑 규칙이 있으면 적용
|
||||
mappingRules.forEach((rule: any) => {
|
||||
newRow[rule.targetField] = item[rule.sourceField];
|
||||
});
|
||||
} else {
|
||||
// 매핑 규칙 없으면 그대로 복사
|
||||
Object.assign(newRow, item);
|
||||
}
|
||||
|
||||
return newRow;
|
||||
});
|
||||
|
||||
// mode에 따라 데이터 처리
|
||||
if (mode === "replace") {
|
||||
handleDataChange(mappedData);
|
||||
} else if (mode === "merge") {
|
||||
// 중복 제거 후 병합 (id 기준)
|
||||
const existingIds = new Set(data.map((row) => row.id || row._id));
|
||||
const newItems = mappedData.filter((row: any) => !existingIds.has(row.id || row._id));
|
||||
handleDataChange([...data, ...newItems]);
|
||||
} else {
|
||||
// 기본: append
|
||||
handleDataChange([...data, ...mappedData]);
|
||||
}
|
||||
};
|
||||
|
||||
// splitPanelDataTransfer: 분할 패널에서 전역 이벤트로 전달
|
||||
const handleSplitPanelDataTransfer = async (event: Event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
const { data: transferData, mappingRules, mode, sourcePosition } = customEvent.detail || {};
|
||||
|
||||
console.log("📥 [UnifiedRepeater] splitPanelDataTransfer 수신:", {
|
||||
dataCount: transferData?.length,
|
||||
mode,
|
||||
sourcePosition,
|
||||
});
|
||||
|
||||
if (!transferData || transferData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 데이터 매핑 처리
|
||||
const mappedData = transferData.map((item: any, index: number) => {
|
||||
const newRow: any = { _id: `transfer_${Date.now()}_${index}` };
|
||||
|
||||
if (mappingRules && mappingRules.length > 0) {
|
||||
mappingRules.forEach((rule: any) => {
|
||||
newRow[rule.targetField] = item[rule.sourceField];
|
||||
});
|
||||
} else {
|
||||
Object.assign(newRow, item);
|
||||
}
|
||||
|
||||
return newRow;
|
||||
});
|
||||
|
||||
// mode에 따라 데이터 처리
|
||||
if (mode === "replace") {
|
||||
handleDataChange(mappedData);
|
||||
} else {
|
||||
handleDataChange([...data, ...mappedData]);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("componentDataTransfer", handleComponentDataTransfer as EventListener);
|
||||
window.addEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer as EventListener);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("componentDataTransfer", handleComponentDataTransfer as EventListener);
|
||||
window.removeEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer as EventListener);
|
||||
};
|
||||
}, [parentId, config.fieldName, data, handleDataChange]);
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-4", className)}>
|
||||
{/* 헤더 영역 */}
|
||||
|
|
|
|||
|
|
@ -529,6 +529,103 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
|||
|
||||
<Separator />
|
||||
|
||||
{/* 저장 대상 테이블 설정 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium">저장 대상 테이블</Label>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
화면 메인 테이블과 다른 테이블에 저장할 경우 설정
|
||||
</p>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="useCustomTable"
|
||||
checked={config.useCustomTable || false}
|
||||
onCheckedChange={(checked) => {
|
||||
if (!checked) {
|
||||
updateConfig({
|
||||
useCustomTable: false,
|
||||
mainTableName: undefined,
|
||||
foreignKeyColumn: undefined,
|
||||
foreignKeySourceColumn: undefined,
|
||||
});
|
||||
} else {
|
||||
updateConfig({ useCustomTable: true });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="useCustomTable" className="text-xs">다른 테이블에 저장</label>
|
||||
</div>
|
||||
|
||||
{config.useCustomTable && (
|
||||
<div className="space-y-2 rounded border border-orange-200 bg-orange-50 p-2">
|
||||
{/* 저장 테이블 선택 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">저장 테이블 *</Label>
|
||||
<Select
|
||||
value={config.mainTableName || ""}
|
||||
onValueChange={(value) => updateConfig({ mainTableName: value })}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="테이블 선택..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{currentTableColumns.length === 0 ? (
|
||||
<div className="p-2 text-xs text-gray-500">테이블 목록 로딩 중...</div>
|
||||
) : (
|
||||
<SelectItem value={currentTableName || ""} disabled>
|
||||
<span className="text-gray-400">(화면 메인 테이블을 선택하세요 - 별도 로드 필요)</span>
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={config.mainTableName || ""}
|
||||
onChange={(e) => updateConfig({ mainTableName: e.target.value })}
|
||||
placeholder="테이블명 직접 입력 (예: receiving_detail)"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* FK 컬럼 설정 */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">FK 컬럼 (저장 테이블)</Label>
|
||||
<Input
|
||||
value={config.foreignKeyColumn || ""}
|
||||
onChange={(e) => updateConfig({ foreignKeyColumn: e.target.value })}
|
||||
placeholder="예: receiving_id"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">PK 컬럼 (마스터 테이블)</Label>
|
||||
<Input
|
||||
value={config.foreignKeySourceColumn || "id"}
|
||||
onChange={(e) => updateConfig({ foreignKeySourceColumn: e.target.value })}
|
||||
placeholder="예: id"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{config.mainTableName && config.foreignKeyColumn && (
|
||||
<div className="text-[10px] text-orange-700">
|
||||
<strong>저장 흐름:</strong> {config.mainTableName}.{config.foreignKeyColumn} →
|
||||
{currentTableName}.{config.foreignKeySourceColumn || "id"} (자동 연결)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!config.useCustomTable && (
|
||||
<div className="text-[10px] text-muted-foreground">
|
||||
현재: 화면 메인 테이블 ({currentTableName || "미설정"})에 저장
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* 현재 화면 정보 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium">현재 화면</Label>
|
||||
|
|
|
|||
|
|
@ -743,6 +743,111 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<div className="text-sm font-medium">테이블 리스트 설정</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 커스텀 테이블 설정 */}
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">데이터 소스 테이블</h3>
|
||||
<p className="text-muted-foreground text-[10px]">화면 메인 테이블과 다른 테이블을 사용할 수 있습니다</p>
|
||||
</div>
|
||||
<hr className="border-border" />
|
||||
|
||||
{/* 커스텀 테이블 사용 여부 */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="useCustomTable"
|
||||
checked={config.useCustomTable || false}
|
||||
onCheckedChange={(checked) => {
|
||||
handleChange("useCustomTable", checked);
|
||||
if (!checked) {
|
||||
// 커스텀 테이블 해제 시 화면 메인 테이블로 복원
|
||||
handleChange("customTableName", undefined);
|
||||
handleChange("selectedTable", screenTableName);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="useCustomTable" className="text-xs">다른 테이블 사용</Label>
|
||||
</div>
|
||||
|
||||
{config.useCustomTable && (
|
||||
<div className="space-y-2">
|
||||
{/* 테이블 선택 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">테이블 선택 *</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className="h-8 w-full justify-between text-xs"
|
||||
disabled={loadingTables}
|
||||
>
|
||||
{config.customTableName
|
||||
? availableTables.find((t) => t.tableName === config.customTableName)?.displayName ||
|
||||
config.customTableName
|
||||
: "테이블을 선택하세요..."}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="h-8 text-xs" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="py-2 text-xs">테이블을 찾을 수 없습니다</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{availableTables.map((table) => (
|
||||
<CommandItem
|
||||
key={table.tableName}
|
||||
value={table.tableName}
|
||||
onSelect={() => {
|
||||
handleChange("customTableName", table.tableName);
|
||||
handleChange("selectedTable", table.tableName);
|
||||
// 테이블 변경 시 컬럼 초기화
|
||||
handleChange("columns", []);
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
config.customTableName === table.tableName ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{table.displayName || table.tableName}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
{/* 읽기전용 설정 */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="isReadOnly"
|
||||
checked={config.isReadOnly || false}
|
||||
onCheckedChange={(checked) => handleChange("isReadOnly", checked)}
|
||||
/>
|
||||
<Label htmlFor="isReadOnly" className="text-xs">읽기전용 (조회만 가능, 저장 안함)</Label>
|
||||
</div>
|
||||
|
||||
{config.customTableName && (
|
||||
<div className="bg-muted/50 text-muted-foreground rounded p-2 text-[10px]">
|
||||
<strong>현재 설정:</strong> {config.customTableName} 테이블 사용
|
||||
{config.isReadOnly && " (읽기전용)"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!config.useCustomTable && screenTableName && (
|
||||
<div className="bg-muted/50 text-muted-foreground rounded p-2 text-[10px]">
|
||||
<strong>현재:</strong> 화면 메인 테이블 ({screenTableName}) 사용
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 테이블 제목 설정 */}
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -235,6 +235,11 @@ export interface TableListConfig extends ComponentConfig {
|
|||
showHeader: boolean;
|
||||
showFooter: boolean;
|
||||
|
||||
// 🆕 커스텀 테이블 설정 (화면 메인 테이블과 다른 테이블 사용 시)
|
||||
customTableName?: string; // 컴포넌트가 사용할 커스텀 테이블명
|
||||
useCustomTable?: boolean; // true면 customTableName 사용, false면 화면 메인 테이블 사용
|
||||
isReadOnly?: boolean; // 읽기전용 여부 (조회용 테이블인 경우 true)
|
||||
|
||||
// 체크박스 설정
|
||||
checkbox: CheckboxConfig;
|
||||
|
||||
|
|
|
|||
|
|
@ -1533,6 +1533,7 @@ export class ButtonActionExecutor {
|
|||
tableName: context.tableName,
|
||||
mainFormDataKeys: Object.keys(mainFormData),
|
||||
saveResultData: saveResult?.data,
|
||||
masterRecordId: savedId, // 🆕 마스터 레코드 ID (FK 연결용)
|
||||
});
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
|
|
@ -1540,6 +1541,7 @@ export class ButtonActionExecutor {
|
|||
parentId: savedId,
|
||||
tableName: context.tableName,
|
||||
mainFormData, // 🆕 메인 폼 데이터 전달
|
||||
masterRecordId: savedId, // 🆕 마스터 레코드 ID (FK 자동 연결용)
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -139,6 +139,12 @@ export interface UnifiedRepeaterConfig {
|
|||
// 렌더링 모드
|
||||
renderMode: RepeaterRenderMode;
|
||||
|
||||
// 🆕 저장 대상 테이블 설정 (화면 메인 테이블과 다른 테이블에 저장할 경우)
|
||||
mainTableName?: string; // 리피터 데이터가 저장될 테이블명 (미설정 시 화면 메인 테이블)
|
||||
useCustomTable?: boolean; // true면 mainTableName 사용
|
||||
foreignKeyColumn?: string; // 마스터 테이블과 연결할 FK 컬럼명 (예: receiving_id)
|
||||
foreignKeySourceColumn?: string; // 마스터 테이블의 PK 컬럼명 (예: id) - 자동 연결용
|
||||
|
||||
// 데이터 소스 설정
|
||||
dataSource: RepeaterDataSource;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue