Compare commits
No commits in common. "5b44a416514eba97364a8f3a29fc4d98f0dcb874" and "8253be00481d2d434271f37d237a540ec00b292f" have entirely different histories.
5b44a41651
...
8253be0048
|
|
@ -730,91 +730,6 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
||||||
return activeComps;
|
return activeComps;
|
||||||
}, [formData, conditionalLayers, screenData?.components]);
|
}, [formData, conditionalLayers, screenData?.components]);
|
||||||
|
|
||||||
// 🆕 이전 활성 레이어 ID 추적 (레이어 전환 감지용)
|
|
||||||
const prevActiveLayerIdsRef = useRef<string[]>([]);
|
|
||||||
|
|
||||||
// 🆕 레이어 전환 시 비활성화된 레이어의 필드값을 formData에서 제거
|
|
||||||
// (품목우선 → 공급업체우선 전환 시, 품목우선 레이어의 데이터가 남지 않도록)
|
|
||||||
useEffect(() => {
|
|
||||||
if (conditionalLayers.length === 0) return;
|
|
||||||
|
|
||||||
// 현재 활성 레이어 ID 목록
|
|
||||||
const currentActiveLayerIds = conditionalLayers
|
|
||||||
.filter((layer) => {
|
|
||||||
if (!layer.condition) return false;
|
|
||||||
const { targetComponentId, operator, value } = layer.condition;
|
|
||||||
if (!targetComponentId) return false;
|
|
||||||
|
|
||||||
const allComponents = screenData?.components || [];
|
|
||||||
const comp = allComponents.find((c: any) => c.id === targetComponentId);
|
|
||||||
const fieldKey =
|
|
||||||
(comp as any)?.overrides?.columnName ||
|
|
||||||
(comp as any)?.columnName ||
|
|
||||||
(comp as any)?.componentConfig?.columnName ||
|
|
||||||
targetComponentId;
|
|
||||||
|
|
||||||
const targetValue = formData[fieldKey];
|
|
||||||
switch (operator) {
|
|
||||||
case "eq":
|
|
||||||
return String(targetValue ?? "") === String(value ?? "");
|
|
||||||
case "neq":
|
|
||||||
return String(targetValue ?? "") !== String(value ?? "");
|
|
||||||
case "in":
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.some((v) => String(v) === String(targetValue ?? ""));
|
|
||||||
} else if (typeof value === "string" && value.includes(",")) {
|
|
||||||
return value.split(",").map((v) => v.trim()).includes(String(targetValue ?? ""));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map((l) => l.id);
|
|
||||||
|
|
||||||
const prevIds = prevActiveLayerIdsRef.current;
|
|
||||||
|
|
||||||
// 이전에 활성이었는데 이번에 비활성이 된 레이어 찾기
|
|
||||||
const deactivatedLayerIds = prevIds.filter((id) => !currentActiveLayerIds.includes(id));
|
|
||||||
|
|
||||||
if (deactivatedLayerIds.length > 0) {
|
|
||||||
// 비활성화된 레이어의 컴포넌트 필드명 수집
|
|
||||||
const fieldsToRemove: string[] = [];
|
|
||||||
deactivatedLayerIds.forEach((layerId) => {
|
|
||||||
const layer = conditionalLayers.find((l) => l.id === layerId);
|
|
||||||
if (!layer) return;
|
|
||||||
|
|
||||||
layer.components.forEach((comp: any) => {
|
|
||||||
const fieldName =
|
|
||||||
comp?.overrides?.columnName ||
|
|
||||||
comp?.columnName ||
|
|
||||||
comp?.componentConfig?.columnName;
|
|
||||||
if (fieldName) {
|
|
||||||
fieldsToRemove.push(fieldName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fieldsToRemove.length > 0) {
|
|
||||||
console.log("[ScreenModal] 레이어 전환 감지 - 비활성 레이어 필드 제거:", {
|
|
||||||
deactivatedLayerIds,
|
|
||||||
fieldsToRemove,
|
|
||||||
});
|
|
||||||
|
|
||||||
setFormData((prev) => {
|
|
||||||
const cleaned = { ...prev };
|
|
||||||
fieldsToRemove.forEach((field) => {
|
|
||||||
delete cleaned[field];
|
|
||||||
});
|
|
||||||
return cleaned;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 현재 상태 저장
|
|
||||||
prevActiveLayerIdsRef.current = currentActiveLayerIds;
|
|
||||||
}, [formData, conditionalLayers, screenData?.components]);
|
|
||||||
|
|
||||||
// 사용자가 바깥 클릭/ESC/X 버튼으로 닫으려 할 때
|
// 사용자가 바깥 클릭/ESC/X 버튼으로 닫으려 할 때
|
||||||
// 폼 데이터 변경이 있으면 확인 다이얼로그, 없으면 바로 닫기
|
// 폼 데이터 변경이 있으면 확인 다이얼로그, 없으면 바로 닫기
|
||||||
const handleCloseAttempt = useCallback(() => {
|
const handleCloseAttempt = useCallback(() => {
|
||||||
|
|
|
||||||
|
|
@ -233,8 +233,8 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tableName 확인 (props에서 전달받은 tableName 또는 componentConfig에서 추출)
|
// tableName 확인 (props에서 전달받은 tableName 사용)
|
||||||
const tableNameToUse = tableName || component.componentConfig?.tableName;
|
const tableNameToUse = tableName || component.componentConfig?.tableName || 'user_info'; // 기본 테이블명 설정
|
||||||
|
|
||||||
if (!tableNameToUse) {
|
if (!tableNameToUse) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
const [rightData, setRightData] = useState<any[] | any>(null); // 조인 모드는 배열, 상세 모드는 객체
|
const [rightData, setRightData] = useState<any[] | any>(null); // 조인 모드는 배열, 상세 모드는 객체
|
||||||
const [selectedLeftItem, setSelectedLeftItem] = useState<any>(null);
|
const [selectedLeftItem, setSelectedLeftItem] = useState<any>(null);
|
||||||
const [expandedRightItems, setExpandedRightItems] = useState<Set<string | number>>(new Set()); // 확장된 우측 아이템
|
const [expandedRightItems, setExpandedRightItems] = useState<Set<string | number>>(new Set()); // 확장된 우측 아이템
|
||||||
const [customLeftSelectedData, setCustomLeftSelectedData] = useState<Record<string, any>>({}); // 커스텀 모드: 좌측 선택 데이터
|
|
||||||
const [leftSearchQuery, setLeftSearchQuery] = useState("");
|
const [leftSearchQuery, setLeftSearchQuery] = useState("");
|
||||||
const [rightSearchQuery, setRightSearchQuery] = useState("");
|
const [rightSearchQuery, setRightSearchQuery] = useState("");
|
||||||
const [isLoadingLeft, setIsLoadingLeft] = useState(false);
|
const [isLoadingLeft, setIsLoadingLeft] = useState(false);
|
||||||
|
|
@ -221,8 +220,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
const [resizeSize, setResizeSize] = useState<{ width: number; height: number } | null>(null);
|
const [resizeSize, setResizeSize] = useState<{ width: number; height: number } | null>(null);
|
||||||
// 🆕 외부에서 전달받은 선택 상태 사용 (탭 컴포넌트와 동일 구조)
|
// 🆕 외부에서 전달받은 선택 상태 사용 (탭 컴포넌트와 동일 구조)
|
||||||
const selectedPanelComponentId = externalSelectedPanelComponentId || null;
|
const selectedPanelComponentId = externalSelectedPanelComponentId || null;
|
||||||
// 🆕 커스텀 모드: 분할패널 내 탭 컴포넌트의 선택 상태 관리
|
|
||||||
const [nestedTabSelectedCompId, setNestedTabSelectedCompId] = useState<string | undefined>(undefined);
|
|
||||||
const rafRef = useRef<number | null>(null);
|
const rafRef = useRef<number | null>(null);
|
||||||
|
|
||||||
// 🆕 10px 단위 스냅 함수
|
// 🆕 10px 단위 스냅 함수
|
||||||
|
|
@ -302,9 +299,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
rafRef.current = requestAnimationFrame(() => {
|
rafRef.current = requestAnimationFrame(() => {
|
||||||
const deltaX = moveEvent.clientX - startMouseX;
|
const deltaX = moveEvent.clientX - startMouseX;
|
||||||
const deltaY = moveEvent.clientY - startMouseY;
|
const deltaY = moveEvent.clientY - startMouseY;
|
||||||
// 10px 단위 스냅 적용
|
const newX = Math.max(0, startLeft + deltaX);
|
||||||
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
const newY = Math.max(0, startTop + deltaY);
|
||||||
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
|
||||||
setDragPosition({ x: newX, y: newY });
|
setDragPosition({ x: newX, y: newY });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -320,9 +316,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
|
|
||||||
const deltaX = upEvent.clientX - startMouseX;
|
const deltaX = upEvent.clientX - startMouseX;
|
||||||
const deltaY = upEvent.clientY - startMouseY;
|
const deltaY = upEvent.clientY - startMouseY;
|
||||||
// 10px 단위 스냅 적용
|
const newX = Math.max(0, startLeft + deltaX);
|
||||||
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
const newY = Math.max(0, startTop + deltaY);
|
||||||
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
|
||||||
|
|
||||||
setDraggingCompId(null);
|
setDraggingCompId(null);
|
||||||
setDragPosition(null);
|
setDragPosition(null);
|
||||||
|
|
@ -332,7 +327,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
const panelConfig = componentConfig[panelKey] || {};
|
const panelConfig = componentConfig[panelKey] || {};
|
||||||
const updatedComponents = (panelConfig.components || []).map((c: PanelInlineComponent) =>
|
const updatedComponents = (panelConfig.components || []).map((c: PanelInlineComponent) =>
|
||||||
c.id === comp.id
|
c.id === comp.id
|
||||||
? { ...c, position: { x: newX, y: newY } }
|
? { ...c, position: { x: Math.round(newX), y: Math.round(newY) } }
|
||||||
: c
|
: c
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -352,7 +347,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
document.addEventListener("mousemove", handleMouseMove);
|
document.addEventListener("mousemove", handleMouseMove);
|
||||||
document.addEventListener("mouseup", handleMouseUp);
|
document.addEventListener("mouseup", handleMouseUp);
|
||||||
},
|
},
|
||||||
[component, componentConfig, onUpdateComponent, snapTo10]
|
[component, componentConfig, onUpdateComponent]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 🆕 커스텀 모드: 리사이즈 시작 핸들러
|
// 🆕 커스텀 모드: 리사이즈 시작 핸들러
|
||||||
|
|
@ -2605,10 +2600,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// 패널 컴포넌트 선택 시 탭 내 선택 해제
|
|
||||||
if (comp.componentType !== "v2-tabs-widget") {
|
|
||||||
setNestedTabSelectedCompId(undefined);
|
|
||||||
}
|
|
||||||
onSelectPanelComponent?.("left", comp.id, comp);
|
onSelectPanelComponent?.("left", comp.id, comp);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -2688,8 +2679,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// 🆕 중첩된 탭 내부 컴포넌트 선택 핸들러 - 부모 분할 패널 정보 포함
|
// 🆕 중첩된 탭 내부 컴포넌트 선택 핸들러 - 부모 분할 패널 정보 포함
|
||||||
onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
|
onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
|
||||||
console.log("🔍 [SplitPanel-Left] onSelectTabComponent 호출:", { tabId, compId, tabComp, parentSplitPanelId: component.id });
|
console.log("🔍 [SplitPanel-Left] onSelectTabComponent 호출:", { tabId, compId, tabComp, parentSplitPanelId: component.id });
|
||||||
// 탭 내 컴포넌트 선택 상태 업데이트
|
|
||||||
setNestedTabSelectedCompId(compId);
|
|
||||||
// 부모 분할 패널 정보와 함께 전역 이벤트 발생
|
// 부모 분할 패널 정보와 함께 전역 이벤트 발생
|
||||||
const event = new CustomEvent("nested-tab-component-select", {
|
const event = new CustomEvent("nested-tab-component-select", {
|
||||||
detail: {
|
detail: {
|
||||||
|
|
@ -2703,7 +2692,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
});
|
});
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
}}
|
}}
|
||||||
selectedTabComponentId={nestedTabSelectedCompId}
|
selectedTabComponentId={undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2758,17 +2747,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
component={componentData as any}
|
component={componentData as any}
|
||||||
isDesignMode={false}
|
isDesignMode={false}
|
||||||
formData={{}}
|
formData={{}}
|
||||||
tableName={componentConfig.leftPanel?.tableName}
|
|
||||||
onFormDataChange={(data: any) => {
|
|
||||||
// 커스텀 모드: 좌측 카드/테이블 선택 시 데이터 캡처
|
|
||||||
if (data?.selectedRowsData && data.selectedRowsData.length > 0) {
|
|
||||||
setCustomLeftSelectedData(data.selectedRowsData[0]);
|
|
||||||
setSelectedLeftItem(data.selectedRowsData[0]);
|
|
||||||
} else if (data?.selectedRowsData && data.selectedRowsData.length === 0) {
|
|
||||||
setCustomLeftSelectedData({});
|
|
||||||
setSelectedLeftItem(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -3504,10 +3482,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// 패널 컴포넌트 선택 시 탭 내 선택 해제
|
|
||||||
if (comp.componentType !== "v2-tabs-widget") {
|
|
||||||
setNestedTabSelectedCompId(undefined);
|
|
||||||
}
|
|
||||||
onSelectPanelComponent?.("right", comp.id, comp);
|
onSelectPanelComponent?.("right", comp.id, comp);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -3587,8 +3561,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// 🆕 중첩된 탭 내부 컴포넌트 선택 핸들러 - 부모 분할 패널 정보 포함
|
// 🆕 중첩된 탭 내부 컴포넌트 선택 핸들러 - 부모 분할 패널 정보 포함
|
||||||
onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
|
onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
|
||||||
console.log("🔍 [SplitPanel-Right] onSelectTabComponent 호출:", { tabId, compId, tabComp, parentSplitPanelId: component.id });
|
console.log("🔍 [SplitPanel-Right] onSelectTabComponent 호출:", { tabId, compId, tabComp, parentSplitPanelId: component.id });
|
||||||
// 탭 내 컴포넌트 선택 상태 업데이트
|
|
||||||
setNestedTabSelectedCompId(compId);
|
|
||||||
// 부모 분할 패널 정보와 함께 전역 이벤트 발생
|
// 부모 분할 패널 정보와 함께 전역 이벤트 발생
|
||||||
const event = new CustomEvent("nested-tab-component-select", {
|
const event = new CustomEvent("nested-tab-component-select", {
|
||||||
detail: {
|
detail: {
|
||||||
|
|
@ -3602,7 +3574,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
});
|
});
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
}}
|
}}
|
||||||
selectedTabComponentId={nestedTabSelectedCompId}
|
selectedTabComponentId={undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -3645,8 +3617,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
<DynamicComponentRenderer
|
<DynamicComponentRenderer
|
||||||
component={componentData as any}
|
component={componentData as any}
|
||||||
isDesignMode={false}
|
isDesignMode={false}
|
||||||
formData={customLeftSelectedData}
|
formData={{}}
|
||||||
tableName={componentConfig.rightPanel?.tableName || componentConfig.leftPanel?.tableName}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -186,10 +186,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tableColumns prop은 화면의 기본 테이블 컬럼이므로,
|
// 🆕 customTableName이 설정된 경우 반드시 API에서 가져오기
|
||||||
// 다른 테이블을 선택한 경우 반드시 API에서 가져오기
|
// tableColumns prop은 화면의 기본 테이블 컬럼이므로, customTableName 사용 시 무시
|
||||||
const isUsingDifferentTable = config.selectedTable && screenTableName && config.selectedTable !== screenTableName;
|
const shouldUseTableColumnsProp = !config.useCustomTable && tableColumns && tableColumns.length > 0;
|
||||||
const shouldUseTableColumnsProp = !config.useCustomTable && !isUsingDifferentTable && tableColumns && tableColumns.length > 0;
|
|
||||||
|
|
||||||
if (shouldUseTableColumnsProp) {
|
if (shouldUseTableColumnsProp) {
|
||||||
const mappedColumns = tableColumns.map((column: any) => ({
|
const mappedColumns = tableColumns.map((column: any) => ({
|
||||||
|
|
@ -773,113 +772,11 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
handleChange("columns", columns);
|
handleChange("columns", columns);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 테이블 변경 핸들러 - 테이블 변경 시 컬럼 설정 초기화
|
|
||||||
const handleTableChange = (newTableName: string) => {
|
|
||||||
if (newTableName === targetTableName) return;
|
|
||||||
|
|
||||||
const updatedConfig = {
|
|
||||||
...config,
|
|
||||||
selectedTable: newTableName,
|
|
||||||
// 테이블이 변경되면 컬럼 설정 초기화
|
|
||||||
columns: [],
|
|
||||||
};
|
|
||||||
onChange(updatedConfig);
|
|
||||||
setTableComboboxOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="text-sm font-medium">테이블 리스트 설정</div>
|
<div className="text-sm font-medium">테이블 리스트 설정</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* 테이블 선택 */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-sm font-semibold">데이터 소스</h3>
|
|
||||||
<p className="text-muted-foreground text-[10px]">
|
|
||||||
테이블을 선택하세요. 미선택 시 화면 메인 테이블을 사용합니다.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<hr className="border-border" />
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">테이블 선택</Label>
|
|
||||||
<Popover open={tableComboboxOpen} onOpenChange={setTableComboboxOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
role="combobox"
|
|
||||||
aria-expanded={tableComboboxOpen}
|
|
||||||
className="h-8 w-full justify-between text-xs"
|
|
||||||
disabled={loadingTables}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2 truncate">
|
|
||||||
<Table2 className="h-3 w-3 shrink-0" />
|
|
||||||
<span className="truncate">
|
|
||||||
{loadingTables
|
|
||||||
? "테이블 로딩 중..."
|
|
||||||
: targetTableName
|
|
||||||
? availableTables.find((t) => t.tableName === targetTableName)?.displayName ||
|
|
||||||
targetTableName
|
|
||||||
: "테이블 선택"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent
|
|
||||||
className="p-0"
|
|
||||||
style={{ width: "var(--radix-popover-trigger-width)" }}
|
|
||||||
align="start"
|
|
||||||
>
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="테이블 검색..." className="text-xs" />
|
|
||||||
<CommandList>
|
|
||||||
<CommandEmpty className="text-xs">테이블을 찾을 수 없습니다.</CommandEmpty>
|
|
||||||
<CommandGroup>
|
|
||||||
{availableTables.map((table) => (
|
|
||||||
<CommandItem
|
|
||||||
key={table.tableName}
|
|
||||||
value={`${table.tableName} ${table.displayName}`}
|
|
||||||
onSelect={() => handleTableChange(table.tableName)}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
<Check
|
|
||||||
className={cn(
|
|
||||||
"mr-2 h-3 w-3",
|
|
||||||
targetTableName === table.tableName ? "opacity-100" : "opacity-0",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span>{table.displayName}</span>
|
|
||||||
{table.displayName !== table.tableName && (
|
|
||||||
<span className="text-[10px] text-gray-400">{table.tableName}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
{screenTableName && targetTableName !== screenTableName && (
|
|
||||||
<div className="flex items-center justify-between rounded bg-amber-50 px-2 py-1">
|
|
||||||
<span className="text-[10px] text-amber-700">
|
|
||||||
화면 기본 테이블({screenTableName})과 다른 테이블을 사용 중
|
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-5 px-1.5 text-[10px] text-amber-700 hover:text-amber-900"
|
|
||||||
onClick={() => handleTableChange(screenTableName)}
|
|
||||||
>
|
|
||||||
기본으로
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 툴바 버튼 설정 */}
|
{/* 툴바 버튼 설정 */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -1270,11 +1167,11 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!targetTableName ? (
|
{!screenTableName ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-center text-gray-500">
|
<div className="text-center text-gray-500">
|
||||||
<p>테이블이 선택되지 않았습니다.</p>
|
<p>테이블이 연결되지 않았습니다.</p>
|
||||||
<p className="text-sm">위 데이터 소스에서 테이블을 선택하세요.</p>
|
<p className="text-sm">화면에 테이블을 연결한 후 컬럼을 설정할 수 있습니다.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : availableColumns.length === 0 ? (
|
) : availableColumns.length === 0 ? (
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,6 @@ const TabsDesignEditor: React.FC<{
|
||||||
[activeTabId, component, onUpdateComponent, tabs]
|
[activeTabId, component, onUpdateComponent, tabs]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 10px 단위 스냅 함수
|
|
||||||
const snapTo10 = useCallback((value: number) => Math.round(value / 10) * 10, []);
|
|
||||||
|
|
||||||
// 컴포넌트 드래그 시작
|
// 컴포넌트 드래그 시작
|
||||||
const handleDragStart = useCallback(
|
const handleDragStart = useCallback(
|
||||||
(e: React.MouseEvent, comp: TabInlineComponent) => {
|
(e: React.MouseEvent, comp: TabInlineComponent) => {
|
||||||
|
|
@ -107,9 +104,9 @@ const TabsDesignEditor: React.FC<{
|
||||||
const deltaX = moveEvent.clientX - startMouseX;
|
const deltaX = moveEvent.clientX - startMouseX;
|
||||||
const deltaY = moveEvent.clientY - startMouseY;
|
const deltaY = moveEvent.clientY - startMouseY;
|
||||||
|
|
||||||
// 새 위치 = 시작 위치 + 이동량 (10px 단위 스냅 적용)
|
// 새 위치 = 시작 위치 + 이동량
|
||||||
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
const newX = Math.max(0, startLeft + deltaX);
|
||||||
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
const newY = Math.max(0, startTop + deltaY);
|
||||||
|
|
||||||
// React 상태로 위치 업데이트 (리렌더링 트리거)
|
// React 상태로 위치 업데이트 (리렌더링 트리거)
|
||||||
setDragPosition({ x: newX, y: newY });
|
setDragPosition({ x: newX, y: newY });
|
||||||
|
|
@ -129,9 +126,9 @@ const TabsDesignEditor: React.FC<{
|
||||||
const deltaX = upEvent.clientX - startMouseX;
|
const deltaX = upEvent.clientX - startMouseX;
|
||||||
const deltaY = upEvent.clientY - startMouseY;
|
const deltaY = upEvent.clientY - startMouseY;
|
||||||
|
|
||||||
// 새 위치 = 시작 위치 + 이동량 (10px 단위 스냅 적용)
|
// 새 위치 = 시작 위치 + 이동량
|
||||||
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
const newX = Math.max(0, startLeft + deltaX);
|
||||||
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
const newY = Math.max(0, startTop + deltaY);
|
||||||
|
|
||||||
setDraggingCompId(null);
|
setDraggingCompId(null);
|
||||||
setDragPosition(null);
|
setDragPosition(null);
|
||||||
|
|
@ -147,8 +144,8 @@ const TabsDesignEditor: React.FC<{
|
||||||
? {
|
? {
|
||||||
...c,
|
...c,
|
||||||
position: {
|
position: {
|
||||||
x: newX,
|
x: Math.max(0, Math.round(newX)),
|
||||||
y: newY,
|
y: Math.max(0, Math.round(newY)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: c
|
: c
|
||||||
|
|
@ -175,9 +172,12 @@ const TabsDesignEditor: React.FC<{
|
||||||
document.addEventListener("mousemove", handleMouseMove);
|
document.addEventListener("mousemove", handleMouseMove);
|
||||||
document.addEventListener("mouseup", handleMouseUp);
|
document.addEventListener("mouseup", handleMouseUp);
|
||||||
},
|
},
|
||||||
[activeTabId, component, onUpdateComponent, tabs, snapTo10]
|
[activeTabId, component, onUpdateComponent, tabs]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 10px 단위 스냅 함수
|
||||||
|
const snapTo10 = useCallback((value: number) => Math.round(value / 10) * 10, []);
|
||||||
|
|
||||||
// 리사이즈 시작 핸들러
|
// 리사이즈 시작 핸들러
|
||||||
const handleResizeStart = useCallback(
|
const handleResizeStart = useCallback(
|
||||||
(e: React.MouseEvent, comp: TabInlineComponent, direction: "e" | "s" | "se") => {
|
(e: React.MouseEvent, comp: TabInlineComponent, direction: "e" | "s" | "se") => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue