Merge branch 'feature/v2-renewal' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
@@ -565,12 +565,32 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||
return newActiveIds;
|
||||
}, [formData, groupData, conditionalLayers, screenData?.components]);
|
||||
|
||||
// 🆕 활성화된 조건부 레이어의 컴포넌트 가져오기
|
||||
// 활성화된 조건부 레이어의 컴포넌트 가져오기 (Zone 오프셋 적용)
|
||||
const activeConditionalComponents = useMemo(() => {
|
||||
return conditionalLayers
|
||||
.filter((layer) => activeConditionalLayerIds.includes(layer.id))
|
||||
.flatMap((layer) => (layer as LayerDefinition & { components: ComponentData[] }).components || []);
|
||||
}, [conditionalLayers, activeConditionalLayerIds]);
|
||||
.flatMap((layer) => {
|
||||
const layerWithComps = layer as LayerDefinition & { components: ComponentData[] };
|
||||
const comps = layerWithComps.components || [];
|
||||
|
||||
// Zone 오프셋 적용: 조건부 레이어 컴포넌트는 Zone 내부 상대 좌표로 저장되므로
|
||||
// Zone의 절대 좌표를 더해줘야 EditModal에서 올바른 위치에 렌더링됨
|
||||
const associatedZone = zones.find((z) => z.zone_id === (layer as any).zoneId);
|
||||
if (!associatedZone) return comps;
|
||||
|
||||
const zoneOffsetX = associatedZone.x || 0;
|
||||
const zoneOffsetY = associatedZone.y || 0;
|
||||
|
||||
return comps.map((comp) => ({
|
||||
...comp,
|
||||
position: {
|
||||
...comp.position,
|
||||
x: parseFloat(comp.position?.x?.toString() || "0") + zoneOffsetX,
|
||||
y: parseFloat(comp.position?.y?.toString() || "0") + zoneOffsetY,
|
||||
},
|
||||
}));
|
||||
});
|
||||
}, [conditionalLayers, activeConditionalLayerIds, zones]);
|
||||
|
||||
const handleClose = () => {
|
||||
setModalState({
|
||||
@@ -881,14 +901,31 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// V2Repeater 저장 이벤트 발생 (디테일 테이블 데이터 저장)
|
||||
const hasRepeaterInstances = window.__v2RepeaterInstances && window.__v2RepeaterInstances.size > 0;
|
||||
if (hasRepeaterInstances) {
|
||||
const masterRecordId = groupData[0]?.id || formData.id;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: masterRecordId,
|
||||
masterRecordId,
|
||||
mainFormData: formData,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
},
|
||||
}),
|
||||
);
|
||||
console.log("📋 [EditModal] 그룹 저장 후 repeaterSave 이벤트 발생:", { masterRecordId });
|
||||
}
|
||||
|
||||
// 결과 메시지
|
||||
const messages: string[] = [];
|
||||
if (insertedCount > 0) messages.push(`${insertedCount}개 추가`);
|
||||
if (updatedCount > 0) messages.push(`${updatedCount}개 수정`);
|
||||
if (deletedCount > 0) messages.push(`${deletedCount}개 삭제`);
|
||||
|
||||
if (messages.length > 0) {
|
||||
toast.success(`품목이 저장되었습니다 (${messages.join(", ")})`);
|
||||
if (messages.length > 0 || hasRepeaterInstances) {
|
||||
toast.success(messages.length > 0 ? `품목이 저장되었습니다 (${messages.join(", ")})` : "저장되었습니다.");
|
||||
|
||||
// 부모 컴포넌트의 onSave 콜백 실행 (테이블 새로고침)
|
||||
if (modalState.onSave) {
|
||||
|
||||
@@ -2231,11 +2231,20 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
}
|
||||
: component;
|
||||
|
||||
// 🆕 모든 레이어의 컴포넌트를 통합 (조건부 레이어 내 컴포넌트가 기본 레이어 formData 참조 가능하도록)
|
||||
// 모든 레이어의 컴포넌트 통합 (조건 평가용 - 트리거 컴포넌트 검색에 필요)
|
||||
const allLayerComponents = useMemo(() => {
|
||||
return layers.flatMap((layer) => layer.components);
|
||||
}, [layers]);
|
||||
|
||||
// 🔧 활성 레이어 컴포넌트만 통합 (저장/데이터 수집용)
|
||||
// 기본 레이어(base) + 현재 활성화된 조건부 레이어만 포함
|
||||
// 비활성 레이어의 중복 columnName 컴포넌트가 저장 데이터를 오염시키는 문제 해결
|
||||
const visibleLayerComponents = useMemo(() => {
|
||||
return layers
|
||||
.filter((layer) => layer.type === "base" || activeLayerIds.includes(layer.id))
|
||||
.flatMap((layer) => layer.components);
|
||||
}, [layers, activeLayerIds]);
|
||||
|
||||
// 🆕 레이어별 컴포넌트 렌더링 함수
|
||||
const renderLayerComponents = useCallback((layer: LayerDefinition) => {
|
||||
// 활성화되지 않은 레이어는 렌더링하지 않음
|
||||
@@ -2272,7 +2281,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
>
|
||||
<InteractiveScreenViewer
|
||||
component={comp}
|
||||
allComponents={allLayerComponents}
|
||||
allComponents={visibleLayerComponents}
|
||||
formData={externalFormData}
|
||||
onFormDataChange={onFormDataChange}
|
||||
screenInfo={screenInfo}
|
||||
@@ -2344,7 +2353,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
>
|
||||
<InteractiveScreenViewer
|
||||
component={comp}
|
||||
allComponents={allLayerComponents}
|
||||
allComponents={visibleLayerComponents}
|
||||
formData={externalFormData}
|
||||
onFormDataChange={onFormDataChange}
|
||||
screenInfo={screenInfo}
|
||||
@@ -2387,7 +2396,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
>
|
||||
<InteractiveScreenViewer
|
||||
component={comp}
|
||||
allComponents={allLayerComponents}
|
||||
allComponents={visibleLayerComponents}
|
||||
formData={externalFormData}
|
||||
onFormDataChange={onFormDataChange}
|
||||
screenInfo={screenInfo}
|
||||
@@ -2423,7 +2432,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
>
|
||||
<InteractiveScreenViewer
|
||||
component={comp}
|
||||
allComponents={allLayerComponents}
|
||||
allComponents={visibleLayerComponents}
|
||||
formData={externalFormData}
|
||||
onFormDataChange={onFormDataChange}
|
||||
screenInfo={screenInfo}
|
||||
@@ -2433,7 +2442,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}, [activeLayerIds, handleLayerAction, externalFormData, onFormDataChange, screenInfo, calculateYOffset, allLayerComponents, layers]);
|
||||
}, [activeLayerIds, handleLayerAction, externalFormData, onFormDataChange, screenInfo, calculateYOffset, visibleLayerComponents, layers]);
|
||||
|
||||
return (
|
||||
<SplitPanelProvider>
|
||||
@@ -2485,7 +2494,13 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
setPopupScreen(null);
|
||||
setPopupFormData({}); // 팝업 닫을 때 formData도 초기화
|
||||
}}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden p-0">
|
||||
<DialogContent
|
||||
className="max-w-none w-auto max-h-[90vh] overflow-hidden p-0"
|
||||
style={popupScreenResolution ? {
|
||||
width: `${Math.min(popupScreenResolution.width + 48, window.innerWidth * 0.98)}px`,
|
||||
maxWidth: "98vw",
|
||||
} : { maxWidth: "56rem" }}
|
||||
>
|
||||
<DialogHeader className="px-6 pt-4 pb-2">
|
||||
<DialogTitle>{popupScreen?.title || "상세 정보"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -5556,8 +5556,12 @@ export default function ScreenDesigner({
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. 삭제 (단일/다중 선택 지원)
|
||||
if (e.key === "Delete" && (selectedComponent || groupState.selectedComponents.length > 0)) {
|
||||
// 6. 삭제 (단일/다중 선택 지원) - Delete 또는 Backspace(Mac)
|
||||
const isInputFocused = document.activeElement instanceof HTMLInputElement ||
|
||||
document.activeElement instanceof HTMLTextAreaElement ||
|
||||
document.activeElement instanceof HTMLSelectElement ||
|
||||
(document.activeElement as HTMLElement)?.isContentEditable;
|
||||
if ((e.key === "Delete" || (e.key === "Backspace" && !isInputFocused)) && (selectedComponent || groupState.selectedComponents.length > 0)) {
|
||||
// console.log("🗑️ 컴포넌트 삭제 (단축키)");
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -7419,7 +7423,7 @@ export default function ScreenDesigner({
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-medium">편집:</span> Ctrl+C(복사), Ctrl+V(붙여넣기), Ctrl+S(저장),
|
||||
Ctrl+Z(실행취소), Delete(삭제)
|
||||
Ctrl+Z(실행취소), Delete/Backspace(삭제)
|
||||
</p>
|
||||
<p className="text-warning flex items-center justify-center gap-2">
|
||||
<span>⚠️</span>
|
||||
|
||||
Reference in New Issue
Block a user