조건부 설정 구현
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user