Merge branch 'mhkim-node' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node

This commit is contained in:
kjs
2026-03-17 21:08:07 +09:00
4 changed files with 44 additions and 41 deletions

View File

@@ -563,8 +563,20 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
if (screenInfo && layoutData) {
const components = layoutData.components || [];
// 화면의 실제 크기 계산
const dimensions = calculateScreenDimensions(components);
// 화면 관리에서 설정한 해상도 우선 사용 (ScreenModal과 동일)
const screenResolution = (layoutData as any).screenResolution || (screenInfo as any).screenResolution;
let dimensions;
if (screenResolution && screenResolution.width && screenResolution.height) {
dimensions = {
width: screenResolution.width,
height: screenResolution.height,
offsetX: 0,
offsetY: 0,
};
} else {
dimensions = calculateScreenDimensions(components);
}
setScreenDimensions(dimensions);
setScreenData({
@@ -1547,31 +1559,25 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
}
};
// 모달 크기 설정 - 화면관리 설정 크기 + 헤더
// 모달 크기 설정 - ScreenModal과 동일한 방식 (maxHeight로 유연 처리)
const getModalStyle = () => {
if (!screenDimensions) {
return {
className: "w-fit min-w-[400px] max-w-4xl max-h-[90vh] overflow-hidden p-0",
style: undefined, // undefined로 변경 - defaultWidth/defaultHeight 사용
className: "w-fit min-w-[400px] max-w-4xl overflow-hidden",
style: { padding: 0, gap: 0, maxHeight: "calc(100dvh - 8px)" },
};
}
// 화면관리에서 설정한 크기 = 컨텐츠 영역 크기
// 실제 모달 크기 = 컨텐츠 + 헤더 + gap + padding + 라벨 공간
const headerHeight = 52; // DialogHeader (타이틀 + border-b + py-3)
const dialogGap = 16; // DialogContent gap-4
const extraPadding = 24; // 추가 여백 (안전 마진)
const labelSpace = 30; // 입력 필드 위 라벨 공간 (-top-6 = 24px + 여유)
const totalHeight = screenDimensions.height + headerHeight + dialogGap + extraPadding + labelSpace;
const finalWidth = Math.min(screenDimensions.width, window.innerWidth * 0.98);
return {
className: "overflow-hidden p-0",
style: {
width: `${Math.min(screenDimensions.width + 48, window.innerWidth * 0.98)}px`, // 좌우 패딩 추가
height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`,
width: `${finalWidth}px`,
maxHeight: "calc(100dvh - 8px)",
maxWidth: "98vw",
maxHeight: "95vh",
padding: 0,
gap: 0,
},
};
};
@@ -1593,7 +1599,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
</div>
</DialogHeader>
<div className="[&::-webkit-scrollbar-thumb]:bg-muted/60 flex flex-1 justify-center overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-transparent">
<div className="flex-1 min-h-0 flex items-start justify-center overflow-auto">
{loading ? (
<div className="flex h-full items-center justify-center">
<div className="text-center">
@@ -1608,42 +1614,41 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
>
<div
data-screen-runtime="true"
className="bg-card relative m-auto"
className="relative bg-white"
style={{
width: screenDimensions?.width || 800,
// 조건부 레이어가 활성화되면 높이 자동 확장
width: `${screenDimensions?.width || 800}px`,
minHeight: `${screenDimensions?.height || 600}px`,
height: (() => {
const baseHeight = (screenDimensions?.height || 600) + 30;
const baseHeight = screenDimensions?.height || 600;
if (activeConditionalComponents.length > 0) {
// 조건부 레이어 컴포넌트 중 가장 아래 위치 계산
const offsetY = screenDimensions?.offsetY || 0;
let maxBottom = 0;
activeConditionalComponents.forEach((comp) => {
const y = parseFloat(comp.position?.y?.toString() || "0") - offsetY + 30;
const y = parseFloat(comp.position?.y?.toString() || "0") - offsetY;
const h = parseFloat(comp.size?.height?.toString() || "40");
maxBottom = Math.max(maxBottom, y + h);
});
return Math.max(baseHeight, maxBottom + 20); // 20px 여백
return `${Math.max(baseHeight, maxBottom + 20)}px`;
}
return baseHeight;
return `${baseHeight}px`;
})(),
transformOrigin: "center center",
maxWidth: "100%",
overflow: "visible",
}}
>
{/* 기본 레이어 컴포넌트 렌더링 */}
{screenData.components.map((component) => {
// 컴포넌트 위치를 offset만큼 조정
const offsetX = screenDimensions?.offsetX || 0;
const offsetY = screenDimensions?.offsetY || 0;
const labelSpace = 30; // 라벨 공간 (입력 필드 위 -top-6 라벨용)
// screenResolution이 있으면 offsetY=0이므로 디자이너 좌표 그대로 사용
// offsetY > 0 (자동 계산)일 때만 라벨 공간 보정
const labelSpace = offsetY > 0 ? 30 : 0;
const adjustedComponent = {
...component,
position: {
...component.position,
x: parseFloat(component.position?.x?.toString() || "0") - offsetX,
y: parseFloat(component.position?.y?.toString() || "0") - offsetY + labelSpace, // 라벨 공간 추가
y: parseFloat(component.position?.y?.toString() || "0") - offsetY + labelSpace,
},
};
@@ -1709,11 +1714,11 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
);
})}
{/* 🆕 조건부 레이어 컴포넌트 렌더링 */}
{/* 조건부 레이어 컴포넌트 렌더링 */}
{activeConditionalComponents.map((component) => {
const offsetX = screenDimensions?.offsetX || 0;
const offsetY = screenDimensions?.offsetY || 0;
const labelSpace = 30;
const labelSpace = offsetY > 0 ? 30 : 0;
const adjustedComponent = {
...component,

View File

@@ -89,12 +89,12 @@ function formatTel(value: string): string {
return `${digits.slice(0, 4)}-${digits.slice(4, 8)}`;
}
// 서울: 02 → 2-4-4
// 서울: 02 → 9자리 2-3-4, 10자리 2-4-4
if (digits.startsWith("02")) {
if (digits.length <= 2) return digits;
if (digits.length <= 6) return `${digits.slice(0, 2)}-${digits.slice(2)}`;
if (digits.length <= 10) return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6)}`;
return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
if (digits.length <= 5) return `${digits.slice(0, 2)}-${digits.slice(2)}`;
const mid = digits.length >= 10 ? 4 : 3;
return `${digits.slice(0, 2)}-${digits.slice(2, 2 + mid)}-${digits.slice(2 + mid, 2 + mid + 4)}`;
}
// 안심번호: 050x → 4-4-4
@@ -1228,7 +1228,7 @@ export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) =>
ref={ref}
id={id}
className={cn(
"flex gap-1",
"flex",
labelPos === "left" ? "flex-row items-center" : "flex-row-reverse items-center",
)}
style={{

View File

@@ -1291,7 +1291,7 @@ export const V2Select = forwardRef<HTMLDivElement, V2SelectProps>((props, ref) =
ref={ref}
id={id}
className={cn(
"flex gap-1",
"flex",
labelPos === "left" ? "flex-row items-center" : "flex-row-reverse items-center",
isDesignMode && "pointer-events-none",
)}

View File

@@ -808,12 +808,10 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
? component.style?.labelText || (component as any).label || component.componentConfig?.label
: undefined;
// 🔧 수평 라벨(left/right) 감지 → 런타임에서만 외부 flex 컨테이너로 라벨 처리
// 디자인 모드에서는 V2 컴포넌트가 자체적으로 라벨을 렌더링 (height 체인 문제 방지)
// 🔧 수평 라벨(left/right) 감지 → 외부 absolute 래퍼로 라벨 처리 (카테고리 셀렉트와 동일 방식)
const labelPosition = component.style?.labelPosition;
const isV2Component = componentType?.startsWith("v2-");
const needsExternalHorizLabel = !!(
!props.isDesignMode &&
isV2Component &&
effectiveLabel &&
(labelPosition === "left" || labelPosition === "right")