feat: 파일 미리보기 및 동적 컴포넌트 조정 기능 추가
- 파일 미리보기 API에 공개 접근을 허용하여 인증되지 않은 사용자도 이미지 미리보기를 할 수 있도록 수정하였습니다. - ScreenModal 컴포넌트에서 숨겨진 컴포넌트의 동적 y 좌표 조정 로직을 추가하여 사용자 인터페이스의 일관성을 개선하였습니다. - V2Media 및 V2Select 컴포넌트에서 기본값 설정 기능을 추가하여 사용자 경험을 향상시켰습니다. - RepeaterTable 및 SimpleRepeaterTableComponent에서 키 값을 개선하여 렌더링 성능을 최적화하였습니다. - formData의 디버깅 로그를 추가하여 개발 중 상태 확인을 용이하게 하였습니다.
This commit is contained in:
@@ -604,23 +604,135 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
||||
transformOrigin: "center center",
|
||||
}}
|
||||
>
|
||||
{screenData.components.map((component) => {
|
||||
{(() => {
|
||||
// 🆕 동적 y 좌표 조정을 위해 먼저 숨겨지는 컴포넌트들 파악
|
||||
const isComponentHidden = (comp: any) => {
|
||||
const cc = comp.componentConfig?.conditionalConfig || comp.conditionalConfig;
|
||||
if (!cc?.enabled || !formData) return false;
|
||||
|
||||
const { field, operator, value, action } = cc;
|
||||
const fieldValue = formData[field];
|
||||
|
||||
let conditionMet = false;
|
||||
switch (operator) {
|
||||
case "=":
|
||||
case "==":
|
||||
case "===":
|
||||
conditionMet = fieldValue === value;
|
||||
break;
|
||||
case "!=":
|
||||
case "!==":
|
||||
conditionMet = fieldValue !== value;
|
||||
break;
|
||||
default:
|
||||
conditionMet = fieldValue === value;
|
||||
}
|
||||
|
||||
return (action === "show" && !conditionMet) || (action === "hide" && conditionMet);
|
||||
};
|
||||
|
||||
// 표시되는 컴포넌트들의 y 범위 수집
|
||||
const visibleRanges: { y: number; bottom: number }[] = [];
|
||||
screenData.components.forEach((comp: any) => {
|
||||
if (!isComponentHidden(comp)) {
|
||||
const y = parseFloat(comp.position?.y?.toString() || "0");
|
||||
const height = parseFloat(comp.size?.height?.toString() || "0");
|
||||
visibleRanges.push({ y, bottom: y + height });
|
||||
}
|
||||
});
|
||||
|
||||
// 숨겨지는 컴포넌트의 "실제 빈 공간" 계산 (표시되는 컴포넌트와 겹치지 않는 영역)
|
||||
const getActualGap = (hiddenY: number, hiddenBottom: number): number => {
|
||||
// 숨겨지는 영역 중 표시되는 컴포넌트와 겹치는 부분을 제외
|
||||
let gapStart = hiddenY;
|
||||
let gapEnd = hiddenBottom;
|
||||
|
||||
for (const visible of visibleRanges) {
|
||||
// 겹치는 영역 확인
|
||||
if (visible.y < gapEnd && visible.bottom > gapStart) {
|
||||
// 겹치는 부분을 제외
|
||||
if (visible.y <= gapStart && visible.bottom >= gapEnd) {
|
||||
// 완전히 덮힘 - 빈 공간 없음
|
||||
return 0;
|
||||
} else if (visible.y <= gapStart) {
|
||||
// 위쪽이 덮힘
|
||||
gapStart = visible.bottom;
|
||||
} else if (visible.bottom >= gapEnd) {
|
||||
// 아래쪽이 덮힘
|
||||
gapEnd = visible.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(0, gapEnd - gapStart);
|
||||
};
|
||||
|
||||
// 숨겨지는 컴포넌트들의 실제 빈 공간 수집
|
||||
const hiddenGaps: { bottom: number; gap: number }[] = [];
|
||||
screenData.components.forEach((comp: any) => {
|
||||
if (isComponentHidden(comp)) {
|
||||
const y = parseFloat(comp.position?.y?.toString() || "0");
|
||||
const height = parseFloat(comp.size?.height?.toString() || "0");
|
||||
const bottom = y + height;
|
||||
const gap = getActualGap(y, bottom);
|
||||
if (gap > 0) {
|
||||
hiddenGaps.push({ bottom, gap });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// bottom 기준으로 정렬 및 중복 제거 (같은 bottom은 가장 큰 gap만 유지)
|
||||
const mergedGaps = new Map<number, number>();
|
||||
hiddenGaps.forEach(({ bottom, gap }) => {
|
||||
const existing = mergedGaps.get(bottom) || 0;
|
||||
mergedGaps.set(bottom, Math.max(existing, gap));
|
||||
});
|
||||
|
||||
const sortedGaps = Array.from(mergedGaps.entries())
|
||||
.map(([bottom, gap]) => ({ bottom, gap }))
|
||||
.sort((a, b) => a.bottom - b.bottom);
|
||||
|
||||
console.log('🔍 [Y조정] visibleRanges:', visibleRanges.filter(r => r.bottom - r.y > 50).map(r => `${r.y}~${r.bottom}`));
|
||||
console.log('🔍 [Y조정] hiddenGaps:', sortedGaps);
|
||||
|
||||
// 각 컴포넌트의 y 조정값 계산 함수
|
||||
const getYOffset = (compY: number, compId?: string) => {
|
||||
let offset = 0;
|
||||
for (const { bottom, gap } of sortedGaps) {
|
||||
// 컴포넌트가 숨겨진 영역 아래에 있으면 그 빈 공간만큼 위로 이동
|
||||
if (compY > bottom) {
|
||||
offset += gap;
|
||||
}
|
||||
}
|
||||
if (offset > 0 && compId) {
|
||||
console.log(`🔍 [Y조정] ${compId}: y=${compY} → ${compY - offset} (offset=${offset})`);
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
return screenData.components.map((component: any) => {
|
||||
// 숨겨지는 컴포넌트는 렌더링 안함
|
||||
if (isComponentHidden(component)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 화면 관리 해상도를 사용하는 경우 offset 조정 불필요
|
||||
const offsetX = screenDimensions?.offsetX || 0;
|
||||
const offsetY = screenDimensions?.offsetY || 0;
|
||||
|
||||
// 🆕 동적 y 좌표 조정 (숨겨진 컴포넌트 높이만큼 위로 이동)
|
||||
const compY = parseFloat(component.position?.y?.toString() || "0");
|
||||
const yAdjustment = getYOffset(compY, component.id);
|
||||
|
||||
// offset이 0이면 원본 위치 사용 (화면 관리 해상도 사용 시)
|
||||
const adjustedComponent =
|
||||
offsetX === 0 && offsetY === 0
|
||||
? component
|
||||
: {
|
||||
...component,
|
||||
position: {
|
||||
...component.position,
|
||||
x: parseFloat(component.position?.x?.toString() || "0") - offsetX,
|
||||
y: parseFloat(component.position?.y?.toString() || "0") - offsetY,
|
||||
},
|
||||
};
|
||||
const adjustedComponent = {
|
||||
...component,
|
||||
position: {
|
||||
...component.position,
|
||||
x: parseFloat(component.position?.x?.toString() || "0") - offsetX,
|
||||
y: compY - offsetY - yAdjustment, // 🆕 동적 조정 적용
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<InteractiveScreenViewerDynamic
|
||||
@@ -652,7 +764,8 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
||||
companyCode={user?.companyCode}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
});
|
||||
})()}
|
||||
</div>
|
||||
</TableOptionsProvider>
|
||||
</ActiveTabProvider>
|
||||
|
||||
Reference in New Issue
Block a user