조건부 설정 구현

This commit is contained in:
kjs 2025-12-22 10:44:22 +09:00
parent a717f97b34
commit ac526c8578
8 changed files with 459 additions and 133 deletions

View File

@ -23,6 +23,7 @@ import { TableSearchWidgetHeightProvider, useTableSearchWidgetHeight } from "@/c
import { ScreenContextProvider } from "@/contexts/ScreenContext"; // 컴포넌트 간 통신
import { SplitPanelProvider } from "@/lib/registry/components/split-panel-layout/SplitPanelContext"; // 분할 패널 리사이즈
import { ActiveTabProvider } from "@/contexts/ActiveTabContext"; // 활성 탭 관리
import { evaluateConditional } from "@/lib/utils/conditionalEvaluator"; // 조건부 표시 평가
function ScreenViewPage() {
const params = useParams();
@ -218,6 +219,67 @@ function ScreenViewPage() {
initAutoFill();
}, [layout, user]);
// 🆕 조건부 비활성화/숨김 시 해당 필드 값 초기화
// 조건 필드들의 값을 추적하여 변경 시에만 실행
const conditionalFieldValues = useMemo(() => {
if (!layout?.components) return "";
// 조건부 설정에 사용되는 필드들의 현재 값을 JSON 문자열로 만들어 비교
const conditionFields = new Set<string>();
layout.components.forEach((component) => {
const conditional = (component as any).conditional;
if (conditional?.enabled && conditional.field) {
conditionFields.add(conditional.field);
}
});
const values: Record<string, any> = {};
conditionFields.forEach((field) => {
values[field] = (formData as Record<string, any>)[field];
});
return JSON.stringify(values);
}, [layout?.components, formData]);
useEffect(() => {
if (!layout?.components) return;
const fieldsToReset: string[] = [];
layout.components.forEach((component) => {
const conditional = (component as any).conditional;
if (!conditional?.enabled) return;
const conditionalResult = evaluateConditional(
conditional,
formData as Record<string, any>,
layout.components,
);
// 숨김 또는 비활성화 상태인 경우
if (!conditionalResult.visible || conditionalResult.disabled) {
const fieldName = (component as any).columnName || component.id;
const currentValue = (formData as Record<string, any>)[fieldName];
// 값이 있으면 초기화 대상에 추가
if (currentValue !== undefined && currentValue !== "" && currentValue !== null) {
fieldsToReset.push(fieldName);
}
}
});
// 초기화할 필드가 있으면 한 번에 처리
if (fieldsToReset.length > 0) {
setFormData((prev) => {
const updated = { ...prev };
fieldsToReset.forEach((fieldName) => {
updated[fieldName] = "";
});
return updated;
});
}
}, [conditionalFieldValues, layout?.components]);
// 캔버스 비율 조정 (사용자 화면에 맞게 자동 스케일) - 초기 로딩 시에만 계산
// 브라우저 배율 조정 시 메뉴와 화면이 함께 축소/확대되도록 resize 이벤트는 감지하지 않음
useEffect(() => {
@ -469,9 +531,30 @@ function ScreenViewPage() {
<>
{/* 일반 컴포넌트들 */}
{adjustedComponents.map((component) => {
// 조건부 표시 설정이 있는 경우에만 평가
const conditional = (component as any).conditional;
let conditionalDisabled = false;
if (conditional?.enabled) {
const conditionalResult = evaluateConditional(
conditional,
formData as Record<string, any>,
layout?.components || [],
);
// 조건에 따라 숨김 처리
if (!conditionalResult.visible) {
return null;
}
// 조건에 따라 비활성화 처리
conditionalDisabled = conditionalResult.disabled;
}
// 화면 관리 해상도를 사용하므로 위치 조정 불필요
return (
<RealtimePreview
conditionalDisabled={conditionalDisabled}
key={component.id}
component={component}
isSelected={false}

View File

@ -19,84 +19,7 @@ import { FlowVisibilityConfig } from "@/types/control-management";
import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils";
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
// 조건부 표시 평가 함수
function evaluateConditional(
conditional: ComponentData["conditional"],
formData: Record<string, any>,
allComponents: ComponentData[],
): { visible: boolean; disabled: boolean } {
if (!conditional || !conditional.enabled) {
return { visible: true, disabled: false };
}
const { field, operator, value, action } = conditional;
// 참조 필드의 현재 값 가져오기
// 필드 ID로 컴포넌트를 찾아 columnName 또는 id로 formData에서 값 조회
const refComponent = allComponents.find((c) => c.id === field);
const fieldName = (refComponent as any)?.columnName || field;
const fieldValue = formData[fieldName];
// 조건 평가
let conditionMet = false;
switch (operator) {
case "=":
conditionMet = fieldValue === value || String(fieldValue) === String(value);
break;
case "!=":
conditionMet = fieldValue !== value && String(fieldValue) !== String(value);
break;
case ">":
conditionMet = Number(fieldValue) > Number(value);
break;
case "<":
conditionMet = Number(fieldValue) < Number(value);
break;
case "in":
if (Array.isArray(value)) {
conditionMet = value.includes(fieldValue) || value.map(String).includes(String(fieldValue));
}
break;
case "notIn":
if (Array.isArray(value)) {
conditionMet = !value.includes(fieldValue) && !value.map(String).includes(String(fieldValue));
} else {
conditionMet = true;
}
break;
case "isEmpty":
conditionMet =
fieldValue === null ||
fieldValue === undefined ||
fieldValue === "" ||
(Array.isArray(fieldValue) && fieldValue.length === 0);
break;
case "isNotEmpty":
conditionMet =
fieldValue !== null &&
fieldValue !== undefined &&
fieldValue !== "" &&
!(Array.isArray(fieldValue) && fieldValue.length === 0);
break;
default:
conditionMet = true;
}
// 액션에 따른 결과 반환
switch (action) {
case "show":
return { visible: conditionMet, disabled: false };
case "hide":
return { visible: !conditionMet, disabled: false };
case "enable":
return { visible: true, disabled: !conditionMet };
case "disable":
return { visible: true, disabled: conditionMet };
default:
return { visible: true, disabled: false };
}
}
import { evaluateConditional } from "@/lib/utils/conditionalEvaluator";
// 컴포넌트 렌더러들을 강제로 로드하여 레지스트리에 등록
import "@/lib/registry/components/ButtonRenderer";

View File

@ -64,6 +64,9 @@ interface RealtimePreviewProps {
// 🆕 조건부 컨테이너 높이 변화 콜백
onHeightChange?: (componentId: string, newHeight: number) => void;
// 🆕 조건부 비활성화 상태
conditionalDisabled?: boolean;
}
// 동적 위젯 타입 아이콘 (레지스트리에서 조회)
@ -93,7 +96,7 @@ const getWidgetIcon = (widgetType: WebType | undefined): React.ReactNode => {
return iconMap[widgetType] || <Type className="h-3 w-3" />;
};
export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
component,
isSelected = false,
isDesignMode = true, // 기본값은 편집 모드
@ -128,6 +131,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
formData,
onFormDataChange,
onHeightChange, // 🆕 조건부 컨테이너 높이 변화 콜백
conditionalDisabled, // 🆕 조건부 비활성화 상태
}) => {
const [actualHeight, setActualHeight] = React.useState<number | null>(null);
const contentRef = React.useRef<HTMLDivElement>(null);
@ -509,6 +513,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
sortOrder={sortOrder}
columnOrder={columnOrder}
onHeightChange={onHeightChange}
conditionalDisabled={conditionalDisabled}
/>
</div>
@ -532,6 +537,12 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
);
};
// React.memo로 래핑하여 불필요한 리렌더링 방지
export const RealtimePreviewDynamic = React.memo(RealtimePreviewDynamicComponent);
// displayName 설정 (디버깅용)
RealtimePreviewDynamic.displayName = "RealtimePreviewDynamic";
// 기존 RealtimePreview와의 호환성을 위한 export
export { RealtimePreviewDynamic as RealtimePreview };
export default RealtimePreviewDynamic;

View File

@ -1548,18 +1548,67 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
action: "show",
}
}
onChange={(newConfig: ConditionalConfig) => {
onChange={(newConfig: ConditionalConfig | undefined) => {
handleUpdate("conditional", newConfig);
}}
availableFields={
allComponents
?.filter((c) => c.type === "widget" && c.id !== selectedComponent.id)
.map((c) => ({
id: (c as any).columnName || c.id,
label: (c as any).label || c.id,
type: (c as any).widgetType || "text",
})) || []
?.filter((c) => {
// 자기 자신 제외
if (c.id === selectedComponent.id) return false;
// widget 타입 또는 component 타입 (Unified 컴포넌트 포함)
return c.type === "widget" || c.type === "component";
})
.map((c) => {
const widgetType = (c as any).widgetType || (c as any).componentType || "text";
const config = (c as any).componentConfig || (c as any).webTypeConfig || {};
const detailSettings = (c as any).detailSettings || {};
// 정적 옵션 추출 (select, dropdown, radio, entity 등)
let options: Array<{ value: string; label: string }> | undefined;
// Unified 컴포넌트의 경우
if (config.options && Array.isArray(config.options)) {
options = config.options;
}
// 레거시 컴포넌트의 경우
else if ((c as any).options && Array.isArray((c as any).options)) {
options = (c as any).options;
}
// 엔티티 정보 추출 (config > detailSettings > 직접 속성 순으로 우선순위)
const entityTable =
config.entityTable ||
detailSettings.referenceTable ||
(c as any).entityTable ||
(c as any).referenceTable;
const entityValueColumn =
config.entityValueColumn ||
detailSettings.referenceColumn ||
(c as any).entityValueColumn ||
(c as any).referenceColumn;
const entityLabelColumn =
config.entityLabelColumn ||
detailSettings.displayColumn ||
(c as any).entityLabelColumn ||
(c as any).displayColumn;
// 공통코드 정보 추출
const codeGroup = config.codeGroup || detailSettings.codeGroup || (c as any).codeGroup;
return {
id: (c as any).columnName || c.id,
label: (c as any).label || config.label || c.id,
type: widgetType,
options,
entityTable,
entityValueColumn,
entityLabelColumn,
codeGroup,
};
}) || []
}
currentComponentId={selectedComponent.id}
/>
</div>
</div>

View File

@ -17,16 +17,24 @@ import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Zap, Plus, Trash2, HelpCircle } from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Zap, Plus, Trash2, HelpCircle, Check, ChevronsUpDown } from "lucide-react";
import { ConditionalConfig } from "@/types/unified-components";
import { cn } from "@/lib/utils";
// ===== 타입 정의 =====
interface FieldOption {
id: string;
label: string;
type?: string; // text, number, select, checkbox
type?: string; // text, number, select, checkbox, entity, code
options?: Array<{ value: string; label: string }>; // select 타입일 경우 옵션들
// 동적 옵션 로드를 위한 정보
entityTable?: string;
entityValueColumn?: string;
entityLabelColumn?: string;
codeGroup?: string;
}
interface ConditionalConfigPanelProps {
@ -85,6 +93,86 @@ export function ConditionalConfigPanel({
return selectableFields.find((f) => f.id === field);
}, [selectableFields, field]);
// 동적 옵션 로드 상태
const [dynamicOptions, setDynamicOptions] = useState<Array<{ value: string; label: string }>>([]);
const [loadingOptions, setLoadingOptions] = useState(false);
// Combobox 열림 상태
const [comboboxOpen, setComboboxOpen] = useState(false);
// 엔티티/공통코드 필드 선택 시 동적으로 옵션 로드
useEffect(() => {
const loadDynamicOptions = async () => {
if (!selectedField) {
setDynamicOptions([]);
return;
}
// 정적 옵션이 있으면 사용
if (selectedField.options && selectedField.options.length > 0) {
setDynamicOptions([]);
return;
}
// 엔티티 타입 (타입이 entity이거나, entityTable이 있으면 엔티티로 간주)
if (selectedField.entityTable) {
setLoadingOptions(true);
try {
const { apiClient } = await import("@/lib/api/client");
const valueCol = selectedField.entityValueColumn || "id";
const labelCol = selectedField.entityLabelColumn || "name";
const response = await apiClient.get(`/entity/${selectedField.entityTable}/options`, {
params: { value: valueCol, label: labelCol },
});
if (response.data.success && response.data.data) {
setDynamicOptions(response.data.data);
}
} catch (error) {
console.error("엔티티 옵션 로드 실패:", error);
setDynamicOptions([]);
} finally {
setLoadingOptions(false);
}
return;
}
// 공통코드 타입 (타입이 code이거나, codeGroup이 있으면 공통코드로 간주)
if (selectedField.codeGroup) {
setLoadingOptions(true);
try {
const { apiClient } = await import("@/lib/api/client");
const response = await apiClient.get(`/common-codes/${selectedField.codeGroup}/items`);
if (response.data.success && response.data.data) {
setDynamicOptions(
response.data.data.map((item: { code: string; codeName: string }) => ({
value: item.code,
label: item.codeName,
}))
);
}
} catch (error) {
console.error("공통코드 옵션 로드 실패:", error);
setDynamicOptions([]);
} finally {
setLoadingOptions(false);
}
return;
}
setDynamicOptions([]);
};
loadDynamicOptions();
}, [selectedField?.id, selectedField?.entityTable, selectedField?.entityValueColumn, selectedField?.entityLabelColumn, selectedField?.codeGroup]);
// 최종 옵션 (정적 + 동적)
const fieldOptions = useMemo(() => {
if (selectedField?.options && selectedField.options.length > 0) {
return selectedField.options;
}
return dynamicOptions;
}, [selectedField?.options, dynamicOptions]);
// config prop 변경 시 로컬 상태 동기화
useEffect(() => {
setEnabled(config?.enabled ?? false);
@ -171,21 +259,66 @@ export function ConditionalConfigPanel({
);
}
// 선택된 필드에 옵션이 있으면 Select로 표시
if (selectedField?.options && selectedField.options.length > 0) {
// 옵션 로딩 중
if (loadingOptions) {
return (
<Select value={value} onValueChange={handleValueChange}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="값 선택" />
</SelectTrigger>
<SelectContent>
{selectedField.options.map((opt) => (
<SelectItem key={opt.value} value={opt.value} className="text-xs">
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="text-xs text-muted-foreground italic">
...
</div>
);
}
// 옵션이 있으면 검색 가능한 Combobox로 표시
if (fieldOptions.length > 0) {
const selectedOption = fieldOptions.find((opt) => opt.value === value);
return (
<Popover open={comboboxOpen} onOpenChange={setComboboxOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={comboboxOpen}
className="h-8 w-full justify-between text-xs font-normal"
>
<span className="truncate">
{selectedOption ? selectedOption.label : "값 선택"}
</span>
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Command>
<CommandInput placeholder="검색..." className="h-8 text-xs" />
<CommandList>
<CommandEmpty className="py-2 text-center text-xs">
</CommandEmpty>
<CommandGroup>
{fieldOptions.map((opt) => (
<CommandItem
key={opt.value}
value={opt.label}
onSelect={() => {
handleValueChange(opt.value);
setComboboxOpen(false);
}}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
value === opt.value ? "opacity-100" : "opacity-0"
)}
/>
<span className="truncate">{opt.label}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@ -460,24 +460,31 @@ export const UnifiedSelect = forwardRef<HTMLDivElement, UnifiedSelectProps>(
const [options, setOptions] = useState<SelectOption[]>(config.options || []);
const [loading, setLoading] = useState(false);
const [optionsLoaded, setOptionsLoaded] = useState(false);
// 데이터 소스에 따른 옵션 로딩
// 옵션 로딩에 필요한 값들만 추출 (객체 참조 대신 원시값 사용)
const source = config.source;
const entityTable = config.entityTable;
const entityValueColumn = config.entityValueColumn || config.entityValueField;
const entityLabelColumn = config.entityLabelColumn || config.entityLabelField;
const codeGroup = config.codeGroup;
const table = config.table;
const valueColumn = config.valueColumn;
const labelColumn = config.labelColumn;
const apiEndpoint = config.apiEndpoint;
const staticOptions = config.options;
// 데이터 소스에 따른 옵션 로딩 (원시값 의존성만 사용)
useEffect(() => {
const loadOptions = async () => {
console.log("🎯 UnifiedSelect 전체 props:", props);
console.log("🎯 UnifiedSelect config:", config);
console.log("🎯 UnifiedSelect loadOptions 호출:", {
source: config.source,
entityTable: config.entityTable,
entityValueColumn: config.entityValueColumn,
entityLabelColumn: config.entityLabelColumn,
codeGroup: config.codeGroup,
table: config.table,
config,
});
// 이미 로드된 경우 스킵 (static 제외)
if (optionsLoaded && source !== "static") {
return;
}
if (config.source === "static") {
setOptions(config.options || []);
const loadOptions = async () => {
if (source === "static") {
setOptions(staticOptions || []);
setOptionsLoaded(true);
return;
}
@ -485,9 +492,9 @@ export const UnifiedSelect = forwardRef<HTMLDivElement, UnifiedSelectProps>(
try {
let fetchedOptions: SelectOption[] = [];
if (config.source === "code" && config.codeGroup) {
if (source === "code" && codeGroup) {
// 공통코드에서 로드
const response = await apiClient.get(`/common-codes/${config.codeGroup}/items`);
const response = await apiClient.get(`/common-codes/${codeGroup}/items`);
const data = response.data;
if (data.success && data.data) {
fetchedOptions = data.data.map((item: { code: string; codeName: string }) => ({
@ -495,37 +502,35 @@ export const UnifiedSelect = forwardRef<HTMLDivElement, UnifiedSelectProps>(
label: item.codeName,
}));
}
} else if (config.source === "db" && config.table) {
} else if (source === "db" && table) {
// DB 테이블에서 로드
const response = await apiClient.get(`/entity/${config.table}/options`, {
const response = await apiClient.get(`/entity/${table}/options`, {
params: {
value: config.valueColumn || "id",
label: config.labelColumn || "name",
value: valueColumn || "id",
label: labelColumn || "name",
},
});
const data = response.data;
if (data.success && data.data) {
fetchedOptions = data.data;
}
} else if (config.source === "entity" && config.entityTable) {
} else if (source === "entity" && entityTable) {
// 엔티티(참조 테이블)에서 로드
const valueCol = config.entityValueColumn || config.entityValueField || "id";
const labelCol = config.entityLabelColumn || config.entityLabelField || "name";
console.log("🔍 Entity 옵션 API 호출:", `/entity/${config.entityTable}/options`, { value: valueCol, label: labelCol });
const response = await apiClient.get(`/entity/${config.entityTable}/options`, {
const valueCol = entityValueColumn || "id";
const labelCol = entityLabelColumn || "name";
const response = await apiClient.get(`/entity/${entityTable}/options`, {
params: {
value: valueCol,
label: labelCol,
},
});
const data = response.data;
console.log("🔍 Entity 옵션 API 응답:", data);
if (data.success && data.data) {
fetchedOptions = data.data;
}
} else if (config.source === "api" && config.apiEndpoint) {
} else if (source === "api" && apiEndpoint) {
// 외부 API에서 로드
const response = await apiClient.get(config.apiEndpoint);
const response = await apiClient.get(apiEndpoint);
const data = response.data;
if (Array.isArray(data)) {
fetchedOptions = data;
@ -533,6 +538,7 @@ export const UnifiedSelect = forwardRef<HTMLDivElement, UnifiedSelectProps>(
}
setOptions(fetchedOptions);
setOptionsLoaded(true);
} catch (error) {
console.error("옵션 로딩 실패:", error);
setOptions([]);
@ -542,7 +548,7 @@ export const UnifiedSelect = forwardRef<HTMLDivElement, UnifiedSelectProps>(
};
loadOptions();
}, [config]);
}, [source, entityTable, entityValueColumn, entityLabelColumn, codeGroup, table, valueColumn, labelColumn, apiEndpoint, staticOptions, optionsLoaded]);
// 모드별 컴포넌트 렌더링
const renderSelect = () => {

View File

@ -148,6 +148,8 @@ export interface DynamicComponentRendererProps {
// 탭 관련 정보 (탭 내부의 컴포넌트에서 사용)
parentTabId?: string; // 부모 탭 ID
parentTabsComponentId?: string; // 부모 탭 컴포넌트 ID
// 🆕 조건부 비활성화 상태
conditionalDisabled?: boolean;
[key: string]: any;
}
@ -183,7 +185,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
label: (component as any).label,
required: (component as any).required,
readonly: (component as any).readonly,
disabled: (component as any).disabled || props.disabledFields?.includes(fieldName),
// conditionalDisabled가 true이면 비활성화
disabled: (component as any).disabled || props.disabledFields?.includes(fieldName) || props.conditionalDisabled,
value: currentValue,
onChange: handleChange,
tableName: (component as any).tableName || props.tableName,

View File

@ -0,0 +1,118 @@
/**
*
* visible/disabled .
*/
import { ComponentData } from "@/types/screen";
export interface ConditionalResult {
visible: boolean;
disabled: boolean;
}
export interface ConditionalConfig {
enabled: boolean;
field: string;
operator: "=" | "!=" | ">" | "<" | "in" | "notIn" | "isEmpty" | "isNotEmpty";
value: string | string[];
action: "show" | "hide" | "enable" | "disable";
}
/**
* .
* @param conditional -
* @param formData -
* @param allComponents - ( )
* @returns visible/disabled
*/
export function evaluateConditional(
conditional: ConditionalConfig | undefined,
formData: Record<string, any>,
allComponents: ComponentData[],
): ConditionalResult {
// 조건부 설정이 없거나 비활성화된 경우 기본값 반환
if (!conditional || !conditional.enabled) {
return { visible: true, disabled: false };
}
const { field, operator, value, action } = conditional;
// 필드가 설정되지 않은 경우 기본값 반환
if (!field) {
console.warn("[evaluateConditional] 조건 필드가 설정되지 않음");
return { visible: true, disabled: false };
}
// 참조 필드의 현재 값 가져오기
// field 값은 columnName 또는 id일 수 있으므로 양쪽으로 찾기
const refComponent = allComponents.find((c) => {
const columnName = (c as any)?.columnName;
return c.id === field || columnName === field;
});
// formData에서 값 조회: columnName 우선, 없으면 field 값 직접 사용
const fieldName = (refComponent as any)?.columnName || field;
const fieldValue = formData[fieldName];
// 조건 평가
let conditionMet = false;
switch (operator) {
case "=":
conditionMet = fieldValue === value || String(fieldValue) === String(value);
break;
case "!=":
conditionMet = fieldValue !== value && String(fieldValue) !== String(value);
break;
case ">":
conditionMet = Number(fieldValue) > Number(value);
break;
case "<":
conditionMet = Number(fieldValue) < Number(value);
break;
case "in":
if (Array.isArray(value)) {
conditionMet = value.includes(fieldValue) || value.map(String).includes(String(fieldValue));
}
break;
case "notIn":
if (Array.isArray(value)) {
conditionMet = !value.includes(fieldValue) && !value.map(String).includes(String(fieldValue));
} else {
conditionMet = true;
}
break;
case "isEmpty":
conditionMet =
fieldValue === null ||
fieldValue === undefined ||
fieldValue === "" ||
(Array.isArray(fieldValue) && fieldValue.length === 0);
break;
case "isNotEmpty":
conditionMet =
fieldValue !== null &&
fieldValue !== undefined &&
fieldValue !== "" &&
!(Array.isArray(fieldValue) && fieldValue.length === 0);
break;
default:
conditionMet = true;
}
// 액션에 따른 결과 반환
switch (action) {
case "show":
// 조건이 참이면 표시, 거짓이면 숨김
return { visible: conditionMet, disabled: false };
case "hide":
// 조건이 참이면 숨김, 거짓이면 표시
return { visible: !conditionMet, disabled: false };
case "enable":
return { visible: true, disabled: !conditionMet };
case "disable":
return { visible: true, disabled: conditionMet };
default:
return { visible: true, disabled: false };
}
}