WACE 솔루션에 오신 것을 환영합니다!
diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx
index 3b75f262..ce99a685 100644
--- a/frontend/app/(main)/screens/[screenId]/page.tsx
+++ b/frontend/app/(main)/screens/[screenId]/page.tsx
@@ -26,7 +26,7 @@ function ScreenViewPage() {
const searchParams = useSearchParams();
const router = useRouter();
const screenId = parseInt(params.screenId as string);
-
+
// URL 쿼리에서 menuObjid 가져오기 (메뉴 스코프)
const menuObjid = searchParams.get("menuObjid") ? parseInt(searchParams.get("menuObjid")!) : undefined;
@@ -178,31 +178,26 @@ function ScreenViewPage() {
for (const comp of layout.components) {
// type: "component" 또는 type: "widget" 모두 처리
- if (comp.type === 'widget' || comp.type === 'component') {
+ if (comp.type === "widget" || comp.type === "component") {
const widget = comp as any;
const fieldName = widget.columnName || widget.id;
-
+
// autoFill 처리
if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) {
const autoFillConfig = widget.autoFill || (comp as any).autoFill;
const currentValue = formData[fieldName];
-
- if (currentValue === undefined || currentValue === '') {
+
+ if (currentValue === undefined || currentValue === "") {
const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig;
-
+
// 사용자 정보에서 필터 값 가져오기
const userValue = user?.[userField as keyof typeof user];
-
+
if (userValue && sourceTable && filterColumn && displayColumn) {
try {
const { tableTypeApi } = await import("@/lib/api/screen");
- const result = await tableTypeApi.getTableRecord(
- sourceTable,
- filterColumn,
- userValue,
- displayColumn
- );
-
+ const result = await tableTypeApi.getTableRecord(sourceTable, filterColumn, userValue, displayColumn);
+
setFormData((prev) => ({
...prev,
[fieldName]: result.value,
@@ -233,24 +228,27 @@ function ScreenViewPage() {
const designWidth = layout?.screenResolution?.width || 1200;
const designHeight = layout?.screenResolution?.height || 800;
- // containerRef는 이미 패딩이 적용된 영역 내부이므로 offsetWidth는 패딩을 제외한 크기입니다
+ // 컨테이너의 실제 크기
const containerWidth = containerRef.current.offsetWidth;
const containerHeight = containerRef.current.offsetHeight;
- // 화면이 잘리지 않도록 가로/세로 중 작은 쪽 기준으로 스케일 조정
- const scaleX = containerWidth / designWidth;
- const scaleY = containerHeight / designHeight;
- // 전체 화면이 보이도록 작은 쪽 기준으로 스케일 설정
- const newScale = Math.min(scaleX, scaleY);
-
+ // 여백 설정: 좌우 16px씩 (총 32px), 상단 패딩 32px (pt-8)
+ const MARGIN_X = 32;
+ const availableWidth = containerWidth - MARGIN_X;
+
+ // 가로 기준 스케일 계산 (좌우 여백 16px씩 고정)
+ const newScale = availableWidth / designWidth;
+
console.log("📐 스케일 계산:", {
containerWidth,
containerHeight,
+ MARGIN_X,
+ availableWidth,
designWidth,
designHeight,
- scaleX,
- scaleY,
finalScale: newScale,
+ "스케일된 화면 크기": `${designWidth * newScale}px × ${designHeight * newScale}px`,
+ "실제 좌우 여백": `${(containerWidth - designWidth * newScale) / 2}px씩`,
});
setScale(newScale);
@@ -307,503 +305,531 @@ function ScreenViewPage() {
return (
-
- {/* 레이아웃 준비 중 로딩 표시 */}
- {!layoutReady && (
-
-
+
+ {/* 레이아웃 준비 중 로딩 표시 */}
+ {!layoutReady && (
+
+ )}
- {/* 절대 위치 기반 렌더링 (화면관리와 동일한 방식) */}
- {layoutReady && layout && layout.components.length > 0 ? (
-
- {/* 최상위 컴포넌트들 렌더링 */}
- {(() => {
- // 🆕 플로우 버튼 그룹 감지 및 처리
- const topLevelComponents = layout.components.filter((component) => !component.parentId);
+ {/* 절대 위치 기반 렌더링 (화면관리와 동일한 방식) */}
+ {layoutReady && layout && layout.components.length > 0 ? (
+
+ {/* 최상위 컴포넌트들 렌더링 */}
+ {(() => {
+ // 🆕 플로우 버튼 그룹 감지 및 처리
+ const topLevelComponents = layout.components.filter((component) => !component.parentId);
- // 화면 관리에서 설정한 해상도를 사용하므로 widthOffset 계산 불필요
- // 모든 컴포넌트는 원본 위치 그대로 사용
- const widthOffset = 0;
+ // 화면 관리에서 설정한 해상도를 사용하므로 widthOffset 계산 불필요
+ // 모든 컴포넌트는 원본 위치 그대로 사용
+ const widthOffset = 0;
- const buttonGroups: Record
= {};
- const processedButtonIds = new Set();
- // 🔍 전체 버튼 목록 확인
- const allButtons = topLevelComponents.filter((component) => {
- const isButton =
- (component.type === "component" &&
- ["button-primary", "button-secondary"].includes((component as any).componentType)) ||
- (component.type === "widget" && (component as any).widgetType === "button");
- return isButton;
- });
+ const buttonGroups: Record = {};
+ const processedButtonIds = new Set();
+ // 🔍 전체 버튼 목록 확인
+ const allButtons = topLevelComponents.filter((component) => {
+ const isButton =
+ (component.type === "component" &&
+ ["button-primary", "button-secondary"].includes((component as any).componentType)) ||
+ (component.type === "widget" && (component as any).widgetType === "button");
+ return isButton;
+ });
- console.log(
- "🔍 메뉴에서 발견된 전체 버튼:",
- allButtons.map((b) => ({
- id: b.id,
- label: b.label,
- positionX: b.position.x,
- positionY: b.position.y,
- width: b.size?.width,
- height: b.size?.height,
- })),
- );
+ console.log(
+ "🔍 메뉴에서 발견된 전체 버튼:",
+ allButtons.map((b) => ({
+ id: b.id,
+ label: b.label,
+ positionX: b.position.x,
+ positionY: b.position.y,
+ width: b.size?.width,
+ height: b.size?.height,
+ })),
+ );
- topLevelComponents.forEach((component) => {
- const isButton =
- (component.type === "component" &&
- ["button-primary", "button-secondary"].includes((component as any).componentType)) ||
- (component.type === "widget" && (component as any).widgetType === "button");
+ topLevelComponents.forEach((component) => {
+ const isButton =
+ (component.type === "component" &&
+ ["button-primary", "button-secondary"].includes((component as any).componentType)) ||
+ (component.type === "widget" && (component as any).widgetType === "button");
- if (isButton) {
- const flowConfig = (component as any).webTypeConfig?.flowVisibilityConfig as
- | FlowVisibilityConfig
- | undefined;
+ if (isButton) {
+ const flowConfig = (component as any).webTypeConfig?.flowVisibilityConfig as
+ | FlowVisibilityConfig
+ | undefined;
- // 🔧 임시: 버튼 그룹 기능 완전 비활성화
- // TODO: 사용자가 명시적으로 그룹을 원하는 경우에만 활성화하도록 UI 개선 필요
- const DISABLE_BUTTON_GROUPS = false;
+ // 🔧 임시: 버튼 그룹 기능 완전 비활성화
+ // TODO: 사용자가 명시적으로 그룹을 원하는 경우에만 활성화하도록 UI 개선 필요
+ const DISABLE_BUTTON_GROUPS = false;
- if (
- !DISABLE_BUTTON_GROUPS &&
- flowConfig?.enabled &&
- flowConfig.layoutBehavior === "auto-compact" &&
- flowConfig.groupId
- ) {
- if (!buttonGroups[flowConfig.groupId]) {
- buttonGroups[flowConfig.groupId] = [];
+ if (
+ !DISABLE_BUTTON_GROUPS &&
+ flowConfig?.enabled &&
+ flowConfig.layoutBehavior === "auto-compact" &&
+ flowConfig.groupId
+ ) {
+ if (!buttonGroups[flowConfig.groupId]) {
+ buttonGroups[flowConfig.groupId] = [];
+ }
+ buttonGroups[flowConfig.groupId].push(component);
+ processedButtonIds.add(component.id);
}
- buttonGroups[flowConfig.groupId].push(component);
- processedButtonIds.add(component.id);
+ // else: 모든 버튼을 개별 렌더링
}
- // else: 모든 버튼을 개별 렌더링
- }
- });
+ });
- const regularComponents = topLevelComponents.filter((c) => !processedButtonIds.has(c.id));
+ const regularComponents = topLevelComponents.filter((c) => !processedButtonIds.has(c.id));
- // TableSearchWidget들을 먼저 찾기
- const tableSearchWidgets = regularComponents.filter(
- (c) => (c as any).componentId === "table-search-widget"
- );
+ // TableSearchWidget들을 먼저 찾기
+ const tableSearchWidgets = regularComponents.filter(
+ (c) => (c as any).componentId === "table-search-widget",
+ );
- // 디버그: 모든 컴포넌트 타입 확인
- console.log("🔍 전체 컴포넌트 타입:", regularComponents.map(c => ({
- id: c.id,
- type: c.type,
- componentType: (c as any).componentType,
- componentId: (c as any).componentId,
- })));
-
- // 🆕 조건부 컨테이너들을 찾기
- const conditionalContainers = regularComponents.filter(
- (c) => (c as any).componentId === "conditional-container" || (c as any).componentType === "conditional-container"
- );
-
- console.log("🔍 조건부 컨테이너 발견:", conditionalContainers.map(c => ({
- id: c.id,
- y: c.position.y,
- size: c.size,
- })));
+ // 디버그: 모든 컴포넌트 타입 확인
+ console.log(
+ "🔍 전체 컴포넌트 타입:",
+ regularComponents.map((c) => ({
+ id: c.id,
+ type: c.type,
+ componentType: (c as any).componentType,
+ componentId: (c as any).componentId,
+ })),
+ );
- // TableSearchWidget 및 조건부 컨테이너 높이 차이를 계산하여 Y 위치 조정
- const adjustedComponents = regularComponents.map((component) => {
- const isTableSearchWidget = (component as any).componentId === "table-search-widget";
- const isConditionalContainer = (component as any).componentId === "conditional-container";
-
- if (isTableSearchWidget || isConditionalContainer) {
- // 자기 자신은 조정하지 않음
- return component;
- }
-
- let totalHeightAdjustment = 0;
-
- // TableSearchWidget 높이 조정
- for (const widget of tableSearchWidgets) {
- const isBelow = component.position.y > widget.position.y;
- const heightDiff = getHeightDiff(screenId, widget.id);
-
- if (isBelow && heightDiff > 0) {
- totalHeightAdjustment += heightDiff;
+ // 🆕 조건부 컨테이너들을 찾기
+ const conditionalContainers = regularComponents.filter(
+ (c) =>
+ (c as any).componentId === "conditional-container" ||
+ (c as any).componentType === "conditional-container",
+ );
+
+ console.log(
+ "🔍 조건부 컨테이너 발견:",
+ conditionalContainers.map((c) => ({
+ id: c.id,
+ y: c.position.y,
+ size: c.size,
+ })),
+ );
+
+ // TableSearchWidget 및 조건부 컨테이너 높이 차이를 계산하여 Y 위치 조정
+ const adjustedComponents = regularComponents.map((component) => {
+ const isTableSearchWidget = (component as any).componentId === "table-search-widget";
+ const isConditionalContainer = (component as any).componentId === "conditional-container";
+
+ if (isTableSearchWidget || isConditionalContainer) {
+ // 자기 자신은 조정하지 않음
+ return component;
}
- }
-
- // 🆕 조건부 컨테이너 높이 조정
- for (const container of conditionalContainers) {
- const isBelow = component.position.y > container.position.y;
- const actualHeight = conditionalContainerHeights[container.id];
- const originalHeight = container.size?.height || 200;
- const heightDiff = actualHeight ? (actualHeight - originalHeight) : 0;
-
- console.log(`🔍 높이 조정 체크:`, {
- componentId: component.id,
- componentY: component.position.y,
- containerY: container.position.y,
- isBelow,
- actualHeight,
- originalHeight,
- heightDiff,
- containerId: container.id,
- containerSize: container.size,
- });
-
- if (isBelow && heightDiff > 0) {
- totalHeightAdjustment += heightDiff;
- console.log(`📐 컴포넌트 ${component.id} 위치 조정: ${heightDiff}px (조건부 컨테이너 ${container.id})`);
+
+ let totalHeightAdjustment = 0;
+
+ // TableSearchWidget 높이 조정
+ for (const widget of tableSearchWidgets) {
+ const isBelow = component.position.y > widget.position.y;
+ const heightDiff = getHeightDiff(screenId, widget.id);
+
+ if (isBelow && heightDiff > 0) {
+ totalHeightAdjustment += heightDiff;
+ }
}
- }
-
- if (totalHeightAdjustment > 0) {
- return {
- ...component,
- position: {
- ...component.position,
- y: component.position.y + totalHeightAdjustment,
- },
- };
- }
-
- return component;
- });
- return (
- <>
- {/* 일반 컴포넌트들 */}
- {adjustedComponents.map((component) => {
- // 화면 관리 해상도를 사용하므로 위치 조정 불필요
- return (
- {}}
- menuObjid={menuObjid}
- screenId={screenId}
- tableName={screen?.tableName}
- userId={user?.userId}
- userName={userName}
- companyCode={companyCode}
- menuObjid={menuObjid}
- selectedRowsData={selectedRowsData}
- sortBy={tableSortBy}
- sortOrder={tableSortOrder}
- columnOrder={tableColumnOrder}
- tableDisplayData={tableDisplayData}
- onSelectedRowsChange={(_, selectedData, sortBy, sortOrder, columnOrder, tableDisplayData) => {
- console.log("🔍 화면에서 선택된 행 데이터:", selectedData);
- console.log("📊 정렬 정보:", { sortBy, sortOrder, columnOrder });
- console.log("📊 화면 표시 데이터:", { count: tableDisplayData?.length, firstRow: tableDisplayData?.[0] });
- setSelectedRowsData(selectedData);
- setTableSortBy(sortBy);
- setTableSortOrder(sortOrder || "asc");
- setTableColumnOrder(columnOrder);
- setTableDisplayData(tableDisplayData || []);
- }}
- flowSelectedData={flowSelectedData}
- flowSelectedStepId={flowSelectedStepId}
- onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => {
- setFlowSelectedData(selectedData);
- setFlowSelectedStepId(stepId);
- }}
- refreshKey={tableRefreshKey}
- onRefresh={() => {
- setTableRefreshKey((prev) => prev + 1);
- setSelectedRowsData([]); // 선택 해제
- }}
- flowRefreshKey={flowRefreshKey}
- onFlowRefresh={() => {
- setFlowRefreshKey((prev) => prev + 1);
- setFlowSelectedData([]); // 선택 해제
- setFlowSelectedStepId(null);
- }}
- formData={formData}
- onFormDataChange={(fieldName, value) => {
- setFormData((prev) => ({ ...prev, [fieldName]: value }));
- }}
- onHeightChange={(componentId, newHeight) => {
- setConditionalContainerHeights((prev) => ({
- ...prev,
- [componentId]: newHeight,
- }));
- }}
- >
- {/* 자식 컴포넌트들 */}
- {(component.type === "group" || component.type === "container" || component.type === "area") &&
- layout.components
- .filter((child) => child.parentId === component.id)
- .map((child) => {
- // 자식 컴포넌트의 위치를 부모 기준 상대 좌표로 조정
- const relativeChildComponent = {
- ...child,
- position: {
- x: child.position.x - component.position.x,
- y: child.position.y - component.position.y,
- z: child.position.z || 1,
- },
- };
+ // 🆕 조건부 컨테이너 높이 조정
+ for (const container of conditionalContainers) {
+ const isBelow = component.position.y > container.position.y;
+ const actualHeight = conditionalContainerHeights[container.id];
+ const originalHeight = container.size?.height || 200;
+ const heightDiff = actualHeight ? actualHeight - originalHeight : 0;
- return (
- {}}
- menuObjid={menuObjid}
- screenId={screenId}
- tableName={screen?.tableName}
- userId={user?.userId}
- userName={userName}
- companyCode={companyCode}
- menuObjid={menuObjid}
- selectedRowsData={selectedRowsData}
- sortBy={tableSortBy}
- sortOrder={tableSortOrder}
- columnOrder={tableColumnOrder}
- tableDisplayData={tableDisplayData}
- onSelectedRowsChange={(_, selectedData, sortBy, sortOrder, columnOrder, tableDisplayData) => {
- console.log("🔍 화면에서 선택된 행 데이터 (자식):", selectedData);
- console.log("📊 정렬 정보 (자식):", { sortBy, sortOrder, columnOrder });
- console.log("📊 화면 표시 데이터 (자식):", { count: tableDisplayData?.length, firstRow: tableDisplayData?.[0] });
- setSelectedRowsData(selectedData);
- setTableSortBy(sortBy);
- setTableSortOrder(sortOrder || "asc");
- setTableColumnOrder(columnOrder);
- setTableDisplayData(tableDisplayData || []);
- }}
- refreshKey={tableRefreshKey}
- onRefresh={() => {
- console.log("🔄 테이블 새로고침 요청됨 (자식)");
- setTableRefreshKey((prev) => prev + 1);
- setSelectedRowsData([]); // 선택 해제
- }}
- formData={formData}
- onFormDataChange={(fieldName, value) => {
- setFormData((prev) => ({ ...prev, [fieldName]: value }));
- }}
- />
- );
- })}
-
- );
- })}
-
- {/* 🆕 플로우 버튼 그룹들 */}
- {Object.entries(buttonGroups).map(([groupId, buttons]) => {
- if (buttons.length === 0) return null;
-
- const firstButton = buttons[0];
- const groupConfig = (firstButton as any).webTypeConfig
- ?.flowVisibilityConfig as FlowVisibilityConfig;
-
- // 🔍 버튼 그룹 설정 확인
- console.log("🔍 버튼 그룹 설정:", {
- groupId,
- buttonCount: buttons.length,
- buttons: buttons.map((b) => ({
- id: b.id,
- label: b.label,
- x: b.position.x,
- y: b.position.y,
- })),
- groupConfig: {
- layoutBehavior: groupConfig.layoutBehavior,
- groupDirection: groupConfig.groupDirection,
- groupAlign: groupConfig.groupAlign,
- groupGap: groupConfig.groupGap,
- },
+ console.log(`🔍 높이 조정 체크:`, {
+ componentId: component.id,
+ componentY: component.position.y,
+ containerY: container.position.y,
+ isBelow,
+ actualHeight,
+ originalHeight,
+ heightDiff,
+ containerId: container.id,
+ containerSize: container.size,
});
- // 🔧 수정: 그룹 컨테이너는 첫 번째 버튼 위치를 기준으로 하되,
- // 각 버튼의 상대 위치는 원래 위치를 유지
- const firstButtonPosition = {
- x: buttons[0].position.x,
- y: buttons[0].position.y,
- z: buttons[0].position.z || 2,
- };
-
- // 버튼 그룹 위치에도 widthOffset 적용
- const adjustedGroupPosition = {
- ...firstButtonPosition,
- x: firstButtonPosition.x + widthOffset,
- };
-
- // 그룹의 크기 계산: 버튼들의 실제 크기 + 간격을 기준으로 계산
- const direction = groupConfig.groupDirection || "horizontal";
- const gap = groupConfig.groupGap ?? 8;
-
- let groupWidth = 0;
- let groupHeight = 0;
-
- if (direction === "horizontal") {
- groupWidth = buttons.reduce((total, button, index) => {
- const buttonWidth = button.size?.width || 100;
- const gapWidth = index < buttons.length - 1 ? gap : 0;
- return total + buttonWidth + gapWidth;
- }, 0);
- groupHeight = Math.max(...buttons.map((b) => b.size?.height || 40));
- } else {
- groupWidth = Math.max(...buttons.map((b) => b.size?.width || 100));
- groupHeight = buttons.reduce((total, button, index) => {
- const buttonHeight = button.size?.height || 40;
- const gapHeight = index < buttons.length - 1 ? gap : 0;
- return total + buttonHeight + gapHeight;
- }, 0);
+ if (isBelow && heightDiff > 0) {
+ totalHeightAdjustment += heightDiff;
+ console.log(
+ `📐 컴포넌트 ${component.id} 위치 조정: ${heightDiff}px (조건부 컨테이너 ${container.id})`,
+ );
}
+ }
- return (
-
-
0) {
+ return {
+ ...component,
+ position: {
+ ...component.position,
+ y: component.position.y + totalHeightAdjustment,
+ },
+ };
+ }
+
+ return component;
+ });
+
+ return (
+ <>
+ {/* 일반 컴포넌트들 */}
+ {adjustedComponents.map((component) => {
+ // 화면 관리 해상도를 사용하므로 위치 조정 불필요
+ return (
+ {
- // 🔧 각 버튼의 상대 위치 = 버튼의 원래 위치 - 첫 번째 버튼 위치
- const relativeButton = {
- ...button,
- position: {
- x: button.position.x - firstButtonPosition.x,
- y: button.position.y - firstButtonPosition.y,
- z: button.position.z || 1,
- },
- };
+ onClick={() => {}}
+ menuObjid={menuObjid}
+ screenId={screenId}
+ tableName={screen?.tableName}
+ userId={user?.userId}
+ userName={userName}
+ companyCode={companyCode}
+ menuObjid={menuObjid}
+ selectedRowsData={selectedRowsData}
+ sortBy={tableSortBy}
+ sortOrder={tableSortOrder}
+ columnOrder={tableColumnOrder}
+ tableDisplayData={tableDisplayData}
+ onSelectedRowsChange={(_, selectedData, sortBy, sortOrder, columnOrder, tableDisplayData) => {
+ console.log("🔍 화면에서 선택된 행 데이터:", selectedData);
+ console.log("📊 정렬 정보:", { sortBy, sortOrder, columnOrder });
+ console.log("📊 화면 표시 데이터:", {
+ count: tableDisplayData?.length,
+ firstRow: tableDisplayData?.[0],
+ });
+ setSelectedRowsData(selectedData);
+ setTableSortBy(sortBy);
+ setTableSortOrder(sortOrder || "asc");
+ setTableColumnOrder(columnOrder);
+ setTableDisplayData(tableDisplayData || []);
+ }}
+ flowSelectedData={flowSelectedData}
+ flowSelectedStepId={flowSelectedStepId}
+ onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => {
+ setFlowSelectedData(selectedData);
+ setFlowSelectedStepId(stepId);
+ }}
+ refreshKey={tableRefreshKey}
+ onRefresh={() => {
+ setTableRefreshKey((prev) => prev + 1);
+ setSelectedRowsData([]); // 선택 해제
+ }}
+ flowRefreshKey={flowRefreshKey}
+ onFlowRefresh={() => {
+ setFlowRefreshKey((prev) => prev + 1);
+ setFlowSelectedData([]); // 선택 해제
+ setFlowSelectedStepId(null);
+ }}
+ formData={formData}
+ onFormDataChange={(fieldName, value) => {
+ setFormData((prev) => ({ ...prev, [fieldName]: value }));
+ }}
+ onHeightChange={(componentId, newHeight) => {
+ setConditionalContainerHeights((prev) => ({
+ ...prev,
+ [componentId]: newHeight,
+ }));
+ }}
+ >
+ {/* 자식 컴포넌트들 */}
+ {(component.type === "group" ||
+ component.type === "container" ||
+ component.type === "area") &&
+ layout.components
+ .filter((child) => child.parentId === component.id)
+ .map((child) => {
+ // 자식 컴포넌트의 위치를 부모 기준 상대 좌표로 조정
+ const relativeChildComponent = {
+ ...child,
+ position: {
+ x: child.position.x - component.position.x,
+ y: child.position.y - component.position.y,
+ z: child.position.z || 1,
+ },
+ };
- return (
-
-
- {}}
+ onClick={() => {}}
+ menuObjid={menuObjid}
screenId={screenId}
tableName={screen?.tableName}
userId={user?.userId}
userName={userName}
companyCode={companyCode}
- tableDisplayData={tableDisplayData}
+ menuObjid={menuObjid}
selectedRowsData={selectedRowsData}
sortBy={tableSortBy}
sortOrder={tableSortOrder}
columnOrder={tableColumnOrder}
- onSelectedRowsChange={(_, selectedData, sortBy, sortOrder, columnOrder) => {
+ tableDisplayData={tableDisplayData}
+ onSelectedRowsChange={(
+ _,
+ selectedData,
+ sortBy,
+ sortOrder,
+ columnOrder,
+ tableDisplayData,
+ ) => {
+ console.log("🔍 화면에서 선택된 행 데이터 (자식):", selectedData);
+ console.log("📊 정렬 정보 (자식):", { sortBy, sortOrder, columnOrder });
+ console.log("📊 화면 표시 데이터 (자식):", {
+ count: tableDisplayData?.length,
+ firstRow: tableDisplayData?.[0],
+ });
setSelectedRowsData(selectedData);
setTableSortBy(sortBy);
setTableSortOrder(sortOrder || "asc");
setTableColumnOrder(columnOrder);
- }}
- flowSelectedData={flowSelectedData}
- flowSelectedStepId={flowSelectedStepId}
- onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => {
- setFlowSelectedData(selectedData);
- setFlowSelectedStepId(stepId);
+ setTableDisplayData(tableDisplayData || []);
}}
refreshKey={tableRefreshKey}
onRefresh={() => {
+ console.log("🔄 테이블 새로고침 요청됨 (자식)");
setTableRefreshKey((prev) => prev + 1);
- setSelectedRowsData([]);
- }}
- flowRefreshKey={flowRefreshKey}
- onFlowRefresh={() => {
- setFlowRefreshKey((prev) => prev + 1);
- setFlowSelectedData([]);
- setFlowSelectedStepId(null);
+ setSelectedRowsData([]); // 선택 해제
}}
+ formData={formData}
onFormDataChange={(fieldName, value) => {
setFormData((prev) => ({ ...prev, [fieldName]: value }));
}}
/>
-
-
- );
- }}
- />
-
- );
- })}
- >
- );
- })()}
-
- ) : (
- // 빈 화면일 때
-
-
-
- 📄
-
-
화면이 비어있습니다
-
이 화면에는 아직 설계된 컴포넌트가 없습니다.
-
-
- )}
+ );
+ })}
+
+ );
+ })}
- {/* 편집 모달 */}
-
{
- setEditModalOpen(false);
- setEditModalConfig({});
- }}
- screenId={editModalConfig.screenId}
- modalSize={editModalConfig.modalSize}
- editData={editModalConfig.editData}
- onSave={editModalConfig.onSave}
- modalTitle={editModalConfig.modalTitle}
- modalDescription={editModalConfig.modalDescription}
- onDataChange={(changedFormData) => {
- console.log("📝 EditModal에서 데이터 변경 수신:", changedFormData);
- // 변경된 데이터를 메인 폼에 반영
- setFormData((prev) => {
- const updatedFormData = {
- ...prev,
- ...changedFormData, // 변경된 필드들만 업데이트
- };
- console.log("📊 메인 폼 데이터 업데이트:", updatedFormData);
- return updatedFormData;
- });
- }}
- />
+ {/* 🆕 플로우 버튼 그룹들 */}
+ {Object.entries(buttonGroups).map(([groupId, buttons]) => {
+ if (buttons.length === 0) return null;
+
+ const firstButton = buttons[0];
+ const groupConfig = (firstButton as any).webTypeConfig
+ ?.flowVisibilityConfig as FlowVisibilityConfig;
+
+ // 🔍 버튼 그룹 설정 확인
+ console.log("🔍 버튼 그룹 설정:", {
+ groupId,
+ buttonCount: buttons.length,
+ buttons: buttons.map((b) => ({
+ id: b.id,
+ label: b.label,
+ x: b.position.x,
+ y: b.position.y,
+ })),
+ groupConfig: {
+ layoutBehavior: groupConfig.layoutBehavior,
+ groupDirection: groupConfig.groupDirection,
+ groupAlign: groupConfig.groupAlign,
+ groupGap: groupConfig.groupGap,
+ },
+ });
+
+ // 🔧 수정: 그룹 컨테이너는 첫 번째 버튼 위치를 기준으로 하되,
+ // 각 버튼의 상대 위치는 원래 위치를 유지
+ const firstButtonPosition = {
+ x: buttons[0].position.x,
+ y: buttons[0].position.y,
+ z: buttons[0].position.z || 2,
+ };
+
+ // 버튼 그룹 위치에도 widthOffset 적용
+ const adjustedGroupPosition = {
+ ...firstButtonPosition,
+ x: firstButtonPosition.x + widthOffset,
+ };
+
+ // 그룹의 크기 계산: 버튼들의 실제 크기 + 간격을 기준으로 계산
+ const direction = groupConfig.groupDirection || "horizontal";
+ const gap = groupConfig.groupGap ?? 8;
+
+ let groupWidth = 0;
+ let groupHeight = 0;
+
+ if (direction === "horizontal") {
+ groupWidth = buttons.reduce((total, button, index) => {
+ const buttonWidth = button.size?.width || 100;
+ const gapWidth = index < buttons.length - 1 ? gap : 0;
+ return total + buttonWidth + gapWidth;
+ }, 0);
+ groupHeight = Math.max(...buttons.map((b) => b.size?.height || 40));
+ } else {
+ groupWidth = Math.max(...buttons.map((b) => b.size?.width || 100));
+ groupHeight = buttons.reduce((total, button, index) => {
+ const buttonHeight = button.size?.height || 40;
+ const gapHeight = index < buttons.length - 1 ? gap : 0;
+ return total + buttonHeight + gapHeight;
+ }, 0);
+ }
+
+ return (
+
+
{
+ // 🔧 각 버튼의 상대 위치 = 버튼의 원래 위치 - 첫 번째 버튼 위치
+ const relativeButton = {
+ ...button,
+ position: {
+ x: button.position.x - firstButtonPosition.x,
+ y: button.position.y - firstButtonPosition.y,
+ z: button.position.z || 1,
+ },
+ };
+
+ return (
+
+
+ {}}
+ screenId={screenId}
+ tableName={screen?.tableName}
+ userId={user?.userId}
+ userName={userName}
+ companyCode={companyCode}
+ tableDisplayData={tableDisplayData}
+ selectedRowsData={selectedRowsData}
+ sortBy={tableSortBy}
+ sortOrder={tableSortOrder}
+ columnOrder={tableColumnOrder}
+ onSelectedRowsChange={(_, selectedData, sortBy, sortOrder, columnOrder) => {
+ setSelectedRowsData(selectedData);
+ setTableSortBy(sortBy);
+ setTableSortOrder(sortOrder || "asc");
+ setTableColumnOrder(columnOrder);
+ }}
+ flowSelectedData={flowSelectedData}
+ flowSelectedStepId={flowSelectedStepId}
+ onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => {
+ setFlowSelectedData(selectedData);
+ setFlowSelectedStepId(stepId);
+ }}
+ refreshKey={tableRefreshKey}
+ onRefresh={() => {
+ setTableRefreshKey((prev) => prev + 1);
+ setSelectedRowsData([]);
+ }}
+ flowRefreshKey={flowRefreshKey}
+ onFlowRefresh={() => {
+ setFlowRefreshKey((prev) => prev + 1);
+ setFlowSelectedData([]);
+ setFlowSelectedStepId(null);
+ }}
+ onFormDataChange={(fieldName, value) => {
+ setFormData((prev) => ({ ...prev, [fieldName]: value }));
+ }}
+ />
+
+
+ );
+ }}
+ />
+
+ );
+ })}
+ >
+ );
+ })()}
+
+ ) : (
+ // 빈 화면일 때
+
+
+
+ 📄
+
+
화면이 비어있습니다
+
이 화면에는 아직 설계된 컴포넌트가 없습니다.
+
+
+ )}
+
+ {/* 편집 모달 */}
+
{
+ setEditModalOpen(false);
+ setEditModalConfig({});
+ }}
+ screenId={editModalConfig.screenId}
+ modalSize={editModalConfig.modalSize}
+ editData={editModalConfig.editData}
+ onSave={editModalConfig.onSave}
+ modalTitle={editModalConfig.modalTitle}
+ modalDescription={editModalConfig.modalDescription}
+ onDataChange={(changedFormData) => {
+ console.log("📝 EditModal에서 데이터 변경 수신:", changedFormData);
+ // 변경된 데이터를 메인 폼에 반영
+ setFormData((prev) => {
+ const updatedFormData = {
+ ...prev,
+ ...changedFormData, // 변경된 필드들만 업데이트
+ };
+ console.log("📊 메인 폼 데이터 업데이트:", updatedFormData);
+ return updatedFormData;
+ });
+ }}
+ />
diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx
index e87dc73d..8394cd6d 100644
--- a/frontend/components/layout/AppLayout.tsx
+++ b/frontend/components/layout/AppLayout.tsx
@@ -470,7 +470,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
{/* 가운데 컨텐츠 영역 - 스크롤 가능 */}
- {children}
+ {children}
diff --git a/frontend/components/screen/ResponsiveDesignerContainer.tsx b/frontend/components/screen/ResponsiveDesignerContainer.tsx
index 57f55b14..392e59ce 100644
--- a/frontend/components/screen/ResponsiveDesignerContainer.tsx
+++ b/frontend/components/screen/ResponsiveDesignerContainer.tsx
@@ -35,9 +35,9 @@ export const ResponsiveDesignerContainer: React.FC