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;
|
||||
}, [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 버튼으로 닫으려 할 때
|
||||
// 폼 데이터 변경이 있으면 확인 다이얼로그, 없으면 바로 닫기
|
||||
const handleCloseAttempt = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -233,8 +233,8 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
return;
|
||||
}
|
||||
|
||||
// tableName 확인 (props에서 전달받은 tableName 또는 componentConfig에서 추출)
|
||||
const tableNameToUse = tableName || component.componentConfig?.tableName;
|
||||
// tableName 확인 (props에서 전달받은 tableName 사용)
|
||||
const tableNameToUse = tableName || component.componentConfig?.tableName || 'user_info'; // 기본 테이블명 설정
|
||||
|
||||
if (!tableNameToUse) {
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -193,7 +193,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
const [rightData, setRightData] = useState<any[] | any>(null); // 조인 모드는 배열, 상세 모드는 객체
|
||||
const [selectedLeftItem, setSelectedLeftItem] = useState<any>(null);
|
||||
const [expandedRightItems, setExpandedRightItems] = useState<Set<string | number>>(new Set()); // 확장된 우측 아이템
|
||||
const [customLeftSelectedData, setCustomLeftSelectedData] = useState<Record<string, any>>({}); // 커스텀 모드: 좌측 선택 데이터
|
||||
const [leftSearchQuery, setLeftSearchQuery] = useState("");
|
||||
const [rightSearchQuery, setRightSearchQuery] = useState("");
|
||||
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 selectedPanelComponentId = externalSelectedPanelComponentId || null;
|
||||
// 🆕 커스텀 모드: 분할패널 내 탭 컴포넌트의 선택 상태 관리
|
||||
const [nestedTabSelectedCompId, setNestedTabSelectedCompId] = useState<string | undefined>(undefined);
|
||||
const rafRef = useRef<number | null>(null);
|
||||
|
||||
// 🆕 10px 단위 스냅 함수
|
||||
|
|
@ -302,9 +299,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
rafRef.current = requestAnimationFrame(() => {
|
||||
const deltaX = moveEvent.clientX - startMouseX;
|
||||
const deltaY = moveEvent.clientY - startMouseY;
|
||||
// 10px 단위 스냅 적용
|
||||
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
||||
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
||||
const newX = Math.max(0, startLeft + deltaX);
|
||||
const newY = Math.max(0, startTop + deltaY);
|
||||
setDragPosition({ x: newX, y: newY });
|
||||
});
|
||||
};
|
||||
|
|
@ -320,9 +316,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
|
||||
const deltaX = upEvent.clientX - startMouseX;
|
||||
const deltaY = upEvent.clientY - startMouseY;
|
||||
// 10px 단위 스냅 적용
|
||||
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
||||
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
||||
const newX = Math.max(0, startLeft + deltaX);
|
||||
const newY = Math.max(0, startTop + deltaY);
|
||||
|
||||
setDraggingCompId(null);
|
||||
setDragPosition(null);
|
||||
|
|
@ -332,7 +327,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
const panelConfig = componentConfig[panelKey] || {};
|
||||
const updatedComponents = (panelConfig.components || []).map((c: PanelInlineComponent) =>
|
||||
c.id === comp.id
|
||||
? { ...c, position: { x: newX, y: newY } }
|
||||
? { ...c, position: { x: Math.round(newX), y: Math.round(newY) } }
|
||||
: c
|
||||
);
|
||||
|
||||
|
|
@ -352,7 +347,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
},
|
||||
[component, componentConfig, onUpdateComponent, snapTo10]
|
||||
[component, componentConfig, onUpdateComponent]
|
||||
);
|
||||
|
||||
// 🆕 커스텀 모드: 리사이즈 시작 핸들러
|
||||
|
|
@ -2605,10 +2600,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 패널 컴포넌트 선택 시 탭 내 선택 해제
|
||||
if (comp.componentType !== "v2-tabs-widget") {
|
||||
setNestedTabSelectedCompId(undefined);
|
||||
}
|
||||
onSelectPanelComponent?.("left", comp.id, comp);
|
||||
}}
|
||||
>
|
||||
|
|
@ -2688,8 +2679,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
// 🆕 중첩된 탭 내부 컴포넌트 선택 핸들러 - 부모 분할 패널 정보 포함
|
||||
onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
|
||||
console.log("🔍 [SplitPanel-Left] onSelectTabComponent 호출:", { tabId, compId, tabComp, parentSplitPanelId: component.id });
|
||||
// 탭 내 컴포넌트 선택 상태 업데이트
|
||||
setNestedTabSelectedCompId(compId);
|
||||
// 부모 분할 패널 정보와 함께 전역 이벤트 발생
|
||||
const event = new CustomEvent("nested-tab-component-select", {
|
||||
detail: {
|
||||
|
|
@ -2703,7 +2692,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
});
|
||||
window.dispatchEvent(event);
|
||||
}}
|
||||
selectedTabComponentId={nestedTabSelectedCompId}
|
||||
selectedTabComponentId={undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -2758,17 +2747,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
component={componentData as any}
|
||||
isDesignMode={false}
|
||||
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>
|
||||
);
|
||||
|
|
@ -3504,10 +3482,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// 패널 컴포넌트 선택 시 탭 내 선택 해제
|
||||
if (comp.componentType !== "v2-tabs-widget") {
|
||||
setNestedTabSelectedCompId(undefined);
|
||||
}
|
||||
onSelectPanelComponent?.("right", comp.id, comp);
|
||||
}}
|
||||
>
|
||||
|
|
@ -3587,8 +3561,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
// 🆕 중첩된 탭 내부 컴포넌트 선택 핸들러 - 부모 분할 패널 정보 포함
|
||||
onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
|
||||
console.log("🔍 [SplitPanel-Right] onSelectTabComponent 호출:", { tabId, compId, tabComp, parentSplitPanelId: component.id });
|
||||
// 탭 내 컴포넌트 선택 상태 업데이트
|
||||
setNestedTabSelectedCompId(compId);
|
||||
// 부모 분할 패널 정보와 함께 전역 이벤트 발생
|
||||
const event = new CustomEvent("nested-tab-component-select", {
|
||||
detail: {
|
||||
|
|
@ -3602,7 +3574,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
});
|
||||
window.dispatchEvent(event);
|
||||
}}
|
||||
selectedTabComponentId={nestedTabSelectedCompId}
|
||||
selectedTabComponentId={undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -3645,8 +3617,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
<DynamicComponentRenderer
|
||||
component={componentData as any}
|
||||
isDesignMode={false}
|
||||
formData={customLeftSelectedData}
|
||||
tableName={componentConfig.rightPanel?.tableName || componentConfig.leftPanel?.tableName}
|
||||
formData={{}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -186,10 +186,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
return;
|
||||
}
|
||||
|
||||
// tableColumns prop은 화면의 기본 테이블 컬럼이므로,
|
||||
// 다른 테이블을 선택한 경우 반드시 API에서 가져오기
|
||||
const isUsingDifferentTable = config.selectedTable && screenTableName && config.selectedTable !== screenTableName;
|
||||
const shouldUseTableColumnsProp = !config.useCustomTable && !isUsingDifferentTable && tableColumns && tableColumns.length > 0;
|
||||
// 🆕 customTableName이 설정된 경우 반드시 API에서 가져오기
|
||||
// tableColumns prop은 화면의 기본 테이블 컬럼이므로, customTableName 사용 시 무시
|
||||
const shouldUseTableColumnsProp = !config.useCustomTable && tableColumns && tableColumns.length > 0;
|
||||
|
||||
if (shouldUseTableColumnsProp) {
|
||||
const mappedColumns = tableColumns.map((column: any) => ({
|
||||
|
|
@ -773,113 +772,11 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
handleChange("columns", columns);
|
||||
};
|
||||
|
||||
// 테이블 변경 핸들러 - 테이블 변경 시 컬럼 설정 초기화
|
||||
const handleTableChange = (newTableName: string) => {
|
||||
if (newTableName === targetTableName) return;
|
||||
|
||||
const updatedConfig = {
|
||||
...config,
|
||||
selectedTable: newTableName,
|
||||
// 테이블이 변경되면 컬럼 설정 초기화
|
||||
columns: [],
|
||||
};
|
||||
onChange(updatedConfig);
|
||||
setTableComboboxOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm font-medium">테이블 리스트 설정</div>
|
||||
|
||||
<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>
|
||||
|
|
@ -1270,11 +1167,11 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{!targetTableName ? (
|
||||
{!screenTableName ? (
|
||||
<div className="space-y-3">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>테이블이 선택되지 않았습니다.</p>
|
||||
<p className="text-sm">위 데이터 소스에서 테이블을 선택하세요.</p>
|
||||
<p>테이블이 연결되지 않았습니다.</p>
|
||||
<p className="text-sm">화면에 테이블을 연결한 후 컬럼을 설정할 수 있습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
) : availableColumns.length === 0 ? (
|
||||
|
|
|
|||
|
|
@ -78,9 +78,6 @@ const TabsDesignEditor: React.FC<{
|
|||
[activeTabId, component, onUpdateComponent, tabs]
|
||||
);
|
||||
|
||||
// 10px 단위 스냅 함수
|
||||
const snapTo10 = useCallback((value: number) => Math.round(value / 10) * 10, []);
|
||||
|
||||
// 컴포넌트 드래그 시작
|
||||
const handleDragStart = useCallback(
|
||||
(e: React.MouseEvent, comp: TabInlineComponent) => {
|
||||
|
|
@ -107,9 +104,9 @@ const TabsDesignEditor: React.FC<{
|
|||
const deltaX = moveEvent.clientX - startMouseX;
|
||||
const deltaY = moveEvent.clientY - startMouseY;
|
||||
|
||||
// 새 위치 = 시작 위치 + 이동량 (10px 단위 스냅 적용)
|
||||
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
||||
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
||||
// 새 위치 = 시작 위치 + 이동량
|
||||
const newX = Math.max(0, startLeft + deltaX);
|
||||
const newY = Math.max(0, startTop + deltaY);
|
||||
|
||||
// React 상태로 위치 업데이트 (리렌더링 트리거)
|
||||
setDragPosition({ x: newX, y: newY });
|
||||
|
|
@ -129,9 +126,9 @@ const TabsDesignEditor: React.FC<{
|
|||
const deltaX = upEvent.clientX - startMouseX;
|
||||
const deltaY = upEvent.clientY - startMouseY;
|
||||
|
||||
// 새 위치 = 시작 위치 + 이동량 (10px 단위 스냅 적용)
|
||||
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
||||
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
||||
// 새 위치 = 시작 위치 + 이동량
|
||||
const newX = Math.max(0, startLeft + deltaX);
|
||||
const newY = Math.max(0, startTop + deltaY);
|
||||
|
||||
setDraggingCompId(null);
|
||||
setDragPosition(null);
|
||||
|
|
@ -147,8 +144,8 @@ const TabsDesignEditor: React.FC<{
|
|||
? {
|
||||
...c,
|
||||
position: {
|
||||
x: newX,
|
||||
y: newY,
|
||||
x: Math.max(0, Math.round(newX)),
|
||||
y: Math.max(0, Math.round(newY)),
|
||||
},
|
||||
}
|
||||
: c
|
||||
|
|
@ -175,9 +172,12 @@ const TabsDesignEditor: React.FC<{
|
|||
document.addEventListener("mousemove", handleMouseMove);
|
||||
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(
|
||||
(e: React.MouseEvent, comp: TabInlineComponent, direction: "e" | "s" | "se") => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue