Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management

This commit is contained in:
kjs
2025-11-06 11:58:09 +09:00
16 changed files with 749 additions and 465 deletions

View File

@@ -2,13 +2,13 @@
import React, { useState, useEffect } from "react";
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
DialogHeader,
DialogFooter,
} from "@/components/ui/dialog";
ResizableDialog,
ResizableDialogContent,
ResizableDialogHeader,
ResizableDialogTitle,
ResizableDialogDescription,
ResizableDialogFooter,
} from "@/components/ui/resizable-dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -101,17 +101,17 @@ export default function CopyScreenModal({ isOpen, onClose, sourceScreen, onCopyS
};
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
<ResizableDialogContent className="sm:max-w-[500px]">
<ResizableDialogHeader>
<ResizableDialogTitle className="flex items-center gap-2">
<Copy className="h-5 w-5" />
</DialogTitle>
<DialogDescription>
</ResizableDialogTitle>
<ResizableDialogDescription>
{sourceScreen?.screenName} . .
</DialogDescription>
</DialogHeader>
</ResizableDialogDescription>
</ResizableDialogHeader>
<div className="space-y-4">
{/* 원본 화면 정보 */}
@@ -185,8 +185,8 @@ export default function CopyScreenModal({ isOpen, onClose, sourceScreen, onCopyS
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</ResizableDialogFooter>
</ResizableDialogContent>
</ResizableDialog>
);
}

View File

@@ -4,11 +4,7 @@ import React, { useState, useEffect, useRef } from "react";
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
DialogHeader,
DialogFooter,
} from "@/components/ui/dialog";
} from "@/components/ui/resizable-dialog";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -550,7 +546,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
)}
</div>
<DialogFooter className="flex gap-2">
<ResizableDialogFooter className="flex gap-2">
<Button variant="outline" onClick={handleAssignLater} disabled={assigning}>
<X className="mr-2 h-4 w-4" />
@@ -628,7 +624,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
</div>
</div>
<DialogFooter className="flex gap-2">
<ResizableDialogFooter className="flex gap-2">
<Button variant="outline" onClick={() => setShowReplaceDialog(false)} disabled={assigning}>
</Button>

View File

@@ -54,7 +54,7 @@ interface RealtimePreviewProps {
// 폼 데이터 관련 props
formData?: Record<string, any>;
onFormDataChange?: (fieldName: string, value: any) => void;
// 테이블 정렬 정보
sortBy?: string;
sortOrder?: "asc" | "desc";
@@ -229,10 +229,10 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
}
// 2순위: x=0인 컴포넌트는 전체 너비 사용 (버튼 제외)
const isButtonComponent =
const isButtonComponent =
(component.type === "widget" && (component as WidgetComponent).widgetType === "button") ||
(component.type === "component" && (component as any).componentType?.includes("button"));
if (position.x === 0 && !isButtonComponent) {
console.log("⚠️ [getWidth] 100% 사용 (x=0):", {
componentId: id,
@@ -269,9 +269,9 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
return `${actualHeight}px`;
}
// 1순위: style.height가 있으면 우선 사용
// 1순위: style.height가 있으면 우선 사용 (문자열 그대로 또는 숫자+px)
if (componentStyle?.height) {
return componentStyle.height;
return typeof componentStyle.height === "number" ? `${componentStyle.height}px` : componentStyle.height;
}
// 2순위: size.height (픽셀)
@@ -283,6 +283,20 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
return `${size?.height || 10}px`;
};
// layout 타입 컴포넌트인지 확인
const isLayoutComponent = component.type === "layout" || (component.componentConfig as any)?.type?.includes("layout");
// layout 컴포넌트는 component 객체에 style.height 추가
const enhancedComponent = isLayoutComponent
? {
...component,
style: {
...component.style,
height: getHeight(),
},
}
: component;
const baseStyle = {
left: `${position.x}px`,
top: `${position.y}px`,
@@ -296,14 +310,14 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
// 🔍 DOM 렌더링 후 실제 크기 측정
const innerDivRef = React.useRef<HTMLDivElement>(null);
const outerDivRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (outerDivRef.current && innerDivRef.current) {
const outerRect = outerDivRef.current.getBoundingClientRect();
const innerRect = innerDivRef.current.getBoundingClientRect();
const computedOuter = window.getComputedStyle(outerDivRef.current);
const computedInner = window.getComputedStyle(innerDivRef.current);
console.log("📐 [DOM 실제 크기 상세]:", {
componentId: id,
label: component.label,
@@ -325,7 +339,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
},
"4. 너비 비교": {
"외부 / 내부": `${outerRect.width}px / ${innerRect.width}px`,
"비율": `${((innerRect.width / outerRect.width) * 100).toFixed(2)}%`,
: `${((innerRect.width / outerRect.width) * 100).toFixed(2)}%`,
},
});
}
@@ -376,7 +390,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
style={{ width: "100%", maxWidth: "100%" }}
>
<DynamicComponentRenderer
component={component}
component={enhancedComponent}
isSelected={isSelected}
isDesignMode={isDesignMode}
isInteractive={!isDesignMode} // 편집 모드가 아닐 때만 인터랙티브

View File

@@ -25,16 +25,13 @@ import {
} from "@/components/ui/alert-dialog";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
ResizableDialog,
ResizableDialogContent,
ResizableDialogHeader
} from "@/components/ui/dialog";
import {
ResizableDialog,
ResizableDialogContent,
ResizableDialogHeader,
ResizableDialogTitle,
ResizableDialogFooter,
} from "@/components/ui/resizable-dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { MoreHorizontal, Edit, Trash2, Copy, Eye, Plus, Search, Palette, RotateCcw, Trash } from "lucide-react";
import { ScreenDefinition } from "@/types/screen";
@@ -459,7 +456,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
}`}
onClick={() => onDesignScreen(screen)}
>
<TableCell className="h-16 px-6 py-3 cursor-pointer">
<TableCell className="h-16 cursor-pointer px-6 py-3">
<div>
<div className="font-medium">{screen.screenName}</div>
{screen.description && (
@@ -699,7 +696,10 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
</TableHeader>
<TableBody>
{deletedScreens.map((screen) => (
<TableRow key={screen.screenId} className="bg-background hover:bg-muted/50 border-b transition-colors">
<TableRow
key={screen.screenId}
className="bg-background hover:bg-muted/50 border-b transition-colors"
>
<TableCell className="h-16 px-6 py-3">
<Checkbox
checked={selectedScreenIds.includes(screen.screenId)}
@@ -1065,11 +1065,11 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
</AlertDialog>
{/* 화면 편집 다이얼로그 */}
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle> </DialogTitle>
</DialogHeader>
<ResizableDialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
<ResizableDialogContent className="sm:max-w-[500px]">
<ResizableDialogHeader>
<ResizableDialogTitle> </ResizableDialogTitle>
</ResizableDialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="edit-screenName"> *</Label>
@@ -1106,23 +1106,23 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
</Select>
</div>
</div>
<DialogFooter>
<ResizableDialogFooter>
<Button variant="outline" onClick={() => setEditDialogOpen(false)}>
</Button>
<Button onClick={handleEditSave} disabled={!editFormData.screenName.trim()}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</ResizableDialogFooter>
</ResizableDialogContent>
</ResizableDialog>
{/* 화면 미리보기 다이얼로그 */}
<Dialog open={previewDialogOpen} onOpenChange={setPreviewDialogOpen}>
<DialogContent className="h-[95vh] max-w-[95vw]">
<DialogHeader>
<DialogTitle> - {screenToPreview?.screenName}</DialogTitle>
</DialogHeader>
<ResizableDialog open={previewDialogOpen} onOpenChange={setPreviewDialogOpen}>
<ResizableDialogContent className="h-[95vh] max-w-[95vw]">
<ResizableDialogHeader>
<ResizableDialogTitle> - {screenToPreview?.screenName}</ResizableDialogTitle>
</ResizableDialogHeader>
<div className="flex flex-1 items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 to-slate-100 p-6">
{isLoadingPreview ? (
<div className="flex h-full items-center justify-center">
@@ -1268,11 +1268,12 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
height: component.style?.height || `${component.size.height}px`,
zIndex: component.position.z || 1,
};
// 버튼 타입일 때 디버깅 (widget 타입 또는 component 타입 모두 체크)
if (
(component.type === "widget" && (component as any).widgetType === "button") ||
(component.type === "component" && (component as any).componentType?.includes("button"))
(component.type === "component" &&
(component as any).componentType?.includes("button"))
) {
console.log("🔘 ScreenList 버튼 외부 div 스타일:", {
id: component.id,
@@ -1283,7 +1284,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
appliedStyle: style,
});
}
return style;
})()}
>
@@ -1360,7 +1361,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
</div>
)}
</div>
<DialogFooter>
<ResizableDialogFooter>
<Button variant="outline" onClick={() => setPreviewDialogOpen(false)}>
</Button>
@@ -1368,9 +1369,9 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
<Palette className="mr-2 h-4 w-4" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</ResizableDialogFooter>
</ResizableDialogContent>
</ResizableDialog>
</div>
);
}