디버그 로그 제거 및 버튼 구성 패널 개선
- ScreenDesigner 컴포넌트에서 불필요한 디버그 로그를 제거하여 코드 가독성을 향상시켰습니다. - ButtonConfigPanel에서 actionType을 로컬 상태로 관리하도록 개선하여, 버튼 액션 설정의 일관성을 높였습니다. - RepeatContainerComponent에서 섹션별 폼 데이터 관리 기능을 추가하여, 각 반복 아이템의 독립적인 폼 데이터 처리가 가능해졌습니다. 이로 인해 코드의 효율성과 유지보수성이 향상되었습니다.
This commit is contained in:
parent
8344486e56
commit
8c0572e0ac
|
|
@ -3329,14 +3329,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newComponent.type === "group") {
|
|
||||||
console.log("🔓 그룹 컴포넌트는 격자 스냅 제외:", {
|
|
||||||
type: newComponent.type,
|
|
||||||
position: newComponent.position,
|
|
||||||
size: newComponent.size,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newLayout = {
|
const newLayout = {
|
||||||
...layout,
|
...layout,
|
||||||
components: [...layout.components, newComponent],
|
components: [...layout.components, newComponent],
|
||||||
|
|
@ -3508,27 +3500,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
componentsToMove = [...componentsToMove, ...additionalComponents];
|
componentsToMove = [...componentsToMove, ...additionalComponents];
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log("드래그 시작:", component.id, "이동할 컴포넌트 수:", componentsToMove.length);
|
|
||||||
console.log("마우스 위치 (줌 보정):", {
|
|
||||||
zoomLevel,
|
|
||||||
clientX: event.clientX,
|
|
||||||
clientY: event.clientY,
|
|
||||||
rectLeft: rect.left,
|
|
||||||
rectTop: rect.top,
|
|
||||||
mouseRaw: { x: event.clientX - rect.left, y: event.clientY - rect.top },
|
|
||||||
mouseZoomCorrected: { x: relativeMouseX, y: relativeMouseY },
|
|
||||||
componentX: component.position.x,
|
|
||||||
componentY: component.position.y,
|
|
||||||
grabOffsetX: relativeMouseX - component.position.x,
|
|
||||||
grabOffsetY: relativeMouseY - component.position.y,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("🚀 드래그 시작:", {
|
|
||||||
componentId: component.id,
|
|
||||||
componentType: component.type,
|
|
||||||
initialPosition: { x: component.position.x, y: component.position.y },
|
|
||||||
});
|
|
||||||
|
|
||||||
setDragState({
|
setDragState({
|
||||||
isDragging: true,
|
isDragging: true,
|
||||||
draggedComponent: component, // 주 드래그 컴포넌트 (마우스 위치 기준)
|
draggedComponent: component, // 주 드래그 컴포넌트 (마우스 위치 기준)
|
||||||
|
|
@ -3581,27 +3552,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
};
|
};
|
||||||
|
|
||||||
// 드래그 상태 업데이트
|
// 드래그 상태 업데이트
|
||||||
console.log("🔥 ScreenDesigner updateDragPosition (줌 보정):", {
|
|
||||||
zoomLevel,
|
|
||||||
draggedComponentId: dragState.draggedComponent.id,
|
|
||||||
mouseRaw: { x: event.clientX - rect.left, y: event.clientY - rect.top },
|
|
||||||
mouseZoomCorrected: { x: relativeMouseX, y: relativeMouseY },
|
|
||||||
oldPosition: dragState.currentPosition,
|
|
||||||
newPosition: newPosition,
|
|
||||||
});
|
|
||||||
|
|
||||||
setDragState((prev) => {
|
setDragState((prev) => {
|
||||||
const newState = {
|
const newState = {
|
||||||
...prev,
|
...prev,
|
||||||
currentPosition: { ...newPosition }, // 새로운 객체 생성
|
currentPosition: { ...newPosition }, // 새로운 객체 생성
|
||||||
};
|
};
|
||||||
console.log("🔄 ScreenDesigner dragState 업데이트:", {
|
|
||||||
prevPosition: prev.currentPosition,
|
|
||||||
newPosition: newState.currentPosition,
|
|
||||||
stateChanged:
|
|
||||||
prev.currentPosition.x !== newState.currentPosition.x ||
|
|
||||||
prev.currentPosition.y !== newState.currentPosition.y,
|
|
||||||
});
|
|
||||||
return newState;
|
return newState;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -3646,13 +3601,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("🎯 격자 스냅 적용됨:", {
|
|
||||||
componentType: draggedComponent?.type,
|
|
||||||
resolution: `${screenResolution.width}x${screenResolution.height}`,
|
|
||||||
originalPosition: dragState.currentPosition,
|
|
||||||
snappedPosition: finalPosition,
|
|
||||||
columnWidth: currentGridInfo.columnWidth,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 스냅으로 인한 추가 이동 거리 계산
|
// 스냅으로 인한 추가 이동 거리 계산
|
||||||
|
|
@ -3717,28 +3665,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
height: snappedHeight,
|
height: snappedHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("🎯 드래그 종료 시 그룹 내부 컴포넌트 격자 스냅 (패딩 고려):", {
|
|
||||||
componentId: comp.id,
|
|
||||||
parentId: comp.parentId,
|
|
||||||
beforeSnap: {
|
|
||||||
x: originalComponent.position.x + totalDeltaX,
|
|
||||||
y: originalComponent.position.y + totalDeltaY,
|
|
||||||
},
|
|
||||||
calculation: {
|
|
||||||
effectiveX,
|
|
||||||
effectiveY,
|
|
||||||
columnIndex,
|
|
||||||
rowIndex,
|
|
||||||
columnWidth,
|
|
||||||
fullColumnWidth,
|
|
||||||
widthInColumns,
|
|
||||||
gap: gap || 16,
|
|
||||||
padding,
|
|
||||||
},
|
|
||||||
afterSnap: newPosition,
|
|
||||||
afterSizeSnap: newSize,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...comp,
|
...comp,
|
||||||
position: newPosition as Position,
|
position: newPosition as Position,
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
// 로컬 상태 관리 (실시간 입력 반영)
|
// 로컬 상태 관리 (실시간 입력 반영)
|
||||||
const [localInputs, setLocalInputs] = useState({
|
const [localInputs, setLocalInputs] = useState({
|
||||||
text: config.text !== undefined ? config.text : "버튼",
|
text: config.text !== undefined ? config.text : "버튼",
|
||||||
|
actionType: String(config.action?.type || "save"),
|
||||||
modalTitle: String(config.action?.modalTitle || ""),
|
modalTitle: String(config.action?.modalTitle || ""),
|
||||||
modalDescription: String(config.action?.modalDescription || ""),
|
modalDescription: String(config.action?.modalDescription || ""),
|
||||||
editModalTitle: String(config.action?.editModalTitle || ""),
|
editModalTitle: String(config.action?.editModalTitle || ""),
|
||||||
|
|
@ -135,13 +136,8 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
|
|
||||||
// "flow-widget" 체크
|
// "flow-widget" 체크
|
||||||
const isFlow = compType === "flow-widget" || compType?.toLowerCase().includes("flow");
|
const isFlow = compType === "flow-widget" || compType?.toLowerCase().includes("flow");
|
||||||
|
|
||||||
if (isFlow) {
|
|
||||||
console.log("✅ 플로우 위젯 발견!", { id: comp.id, componentType: comp.componentType });
|
|
||||||
}
|
|
||||||
return isFlow;
|
return isFlow;
|
||||||
});
|
});
|
||||||
console.log("🎯 플로우 위젯 존재 여부:", found);
|
|
||||||
return found;
|
return found;
|
||||||
}, [allComponents]);
|
}, [allComponents]);
|
||||||
|
|
||||||
|
|
@ -152,6 +148,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
|
|
||||||
setLocalInputs({
|
setLocalInputs({
|
||||||
text: latestConfig.text !== undefined ? latestConfig.text : "버튼",
|
text: latestConfig.text !== undefined ? latestConfig.text : "버튼",
|
||||||
|
actionType: String(latestAction.type || "save"),
|
||||||
modalTitle: String(latestAction.modalTitle || ""),
|
modalTitle: String(latestAction.modalTitle || ""),
|
||||||
modalDescription: String(latestAction.modalDescription || ""),
|
modalDescription: String(latestAction.modalDescription || ""),
|
||||||
editModalTitle: String(latestAction.editModalTitle || ""),
|
editModalTitle: String(latestAction.editModalTitle || ""),
|
||||||
|
|
@ -168,7 +165,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
setTitleBlocks([]);
|
setTitleBlocks([]);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [component.id]);
|
}, [component.id, component.componentConfig?.action?.type]);
|
||||||
|
|
||||||
// 🆕 제목 블록 핸들러
|
// 🆕 제목 블록 핸들러
|
||||||
const addTextBlock = () => {
|
const addTextBlock = () => {
|
||||||
|
|
@ -251,7 +248,6 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
label: table.displayName || table.tableName,
|
label: table.displayName || table.tableName,
|
||||||
}));
|
}));
|
||||||
setAvailableTables(tables);
|
setAvailableTables(tables);
|
||||||
console.log("✅ 전체 테이블 목록 로드 성공:", tables.length);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("테이블 목록 로드 실패:", error);
|
console.error("테이블 목록 로드 실패:", error);
|
||||||
|
|
@ -777,14 +773,6 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log("🔧 config-panels/ButtonConfigPanel 렌더링:", {
|
|
||||||
// component,
|
|
||||||
// config,
|
|
||||||
// action: config.action,
|
|
||||||
// actionType: config.action?.type,
|
|
||||||
// screensCount: screens.length,
|
|
||||||
// });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -804,9 +792,11 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="button-action">버튼 액션</Label>
|
<Label htmlFor="button-action">버튼 액션</Label>
|
||||||
<Select
|
<Select
|
||||||
key={`action-${component.id}-${component.componentConfig?.action?.type || "save"}`}
|
key={`action-${component.id}`}
|
||||||
value={component.componentConfig?.action?.type || "save"}
|
value={localInputs.actionType}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
|
// 🔥 로컬 상태 먼저 업데이트
|
||||||
|
setLocalInputs((prev) => ({ ...prev, actionType: value }));
|
||||||
// 🔥 action.type 업데이트
|
// 🔥 action.type 업데이트
|
||||||
onUpdateProperty("componentConfig.action.type", value);
|
onUpdateProperty("componentConfig.action.type", value);
|
||||||
|
|
||||||
|
|
@ -854,7 +844,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 모달 열기 액션 설정 */}
|
{/* 모달 열기 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "modal" && (
|
{localInputs.actionType === "modal" && (
|
||||||
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">모달 설정</h4>
|
<h4 className="text-foreground text-sm font-medium">모달 설정</h4>
|
||||||
|
|
||||||
|
|
@ -1748,7 +1738,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 수정 액션 설정 */}
|
{/* 수정 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "edit" && (
|
{localInputs.actionType === "edit" && (
|
||||||
<div className="bg-success/10 mt-4 space-y-4 rounded-lg border p-4">
|
<div className="bg-success/10 mt-4 space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">수정 설정</h4>
|
<h4 className="text-foreground text-sm font-medium">수정 설정</h4>
|
||||||
|
|
||||||
|
|
@ -2005,7 +1995,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 복사 액션 설정 */}
|
{/* 복사 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "copy" && (
|
{localInputs.actionType === "copy" && (
|
||||||
<div className="mt-4 space-y-4 rounded-lg border bg-blue-50 p-4">
|
<div className="mt-4 space-y-4 rounded-lg border bg-blue-50 p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">복사 설정 (품목코드 자동 초기화)</h4>
|
<h4 className="text-foreground text-sm font-medium">복사 설정 (품목코드 자동 초기화)</h4>
|
||||||
|
|
||||||
|
|
@ -2160,7 +2150,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 테이블 이력 보기 액션 설정 */}
|
{/* 테이블 이력 보기 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "view_table_history" && (
|
{localInputs.actionType === "view_table_history" && (
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label>
|
<Label>
|
||||||
|
|
@ -2221,7 +2211,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 페이지 이동 액션 설정 */}
|
{/* 페이지 이동 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "navigate" && (
|
{localInputs.actionType === "navigate" && (
|
||||||
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">페이지 이동 설정</h4>
|
<h4 className="text-foreground text-sm font-medium">페이지 이동 설정</h4>
|
||||||
|
|
||||||
|
|
@ -2317,7 +2307,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 엑셀 다운로드 액션 설정 */}
|
{/* 엑셀 다운로드 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "excel_download" && (
|
{localInputs.actionType === "excel_download" && (
|
||||||
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">엑셀 다운로드 설정</h4>
|
<h4 className="text-foreground text-sm font-medium">엑셀 다운로드 설정</h4>
|
||||||
|
|
||||||
|
|
@ -2356,7 +2346,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 엑셀 업로드 액션 설정 */}
|
{/* 엑셀 업로드 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "excel_upload" && (
|
{localInputs.actionType === "excel_upload" && (
|
||||||
<ExcelUploadConfigSection
|
<ExcelUploadConfigSection
|
||||||
config={config}
|
config={config}
|
||||||
onUpdateProperty={onUpdateProperty}
|
onUpdateProperty={onUpdateProperty}
|
||||||
|
|
@ -2366,7 +2356,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 바코드 스캔 액션 설정 */}
|
{/* 바코드 스캔 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "barcode_scan" && (
|
{localInputs.actionType === "barcode_scan" && (
|
||||||
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">📷 바코드 스캔 설정</h4>
|
<h4 className="text-foreground text-sm font-medium">📷 바코드 스캔 설정</h4>
|
||||||
|
|
||||||
|
|
@ -2413,7 +2403,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 코드 병합 액션 설정 */}
|
{/* 코드 병합 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "code_merge" && (
|
{localInputs.actionType === "code_merge" && (
|
||||||
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">🔀 코드 병합 설정</h4>
|
<h4 className="text-foreground text-sm font-medium">🔀 코드 병합 설정</h4>
|
||||||
|
|
||||||
|
|
@ -2460,14 +2450,14 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 공차등록 설정 - 운행알림으로 통합되어 주석 처리 */}
|
{/* 공차등록 설정 - 운행알림으로 통합되어 주석 처리 */}
|
||||||
{/* {(component.componentConfig?.action?.type || "save") === "empty_vehicle" && (
|
{/* {localInputs.actionType === "empty_vehicle" && (
|
||||||
<div className="mt-4 space-y-4 rounded-lg border bg-muted/50 p-4">
|
<div className="mt-4 space-y-4 rounded-lg border bg-muted/50 p-4">
|
||||||
... 공차등록 설정 UI 생략 ...
|
... 공차등록 설정 UI 생략 ...
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
|
|
||||||
{/* 운행알림 및 종료 설정 */}
|
{/* 운행알림 및 종료 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "operation_control" && (
|
{localInputs.actionType === "operation_control" && (
|
||||||
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">🚗 운행알림 및 종료 설정</h4>
|
<h4 className="text-foreground text-sm font-medium">🚗 운행알림 및 종료 설정</h4>
|
||||||
|
|
||||||
|
|
@ -2908,7 +2898,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 데이터 전달 액션 설정 */}
|
{/* 데이터 전달 액션 설정 */}
|
||||||
{(component.componentConfig?.action?.type || "save") === "transferData" && (
|
{localInputs.actionType === "transferData" && (
|
||||||
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
<div className="bg-muted/50 mt-4 space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="text-foreground text-sm font-medium">📦 데이터 전달 설정</h4>
|
<h4 className="text-foreground text-sm font-medium">📦 데이터 전달 설정</h4>
|
||||||
|
|
||||||
|
|
@ -3618,7 +3608,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 제어 기능 섹션 - 엑셀 업로드가 아닐 때만 표시 */}
|
{/* 제어 기능 섹션 - 엑셀 업로드가 아닐 때만 표시 */}
|
||||||
{(component.componentConfig?.action?.type || "save") !== "excel_upload" && (
|
{localInputs.actionType !== "excel_upload" && (
|
||||||
<div className="border-border mt-8 border-t pt-6">
|
<div className="border-border mt-8 border-t pt-6">
|
||||||
<ImprovedButtonControlConfigPanel component={component} onUpdateProperty={onUpdateProperty} />
|
<ImprovedButtonControlConfigPanel component={component} onUpdateProperty={onUpdateProperty} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -307,14 +307,8 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
console.warn("⚠️ ComponentRegistry에서 ConfigPanel을 찾을 수 없음 - switch case로 이동:", {
|
|
||||||
componentId,
|
|
||||||
definitionName: definition?.name,
|
|
||||||
hasDefinition: !!definition,
|
|
||||||
});
|
|
||||||
// ConfigPanel이 없으면 아래 switch case로 넘어감
|
|
||||||
}
|
}
|
||||||
|
// ConfigPanel이 없으면 아래 switch case로 넘어감
|
||||||
}
|
}
|
||||||
|
|
||||||
// 기존 하드코딩된 설정 패널들 (레거시)
|
// 기존 하드코딩된 설정 패널들 (레거시)
|
||||||
|
|
@ -322,6 +316,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
case "button":
|
case "button":
|
||||||
case "button-primary":
|
case "button-primary":
|
||||||
case "button-secondary":
|
case "button-secondary":
|
||||||
|
case "v2-button-primary":
|
||||||
// 🔧 component.id만 key로 사용 (unmount 방지)
|
// 🔧 component.id만 key로 사용 (unmount 방지)
|
||||||
return (
|
return (
|
||||||
<ButtonConfigPanel
|
<ButtonConfigPanel
|
||||||
|
|
@ -957,6 +952,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
"button",
|
"button",
|
||||||
"button-primary",
|
"button-primary",
|
||||||
"button-secondary",
|
"button-secondary",
|
||||||
|
"v2-button-primary",
|
||||||
"card",
|
"card",
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"stats",
|
"stats",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
import React, { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
||||||
import { ComponentRendererProps } from "@/types/component";
|
import { ComponentRendererProps } from "@/types/component";
|
||||||
import { RepeatContainerConfig, RepeatItemContext, SlotComponentConfig } from "./types";
|
import { RepeatContainerConfig, RepeatItemContext, SlotComponentConfig } from "./types";
|
||||||
import { Repeat, Package, ChevronLeft, ChevronRight, Plus } from "lucide-react";
|
import { Repeat, Package, ChevronLeft, ChevronRight, Plus } from "lucide-react";
|
||||||
|
|
@ -31,6 +31,14 @@ interface RepeatContainerComponentProps extends ComponentRendererProps {
|
||||||
onUpdateComponent?: (updates: Partial<RepeatContainerConfig>) => void;
|
onUpdateComponent?: (updates: Partial<RepeatContainerConfig>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 섹션별 폼 데이터를 저장하는 타입
|
||||||
|
interface SectionFormData {
|
||||||
|
index: number;
|
||||||
|
originalData: Record<string, any>;
|
||||||
|
formData: Record<string, any>;
|
||||||
|
isDirty: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 리피터 컨테이너 컴포넌트
|
* 리피터 컨테이너 컴포넌트
|
||||||
* 데이터 수만큼 내부 컨텐츠를 반복 렌더링하는 컨테이너
|
* 데이터 수만큼 내부 컨텐츠를 반복 렌더링하는 컨테이너
|
||||||
|
|
@ -126,6 +134,9 @@ export function RepeatContainerComponent({
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
// 섹션별 폼 데이터 관리 (각 반복 아이템별로 독립적인 폼 데이터)
|
||||||
|
const sectionFormDataRef = useRef<Map<number, SectionFormData>>(new Map());
|
||||||
|
|
||||||
// 실제 사용할 테이블명
|
// 실제 사용할 테이블명
|
||||||
const effectiveTableName = useCustomTable ? customTableName : tableName;
|
const effectiveTableName = useCustomTable ? customTableName : tableName;
|
||||||
|
|
||||||
|
|
@ -133,9 +144,116 @@ export function RepeatContainerComponent({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (externalData && Array.isArray(externalData)) {
|
if (externalData && Array.isArray(externalData)) {
|
||||||
setData(externalData);
|
setData(externalData);
|
||||||
|
// 데이터가 변경되면 섹션별 폼 데이터 초기화
|
||||||
|
sectionFormDataRef.current.clear();
|
||||||
}
|
}
|
||||||
}, [externalData]);
|
}, [externalData]);
|
||||||
|
|
||||||
|
// 섹션별 폼 데이터 변경 핸들러
|
||||||
|
const handleSectionFormDataChange = useCallback(
|
||||||
|
(sectionIndex: number, key: string, value: any, originalData: Record<string, any>) => {
|
||||||
|
const currentSection = sectionFormDataRef.current.get(sectionIndex) || {
|
||||||
|
index: sectionIndex,
|
||||||
|
originalData: originalData,
|
||||||
|
formData: { ...originalData },
|
||||||
|
isDirty: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 폼 데이터 업데이트
|
||||||
|
currentSection.formData[key] = value;
|
||||||
|
|
||||||
|
// 변경 여부 확인 (원본 데이터와 비교)
|
||||||
|
currentSection.isDirty = Object.keys(currentSection.formData).some(
|
||||||
|
(k) => currentSection.formData[k] !== currentSection.originalData[k]
|
||||||
|
);
|
||||||
|
|
||||||
|
sectionFormDataRef.current.set(sectionIndex, currentSection);
|
||||||
|
|
||||||
|
// 상위로 변경 알림 (기존 방식 호환)
|
||||||
|
if (onFormDataChange) {
|
||||||
|
onFormDataChange(`_repeat_${sectionIndex}_${key}`, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onFormDataChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
// beforeFormSave 이벤트 리스너 - 외부 저장 버튼 클릭 시 섹션별 데이터 수집
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDesignMode) return;
|
||||||
|
|
||||||
|
const handleBeforeFormSave = (event: Event) => {
|
||||||
|
if (!(event instanceof CustomEvent) || !event.detail) return;
|
||||||
|
|
||||||
|
const componentKey = component?.id || effectiveTableName || "repeat_container_data";
|
||||||
|
|
||||||
|
// 섹션별 데이터 수집
|
||||||
|
const sectionsData: any[] = [];
|
||||||
|
const dirtySectionsData: any[] = [];
|
||||||
|
|
||||||
|
// data 배열의 각 아이템에 대해 폼 데이터 수집
|
||||||
|
data.forEach((originalRow, index) => {
|
||||||
|
const sectionData = sectionFormDataRef.current.get(index);
|
||||||
|
|
||||||
|
if (sectionData) {
|
||||||
|
// 섹션별 폼 데이터가 있는 경우
|
||||||
|
const mergedData = {
|
||||||
|
...originalRow,
|
||||||
|
...sectionData.formData,
|
||||||
|
_sectionIndex: index,
|
||||||
|
_isDirty: sectionData.isDirty,
|
||||||
|
_targetTable: effectiveTableName,
|
||||||
|
};
|
||||||
|
sectionsData.push(mergedData);
|
||||||
|
|
||||||
|
// 변경된 섹션만 별도로 수집
|
||||||
|
if (sectionData.isDirty) {
|
||||||
|
dirtySectionsData.push(mergedData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 폼 데이터가 없으면 원본 데이터 사용
|
||||||
|
sectionsData.push({
|
||||||
|
...originalRow,
|
||||||
|
_sectionIndex: index,
|
||||||
|
_isDirty: false,
|
||||||
|
_targetTable: effectiveTableName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// event.detail.formData에 수집된 데이터 추가
|
||||||
|
if (event.detail.formData) {
|
||||||
|
// 전체 섹션 데이터 (배열)
|
||||||
|
event.detail.formData[componentKey] = sectionsData;
|
||||||
|
|
||||||
|
// 변경된 섹션만 (저장 최적화용)
|
||||||
|
event.detail.formData[`${componentKey}_dirty`] = dirtySectionsData;
|
||||||
|
|
||||||
|
// 테이블별 그룹화 (멀티테이블 저장용)
|
||||||
|
if (effectiveTableName) {
|
||||||
|
if (!event.detail.formData._repeatContainerTables) {
|
||||||
|
event.detail.formData._repeatContainerTables = {};
|
||||||
|
}
|
||||||
|
event.detail.formData._repeatContainerTables[effectiveTableName] = dirtySectionsData.length > 0
|
||||||
|
? dirtySectionsData
|
||||||
|
: sectionsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[RepeatContainer] beforeFormSave 데이터 수집 완료:", {
|
||||||
|
componentKey,
|
||||||
|
tableName: effectiveTableName,
|
||||||
|
totalSections: sectionsData.length,
|
||||||
|
dirtySections: dirtySectionsData.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("beforeFormSave", handleBeforeFormSave as EventListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("beforeFormSave", handleBeforeFormSave as EventListener);
|
||||||
|
};
|
||||||
|
}, [isDesignMode, component?.id, effectiveTableName, data]);
|
||||||
|
|
||||||
// 컴포넌트 데이터 변경 이벤트 리스닝 (componentId 또는 tableName으로 매칭)
|
// 컴포넌트 데이터 변경 이벤트 리스닝 (componentId 또는 tableName으로 매칭)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDesignMode) return;
|
if (isDesignMode) return;
|
||||||
|
|
@ -167,6 +285,8 @@ export function RepeatContainerComponent({
|
||||||
setData(eventData);
|
setData(eventData);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setSelectedIndices([]);
|
setSelectedIndices([]);
|
||||||
|
// 데이터 변경 시 섹션별 폼 데이터 초기화
|
||||||
|
sectionFormDataRef.current.clear();
|
||||||
} else {
|
} else {
|
||||||
console.log("⚠️ 리피터: 컴포넌트 ID 불일치로 무시", { expected: dataSourceComponentId, received: componentId });
|
console.log("⚠️ 리피터: 컴포넌트 ID 불일치로 무시", { expected: dataSourceComponentId, received: componentId });
|
||||||
}
|
}
|
||||||
|
|
@ -179,6 +299,8 @@ export function RepeatContainerComponent({
|
||||||
setData(eventData);
|
setData(eventData);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setSelectedIndices([]);
|
setSelectedIndices([]);
|
||||||
|
// 데이터 변경 시 섹션별 폼 데이터 초기화
|
||||||
|
sectionFormDataRef.current.clear();
|
||||||
} else if (effectiveTableName) {
|
} else if (effectiveTableName) {
|
||||||
console.log("⚠️ 리피터: 테이블명 불일치로 무시", { expected: effectiveTableName, received: eventTableName });
|
console.log("⚠️ 리피터: 테이블명 불일치로 무시", { expected: effectiveTableName, received: eventTableName });
|
||||||
}
|
}
|
||||||
|
|
@ -409,9 +531,8 @@ export function RepeatContainerComponent({
|
||||||
companyCode={companyCode}
|
companyCode={companyCode}
|
||||||
formData={itemFormData}
|
formData={itemFormData}
|
||||||
onFormDataChange={(key, value) => {
|
onFormDataChange={(key, value) => {
|
||||||
if (onFormDataChange) {
|
// 섹션별 폼 데이터 관리
|
||||||
onFormDataChange(`_repeat_${context.index}_${key}`, value);
|
handleSectionFormDataChange(context.index, key, value, context.data);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -430,7 +551,7 @@ export function RepeatContainerComponent({
|
||||||
userId,
|
userId,
|
||||||
userName,
|
userName,
|
||||||
companyCode,
|
companyCode,
|
||||||
onFormDataChange,
|
handleSectionFormDataChange,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue