화면관리ui수정

This commit is contained in:
kjs
2025-10-22 17:19:47 +09:00
parent 96df465a7d
commit 2dd96f5a74
16 changed files with 455 additions and 463 deletions

View File

@@ -176,8 +176,13 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
onSelectedRowsChange,
refreshKey,
onConfigChange,
...safeProps
isPreview,
autoGeneration,
...restProps
} = props;
// DOM 안전한 props만 필터링
const safeProps = filterDOMProps(restProps);
// 컴포넌트의 columnName에 해당하는 formData 값 추출
const fieldName = (component as any).columnName || component.id;
@@ -232,6 +237,9 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
};
// 렌더러 props 구성
// component.style에서 height 제거 (RealtimePreviewDynamic에서 size.height로 처리)
const { height: _height, ...styleWithoutHeight } = component.style || {};
const rendererProps = {
component,
isSelected,
@@ -240,7 +248,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
onDragEnd,
size: component.size || newComponent.defaultSize,
position: component.position,
style: component.style,
style: styleWithoutHeight,
config: component.componentConfig,
componentConfig: component.componentConfig,
value: currentValue, // formData에서 추출한 현재 값 전달

View File

@@ -5,6 +5,7 @@ import { WebTypeRegistry } from "./WebTypeRegistry";
import { DynamicComponentProps } from "./types";
// import { getWidgetComponentByWebType, getWidgetComponentByName } from "@/components/screen/widgets/types"; // 임시 비활성화
import { useWebTypes } from "@/hooks/admin/useWebTypes";
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
/**
* 동적 웹타입 렌더러 컴포넌트
@@ -160,8 +161,10 @@ export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
// 기본 폴백: Input 컴포넌트 사용
const { Input } = require("@/components/ui/input");
const { filterDOMProps } = require("@/lib/utils/domPropsFilter");
console.log(`✅ 폴백: ${webType} 웹타입 → 기본 Input 사용`);
return <Input placeholder={`${webType}`} disabled={props.readonly} className="w-full" {...props} />;
const safeFallbackProps = filterDOMProps(props);
return <Input placeholder={`${webType}`} disabled={props.readonly} className="w-full" {...safeFallbackProps} />;
} catch (error) {
console.error(`웹타입 "${webType}" 폴백 컴포넌트 렌더링 실패:`, error);
return (

View File

@@ -597,8 +597,27 @@ export const AccordionBasicComponent: React.FC<AccordionBasicComponentProps> = (
formData: _formData,
onFormDataChange: _onFormDataChange,
componentConfig: _componentConfig,
...domProps
autoGeneration: _autoGeneration,
hidden: _hidden,
isInModal: _isInModal,
isPreview: _isPreview,
originalData: _originalData,
allComponents: _allComponents,
selectedRows: _selectedRows,
selectedRowsData: _selectedRowsData,
refreshKey: _refreshKey,
onUpdateLayout: _onUpdateLayout,
onSelectedRowsChange: _onSelectedRowsChange,
onConfigChange: _onConfigChange,
onZoneClick: _onZoneClick,
selectedScreen: _selectedScreen,
onZoneComponentDrop: _onZoneComponentDrop,
...restProps
} = props;
// filterDOMProps import 추가 필요
const { filterDOMProps } = require("@/lib/utils/domPropsFilter");
const domProps = filterDOMProps(restProps);
// 사용할 아이템들 결정 (우선순위: 데이터소스 > 정적아이템 > 기본아이템)
const finalItems = (() => {

View File

@@ -186,7 +186,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// selectedRowsData,
// });
// 스타일 계산 (위치는 RealtimePreviewDynamic에서 처리하므로 제외)
// 스타일 계산
// height: 100%로 부모(RealtimePreviewDynamic의 내부 div)의 높이를 따라감
const componentStyle: React.CSSProperties = {
width: "100%",
height: "100%",
@@ -194,9 +195,11 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
...style,
};
// 디자인 모드 스타일
// 디자인 모드 스타일 (border 속성 분리하여 충돌 방지)
if (isDesignMode) {
componentStyle.border = "1px dashed #cbd5e1";
componentStyle.borderWidth = "1px";
componentStyle.borderStyle = "dashed";
componentStyle.borderColor = isSelected ? "#3b82f6" : "#cbd5e1";
}
@@ -483,8 +486,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
style={{
width: "100%",
height: "100%",
minHeight: "100%",
maxHeight: "100%",
minHeight: "40px",
border: "none",
borderRadius: "0.5rem",
background: componentConfig.disabled

View File

@@ -179,6 +179,18 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
tableName: _tableName,
onRefresh: _onRefresh,
onClose: _onClose,
autoGeneration: _autoGeneration,
hidden: _hidden,
isInModal: _isInModal,
isPreview: _isPreview,
originalData: _originalData,
allComponents: _allComponents,
selectedRows: _selectedRows,
selectedRowsData: _selectedRowsData,
refreshKey: _refreshKey,
onUpdateLayout: _onUpdateLayout,
onSelectedRowsChange: _onSelectedRowsChange,
onConfigChange: _onConfigChange,
...domProps
} = props;
@@ -303,10 +315,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
// 이메일 타입 전용 UI
if (webType === "email") {
return (
<div className={`relative w-full ${className || ""}`} {...safeDomProps}>
<div className={`flex w-full flex-col ${className || ""}`} {...safeDomProps}>
{/* 라벨 렌더링 */}
{component.label && component.style?.labelDisplay !== false && (
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
<label className="mb-1.5 text-sm font-medium text-slate-600">
{component.label}
{component.required && <span className="text-red-500">*</span>}
</label>
@@ -405,10 +417,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
// 전화번호 타입 전용 UI
if (webType === "tel") {
return (
<div className={`relative w-full ${className || ""}`} {...safeDomProps}>
<div className={`flex w-full flex-col ${className || ""}`} {...safeDomProps}>
{/* 라벨 렌더링 */}
{component.label && component.style?.labelDisplay !== false && (
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
<label className="mb-1.5 text-sm font-medium text-slate-600">
{component.label}
{component.required && <span className="text-red-500">*</span>}
</label>
@@ -486,10 +498,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
// URL 타입 전용 UI
if (webType === "url") {
return (
<div className={`relative w-full ${className || ""}`} {...safeDomProps}>
<div className={`flex w-full flex-col ${className || ""}`} {...safeDomProps}>
{/* 라벨 렌더링 */}
{component.label && component.style?.labelDisplay !== false && (
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
<label className="mb-1.5 text-sm font-medium text-slate-600">
{component.label}
{component.required && <span className="text-red-500">*</span>}
</label>
@@ -541,10 +553,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
// textarea 타입인 경우 별도 렌더링
if (webType === "textarea") {
return (
<div className={`relative w-full ${className || ""}`} {...safeDomProps}>
<div className={`flex w-full flex-col ${className || ""}`} {...safeDomProps}>
{/* 라벨 렌더링 */}
{component.label && component.style?.labelDisplay !== false && (
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
<label className="mb-1.5 text-sm font-medium text-slate-600">
{component.label}
{component.required && <span className="text-red-500">*</span>}
</label>
@@ -582,10 +594,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
}
return (
<div className={`relative w-full max-w-full overflow-hidden ${className || ""}`} {...safeDomProps}>
<div className={`flex w-full max-w-full flex-col ${className || ""}`} {...safeDomProps}>
{/* 라벨 렌더링 */}
{component.label && component.style?.labelDisplay !== false && (
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
<label className="mb-1.5 text-sm font-medium text-slate-600">
{component.label}
{component.required && <span className="text-red-500">*</span>}
</label>

View File

@@ -40,6 +40,7 @@ const REACT_ONLY_PROPS = new Set([
// 상태 관련
"mode",
"isInModal",
"isPreview",
// 테이블 관련
"selectedRows",
@@ -48,6 +49,9 @@ const REACT_ONLY_PROPS = new Set([
// 컴포넌트 기능 관련
"autoGeneration",
"hidden", // 이미 SAFE_DOM_PROPS에 있지만 커스텀 구현을 위해 제외
// 필터링할 특수 속성
"readonly", // readOnly로 변환되므로 원본은 제거
]);
// DOM에 안전하게 전달할 수 있는 표준 HTML 속성들
@@ -67,6 +71,28 @@ const SAFE_DOM_PROPS = new Set([
"hidden",
"spellCheck",
"translate",
// 폼 관련 속성
"readOnly",
"disabled",
"required",
"placeholder",
"value",
"defaultValue",
"checked",
"defaultChecked",
"name",
"type",
"accept",
"autoComplete",
"autoFocus",
"multiple",
"pattern",
"min",
"max",
"step",
"minLength",
"maxLength",
// ARIA 속성 (aria-로 시작)
// data 속성 (data-로 시작)
@@ -115,6 +141,12 @@ export function filterDOMProps<T extends Record<string, any>>(props: T): Partial
continue;
}
// readonly → readOnly 변환 (React의 camelCase 규칙)
if (key === "readonly") {
filtered["readOnly" as keyof T] = value;
continue;
}
// aria- 또는 data- 속성은 안전하게 포함
if (key.startsWith("aria-") || key.startsWith("data-")) {
filtered[key as keyof T] = value;