Merge branch 'feature/v2-renewal' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
parent
e97fd05e75
commit
86a73267cb
|
|
@ -221,6 +221,8 @@ 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 단위 스냅 함수
|
||||||
|
|
@ -300,8 +302,9 @@ 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;
|
||||||
const newX = Math.max(0, startLeft + deltaX);
|
// 10px 단위 스냅 적용
|
||||||
const newY = Math.max(0, startTop + deltaY);
|
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
||||||
|
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
||||||
setDragPosition({ x: newX, y: newY });
|
setDragPosition({ x: newX, y: newY });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -317,8 +320,9 @@ 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;
|
||||||
const newX = Math.max(0, startLeft + deltaX);
|
// 10px 단위 스냅 적용
|
||||||
const newY = Math.max(0, startTop + deltaY);
|
const newX = snapTo10(Math.max(0, startLeft + deltaX));
|
||||||
|
const newY = snapTo10(Math.max(0, startTop + deltaY));
|
||||||
|
|
||||||
setDraggingCompId(null);
|
setDraggingCompId(null);
|
||||||
setDragPosition(null);
|
setDragPosition(null);
|
||||||
|
|
@ -328,7 +332,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: Math.round(newX), y: Math.round(newY) } }
|
? { ...c, position: { x: newX, y: newY } }
|
||||||
: c
|
: c
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -348,7 +352,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]
|
[component, componentConfig, onUpdateComponent, snapTo10]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 🆕 커스텀 모드: 리사이즈 시작 핸들러
|
// 🆕 커스텀 모드: 리사이즈 시작 핸들러
|
||||||
|
|
@ -2601,6 +2605,10 @@ 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);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -2680,6 +2688,8 @@ 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: {
|
||||||
|
|
@ -2693,7 +2703,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
});
|
});
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
}}
|
}}
|
||||||
selectedTabComponentId={undefined}
|
selectedTabComponentId={nestedTabSelectedCompId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -3494,6 +3504,10 @@ 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);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -3573,6 +3587,8 @@ 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: {
|
||||||
|
|
@ -3586,7 +3602,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
});
|
});
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
}}
|
}}
|
||||||
selectedTabComponentId={undefined}
|
selectedTabComponentId={nestedTabSelectedCompId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,9 +186,10 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 customTableName이 설정된 경우 반드시 API에서 가져오기
|
// tableColumns prop은 화면의 기본 테이블 컬럼이므로,
|
||||||
// tableColumns prop은 화면의 기본 테이블 컬럼이므로, customTableName 사용 시 무시
|
// 다른 테이블을 선택한 경우 반드시 API에서 가져오기
|
||||||
const shouldUseTableColumnsProp = !config.useCustomTable && tableColumns && tableColumns.length > 0;
|
const isUsingDifferentTable = config.selectedTable && screenTableName && config.selectedTable !== screenTableName;
|
||||||
|
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) => ({
|
||||||
|
|
@ -772,11 +773,113 @@ 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>
|
||||||
|
|
@ -1167,11 +1270,11 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!screenTableName ? (
|
{!targetTableName ? (
|
||||||
<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,6 +78,9 @@ 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) => {
|
||||||
|
|
@ -104,9 +107,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 = 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));
|
||||||
|
|
||||||
// React 상태로 위치 업데이트 (리렌더링 트리거)
|
// React 상태로 위치 업데이트 (리렌더링 트리거)
|
||||||
setDragPosition({ x: newX, y: newY });
|
setDragPosition({ x: newX, y: newY });
|
||||||
|
|
@ -126,9 +129,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 = 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);
|
||||||
|
|
@ -144,8 +147,8 @@ const TabsDesignEditor: React.FC<{
|
||||||
? {
|
? {
|
||||||
...c,
|
...c,
|
||||||
position: {
|
position: {
|
||||||
x: Math.max(0, Math.round(newX)),
|
x: newX,
|
||||||
y: Math.max(0, Math.round(newY)),
|
y: newY,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: c
|
: c
|
||||||
|
|
@ -172,12 +175,9 @@ const TabsDesignEditor: React.FC<{
|
||||||
document.addEventListener("mousemove", handleMouseMove);
|
document.addEventListener("mousemove", handleMouseMove);
|
||||||
document.addEventListener("mouseup", handleMouseUp);
|
document.addEventListener("mouseup", handleMouseUp);
|
||||||
},
|
},
|
||||||
[activeTabId, component, onUpdateComponent, tabs]
|
[activeTabId, component, onUpdateComponent, tabs, snapTo10]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 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