feat: Implement NOT NULL validation for form fields based on table metadata
- Added a new function `isColumnRequired` to determine if a column is required based on its NOT NULL status from the table schema. - Updated the `SaveModal` and `InteractiveScreenViewer` components to incorporate this validation, ensuring that required fields are accurately assessed during form submission. - Enhanced the `DynamicComponentRenderer` to reflect the NOT NULL requirement in the component's required state. - Improved user feedback by marking required fields with an asterisk based on both manual settings and database constraints. These changes enhance the form validation process, ensuring that users are prompted for all necessary information based on the underlying data structure.
This commit is contained in:
parent
c0eab878a1
commit
43523a0bba
|
|
@ -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