컴포넌트 리뉴얼 1.0
This commit is contained in:
@@ -20,6 +20,84 @@ 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 "@/lib/registry/components/ButtonRenderer";
|
||||
import "@/lib/registry/components/CardRenderer";
|
||||
@@ -56,7 +134,7 @@ interface InteractiveScreenViewerProps {
|
||||
// 원본 데이터 (수정 모드에서 UPDATE 판단용)
|
||||
originalData?: Record<string, any> | null;
|
||||
// 탭 관련 정보 (탭 내부의 컴포넌트에서 사용)
|
||||
parentTabId?: string; // 부모 탭 ID
|
||||
parentTabId?: string; // 부모 탭 ID
|
||||
parentTabsComponentId?: string; // 부모 탭 컴포넌트 ID
|
||||
}
|
||||
|
||||
@@ -334,6 +412,14 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
|
||||
// 동적 대화형 위젯 렌더링
|
||||
const renderInteractiveWidget = (comp: ComponentData) => {
|
||||
// 조건부 표시 평가
|
||||
const conditionalResult = evaluateConditional(comp.conditional, formData, allComponents);
|
||||
|
||||
// 조건에 따라 숨김 처리
|
||||
if (!conditionalResult.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 데이터 테이블 컴포넌트 처리
|
||||
if (isDataTableComponent(comp)) {
|
||||
return (
|
||||
@@ -431,6 +517,9 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
});
|
||||
};
|
||||
|
||||
// 조건부 비활성화 적용
|
||||
const isConditionallyDisabled = conditionalResult.disabled;
|
||||
|
||||
// 동적 웹타입 렌더링 사용
|
||||
if (widgetType) {
|
||||
try {
|
||||
@@ -444,7 +533,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
onFormDataChange: handleFormDataChange,
|
||||
formData: formData, // 🆕 전체 formData 전달
|
||||
isInteractive: true,
|
||||
readonly: readonly,
|
||||
readonly: readonly || isConditionallyDisabled, // 조건부 비활성화 적용
|
||||
disabled: isConditionallyDisabled, // 조건부 비활성화 전달
|
||||
required: required,
|
||||
placeholder: placeholder,
|
||||
className: "w-full h-full",
|
||||
@@ -470,7 +560,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
value={currentValue}
|
||||
onChange={(e) => handleFormDataChange(fieldName, e.target.value)}
|
||||
placeholder={`${widgetType} (렌더링 오류)`}
|
||||
disabled={readonly}
|
||||
disabled={readonly || isConditionallyDisabled}
|
||||
required={required}
|
||||
className="h-full w-full"
|
||||
/>
|
||||
@@ -486,7 +576,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
value={currentValue}
|
||||
onChange={(e) => handleFormDataChange(fieldName, e.target.value)}
|
||||
placeholder={placeholder || "입력하세요"}
|
||||
disabled={readonly}
|
||||
disabled={readonly || isConditionallyDisabled}
|
||||
required={required}
|
||||
className="h-full w-full"
|
||||
/>
|
||||
@@ -593,7 +683,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
const handleQuickInsertAction = async () => {
|
||||
// componentConfig에서 quickInsertConfig 가져오기
|
||||
const quickInsertConfig = (comp as any).componentConfig?.action?.quickInsertConfig;
|
||||
|
||||
|
||||
if (!quickInsertConfig?.targetTable) {
|
||||
toast.error("대상 테이블이 설정되지 않았습니다.");
|
||||
return;
|
||||
@@ -604,7 +694,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
try {
|
||||
const { default: apiClient } = await import("@/lib/api/client");
|
||||
const columnsResponse = await apiClient.get(
|
||||
`/table-management/tables/${quickInsertConfig.targetTable}/columns`
|
||||
`/table-management/tables/${quickInsertConfig.targetTable}/columns`,
|
||||
);
|
||||
if (columnsResponse.data?.success && columnsResponse.data?.data) {
|
||||
const columnsData = columnsResponse.data.data.columns || columnsResponse.data.data;
|
||||
@@ -618,7 +708,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
// 2. 컬럼 매핑에서 값 수집
|
||||
const insertData: Record<string, any> = {};
|
||||
const columnMappings = quickInsertConfig.columnMappings || [];
|
||||
|
||||
|
||||
for (const mapping of columnMappings) {
|
||||
let value: any;
|
||||
|
||||
@@ -681,31 +771,31 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
if (splitPanelContext?.selectedLeftData && targetTableColumns.length > 0) {
|
||||
const leftData = splitPanelContext.selectedLeftData;
|
||||
console.log("📍 좌측 패널 자동 매핑 시작:", leftData);
|
||||
|
||||
|
||||
for (const [key, val] of Object.entries(leftData)) {
|
||||
// 이미 매핑된 컬럼은 스킵
|
||||
if (insertData[key] !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 대상 테이블에 해당 컬럼이 없으면 스킵
|
||||
if (!targetTableColumns.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 시스템 컬럼 제외
|
||||
const systemColumns = ['id', 'created_date', 'updated_date', 'writer', 'writer_name'];
|
||||
const systemColumns = ["id", "created_date", "updated_date", "writer", "writer_name"];
|
||||
if (systemColumns.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// _label, _name 으로 끝나는 표시용 컬럼 제외
|
||||
if (key.endsWith('_label') || key.endsWith('_name')) {
|
||||
if (key.endsWith("_label") || key.endsWith("_name")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 값이 있으면 자동 추가
|
||||
if (val !== undefined && val !== null && val !== '') {
|
||||
if (val !== undefined && val !== null && val !== "") {
|
||||
insertData[key] = val;
|
||||
console.log(`📍 자동 매핑 추가: ${key} = ${val}`);
|
||||
}
|
||||
@@ -724,7 +814,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
if (quickInsertConfig.duplicateCheck?.enabled && quickInsertConfig.duplicateCheck?.columns?.length > 0) {
|
||||
try {
|
||||
const { default: apiClient } = await import("@/lib/api/client");
|
||||
|
||||
|
||||
// 중복 체크를 위한 검색 조건 구성
|
||||
const searchConditions: Record<string, any> = {};
|
||||
for (const col of quickInsertConfig.duplicateCheck.columns) {
|
||||
@@ -736,14 +826,11 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
console.log("📍 중복 체크 조건:", searchConditions);
|
||||
|
||||
// 기존 데이터 조회
|
||||
const checkResponse = await apiClient.post(
|
||||
`/table-management/tables/${quickInsertConfig.targetTable}/data`,
|
||||
{
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
search: searchConditions,
|
||||
}
|
||||
);
|
||||
const checkResponse = await apiClient.post(`/table-management/tables/${quickInsertConfig.targetTable}/data`, {
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
search: searchConditions,
|
||||
});
|
||||
|
||||
console.log("📍 중복 체크 응답:", checkResponse.data);
|
||||
|
||||
@@ -765,7 +852,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
|
||||
const response = await apiClient.post(
|
||||
`/table-management/tables/${quickInsertConfig.targetTable}/add`,
|
||||
insertData
|
||||
insertData,
|
||||
);
|
||||
|
||||
if (response.data?.success) {
|
||||
@@ -1000,7 +1087,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
{popupScreen && (
|
||||
<Dialog open={!!popupScreen} onOpenChange={() => setPopupScreen(null)}>
|
||||
<DialogContent
|
||||
className="overflow-hidden p-0 max-w-none"
|
||||
className="max-w-none overflow-hidden p-0"
|
||||
style={{
|
||||
width: popupScreen.size === "small" ? "600px" : popupScreen.size === "large" ? "1400px" : "1000px",
|
||||
height: "800px",
|
||||
|
||||
Reference in New Issue
Block a user