jskim-node #410
|
|
@ -245,6 +245,17 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
// 통합된 폼 데이터
|
||||
const finalFormData = { ...localFormData, ...externalFormData };
|
||||
|
||||
// 테이블 타입관리 NOT NULL 기반 필수 여부 판단
|
||||
const isColumnRequired = useCallback((columnName: string): boolean => {
|
||||
if (!columnName || tableColumns.length === 0) return false;
|
||||
const colInfo = tableColumns.find(
|
||||
(c: any) => (c.columnName || c.column_name || "").toLowerCase() === columnName.toLowerCase()
|
||||
);
|
||||
if (!colInfo) return false;
|
||||
const nullable = (colInfo as any).isNullable || (colInfo as any).is_nullable;
|
||||
return nullable === "NO" || nullable === "N";
|
||||
}, [tableColumns]);
|
||||
|
||||
// 🆕 조건부 레이어 로직 (formData 변경 시 자동 평가)
|
||||
useEffect(() => {
|
||||
layers.forEach((layer) => {
|
||||
|
|
@ -1612,8 +1623,11 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
return;
|
||||
}
|
||||
|
||||
// 필수 항목 검증
|
||||
const requiredFields = allComponents.filter(c => c.required && (c.columnName || c.id));
|
||||
// 필수 항목 검증 (테이블 타입관리 NOT NULL 기반 + 기존 required 속성 폴백)
|
||||
const requiredFields = allComponents.filter(c => {
|
||||
const colName = c.columnName || c.id;
|
||||
return (c.required || isColumnRequired(colName)) && colName;
|
||||
});
|
||||
const missingFields = requiredFields.filter(field => {
|
||||
const fieldName = field.columnName || field.id;
|
||||
const value = currentFormData[fieldName];
|
||||
|
|
@ -2486,7 +2500,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
style={labelStyle}
|
||||
>
|
||||
{labelText}
|
||||
{(component.required || component.componentConfig?.required) && <span className="ml-1 text-destructive">*</span>}
|
||||
{(component.required || component.componentConfig?.required || isColumnRequired(component.columnName || component.style?.columnName || "")) && <span className="ml-1 text-destructive">*</span>}
|
||||
</label>
|
||||
)}
|
||||
|
||||
|
|
@ -2505,7 +2519,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
}}
|
||||
>
|
||||
{labelText}
|
||||
{(component.required || component.componentConfig?.required) && <span className="ml-1 text-destructive">*</span>}
|
||||
{(component.required || component.componentConfig?.required || isColumnRequired(component.columnName || component.style?.columnName || "")) && <span className="ml-1 text-destructive">*</span>}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { ComponentData, WidgetComponent, DataTableComponent, FileComponent, Butt
|
|||
import { FileUploadComponent } from "@/lib/registry/components/file-upload/FileUploadComponent";
|
||||
import { InteractiveDataTable } from "./InteractiveDataTable";
|
||||
import { DynamicWebTypeRenderer } from "@/lib/registry";
|
||||
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
||||
import { DynamicComponentRenderer, isColumnRequiredByMeta } from "@/lib/registry/DynamicComponentRenderer";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
import { isFileComponent, isDataTableComponent, isButtonComponent } from "@/lib/utils/componentTypeUtils";
|
||||
import { FlowButtonGroup } from "./widgets/FlowButtonGroup";
|
||||
|
|
@ -1294,9 +1294,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
...(labelPos === "bottom" ? { marginTop: style?.labelMarginBottom || "4px" } : {}),
|
||||
}}
|
||||
>
|
||||
{labelText}
|
||||
{((component as any).required || (component as any).componentConfig?.required) && (
|
||||
<span className="ml-1 text-destructive">*</span>
|
||||
{labelText}{((component as any).required || (component as any).componentConfig?.required || isColumnRequiredByMeta((component as any).tableName, (component as any).columnName)) && (
|
||||
<span className="text-orange-500">*</span>
|
||||
)}
|
||||
</label>
|
||||
) : null;
|
||||
|
|
@ -1349,9 +1348,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{labelText}
|
||||
{((component as any).required || (component as any).componentConfig?.required) && (
|
||||
<span className="ml-1 text-destructive">*</span>
|
||||
{labelText}{((component as any).required || (component as any).componentConfig?.required || isColumnRequiredByMeta((component as any).tableName, (component as any).columnName)) && (
|
||||
<span className="text-orange-500">*</span>
|
||||
)}
|
||||
</label>
|
||||
<div style={{ width: "100%", height: "100%" }}>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRendere
|
|||
import { InteractiveScreenViewer } from "./InteractiveScreenViewer";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm";
|
||||
import { getTableColumns, ColumnTypeInfo } from "@/lib/api/tableManagement";
|
||||
import { ComponentData } from "@/lib/types/screen";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
const [originalData, setOriginalData] = useState<Record<string, any>>(initialData || {});
|
||||
const [screenData, setScreenData] = useState<any>(null);
|
||||
const [components, setComponents] = useState<ComponentData[]>([]);
|
||||
const [tableColumnsInfo, setTableColumnsInfo] = useState<ColumnTypeInfo[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
|
|
@ -70,6 +72,19 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
const layout = await screenApi.getLayout(screenId);
|
||||
setComponents(layout.components || []);
|
||||
|
||||
// 테이블 컬럼 정보 로드 (NOT NULL 필수값 자동 인식용)
|
||||
const tblName = screen?.tableName || layout.components?.find((c: any) => c.columnName)?.tableName;
|
||||
if (tblName) {
|
||||
try {
|
||||
const colResult = await getTableColumns(tblName);
|
||||
if (colResult.success && colResult.data?.columns) {
|
||||
setTableColumnsInfo(colResult.data.columns);
|
||||
}
|
||||
} catch (colErr) {
|
||||
console.warn("테이블 컬럼 정보 로드 실패 (필수값 검증 시 기존 방식 사용):", colErr);
|
||||
}
|
||||
}
|
||||
|
||||
// initialData가 있으면 폼에 채우기
|
||||
if (initialData) {
|
||||
setFormData(initialData);
|
||||
|
|
@ -106,34 +121,39 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
};
|
||||
}, [onClose]);
|
||||
|
||||
// 필수 항목 검증
|
||||
// 테이블 타입관리의 NOT NULL 설정 기반으로 필수 여부 판단
|
||||
const isColumnRequired = (columnName: string): boolean => {
|
||||
if (!columnName || tableColumnsInfo.length === 0) return false;
|
||||
const colInfo = tableColumnsInfo.find(
|
||||
(c) => c.columnName.toLowerCase() === columnName.toLowerCase()
|
||||
);
|
||||
if (!colInfo) return false;
|
||||
// is_nullable가 "NO"이면 필수
|
||||
return colInfo.isNullable === "NO" || colInfo.isNullable === "N";
|
||||
};
|
||||
|
||||
// 필수 항목 검증 (테이블 타입관리 NOT NULL + 기존 required 속성 병합)
|
||||
const validateRequiredFields = (): { isValid: boolean; missingFields: string[] } => {
|
||||
const missingFields: string[] = [];
|
||||
|
||||
components.forEach((component) => {
|
||||
// 컴포넌트의 required 속성 확인 (여러 위치에서 체크)
|
||||
const isRequired =
|
||||
component.required === true ||
|
||||
component.style?.required === true ||
|
||||
component.componentConfig?.required === true;
|
||||
|
||||
const columnName = component.columnName || component.style?.columnName;
|
||||
const label = component.label || component.style?.label || columnName;
|
||||
|
||||
console.log("🔍 필수 항목 검증:", {
|
||||
componentId: component.id,
|
||||
columnName,
|
||||
label,
|
||||
isRequired,
|
||||
"component.required": component.required,
|
||||
"style.required": component.style?.required,
|
||||
"componentConfig.required": component.componentConfig?.required,
|
||||
value: formData[columnName || ""],
|
||||
});
|
||||
// 기존 required 속성 (화면 디자이너에서 수동 설정한 것)
|
||||
const manualRequired =
|
||||
component.required === true ||
|
||||
component.style?.required === true ||
|
||||
component.componentConfig?.required === true;
|
||||
|
||||
// 테이블 타입관리 NOT NULL 기반 필수 (컬럼 정보가 있을 때만)
|
||||
const notNullRequired = columnName ? isColumnRequired(columnName) : false;
|
||||
|
||||
// 둘 중 하나라도 필수이면 검증
|
||||
const isRequired = manualRequired || notNullRequired;
|
||||
|
||||
if (isRequired && columnName) {
|
||||
const value = formData[columnName];
|
||||
// 값이 비어있는지 확인 (null, undefined, 빈 문자열, 공백만 있는 문자열)
|
||||
if (value === null || value === undefined || (typeof value === "string" && value.trim() === "")) {
|
||||
missingFields.push(label || columnName);
|
||||
}
|
||||
|
|
@ -405,7 +425,8 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
}));
|
||||
}}
|
||||
hideLabel={false}
|
||||
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달 (카테고리 스코프용)
|
||||
menuObjid={menuObjid}
|
||||
tableColumns={tableColumnsInfo as any}
|
||||
/>
|
||||
) : (
|
||||
<DynamicComponentRenderer
|
||||
|
|
|
|||
|
|
@ -943,19 +943,31 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
|
|||
|
||||
{/* 옵션 - 입력 필드에서는 항상 표시, 기타 컴포넌트는 속성이 정의된 경우만 표시 */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{(isInputField || widget.required !== undefined) && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={widget.required === true || selectedComponent.componentConfig?.required === true}
|
||||
onCheckedChange={(checked) => {
|
||||
handleUpdate("required", checked);
|
||||
handleUpdate("componentConfig.required", checked);
|
||||
}}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<Label className="text-xs">필수</Label>
|
||||
</div>
|
||||
)}
|
||||
{(isInputField || widget.required !== undefined) && (() => {
|
||||
const colName = widget.columnName || selectedComponent?.columnName;
|
||||
const colMeta = colName ? currentTable?.columns?.find(
|
||||
(c: any) => (c.columnName || c.column_name || "").toLowerCase() === colName.toLowerCase()
|
||||
) : null;
|
||||
const isNotNull = colMeta && ((colMeta as any).isNullable === "NO" || (colMeta as any).isNullable === "N" || (colMeta as any).is_nullable === "NO" || (colMeta as any).is_nullable === "N");
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={isNotNull || widget.required === true || selectedComponent.componentConfig?.required === true}
|
||||
onCheckedChange={(checked) => {
|
||||
if (isNotNull) return;
|
||||
handleUpdate("required", checked);
|
||||
handleUpdate("componentConfig.required", checked);
|
||||
}}
|
||||
disabled={!!isNotNull}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<Label className="text-xs">
|
||||
필수
|
||||
{isNotNull && <span className="text-muted-foreground ml-1">(NOT NULL)</span>}
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{(isInputField || widget.readonly !== undefined) && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
|
|
|
|||
|
|
@ -724,8 +724,7 @@ export const V2Date = forwardRef<HTMLDivElement, V2DateProps>((props, ref) => {
|
|||
}}
|
||||
className="text-sm font-medium whitespace-nowrap"
|
||||
>
|
||||
{label}
|
||||
{required && <span className="ml-0.5 text-orange-500">*</span>}
|
||||
{label}{required && <span className="text-orange-500">*</span>}
|
||||
</Label>
|
||||
) : null;
|
||||
|
||||
|
|
|
|||
|
|
@ -491,8 +491,7 @@ export const V2Hierarchy = forwardRef<HTMLDivElement, V2HierarchyProps>(
|
|||
}}
|
||||
className="text-sm font-medium whitespace-nowrap"
|
||||
>
|
||||
{label}
|
||||
{required && <span className="text-orange-500 ml-0.5">*</span>}
|
||||
{label}{required && <span className="text-orange-500">*</span>}
|
||||
</Label>
|
||||
)}
|
||||
<div className="h-full w-full">
|
||||
|
|
|
|||
|
|
@ -994,8 +994,7 @@ export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) =>
|
|||
}}
|
||||
className="text-sm font-medium whitespace-nowrap"
|
||||
>
|
||||
{actualLabel}
|
||||
{required && <span className="ml-0.5 text-orange-500">*</span>}
|
||||
{actualLabel}{required && <span className="text-orange-500">*</span>}
|
||||
</Label>
|
||||
) : null;
|
||||
|
||||
|
|
|
|||
|
|
@ -840,8 +840,7 @@ export const V2Media = forwardRef<HTMLDivElement, V2MediaProps>((props, ref) =>
|
|||
}}
|
||||
className="shrink-0 text-sm font-medium"
|
||||
>
|
||||
{label}
|
||||
{required && <span className="ml-0.5 text-orange-500">*</span>}
|
||||
{label}{required && <span className="text-orange-500">*</span>}
|
||||
</Label>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1181,8 +1181,7 @@ export const V2Select = forwardRef<HTMLDivElement, V2SelectProps>(
|
|||
}}
|
||||
className="text-sm font-medium whitespace-nowrap"
|
||||
>
|
||||
{label}
|
||||
{required && <span className="text-orange-500 ml-0.5">*</span>}
|
||||
{label}{required && <span className="text-orange-500">*</span>}
|
||||
</Label>
|
||||
) : null;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@ const columnMetaCache: Record<string, Record<string, any>> = {};
|
|||
const columnMetaLoading: Record<string, Promise<void>> = {};
|
||||
|
||||
async function loadColumnMeta(tableName: string): Promise<void> {
|
||||
if (columnMetaCache[tableName] || columnMetaLoading[tableName]) return;
|
||||
if (columnMetaCache[tableName]) return;
|
||||
|
||||
// 이미 로딩 중이면 해당 Promise를 대기 (race condition 방지)
|
||||
if (columnMetaLoading[tableName]) {
|
||||
await columnMetaLoading[tableName];
|
||||
return;
|
||||
}
|
||||
|
||||
columnMetaLoading[tableName] = (async () => {
|
||||
try {
|
||||
|
|
@ -28,7 +34,8 @@ async function loadColumnMeta(tableName: string): Promise<void> {
|
|||
if (name) map[name] = col;
|
||||
}
|
||||
columnMetaCache[tableName] = map;
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error(`[columnMeta] ${tableName} 로드 실패:`, e);
|
||||
columnMetaCache[tableName] = {};
|
||||
} finally {
|
||||
delete columnMetaLoading[tableName];
|
||||
|
|
@ -38,6 +45,15 @@ async function loadColumnMeta(tableName: string): Promise<void> {
|
|||
await columnMetaLoading[tableName];
|
||||
}
|
||||
|
||||
// 테이블 타입관리 NOT NULL 기반 필수 여부 판단
|
||||
export function isColumnRequiredByMeta(tableName?: string, columnName?: string): boolean {
|
||||
if (!tableName || !columnName) return false;
|
||||
const meta = columnMetaCache[tableName]?.[columnName];
|
||||
if (!meta) return false;
|
||||
const nullable = meta.is_nullable || meta.isNullable;
|
||||
return nullable === "NO" || nullable === "N";
|
||||
}
|
||||
|
||||
// table_type_columns 기반 componentConfig 병합 (기존 설정이 없을 때만 DB 메타데이터로 보완)
|
||||
function mergeColumnMeta(tableName: string | undefined, columnName: string | undefined, componentConfig: any): any {
|
||||
if (!tableName || !columnName) return componentConfig;
|
||||
|
|
@ -249,7 +265,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
const screenTableName = props.tableName || (component as any).tableName;
|
||||
const [, forceUpdate] = React.useState(0);
|
||||
React.useEffect(() => {
|
||||
if (screenTableName && !columnMetaCache[screenTableName]) {
|
||||
if (screenTableName) {
|
||||
loadColumnMeta(screenTableName).then(() => forceUpdate((v) => v + 1));
|
||||
}
|
||||
}, [screenTableName]);
|
||||
|
|
@ -435,7 +451,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
const labelFontSize = component.style?.labelFontSize || "14px";
|
||||
const labelColor = component.style?.labelColor || "#64748b";
|
||||
const labelFontWeight = component.style?.labelFontWeight || "500";
|
||||
const isRequired = component.required || (component as any).required;
|
||||
const isRequired = component.required || (component as any).required || isColumnRequiredByMeta(tableName, columnName);
|
||||
const isLeft = catLabelPosition === "left";
|
||||
return (
|
||||
<div style={{ position: "relative", width: "100%", height: "100%" }}>
|
||||
|
|
@ -454,8 +470,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
}}
|
||||
className="text-sm font-medium"
|
||||
>
|
||||
{catLabelText}
|
||||
{isRequired && <span className="text-orange-500 ml-0.5">*</span>}
|
||||
{catLabelText}{isRequired && <span className="text-orange-500">*</span>}
|
||||
</label>
|
||||
<div style={{ width: "100%", height: "100%" }}>
|
||||
{renderedCatSelect}
|
||||
|
|
@ -715,10 +730,14 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
const baseColumnName = isEntityJoinColumn ? undefined : fieldName;
|
||||
const mergedComponentConfig = mergeColumnMeta(screenTableName, baseColumnName, component.componentConfig || {});
|
||||
|
||||
// NOT NULL 기반 필수 여부를 component.required에 반영
|
||||
const notNullRequired = isColumnRequiredByMeta(screenTableName, baseColumnName);
|
||||
const effectiveRequired = component.required || notNullRequired;
|
||||
|
||||
// 엔티티 조인 컬럼은 런타임에서 readonly/disabled 강제 해제
|
||||
const effectiveComponent = isEntityJoinColumn
|
||||
? { ...component, componentConfig: mergedComponentConfig, readonly: false }
|
||||
: { ...component, componentConfig: mergedComponentConfig };
|
||||
? { ...component, componentConfig: mergedComponentConfig, readonly: false, required: effectiveRequired }
|
||||
: { ...component, componentConfig: mergedComponentConfig, required: effectiveRequired };
|
||||
|
||||
const rendererProps = {
|
||||
component: effectiveComponent,
|
||||
|
|
@ -852,7 +871,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
const labelFontSize = component.style?.labelFontSize || "14px";
|
||||
const labelColor = component.style?.labelColor || "#64748b";
|
||||
const labelFontWeight = component.style?.labelFontWeight || "500";
|
||||
const isRequired = component.required || (component as any).required;
|
||||
const isRequired = effectiveComponent.required || isColumnRequiredByMeta(screenTableName, baseColumnName);
|
||||
const isLeft = labelPosition === "left";
|
||||
|
||||
return (
|
||||
|
|
@ -872,8 +891,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
}}
|
||||
className="text-sm font-medium"
|
||||
>
|
||||
{effectiveLabel}
|
||||
{isRequired && <span className="text-orange-500 ml-0.5">*</span>}
|
||||
{effectiveLabel}{isRequired && <span className="text-orange-500">*</span>}
|
||||
</label>
|
||||
<div style={{ width: "100%", height: "100%" }}>
|
||||
{renderedElement}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import React from "react";
|
|||
import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer";
|
||||
import { V2DateDefinition } from "./index";
|
||||
import { V2Date } from "@/components/v2/V2Date";
|
||||
import { isColumnRequiredByMeta } from "../../DynamicComponentRenderer";
|
||||
|
||||
/**
|
||||
* V2Date 렌더러
|
||||
|
|
@ -18,6 +19,7 @@ export class V2DateRenderer extends AutoRegisteringComponentRenderer {
|
|||
// 컴포넌트 설정 추출
|
||||
const config = component.componentConfig || component.config || {};
|
||||
const columnName = component.columnName;
|
||||
const tableName = component.tableName || this.props.tableName;
|
||||
|
||||
// formData에서 현재 값 가져오기
|
||||
const currentValue = formData?.[columnName] ?? component.value ?? "";
|
||||
|
|
@ -37,10 +39,6 @@ export class V2DateRenderer extends AutoRegisteringComponentRenderer {
|
|||
return (
|
||||
<V2Date
|
||||
id={component.id}
|
||||
label={effectiveLabel}
|
||||
required={component.required}
|
||||
readonly={config.readonly || component.readonly}
|
||||
disabled={config.disabled || component.disabled}
|
||||
value={currentValue}
|
||||
onChange={handleChange}
|
||||
config={{
|
||||
|
|
@ -55,6 +53,10 @@ export class V2DateRenderer extends AutoRegisteringComponentRenderer {
|
|||
style={component.style}
|
||||
size={component.size}
|
||||
{...restProps}
|
||||
label={effectiveLabel}
|
||||
required={component.required || isColumnRequiredByMeta(tableName, columnName)}
|
||||
readonly={config.readonly || component.readonly}
|
||||
disabled={config.disabled || component.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import React from "react";
|
|||
import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer";
|
||||
import { V2InputDefinition } from "./index";
|
||||
import { V2Input } from "@/components/v2/V2Input";
|
||||
import { isColumnRequiredByMeta } from "../../DynamicComponentRenderer";
|
||||
|
||||
/**
|
||||
* V2Input 렌더러
|
||||
|
|
@ -52,10 +53,6 @@ export class V2InputRenderer extends AutoRegisteringComponentRenderer {
|
|||
return (
|
||||
<V2Input
|
||||
id={component.id}
|
||||
label={effectiveLabel}
|
||||
required={component.required}
|
||||
readonly={config.readonly || component.readonly}
|
||||
disabled={config.disabled || component.disabled}
|
||||
value={currentValue}
|
||||
onChange={handleChange}
|
||||
onFormDataChange={onFormDataChange}
|
||||
|
|
@ -78,6 +75,10 @@ export class V2InputRenderer extends AutoRegisteringComponentRenderer {
|
|||
autoGeneration={config.autoGeneration || component.autoGeneration}
|
||||
originalData={(this.props as any).originalData}
|
||||
{...restProps}
|
||||
label={effectiveLabel}
|
||||
required={component.required || isColumnRequiredByMeta(tableName, columnName)}
|
||||
readonly={config.readonly || component.readonly}
|
||||
disabled={config.disabled || component.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import React from "react";
|
|||
import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer";
|
||||
import { V2SelectDefinition } from "./index";
|
||||
import { V2Select } from "@/components/v2/V2Select";
|
||||
import { isColumnRequiredByMeta } from "../../DynamicComponentRenderer";
|
||||
|
||||
/**
|
||||
* V2Select 렌더러
|
||||
|
|
@ -112,10 +113,6 @@ export class V2SelectRenderer extends AutoRegisteringComponentRenderer {
|
|||
return (
|
||||
<V2Select
|
||||
id={component.id}
|
||||
label={component.label}
|
||||
required={component.required}
|
||||
readonly={config.readonly || component.readonly}
|
||||
disabled={config.disabled || component.disabled}
|
||||
value={currentValue}
|
||||
onChange={handleChange}
|
||||
onFormDataChange={isInteractive ? onFormDataChange : undefined}
|
||||
|
|
@ -141,6 +138,10 @@ export class V2SelectRenderer extends AutoRegisteringComponentRenderer {
|
|||
{...restPropsClean}
|
||||
style={effectiveStyle}
|
||||
size={effectiveSize}
|
||||
label={component.label}
|
||||
required={component.required || isColumnRequiredByMeta(tableName, columnName)}
|
||||
readonly={config.readonly || component.readonly}
|
||||
disabled={config.disabled || component.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue