Merge branch 'feature/v2-renewal' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node

This commit is contained in:
kjs 2026-02-10 16:23:27 +09:00
parent e97fd05e75
commit 86a73267cb
3 changed files with 145 additions and 26 deletions

View File

@ -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>

View File

@ -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 ? (

View File

@ -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") => {