refactor: POP 그리드 시스템 명칭 통일 + Dead Code 제거

V5→V6 전환 과정에서 누적된 버전 접미사, 미사용 함수, 레거시 잔재를
정리하여 코드 관리성을 확보한다. 14개 파일 수정, 365줄 순감.
[타입 리네이밍] (14개 파일)
- PopLayoutDataV5 → PopLayoutData
- PopComponentDefinitionV5 → PopComponentDefinition
- PopGlobalSettingsV5 → PopGlobalSettings
- PopModeOverrideV5 → PopModeOverride
- createEmptyPopLayoutV5 → createEmptyLayout
- isV5Layout → isPopLayout
- addComponentToV5Layout → addComponentToLayout
- createComponentDefinitionV5 → createComponentDefinition
- 구 이름은 deprecated 별칭으로 유지 (하위 호환)
[Dead Code 삭제] (gridUtils.ts -350줄)
- getAdjustedBreakpoint, convertPositionToMode, isOutOfBounds,
  mouseToGridPosition, gridToPixelPosition, isValidPosition,
  clampPosition, autoLayoutComponents (전부 외부 사용 0건)
- needsReview + ReviewPanel/ReviewItem (항상 false, 미사용)
- getEffectiveComponentPosition export → 내부 함수로 전환
[레거시 로더 분리] (신규 legacyLoader.ts)
- convertV5LayoutToV6 → loadLegacyLayout (legacyLoader.ts)
- V5 변환 상수/함수를 gridUtils에서 분리
[주석 정리]
- "v5 그리드" → "POP 블록 그리드"
- "하위 호환용" → "뷰포트 프리셋" / "레이아웃 설정용"
- 파일 헤더, 섹션 구분, 함수 JSDoc 정리
기능 변경 0건. DB 변경 0건. 백엔드 변경 0건.
This commit is contained in:
SeongHyun Kim 2026-03-13 16:32:20 +09:00
parent 842ac27d60
commit 320100c4e2
15 changed files with 301 additions and 640 deletions

View File

@ -17,17 +17,17 @@ import { ScreenContextProvider } from "@/contexts/ScreenContext";
import { SplitPanelProvider } from "@/lib/registry/components/split-panel-layout/SplitPanelContext"; import { SplitPanelProvider } from "@/lib/registry/components/split-panel-layout/SplitPanelContext";
import { ActiveTabProvider } from "@/contexts/ActiveTabContext"; import { ActiveTabProvider } from "@/contexts/ActiveTabContext";
import { import {
PopLayoutDataV5, PopLayoutData,
GridMode, GridMode,
isV5Layout, isPopLayout,
createEmptyPopLayoutV5, createEmptyLayout,
GAP_PRESETS, GAP_PRESETS,
GRID_BREAKPOINTS, GRID_BREAKPOINTS,
BLOCK_GAP, BLOCK_GAP,
BLOCK_PADDING, BLOCK_PADDING,
detectGridMode, detectGridMode,
} from "@/components/pop/designer/types/pop-layout"; } from "@/components/pop/designer/types/pop-layout";
import { convertV5LayoutToV6 } from "@/components/pop/designer/utils/gridUtils"; import { loadLegacyLayout } from "@/components/pop/designer/utils/legacyLoader";
// POP 컴포넌트 자동 등록 (레지스트리 초기화 - PopRenderer보다 먼저 import) // POP 컴포넌트 자동 등록 (레지스트리 초기화 - PopRenderer보다 먼저 import)
import "@/lib/registry/pop-components"; import "@/lib/registry/pop-components";
import PopViewerWithModals from "@/components/pop/viewer/PopViewerWithModals"; import PopViewerWithModals from "@/components/pop/viewer/PopViewerWithModals";
@ -82,7 +82,7 @@ function PopScreenViewPage() {
const { user } = useAuth(); const { user } = useAuth();
const [screen, setScreen] = useState<ScreenDefinition | null>(null); const [screen, setScreen] = useState<ScreenDefinition | null>(null);
const [layout, setLayout] = useState<PopLayoutDataV5>(createEmptyPopLayoutV5()); const [layout, setLayout] = useState<PopLayoutData>(createEmptyLayout());
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -119,22 +119,22 @@ function PopScreenViewPage() {
try { try {
const popLayout = await screenApi.getLayoutPop(screenId); const popLayout = await screenApi.getLayoutPop(screenId);
if (popLayout && isV5Layout(popLayout)) { if (popLayout && isPopLayout(popLayout)) {
const v6Layout = convertV5LayoutToV6(popLayout); const v6Layout = loadLegacyLayout(popLayout);
setLayout(v6Layout); setLayout(v6Layout);
const componentCount = Object.keys(popLayout.components).length; const componentCount = Object.keys(popLayout.components).length;
console.log(`[POP] v5 레이아웃 로드됨: ${componentCount}개 컴포넌트`); console.log(`[POP] v5 레이아웃 로드됨: ${componentCount}개 컴포넌트`);
} else if (popLayout) { } else if (popLayout) {
// 다른 버전 레이아웃은 빈 v5로 처리 // 다른 버전 레이아웃은 빈 v5로 처리
console.log("[POP] 레거시 레이아웃 감지, 빈 레이아웃으로 시작합니다:", popLayout.version); console.log("[POP] 레거시 레이아웃 감지, 빈 레이아웃으로 시작합니다:", popLayout.version);
setLayout(createEmptyPopLayoutV5()); setLayout(createEmptyLayout());
} else { } else {
console.log("[POP] 레이아웃 없음"); console.log("[POP] 레이아웃 없음");
setLayout(createEmptyPopLayoutV5()); setLayout(createEmptyLayout());
} }
} catch (layoutError) { } catch (layoutError) {
console.warn("[POP] 레이아웃 로드 실패:", layoutError); console.warn("[POP] 레이아웃 로드 실패:", layoutError);
setLayout(createEmptyPopLayoutV5()); setLayout(createEmptyLayout());
} }
} catch (error) { } catch (error) {
console.error("[POP] 화면 로드 실패:", error); console.error("[POP] 화면 로드 실패:", error);

View File

@ -4,8 +4,8 @@ import { useCallback, useRef, useState, useEffect, useMemo } from "react";
import { useDrop } from "react-dnd"; import { useDrop } from "react-dnd";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
PopLayoutDataV5, PopLayoutData,
PopComponentDefinitionV5, PopComponentDefinition,
PopComponentType, PopComponentType,
PopGridPosition, PopGridPosition,
GridMode, GridMode,
@ -22,7 +22,7 @@ import {
BLOCK_PADDING, BLOCK_PADDING,
getBlockColumns, getBlockColumns,
} from "./types/pop-layout"; } from "./types/pop-layout";
import { ZoomIn, ZoomOut, Maximize2, Smartphone, Tablet, Lock, RotateCcw, AlertTriangle, EyeOff, Monitor, ChevronDown, ChevronUp } from "lucide-react"; import { ZoomIn, ZoomOut, Maximize2, Smartphone, Tablet, Lock, RotateCcw, EyeOff, Monitor, ChevronDown, ChevronUp } from "lucide-react";
import { useDrag } from "react-dnd"; import { useDrag } from "react-dnd";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -34,7 +34,7 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { toast } from "sonner"; import { toast } from "sonner";
import PopRenderer from "./renderers/PopRenderer"; import PopRenderer from "./renderers/PopRenderer";
import { findNextEmptyPosition, isOverlapping, getAllEffectivePositions, needsReview } from "./utils/gridUtils"; import { findNextEmptyPosition, isOverlapping, getAllEffectivePositions } from "./utils/gridUtils";
import { DND_ITEM_TYPES } from "./constants"; import { DND_ITEM_TYPES } from "./constants";
/** /**
@ -95,13 +95,13 @@ const CANVAS_EXTRA_ROWS = 3; // 여유 행 수
// Props // Props
// ======================================== // ========================================
interface PopCanvasProps { interface PopCanvasProps {
layout: PopLayoutDataV5; layout: PopLayoutData;
selectedComponentId: string | null; selectedComponentId: string | null;
currentMode: GridMode; currentMode: GridMode;
onModeChange: (mode: GridMode) => void; onModeChange: (mode: GridMode) => void;
onSelectComponent: (id: string | null) => void; onSelectComponent: (id: string | null) => void;
onDropComponent: (type: PopComponentType, position: PopGridPosition) => void; onDropComponent: (type: PopComponentType, position: PopGridPosition) => void;
onUpdateComponent: (componentId: string, updates: Partial<PopComponentDefinitionV5>) => void; onUpdateComponent: (componentId: string, updates: Partial<PopComponentDefinition>) => void;
onDeleteComponent: (componentId: string) => void; onDeleteComponent: (componentId: string) => void;
onMoveComponent?: (componentId: string, newPosition: PopGridPosition) => void; onMoveComponent?: (componentId: string, newPosition: PopGridPosition) => void;
onResizeComponent?: (componentId: string, newPosition: PopGridPosition) => void; onResizeComponent?: (componentId: string, newPosition: PopGridPosition) => void;
@ -163,7 +163,7 @@ export default function PopCanvas({
}, [layout.modals]); }, [layout.modals]);
// activeCanvasId에 따라 렌더링할 layout 분기 // activeCanvasId에 따라 렌더링할 layout 분기
const activeLayout = useMemo((): PopLayoutDataV5 => { const activeLayout = useMemo((): PopLayoutData => {
if (activeCanvasId === "main") return layout; if (activeCanvasId === "main") return layout;
const modal = layout.modals?.find(m => m.id === activeCanvasId); const modal = layout.modals?.find(m => m.id === activeCanvasId);
if (!modal) return layout; // fallback if (!modal) return layout; // fallback
@ -401,7 +401,7 @@ export default function PopCanvas({
const effectivePositions = getAllEffectivePositions(activeLayout, currentMode); const effectivePositions = getAllEffectivePositions(activeLayout, currentMode);
// 이동 중인 컴포넌트의 현재 유효 위치에서 colSpan/rowSpan 가져오기 // 이동 중인 컴포넌트의 현재 유효 위치에서 colSpan/rowSpan 가져오기
// 검토 필요(ReviewPanel에서 클릭)나 숨김 컴포넌트는 effectivePositions에 없으므로 원본 사용 // 숨김 컴포넌트는 effectivePositions에 없으므로 원본 사용
const currentEffectivePos = effectivePositions.get(dragItem.componentId); const currentEffectivePos = effectivePositions.get(dragItem.componentId);
const componentData = layout.components[dragItem.componentId]; const componentData = layout.components[dragItem.componentId];
@ -472,22 +472,8 @@ export default function PopCanvas({
); );
}, [activeLayout.components, hiddenComponentIds]); }, [activeLayout.components, hiddenComponentIds]);
// 검토 필요 컴포넌트 목록
const reviewComponents = useMemo(() => {
return visibleComponents.filter(comp => {
const hasOverride = !!activeLayout.overrides?.[currentMode]?.positions?.[comp.id];
return needsReview(currentMode, hasOverride);
});
}, [visibleComponents, activeLayout.overrides, currentMode]);
// 검토 패널 표시 여부 (12칸 모드가 아니고, 검토 필요 컴포넌트가 있을 때)
const showReviewPanel = currentMode !== "tablet_landscape" && reviewComponents.length > 0;
// 12칸 모드가 아닐 때만 패널 표시
// 숨김 패널: 숨김 컴포넌트가 있거나, 그리드에 컴포넌트가 있을 때 드롭 영역으로 표시
const hasGridComponents = Object.keys(activeLayout.components).length > 0; const hasGridComponents = Object.keys(activeLayout.components).length > 0;
const showHiddenPanel = currentMode !== "tablet_landscape" && (hiddenComponents.length > 0 || hasGridComponents); const showHiddenPanel = currentMode !== "tablet_landscape" && (hiddenComponents.length > 0 || hasGridComponents);
const showRightPanel = showReviewPanel || showHiddenPanel;
return ( return (
<div className="flex h-full flex-col bg-muted"> <div className="flex h-full flex-col bg-muted">
@ -668,7 +654,7 @@ export default function PopCanvas({
<div <div
className="relative mx-auto my-8 origin-top overflow-visible flex gap-4" className="relative mx-auto my-8 origin-top overflow-visible flex gap-4"
style={{ style={{
width: showRightPanel width: showHiddenPanel
? `${customWidth + 32 + 220}px` // 오른쪽 패널 공간 추가 ? `${customWidth + 32 + 220}px` // 오른쪽 패널 공간 추가
: `${customWidth + 32}px`, : `${customWidth + 32}px`,
minHeight: `${dynamicCanvasHeight + 32}px`, minHeight: `${dynamicCanvasHeight + 32}px`,
@ -776,20 +762,11 @@ export default function PopCanvas({
</div> </div>
{/* 오른쪽 패널 영역 (초과 컴포넌트 + 숨김 컴포넌트) */} {/* 오른쪽 패널 영역 (초과 컴포넌트 + 숨김 컴포넌트) */}
{showRightPanel && ( {showHiddenPanel && (
<div <div
className="flex flex-col gap-3" className="flex flex-col gap-3"
style={{ marginTop: "32px" }} style={{ marginTop: "32px" }}
> >
{/* 검토 필요 패널 */}
{showReviewPanel && (
<ReviewPanel
components={reviewComponents}
selectedComponentId={selectedComponentId}
onSelectComponent={onSelectComponent}
/>
)}
{/* 숨김 컴포넌트 패널 */} {/* 숨김 컴포넌트 패널 */}
{showHiddenPanel && ( {showHiddenPanel && (
<HiddenPanel <HiddenPanel
@ -821,99 +798,12 @@ export default function PopCanvas({
// 검토 필요 영역 (오른쪽 패널) // 검토 필요 영역 (오른쪽 패널)
// ======================================== // ========================================
interface ReviewPanelProps {
components: PopComponentDefinitionV5[];
selectedComponentId: string | null;
onSelectComponent: (id: string | null) => void;
}
function ReviewPanel({
components,
selectedComponentId,
onSelectComponent,
}: ReviewPanelProps) {
return (
<div
className="flex flex-col rounded-lg border-2 border-dashed border-primary/40 bg-primary/5"
style={{
width: "200px",
maxHeight: "300px",
}}
>
{/* 헤더 */}
<div className="flex items-center gap-2 border-b border-primary/20 bg-primary/5 px-3 py-2 rounded-t-lg">
<AlertTriangle className="h-4 w-4 text-primary" />
<span className="text-xs font-semibold text-primary">
({components.length})
</span>
</div>
{/* 컴포넌트 목록 */}
<div className="flex-1 overflow-auto p-2 space-y-2">
{components.map((comp) => (
<ReviewItem
key={comp.id}
component={comp}
isSelected={selectedComponentId === comp.id}
onSelect={() => onSelectComponent(comp.id)}
/>
))}
</div>
{/* 안내 문구 */}
<div className="border-t border-primary/20 px-3 py-2 bg-primary/10 rounded-b-lg">
<p className="text-[10px] text-primary leading-tight">
.
</p>
</div>
</div>
);
}
// ========================================
// 검토 필요 아이템 (ReviewPanel 내부)
// ========================================
interface ReviewItemProps {
component: PopComponentDefinitionV5;
isSelected: boolean;
onSelect: () => void;
}
function ReviewItem({
component,
isSelected,
onSelect,
}: ReviewItemProps) {
return (
<div
className={cn(
"flex flex-col gap-1 rounded-md border-2 p-2 cursor-pointer transition-all",
isSelected
? "border-primary bg-primary/10 shadow-sm"
: "border-primary/20 bg-background hover:border-primary/60 hover:bg-primary/10"
)}
onClick={(e) => {
e.stopPropagation();
onSelect();
}}
>
<span className="text-xs font-medium text-primary line-clamp-1">
{component.label || component.id}
</span>
<span className="text-[10px] text-primary bg-primary/10 rounded px-1.5 py-0.5 self-start">
</span>
</div>
);
}
// ======================================== // ========================================
// 숨김 컴포넌트 영역 (오른쪽 패널) // 숨김 컴포넌트 영역 (오른쪽 패널)
// ======================================== // ========================================
interface HiddenPanelProps { interface HiddenPanelProps {
components: PopComponentDefinitionV5[]; components: PopComponentDefinition[];
selectedComponentId: string | null; selectedComponentId: string | null;
onSelectComponent: (id: string | null) => void; onSelectComponent: (id: string | null) => void;
onHideComponent?: (componentId: string) => void; onHideComponent?: (componentId: string) => void;
@ -999,7 +889,7 @@ function HiddenPanel({
// ======================================== // ========================================
interface HiddenItemProps { interface HiddenItemProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
isSelected: boolean; isSelected: boolean;
onSelect: () => void; onSelect: () => void;
} }

View File

@ -19,21 +19,22 @@ import PopCanvas from "./PopCanvas";
import ComponentEditorPanel from "./panels/ComponentEditorPanel"; import ComponentEditorPanel from "./panels/ComponentEditorPanel";
import ComponentPalette from "./panels/ComponentPalette"; import ComponentPalette from "./panels/ComponentPalette";
import { import {
PopLayoutDataV5, PopLayoutData,
PopComponentType, PopComponentType,
PopComponentDefinitionV5, PopComponentDefinition,
PopGridPosition, PopGridPosition,
GridMode, GridMode,
GapPreset, GapPreset,
createEmptyPopLayoutV5, createEmptyLayout,
isV5Layout, isPopLayout,
addComponentToV5Layout, addComponentToLayout,
createComponentDefinitionV5, createComponentDefinition,
GRID_BREAKPOINTS, GRID_BREAKPOINTS,
PopModalDefinition, PopModalDefinition,
PopDataConnection, PopDataConnection,
} from "./types/pop-layout"; } from "./types/pop-layout";
import { getAllEffectivePositions, convertV5LayoutToV6 } from "./utils/gridUtils"; import { getAllEffectivePositions } from "./utils/gridUtils";
import { loadLegacyLayout } from "./utils/legacyLoader";
import { screenApi } from "@/lib/api/screen"; import { screenApi } from "@/lib/api/screen";
import { ScreenDefinition } from "@/types/screen"; import { ScreenDefinition } from "@/types/screen";
import { PopDesignerContext } from "./PopDesignerContext"; import { PopDesignerContext } from "./PopDesignerContext";
@ -59,10 +60,10 @@ export default function PopDesigner({
// ======================================== // ========================================
// 레이아웃 상태 // 레이아웃 상태
// ======================================== // ========================================
const [layout, setLayout] = useState<PopLayoutDataV5>(createEmptyPopLayoutV5()); const [layout, setLayout] = useState<PopLayoutData>(createEmptyLayout());
// 히스토리 // 히스토리
const [history, setHistory] = useState<PopLayoutDataV5[]>([]); const [history, setHistory] = useState<PopLayoutData[]>([]);
const [historyIndex, setHistoryIndex] = useState(-1); const [historyIndex, setHistoryIndex] = useState(-1);
// UI 상태 // UI 상태
@ -84,7 +85,7 @@ export default function PopDesigner({
const [activeCanvasId, setActiveCanvasId] = useState<string>("main"); const [activeCanvasId, setActiveCanvasId] = useState<string>("main");
// 선택된 컴포넌트 (activeCanvasId에 따라 메인 또는 모달에서 조회) // 선택된 컴포넌트 (activeCanvasId에 따라 메인 또는 모달에서 조회)
const selectedComponent: PopComponentDefinitionV5 | null = (() => { const selectedComponent: PopComponentDefinition | null = (() => {
if (!selectedComponentId) return null; if (!selectedComponentId) return null;
if (activeCanvasId === "main") { if (activeCanvasId === "main") {
return layout.components[selectedComponentId] || null; return layout.components[selectedComponentId] || null;
@ -96,7 +97,7 @@ export default function PopDesigner({
// ======================================== // ========================================
// 히스토리 관리 // 히스토리 관리
// ======================================== // ========================================
const saveToHistory = useCallback((newLayout: PopLayoutDataV5) => { const saveToHistory = useCallback((newLayout: PopLayoutData) => {
setHistory((prev) => { setHistory((prev) => {
const newHistory = prev.slice(0, historyIndex + 1); const newHistory = prev.slice(0, historyIndex + 1);
newHistory.push(JSON.parse(JSON.stringify(newLayout))); newHistory.push(JSON.parse(JSON.stringify(newLayout)));
@ -150,11 +151,11 @@ export default function PopDesigner({
try { try {
const loadedLayout = await screenApi.getLayoutPop(selectedScreen.screenId); const loadedLayout = await screenApi.getLayoutPop(selectedScreen.screenId);
if (loadedLayout && isV5Layout(loadedLayout) && loadedLayout.components && Object.keys(loadedLayout.components).length > 0) { if (loadedLayout && isPopLayout(loadedLayout) && loadedLayout.components && Object.keys(loadedLayout.components).length > 0) {
if (!loadedLayout.settings.gapPreset) { if (!loadedLayout.settings.gapPreset) {
loadedLayout.settings.gapPreset = "medium"; loadedLayout.settings.gapPreset = "medium";
} }
const v6Layout = convertV5LayoutToV6(loadedLayout); const v6Layout = loadLegacyLayout(loadedLayout);
setLayout(v6Layout); setLayout(v6Layout);
setHistory([v6Layout]); setHistory([v6Layout]);
setHistoryIndex(0); setHistoryIndex(0);
@ -174,7 +175,7 @@ export default function PopDesigner({
console.log(`POP 레이아웃 로드: ${existingIds.length}개 컴포넌트, idCounter: ${maxId + 1}`); console.log(`POP 레이아웃 로드: ${existingIds.length}개 컴포넌트, idCounter: ${maxId + 1}`);
} else { } else {
// 새 화면 또는 빈 레이아웃 // 새 화면 또는 빈 레이아웃
const emptyLayout = createEmptyPopLayoutV5(); const emptyLayout = createEmptyLayout();
setLayout(emptyLayout); setLayout(emptyLayout);
setHistory([emptyLayout]); setHistory([emptyLayout]);
setHistoryIndex(0); setHistoryIndex(0);
@ -183,7 +184,7 @@ export default function PopDesigner({
} catch (error) { } catch (error) {
console.error("레이아웃 로드 실패:", error); console.error("레이아웃 로드 실패:", error);
toast.error("레이아웃을 불러오는데 실패했습니다"); toast.error("레이아웃을 불러오는데 실패했습니다");
const emptyLayout = createEmptyPopLayoutV5(); const emptyLayout = createEmptyLayout();
setLayout(emptyLayout); setLayout(emptyLayout);
setHistory([emptyLayout]); setHistory([emptyLayout]);
setHistoryIndex(0); setHistoryIndex(0);
@ -224,13 +225,13 @@ export default function PopDesigner({
if (activeCanvasId === "main") { if (activeCanvasId === "main") {
// 메인 캔버스 // 메인 캔버스
const newLayout = addComponentToV5Layout(layout, componentId, type, position, `${type} ${idCounter}`); const newLayout = addComponentToLayout(layout, componentId, type, position, `${type} ${idCounter}`);
setLayout(newLayout); setLayout(newLayout);
saveToHistory(newLayout); saveToHistory(newLayout);
} else { } else {
// 모달 캔버스 // 모달 캔버스
setLayout(prev => { setLayout(prev => {
const comp = createComponentDefinitionV5(componentId, type, position, `${type} ${idCounter}`); const comp = createComponentDefinition(componentId, type, position, `${type} ${idCounter}`);
const newLayout = { const newLayout = {
...prev, ...prev,
modals: (prev.modals || []).map(m => { modals: (prev.modals || []).map(m => {
@ -249,7 +250,7 @@ export default function PopDesigner({
); );
const handleUpdateComponent = useCallback( const handleUpdateComponent = useCallback(
(componentId: string, updates: Partial<PopComponentDefinitionV5>) => { (componentId: string, updates: Partial<PopComponentDefinition>) => {
// 함수적 업데이트로 stale closure 방지 // 함수적 업데이트로 stale closure 방지
setLayout((prev) => { setLayout((prev) => {
if (activeCanvasId === "main") { if (activeCanvasId === "main") {
@ -302,7 +303,7 @@ export default function PopDesigner({
const newId = `conn_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`; const newId = `conn_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
const newConnection: PopDataConnection = { ...conn, id: newId }; const newConnection: PopDataConnection = { ...conn, id: newId };
const prevConnections = prev.dataFlow?.connections || []; const prevConnections = prev.dataFlow?.connections || [];
const newLayout: PopLayoutDataV5 = { const newLayout: PopLayoutData = {
...prev, ...prev,
dataFlow: { dataFlow: {
...prev.dataFlow, ...prev.dataFlow,
@ -321,7 +322,7 @@ export default function PopDesigner({
(connectionId: string, conn: Omit<PopDataConnection, "id">) => { (connectionId: string, conn: Omit<PopDataConnection, "id">) => {
setLayout((prev) => { setLayout((prev) => {
const prevConnections = prev.dataFlow?.connections || []; const prevConnections = prev.dataFlow?.connections || [];
const newLayout: PopLayoutDataV5 = { const newLayout: PopLayoutData = {
...prev, ...prev,
dataFlow: { dataFlow: {
...prev.dataFlow, ...prev.dataFlow,
@ -342,7 +343,7 @@ export default function PopDesigner({
(connectionId: string) => { (connectionId: string) => {
setLayout((prev) => { setLayout((prev) => {
const prevConnections = prev.dataFlow?.connections || []; const prevConnections = prev.dataFlow?.connections || [];
const newLayout: PopLayoutDataV5 = { const newLayout: PopLayoutData = {
...prev, ...prev,
dataFlow: { dataFlow: {
...prev.dataFlow, ...prev.dataFlow,

View File

@ -1,4 +1,4 @@
// POP 디자이너 컴포넌트 export (v5 그리드 시스템) // POP 디자이너 컴포넌트 export (블록 그리드 시스템)
// 타입 // 타입
export * from "./types"; export * from "./types";
@ -17,11 +17,12 @@ export { default as PopRenderer } from "./renderers/PopRenderer";
// 유틸리티 // 유틸리티
export * from "./utils/gridUtils"; export * from "./utils/gridUtils";
export * from "./utils/legacyLoader";
// 핵심 타입 재export (편의) // 핵심 타입 재export (편의)
export type { export type {
PopLayoutDataV5, PopLayoutData,
PopComponentDefinitionV5, PopComponentDefinition,
PopComponentType, PopComponentType,
PopGridPosition, PopGridPosition,
GridMode, GridMode,

View File

@ -3,7 +3,7 @@
import React from "react"; import React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
PopComponentDefinitionV5, PopComponentDefinition,
PopGridPosition, PopGridPosition,
GridMode, GridMode,
GRID_BREAKPOINTS, GRID_BREAKPOINTS,
@ -33,15 +33,15 @@ import ConnectionEditor from "./ConnectionEditor";
interface ComponentEditorPanelProps { interface ComponentEditorPanelProps {
/** 선택된 컴포넌트 */ /** 선택된 컴포넌트 */
component: PopComponentDefinitionV5 | null; component: PopComponentDefinition | null;
/** 현재 모드 */ /** 현재 모드 */
currentMode: GridMode; currentMode: GridMode;
/** 컴포넌트 업데이트 */ /** 컴포넌트 업데이트 */
onUpdateComponent?: (updates: Partial<PopComponentDefinitionV5>) => void; onUpdateComponent?: (updates: Partial<PopComponentDefinition>) => void;
/** 추가 className */ /** 추가 className */
className?: string; className?: string;
/** 그리드에 배치된 모든 컴포넌트 */ /** 그리드에 배치된 모든 컴포넌트 */
allComponents?: PopComponentDefinitionV5[]; allComponents?: PopComponentDefinition[];
/** 컴포넌트 선택 콜백 */ /** 컴포넌트 선택 콜백 */
onSelectComponent?: (componentId: string) => void; onSelectComponent?: (componentId: string) => void;
/** 현재 선택된 컴포넌트 ID */ /** 현재 선택된 컴포넌트 ID */
@ -249,11 +249,11 @@ export default function ComponentEditorPanel({
// ======================================== // ========================================
interface PositionFormProps { interface PositionFormProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
currentMode: GridMode; currentMode: GridMode;
isDefaultMode: boolean; isDefaultMode: boolean;
columns: number; columns: number;
onUpdate?: (updates: Partial<PopComponentDefinitionV5>) => void; onUpdate?: (updates: Partial<PopComponentDefinition>) => void;
} }
function PositionForm({ component, currentMode, isDefaultMode, columns, onUpdate }: PositionFormProps) { function PositionForm({ component, currentMode, isDefaultMode, columns, onUpdate }: PositionFormProps) {
@ -402,13 +402,13 @@ function PositionForm({ component, currentMode, isDefaultMode, columns, onUpdate
// ======================================== // ========================================
interface ComponentSettingsFormProps { interface ComponentSettingsFormProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
onUpdate?: (updates: Partial<PopComponentDefinitionV5>) => void; onUpdate?: (updates: Partial<PopComponentDefinition>) => void;
currentMode?: GridMode; currentMode?: GridMode;
previewPageIndex?: number; previewPageIndex?: number;
onPreviewPage?: (pageIndex: number) => void; onPreviewPage?: (pageIndex: number) => void;
modals?: PopModalDefinition[]; modals?: PopModalDefinition[];
allComponents?: PopComponentDefinitionV5[]; allComponents?: PopComponentDefinition[];
connections?: PopDataConnection[]; connections?: PopDataConnection[];
} }
@ -466,8 +466,8 @@ function ComponentSettingsForm({ component, onUpdate, currentMode, previewPageIn
// ======================================== // ========================================
interface VisibilityFormProps { interface VisibilityFormProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
onUpdate?: (updates: Partial<PopComponentDefinitionV5>) => void; onUpdate?: (updates: Partial<PopComponentDefinition>) => void;
} }
function VisibilityForm({ component, onUpdate }: VisibilityFormProps) { function VisibilityForm({ component, onUpdate }: VisibilityFormProps) {

View File

@ -13,7 +13,7 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { import {
PopComponentDefinitionV5, PopComponentDefinition,
PopDataConnection, PopDataConnection,
} from "../types/pop-layout"; } from "../types/pop-layout";
import { import {
@ -26,8 +26,8 @@ import { getTableColumns } from "@/lib/api/tableManagement";
// ======================================== // ========================================
interface ConnectionEditorProps { interface ConnectionEditorProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
allComponents: PopComponentDefinitionV5[]; allComponents: PopComponentDefinition[];
connections: PopDataConnection[]; connections: PopDataConnection[];
onAddConnection?: (conn: Omit<PopDataConnection, "id">) => void; onAddConnection?: (conn: Omit<PopDataConnection, "id">) => void;
onUpdateConnection?: (connectionId: string, conn: Omit<PopDataConnection, "id">) => void; onUpdateConnection?: (connectionId: string, conn: Omit<PopDataConnection, "id">) => void;
@ -102,8 +102,8 @@ export default function ConnectionEditor({
// ======================================== // ========================================
interface SendSectionProps { interface SendSectionProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
allComponents: PopComponentDefinitionV5[]; allComponents: PopComponentDefinition[];
outgoing: PopDataConnection[]; outgoing: PopDataConnection[];
onAddConnection?: (conn: Omit<PopDataConnection, "id">) => void; onAddConnection?: (conn: Omit<PopDataConnection, "id">) => void;
onUpdateConnection?: (connectionId: string, conn: Omit<PopDataConnection, "id">) => void; onUpdateConnection?: (connectionId: string, conn: Omit<PopDataConnection, "id">) => void;
@ -197,15 +197,15 @@ function SendSection({
// ======================================== // ========================================
interface SimpleConnectionFormProps { interface SimpleConnectionFormProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
allComponents: PopComponentDefinitionV5[]; allComponents: PopComponentDefinition[];
initial?: PopDataConnection; initial?: PopDataConnection;
onSubmit: (data: Omit<PopDataConnection, "id">) => void; onSubmit: (data: Omit<PopDataConnection, "id">) => void;
onCancel?: () => void; onCancel?: () => void;
submitLabel: string; submitLabel: string;
} }
function extractSubTableName(comp: PopComponentDefinitionV5): string | null { function extractSubTableName(comp: PopComponentDefinition): string | null {
const cfg = comp.config as Record<string, unknown> | undefined; const cfg = comp.config as Record<string, unknown> | undefined;
if (!cfg) return null; if (!cfg) return null;
@ -423,8 +423,8 @@ function SimpleConnectionForm({
// ======================================== // ========================================
interface ReceiveSectionProps { interface ReceiveSectionProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
allComponents: PopComponentDefinitionV5[]; allComponents: PopComponentDefinition[];
incoming: PopDataConnection[]; incoming: PopDataConnection[];
} }

View File

@ -5,8 +5,8 @@ import { useDrag } from "react-dnd";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { DND_ITEM_TYPES } from "../constants"; import { DND_ITEM_TYPES } from "../constants";
import { import {
PopLayoutDataV5, PopLayoutData,
PopComponentDefinitionV5, PopComponentDefinition,
PopGridPosition, PopGridPosition,
GridMode, GridMode,
GRID_BREAKPOINTS, GRID_BREAKPOINTS,
@ -31,7 +31,7 @@ import { PopComponentRegistry } from "@/lib/registry/PopComponentRegistry";
interface PopRendererProps { interface PopRendererProps {
/** v5 레이아웃 데이터 */ /** v5 레이아웃 데이터 */
layout: PopLayoutDataV5; layout: PopLayoutData;
/** 현재 뷰포트 너비 */ /** 현재 뷰포트 너비 */
viewportWidth: number; viewportWidth: number;
/** 현재 모드 (자동 감지 또는 수동 지정) */ /** 현재 모드 (자동 감지 또는 수동 지정) */
@ -182,7 +182,7 @@ export default function PopRenderer({
}, [isDesignMode, showGridGuide, columns, dynamicRowCount]); }, [isDesignMode, showGridGuide, columns, dynamicRowCount]);
// visibility 체크 // visibility 체크
const isVisible = (comp: PopComponentDefinitionV5): boolean => { const isVisible = (comp: PopComponentDefinition): boolean => {
if (!comp.visibility) return true; if (!comp.visibility) return true;
const modeVisibility = comp.visibility[mode]; const modeVisibility = comp.visibility[mode];
return modeVisibility !== false; return modeVisibility !== false;
@ -207,7 +207,7 @@ export default function PopRenderer({
}; };
// 오버라이드 적용 또는 자동 재배치 // 오버라이드 적용 또는 자동 재배치
const getEffectivePosition = (comp: PopComponentDefinitionV5): PopGridPosition => { const getEffectivePosition = (comp: PopComponentDefinition): PopGridPosition => {
// 1순위: 오버라이드가 있으면 사용 // 1순위: 오버라이드가 있으면 사용
const override = overrides?.[mode]?.positions?.[comp.id]; const override = overrides?.[mode]?.positions?.[comp.id];
if (override) { if (override) {
@ -225,7 +225,7 @@ export default function PopRenderer({
}; };
// 오버라이드 숨김 체크 // 오버라이드 숨김 체크
const isHiddenByOverride = (comp: PopComponentDefinitionV5): boolean => { const isHiddenByOverride = (comp: PopComponentDefinition): boolean => {
return overrides?.[mode]?.hidden?.includes(comp.id) ?? false; return overrides?.[mode]?.hidden?.includes(comp.id) ?? false;
}; };
@ -322,7 +322,7 @@ export default function PopRenderer({
// ======================================== // ========================================
interface DraggableComponentProps { interface DraggableComponentProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
position: PopGridPosition; position: PopGridPosition;
positionStyle: React.CSSProperties; positionStyle: React.CSSProperties;
isSelected: boolean; isSelected: boolean;
@ -423,7 +423,7 @@ function DraggableComponent({
// ======================================== // ========================================
interface ResizeHandlesProps { interface ResizeHandlesProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
position: PopGridPosition; position: PopGridPosition;
breakpoint: GridBreakpoint; breakpoint: GridBreakpoint;
viewportWidth: number; viewportWidth: number;
@ -544,7 +544,7 @@ function ResizeHandles({
// ======================================== // ========================================
interface ComponentContentProps { interface ComponentContentProps {
component: PopComponentDefinitionV5; component: PopComponentDefinition;
effectivePosition: PopGridPosition; effectivePosition: PopGridPosition;
isDesignMode: boolean; isDesignMode: boolean;
isSelected: boolean; isSelected: boolean;
@ -614,7 +614,7 @@ function ComponentContent({ component, effectivePosition, isDesignMode, isSelect
// ======================================== // ========================================
function renderActualComponent( function renderActualComponent(
component: PopComponentDefinitionV5, component: PopComponentDefinition,
effectivePosition?: PopGridPosition, effectivePosition?: PopGridPosition,
onRequestResize?: (componentId: string, newRowSpan: number, newColSpan?: number) => void, onRequestResize?: (componentId: string, newRowSpan: number, newColSpan?: number) => void,
screenId?: string, screenId?: string,

View File

@ -1,6 +1,4 @@
// POP 디자이너 레이아웃 타입 정의 // POP 블록 그리드 레이아웃 타입 정의
// v5.0: CSS Grid 기반 그리드 시스템
// 2024-02 버전 통합: v1~v4 제거, v5 단일 버전
// ======================================== // ========================================
// 공통 타입 // 공통 타입
@ -122,7 +120,7 @@ export function getBlockColumns(viewportWidth: number): number {
} }
/** /**
* ( - V6에서는 ) * ( )
*/ */
export type GridMode = export type GridMode =
| "mobile_portrait" | "mobile_portrait"
@ -131,7 +129,7 @@ export type GridMode =
| "tablet_landscape"; | "tablet_landscape";
/** /**
* ( ) *
*/ */
export interface GridBreakpoint { export interface GridBreakpoint {
minWidth?: number; minWidth?: number;
@ -200,31 +198,31 @@ export function detectGridMode(viewportWidth: number): GridMode {
} }
/** /**
* v5 ( ) * POP
*/ */
export interface PopLayoutDataV5 { export interface PopLayoutData {
version: "pop-5.0"; version: "pop-5.0";
// 그리드 설정 // 그리드 설정
gridConfig: PopGridConfig; gridConfig: PopGridConfig;
// 컴포넌트 정의 (ID → 정의) // 컴포넌트 정의 (ID → 정의)
components: Record<string, PopComponentDefinitionV5>; components: Record<string, PopComponentDefinition>;
// 데이터 흐름 // 데이터 흐름
dataFlow: PopDataFlow; dataFlow: PopDataFlow;
// 전역 설정 // 전역 설정
settings: PopGlobalSettingsV5; settings: PopGlobalSettings;
// 메타데이터 // 메타데이터
metadata?: PopLayoutMetadata; metadata?: PopLayoutMetadata;
// 모드별 오버라이드 (위치 변경용) // 모드별 오버라이드 (위치 변경용)
overrides?: { overrides?: {
mobile_portrait?: PopModeOverrideV5; mobile_portrait?: PopModeOverride;
mobile_landscape?: PopModeOverrideV5; mobile_landscape?: PopModeOverride;
tablet_portrait?: PopModeOverrideV5; tablet_portrait?: PopModeOverride;
}; };
// 모달 캔버스 목록 (버튼의 "모달 열기" 액션으로 생성) // 모달 캔버스 목록 (버튼의 "모달 열기" 액션으로 생성)
@ -256,9 +254,9 @@ export interface PopGridPosition {
} }
/** /**
* v5 * POP
*/ */
export interface PopComponentDefinitionV5 { export interface PopComponentDefinition {
id: string; id: string;
type: PopComponentType; type: PopComponentType;
label?: string; label?: string;
@ -303,9 +301,9 @@ export const GAP_PRESETS: Record<GapPreset, GapPresetConfig> = {
}; };
/** /**
* v5 * POP
*/ */
export interface PopGlobalSettingsV5 { export interface PopGlobalSettings {
// 터치 최소 크기 (px) // 터치 최소 크기 (px)
touchTargetMin: number; // 기본 48 touchTargetMin: number; // 기본 48
@ -317,9 +315,9 @@ export interface PopGlobalSettingsV5 {
} }
/** /**
* v5 * (/)
*/ */
export interface PopModeOverrideV5 { export interface PopModeOverride {
// 컴포넌트별 위치 오버라이드 // 컴포넌트별 위치 오버라이드
positions?: Record<string, Partial<PopGridPosition>>; positions?: Record<string, Partial<PopGridPosition>>;
@ -328,13 +326,13 @@ export interface PopModeOverrideV5 {
} }
// ======================================== // ========================================
// v5 유틸리티 함수 // 레이아웃 유틸리티 함수
// ======================================== // ========================================
/** /**
* v5 * POP
*/ */
export const createEmptyPopLayoutV5 = (): PopLayoutDataV5 => ({ export const createEmptyLayout = (): PopLayoutData => ({
version: "pop-5.0", version: "pop-5.0",
gridConfig: { gridConfig: {
rowHeight: BLOCK_SIZE, rowHeight: BLOCK_SIZE,
@ -351,9 +349,9 @@ export const createEmptyPopLayoutV5 = (): PopLayoutDataV5 => ({
}); });
/** /**
* v5 * POP
*/ */
export const isV5Layout = (layout: any): layout is PopLayoutDataV5 => { export const isPopLayout = (layout: any): layout is PopLayoutData => {
return layout?.version === "pop-5.0"; return layout?.version === "pop-5.0";
}; };
@ -382,14 +380,14 @@ export const DEFAULT_COMPONENT_GRID_SIZE: Record<PopComponentType, { colSpan: nu
}; };
/** /**
* v5 * POP
*/ */
export const createComponentDefinitionV5 = ( export const createComponentDefinition = (
id: string, id: string,
type: PopComponentType, type: PopComponentType,
position: PopGridPosition, position: PopGridPosition,
label?: string label?: string
): PopComponentDefinitionV5 => ({ ): PopComponentDefinition => ({
id, id,
type, type,
label, label,
@ -397,21 +395,21 @@ export const createComponentDefinitionV5 = (
}); });
/** /**
* v5 * POP
*/ */
export const addComponentToV5Layout = ( export const addComponentToLayout = (
layout: PopLayoutDataV5, layout: PopLayoutData,
componentId: string, componentId: string,
type: PopComponentType, type: PopComponentType,
position: PopGridPosition, position: PopGridPosition,
label?: string label?: string
): PopLayoutDataV5 => { ): PopLayoutData => {
const newLayout = { ...layout }; const newLayout = { ...layout };
// 컴포넌트 정의 추가 // 컴포넌트 정의 추가
newLayout.components = { newLayout.components = {
...newLayout.components, ...newLayout.components,
[componentId]: createComponentDefinitionV5(componentId, type, position, label), [componentId]: createComponentDefinition(componentId, type, position, label),
}; };
return newLayout; return newLayout;
@ -486,12 +484,12 @@ export interface PopModalDefinition {
/** 모달 내부 그리드 설정 */ /** 모달 내부 그리드 설정 */
gridConfig: PopGridConfig; gridConfig: PopGridConfig;
/** 모달 내부 컴포넌트 */ /** 모달 내부 컴포넌트 */
components: Record<string, PopComponentDefinitionV5>; components: Record<string, PopComponentDefinition>;
/** 모드별 오버라이드 */ /** 모드별 오버라이드 */
overrides?: { overrides?: {
mobile_portrait?: PopModeOverrideV5; mobile_portrait?: PopModeOverride;
mobile_landscape?: PopModeOverrideV5; mobile_landscape?: PopModeOverride;
tablet_portrait?: PopModeOverrideV5; tablet_portrait?: PopModeOverride;
}; };
/** 모달 프레임 설정 (닫기 방식) */ /** 모달 프레임 설정 (닫기 방식) */
frameConfig?: { frameConfig?: {
@ -507,15 +505,29 @@ export interface PopModalDefinition {
} }
// ======================================== // ========================================
// 레거시 타입 별칭 (하위 호환 - 추후 제거) // 레거시 타입 별칭 (이전 코드 호환용)
// ======================================== // ========================================
// 기존 코드에서 import 오류 방지용
/** @deprecated v5에서는 PopLayoutDataV5 사용 */ /** @deprecated PopLayoutData 사용 */
export type PopLayoutData = PopLayoutDataV5; export type PopLayoutDataV5 = PopLayoutData;
/** @deprecated v5에서는 PopComponentDefinitionV5 사용 */ /** @deprecated PopComponentDefinition 사용 */
export type PopComponentDefinition = PopComponentDefinitionV5; export type PopComponentDefinitionV5 = PopComponentDefinition;
/** @deprecated v5에서는 PopGridPosition 사용 */ /** @deprecated PopGlobalSettings 사용 */
export type GridPosition = PopGridPosition; export type PopGlobalSettingsV5 = PopGlobalSettings;
/** @deprecated PopModeOverride 사용 */
export type PopModeOverrideV5 = PopModeOverride;
/** @deprecated createEmptyLayout 사용 */
export const createEmptyPopLayoutV5 = createEmptyLayout;
/** @deprecated isPopLayout 사용 */
export const isV5Layout = isPopLayout;
/** @deprecated addComponentToLayout 사용 */
export const addComponentToV5Layout = addComponentToLayout;
/** @deprecated createComponentDefinition 사용 */
export const createComponentDefinitionV5 = createComponentDefinition;

View File

@ -1,53 +1,25 @@
// POP 그리드 유틸리티 (리플로우, 겹침 해결, 위치 계산)
import { import {
PopGridPosition, PopGridPosition,
GridMode, GridMode,
GRID_BREAKPOINTS, GRID_BREAKPOINTS,
GridBreakpoint, PopLayoutData,
GapPreset,
GAP_PRESETS,
PopLayoutDataV5,
PopComponentDefinitionV5,
BLOCK_SIZE,
BLOCK_GAP,
BLOCK_PADDING,
getBlockColumns,
} from "../types/pop-layout"; } from "../types/pop-layout";
// ======================================== // ========================================
// Gap/Padding 조정 (V6: 블록 간격 고정이므로 항상 원본 반환) // 리플로우 (행 그룹 기반 자동 재배치)
// ========================================
export function getAdjustedBreakpoint(
base: GridBreakpoint,
preset: GapPreset
): GridBreakpoint {
return { ...base };
}
// ========================================
// 그리드 위치 변환 (V6: 단일 좌표계이므로 변환 불필요)
// ======================================== // ========================================
/** /**
* V6: 단일 *
* @deprecated V6에서는
*/
export function convertPositionToMode(
position: PopGridPosition,
targetMode: GridMode
): PopGridPosition {
return position;
}
/**
* V6 ( F)
* *
* 원리: CSS Flexbox wrap과 . * CSS Flexbox wrap .
* 1. * 1.
* 2. 2x2칸 ( ) * 2. 2x2칸 ( )
* 3. ( ) * 3. ( )
* 4. 50% * 4. 50%
* 5. (resolveOverlaps) * 5.
*/ */
export function convertAndResolvePositions( export function convertAndResolvePositions(
components: Array<{ id: string; position: PopGridPosition }>, components: Array<{ id: string; position: PopGridPosition }>,
@ -66,7 +38,6 @@ export function convertAndResolvePositions(
const MIN_COL_SPAN = 2; const MIN_COL_SPAN = 2;
const MIN_ROW_SPAN = 2; const MIN_ROW_SPAN = 2;
// 1. 원본 row 기준 그룹핑
const rowGroups: Record<number, Array<{ id: string; position: PopGridPosition }>> = {}; const rowGroups: Record<number, Array<{ id: string; position: PopGridPosition }>> = {};
components.forEach(comp => { components.forEach(comp => {
const r = comp.position.row; const r = comp.position.row;
@ -77,7 +48,6 @@ export function convertAndResolvePositions(
const placed: Array<{ id: string; position: PopGridPosition }> = []; const placed: Array<{ id: string; position: PopGridPosition }> = [];
let outputRow = 1; let outputRow = 1;
// 2. 각 행 그룹을 순서대로 처리
const sortedRows = Object.keys(rowGroups).map(Number).sort((a, b) => a - b); const sortedRows = Object.keys(rowGroups).map(Number).sort((a, b) => a - b);
for (const rowKey of sortedRows) { for (const rowKey of sortedRows) {
@ -96,7 +66,6 @@ export function convertAndResolvePositions(
const scaledRowSpan = Math.max(MIN_ROW_SPAN, pos.rowSpan); const scaledRowSpan = Math.max(MIN_ROW_SPAN, pos.rowSpan);
// 현재 줄에 안 들어가면 줄바꿈
if (currentCol + scaledSpan - 1 > targetColumns) { if (currentCol + scaledSpan - 1 > targetColumns) {
outputRow += Math.max(1, maxRowSpanInLine); outputRow += Math.max(1, maxRowSpanInLine);
currentCol = 1; currentCol = 1;
@ -120,50 +89,18 @@ export function convertAndResolvePositions(
outputRow += Math.max(1, maxRowSpanInLine); outputRow += Math.max(1, maxRowSpanInLine);
} }
// 3. 겹침 해결 (행 그룹 간 rowSpan 충돌 처리)
return resolveOverlaps(placed, targetColumns); return resolveOverlaps(placed, targetColumns);
} }
// ========================================
// 검토 필요 판별 (V6: 자동 줄바꿈이므로 검토 필요 없음)
// ========================================
/**
* V6: 단일 +
* false
*/
export function needsReview(
currentMode: GridMode,
hasOverride: boolean
): boolean {
return false;
}
/**
* @deprecated V6에서는
*/
export function isOutOfBounds(
originalPosition: PopGridPosition,
currentMode: GridMode,
overridePosition?: PopGridPosition | null
): boolean {
return false;
}
// ======================================== // ========================================
// 겹침 감지 및 해결 // 겹침 감지 및 해결
// ======================================== // ========================================
/**
*
*/
export function isOverlapping(a: PopGridPosition, b: PopGridPosition): boolean { export function isOverlapping(a: PopGridPosition, b: PopGridPosition): boolean {
// 열 겹침 체크
const aColEnd = a.col + a.colSpan - 1; const aColEnd = a.col + a.colSpan - 1;
const bColEnd = b.col + b.colSpan - 1; const bColEnd = b.col + b.colSpan - 1;
const colOverlap = !(aColEnd < b.col || bColEnd < a.col); const colOverlap = !(aColEnd < b.col || bColEnd < a.col);
// 행 겹침 체크
const aRowEnd = a.row + a.rowSpan - 1; const aRowEnd = a.row + a.rowSpan - 1;
const bRowEnd = b.row + b.rowSpan - 1; const bRowEnd = b.row + b.rowSpan - 1;
const rowOverlap = !(aRowEnd < b.row || bRowEnd < a.row); const rowOverlap = !(aRowEnd < b.row || bRowEnd < a.row);
@ -171,14 +108,10 @@ export function isOverlapping(a: PopGridPosition, b: PopGridPosition): boolean {
return colOverlap && rowOverlap; return colOverlap && rowOverlap;
} }
/**
* ( )
*/
export function resolveOverlaps( export function resolveOverlaps(
positions: Array<{ id: string; position: PopGridPosition }>, positions: Array<{ id: string; position: PopGridPosition }>,
columns: number columns: number
): Array<{ id: string; position: PopGridPosition }> { ): Array<{ id: string; position: PopGridPosition }> {
// row, col 순으로 정렬
const sorted = [...positions].sort((a, b) => const sorted = [...positions].sort((a, b) =>
a.position.row - b.position.row || a.position.col - b.position.col a.position.row - b.position.row || a.position.col - b.position.col
); );
@ -188,21 +121,15 @@ export function resolveOverlaps(
sorted.forEach((item) => { sorted.forEach((item) => {
let { row, col, colSpan, rowSpan } = item.position; let { row, col, colSpan, rowSpan } = item.position;
// 열이 범위를 초과하면 조정
if (col + colSpan - 1 > columns) { if (col + colSpan - 1 > columns) {
colSpan = columns - col + 1; colSpan = columns - col + 1;
} }
// 기존 배치와 겹치면 아래로 이동
let attempts = 0; let attempts = 0;
const maxAttempts = 100; while (attempts < 100) {
while (attempts < maxAttempts) {
const currentPos: PopGridPosition = { col, row, colSpan, rowSpan }; const currentPos: PopGridPosition = { col, row, colSpan, rowSpan };
const hasOverlap = resolved.some(r => isOverlapping(currentPos, r.position)); const hasOverlap = resolved.some(r => isOverlapping(currentPos, r.position));
if (!hasOverlap) break; if (!hasOverlap) break;
row++; row++;
attempts++; attempts++;
} }
@ -217,110 +144,9 @@ export function resolveOverlaps(
} }
// ======================================== // ========================================
// 좌표 변환 // 자동 배치 (새 컴포넌트 드롭 시)
// ======================================== // ========================================
/**
* V6: 마우스
* (BLOCK_SIZE)
*/
export function mouseToGridPosition(
mouseX: number,
mouseY: number,
canvasRect: DOMRect,
columns: number,
rowHeight: number,
gap: number,
padding: number
): { col: number; row: number } {
const relX = mouseX - canvasRect.left - padding;
const relY = mouseY - canvasRect.top - padding;
const cellStride = BLOCK_SIZE + gap;
const col = Math.max(1, Math.min(columns, Math.floor(relX / cellStride) + 1));
const row = Math.max(1, Math.floor(relY / cellStride) + 1);
return { col, row };
}
/**
* V6: 블록
*/
export function gridToPixelPosition(
col: number,
row: number,
colSpan: number,
rowSpan: number,
canvasWidth: number,
columns: number,
rowHeight: number,
gap: number,
padding: number
): { x: number; y: number; width: number; height: number } {
const cellStride = BLOCK_SIZE + gap;
return {
x: padding + (col - 1) * cellStride,
y: padding + (row - 1) * cellStride,
width: BLOCK_SIZE * colSpan + gap * (colSpan - 1),
height: BLOCK_SIZE * rowSpan + gap * (rowSpan - 1),
};
}
// ========================================
// 위치 검증
// ========================================
/**
*
*/
export function isValidPosition(
position: PopGridPosition,
columns: number
): boolean {
return (
position.col >= 1 &&
position.row >= 1 &&
position.colSpan >= 1 &&
position.rowSpan >= 1 &&
position.col + position.colSpan - 1 <= columns
);
}
/**
*
*/
export function clampPosition(
position: PopGridPosition,
columns: number
): PopGridPosition {
let { col, row, colSpan, rowSpan } = position;
// 최소값 보장
col = Math.max(1, col);
row = Math.max(1, row);
colSpan = Math.max(1, colSpan);
rowSpan = Math.max(1, rowSpan);
// 열 범위 초과 방지
if (col + colSpan - 1 > columns) {
if (col > columns) {
col = 1;
}
colSpan = columns - col + 1;
}
return { col, row, colSpan, rowSpan };
}
// ========================================
// 자동 배치
// ========================================
/**
*
*/
export function findNextEmptyPosition( export function findNextEmptyPosition(
existingPositions: PopGridPosition[], existingPositions: PopGridPosition[],
colSpan: number, colSpan: number,
@ -329,168 +155,94 @@ export function findNextEmptyPosition(
): PopGridPosition { ): PopGridPosition {
let row = 1; let row = 1;
let col = 1; let col = 1;
const maxAttempts = 1000;
let attempts = 0; let attempts = 0;
while (attempts < maxAttempts) { while (attempts < 1000) {
const candidatePos: PopGridPosition = { col, row, colSpan, rowSpan }; const candidatePos: PopGridPosition = { col, row, colSpan, rowSpan };
// 범위 체크
if (col + colSpan - 1 > columns) { if (col + colSpan - 1 > columns) {
col = 1; col = 1;
row++; row++;
continue; continue;
} }
// 겹침 체크 const hasOverlap = existingPositions.some(pos => isOverlapping(candidatePos, pos));
const hasOverlap = existingPositions.some(pos => if (!hasOverlap) return candidatePos;
isOverlapping(candidatePos, pos)
);
if (!hasOverlap) {
return candidatePos;
}
// 다음 위치로 이동
col++; col++;
if (col + colSpan - 1 > columns) { if (col + colSpan - 1 > columns) {
col = 1; col = 1;
row++; row++;
} }
attempts++; attempts++;
} }
// 실패 시 마지막 행에 배치
return { col: 1, row: row + 1, colSpan, rowSpan }; return { col: 1, row: row + 1, colSpan, rowSpan };
} }
/**
*
*/
export function autoLayoutComponents(
components: Array<{ id: string; colSpan: number; rowSpan: number }>,
columns: number
): Array<{ id: string; position: PopGridPosition }> {
const result: Array<{ id: string; position: PopGridPosition }> = [];
let currentRow = 1;
let currentCol = 1;
components.forEach(comp => {
// 현재 행에 공간이 부족하면 다음 행으로
if (currentCol + comp.colSpan - 1 > columns) {
currentRow++;
currentCol = 1;
}
result.push({
id: comp.id,
position: {
col: currentCol,
row: currentRow,
colSpan: comp.colSpan,
rowSpan: comp.rowSpan,
},
});
currentCol += comp.colSpan;
});
return result;
}
// ======================================== // ========================================
// 유효 위치 계산 (통합 함수) // 유효 위치 계산
// ======================================== // ========================================
/** /**
* . * .
* 우선순위: 1. 2. 3. * 우선순위: 1. 2. 3.
*
* @param componentId ID
* @param layout
* @param mode
* @param autoResolvedPositions ()
*/ */
export function getEffectiveComponentPosition( function getEffectiveComponentPosition(
componentId: string, componentId: string,
layout: PopLayoutDataV5, layout: PopLayoutData,
mode: GridMode, mode: GridMode,
autoResolvedPositions?: Array<{ id: string; position: PopGridPosition }> autoResolvedPositions?: Array<{ id: string; position: PopGridPosition }>
): PopGridPosition | null { ): PopGridPosition | null {
const component = layout.components[componentId]; const component = layout.components[componentId];
if (!component) return null; if (!component) return null;
// 1순위: 오버라이드가 있으면 사용
const override = layout.overrides?.[mode]?.positions?.[componentId]; const override = layout.overrides?.[mode]?.positions?.[componentId];
if (override) { if (override) {
return { ...component.position, ...override }; return { ...component.position, ...override };
} }
// 2순위: 자동 재배치된 위치 사용
if (autoResolvedPositions) { if (autoResolvedPositions) {
const autoResolved = autoResolvedPositions.find(p => p.id === componentId); const autoResolved = autoResolvedPositions.find(p => p.id === componentId);
if (autoResolved) { if (autoResolved) return autoResolved.position;
return autoResolved.position;
}
} else { } else {
// 자동 재배치 직접 계산
const componentsArray = Object.entries(layout.components).map(([id, comp]) => ({ const componentsArray = Object.entries(layout.components).map(([id, comp]) => ({
id, id,
position: comp.position, position: comp.position,
})); }));
const resolved = convertAndResolvePositions(componentsArray, mode); const resolved = convertAndResolvePositions(componentsArray, mode);
const autoResolved = resolved.find(p => p.id === componentId); const autoResolved = resolved.find(p => p.id === componentId);
if (autoResolved) { if (autoResolved) return autoResolved.position;
return autoResolved.position;
}
} }
// 3순위: 원본 위치 (12칸 모드)
return component.position; return component.position;
} }
/** /**
* . * .
* . * .
*
* v5.1: 자동
* "화면 밖" .
*/ */
export function getAllEffectivePositions( export function getAllEffectivePositions(
layout: PopLayoutDataV5, layout: PopLayoutData,
mode: GridMode mode: GridMode
): Map<string, PopGridPosition> { ): Map<string, PopGridPosition> {
const result = new Map<string, PopGridPosition>(); const result = new Map<string, PopGridPosition>();
// 숨김 처리된 컴포넌트 ID 목록
const hiddenIds = layout.overrides?.[mode]?.hidden || []; const hiddenIds = layout.overrides?.[mode]?.hidden || [];
// 자동 재배치 위치 미리 계산
const componentsArray = Object.entries(layout.components).map(([id, comp]) => ({ const componentsArray = Object.entries(layout.components).map(([id, comp]) => ({
id, id,
position: comp.position, position: comp.position,
})); }));
const autoResolvedPositions = convertAndResolvePositions(componentsArray, mode); const autoResolvedPositions = convertAndResolvePositions(componentsArray, mode);
// 각 컴포넌트의 유효 위치 계산
Object.keys(layout.components).forEach(componentId => { Object.keys(layout.components).forEach(componentId => {
// 숨김 처리된 컴포넌트는 제외 if (hiddenIds.includes(componentId)) return;
if (hiddenIds.includes(componentId)) {
return;
}
const position = getEffectiveComponentPosition( const position = getEffectiveComponentPosition(
componentId, componentId, layout, mode, autoResolvedPositions
layout,
mode,
autoResolvedPositions
); );
// v5.1: 자동 줄바꿈으로 인해 모든 컴포넌트가 그리드 안에 있음
// 따라서 추가 필터링 불필요
if (position) { if (position) {
result.set(componentId, position); result.set(componentId, position);
} }
@ -498,126 +250,3 @@ export function getAllEffectivePositions(
return result; return result;
} }
// ========================================
// V5 → V6 런타임 변환 (DB 미수정, 로드 시 변환)
// ========================================
const V5_BASE_COLUMNS = 12;
const V5_BASE_ROW_HEIGHT = 48;
const V5_BASE_GAP = 16;
const V5_DESIGN_WIDTH = 1024;
/**
* V5 판별: gridConfig.rowHeight가 V5 (48)
* 12 V5로
*/
function isV5GridConfig(layout: PopLayoutDataV5): boolean {
if (layout.gridConfig?.rowHeight === BLOCK_SIZE) return false;
const maxCol = Object.values(layout.components).reduce((max, comp) => {
const end = comp.position.col + comp.position.colSpan - 1;
return Math.max(max, end);
}, 0);
return maxCol <= V5_BASE_COLUMNS;
}
function convertV5PositionToV6(
pos: PopGridPosition,
v6DesignColumns: number,
): PopGridPosition {
const colRatio = v6DesignColumns / V5_BASE_COLUMNS;
const rowRatio = (V5_BASE_ROW_HEIGHT + V5_BASE_GAP) / (BLOCK_SIZE + BLOCK_GAP);
const newCol = Math.max(1, Math.round((pos.col - 1) * colRatio) + 1);
let newColSpan = Math.max(1, Math.round(pos.colSpan * colRatio));
const newRowSpan = Math.max(1, Math.round(pos.rowSpan * rowRatio));
if (newCol + newColSpan - 1 > v6DesignColumns) {
newColSpan = v6DesignColumns - newCol + 1;
}
return { col: newCol, row: pos.row, colSpan: newColSpan, rowSpan: newRowSpan };
}
/**
* V5 V6
* - (tablet_landscape)
* - overrides ( )
* - DB ( )
*/
export function convertV5LayoutToV6(layout: PopLayoutDataV5): PopLayoutDataV5 {
// V5 오버라이드는 V6에서 무효 (4/6/8칸용 좌표가 13/22/31칸에 맞지 않음)
// 좌표 변환 필요 여부와 무관하게 항상 제거
if (!isV5GridConfig(layout)) {
return {
...layout,
gridConfig: {
rowHeight: BLOCK_SIZE,
gap: BLOCK_GAP,
padding: BLOCK_PADDING,
},
overrides: undefined,
};
}
const v6Columns = getBlockColumns(V5_DESIGN_WIDTH);
const rowGroups: Record<number, string[]> = {};
Object.entries(layout.components).forEach(([id, comp]) => {
const r = comp.position.row;
if (!rowGroups[r]) rowGroups[r] = [];
rowGroups[r].push(id);
});
const convertedPositions: Record<string, PopGridPosition> = {};
Object.entries(layout.components).forEach(([id, comp]) => {
convertedPositions[id] = convertV5PositionToV6(comp.position, v6Columns);
});
const sortedRows = Object.keys(rowGroups).map(Number).sort((a, b) => a - b);
const rowMapping: Record<number, number> = {};
let v6Row = 1;
for (const v5Row of sortedRows) {
rowMapping[v5Row] = v6Row;
const maxSpan = Math.max(
...rowGroups[v5Row].map(id => convertedPositions[id].rowSpan)
);
v6Row += maxSpan;
}
const newComponents = { ...layout.components };
Object.entries(newComponents).forEach(([id, comp]) => {
const converted = convertedPositions[id];
const mappedRow = rowMapping[comp.position.row] ?? converted.row;
newComponents[id] = {
...comp,
position: { ...converted, row: mappedRow },
};
});
const newModals = layout.modals?.map(modal => {
const modalComps = { ...modal.components };
Object.entries(modalComps).forEach(([id, comp]) => {
modalComps[id] = {
...comp,
position: convertV5PositionToV6(comp.position, v6Columns),
};
});
return {
...modal,
gridConfig: { rowHeight: BLOCK_SIZE, gap: BLOCK_GAP, padding: BLOCK_PADDING },
components: modalComps,
overrides: undefined,
};
});
return {
...layout,
gridConfig: { rowHeight: BLOCK_SIZE, gap: BLOCK_GAP, padding: BLOCK_PADDING },
components: newComponents,
overrides: undefined,
modals: newModals,
};
}

View File

@ -0,0 +1,128 @@
// 레거시 레이아웃 로더
// DB에 저장된 V5(12칸) 좌표를 현재 블록 좌표로 변환한다.
// DB 데이터는 건드리지 않고, 로드 시 메모리에서만 변환.
import {
PopGridPosition,
PopLayoutData,
BLOCK_SIZE,
BLOCK_GAP,
BLOCK_PADDING,
getBlockColumns,
} from "../types/pop-layout";
const LEGACY_COLUMNS = 12;
const LEGACY_ROW_HEIGHT = 48;
const LEGACY_GAP = 16;
const DESIGN_WIDTH = 1024;
function isLegacyGridConfig(layout: PopLayoutData): boolean {
if (layout.gridConfig?.rowHeight === BLOCK_SIZE) return false;
const maxCol = Object.values(layout.components).reduce((max, comp) => {
const end = comp.position.col + comp.position.colSpan - 1;
return Math.max(max, end);
}, 0);
return maxCol <= LEGACY_COLUMNS;
}
function convertLegacyPosition(
pos: PopGridPosition,
targetColumns: number,
): PopGridPosition {
const colRatio = targetColumns / LEGACY_COLUMNS;
const rowRatio = (LEGACY_ROW_HEIGHT + LEGACY_GAP) / (BLOCK_SIZE + BLOCK_GAP);
const newCol = Math.max(1, Math.round((pos.col - 1) * colRatio) + 1);
let newColSpan = Math.max(1, Math.round(pos.colSpan * colRatio));
const newRowSpan = Math.max(1, Math.round(pos.rowSpan * rowRatio));
if (newCol + newColSpan - 1 > targetColumns) {
newColSpan = targetColumns - newCol + 1;
}
return { col: newCol, row: pos.row, colSpan: newColSpan, rowSpan: newRowSpan };
}
const BLOCK_GRID_CONFIG = {
rowHeight: BLOCK_SIZE,
gap: BLOCK_GAP,
padding: BLOCK_PADDING,
};
/**
* DB에서 .
*
* - 12
* - gridConfig만
* - overrides는 ( )
*/
export function loadLegacyLayout(layout: PopLayoutData): PopLayoutData {
if (!isLegacyGridConfig(layout)) {
return {
...layout,
gridConfig: BLOCK_GRID_CONFIG,
overrides: undefined,
};
}
const blockColumns = getBlockColumns(DESIGN_WIDTH);
const rowGroups: Record<number, string[]> = {};
Object.entries(layout.components).forEach(([id, comp]) => {
const r = comp.position.row;
if (!rowGroups[r]) rowGroups[r] = [];
rowGroups[r].push(id);
});
const convertedPositions: Record<string, PopGridPosition> = {};
Object.entries(layout.components).forEach(([id, comp]) => {
convertedPositions[id] = convertLegacyPosition(comp.position, blockColumns);
});
const sortedRows = Object.keys(rowGroups).map(Number).sort((a, b) => a - b);
const rowMapping: Record<number, number> = {};
let currentRow = 1;
for (const legacyRow of sortedRows) {
rowMapping[legacyRow] = currentRow;
const maxSpan = Math.max(
...rowGroups[legacyRow].map(id => convertedPositions[id].rowSpan)
);
currentRow += maxSpan;
}
const newComponents = { ...layout.components };
Object.entries(newComponents).forEach(([id, comp]) => {
const converted = convertedPositions[id];
const mappedRow = rowMapping[comp.position.row] ?? converted.row;
newComponents[id] = {
...comp,
position: { ...converted, row: mappedRow },
};
});
const newModals = layout.modals?.map(modal => {
const modalComps = { ...modal.components };
Object.entries(modalComps).forEach(([id, comp]) => {
modalComps[id] = {
...comp,
position: convertLegacyPosition(comp.position, blockColumns),
};
});
return {
...modal,
gridConfig: BLOCK_GRID_CONFIG,
components: modalComps,
overrides: undefined,
};
});
return {
...layout,
gridConfig: BLOCK_GRID_CONFIG,
components: newComponents,
overrides: undefined,
modals: newModals,
};
}

View File

@ -20,7 +20,7 @@ import {
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import PopRenderer from "../designer/renderers/PopRenderer"; import PopRenderer from "../designer/renderers/PopRenderer";
import type { PopLayoutDataV5, PopModalDefinition, GridMode } from "../designer/types/pop-layout"; import type { PopLayoutData, PopModalDefinition, GridMode } from "../designer/types/pop-layout";
import { detectGridMode, resolveModalWidth } from "../designer/types/pop-layout"; import { detectGridMode, resolveModalWidth } from "../designer/types/pop-layout";
import { usePopEvent } from "@/hooks/pop/usePopEvent"; import { usePopEvent } from "@/hooks/pop/usePopEvent";
import { useConnectionResolver } from "@/hooks/pop/useConnectionResolver"; import { useConnectionResolver } from "@/hooks/pop/useConnectionResolver";
@ -31,7 +31,7 @@ import { useConnectionResolver } from "@/hooks/pop/useConnectionResolver";
interface PopViewerWithModalsProps { interface PopViewerWithModalsProps {
/** 전체 레이아웃 (모달 정의 포함) */ /** 전체 레이아웃 (모달 정의 포함) */
layout: PopLayoutDataV5; layout: PopLayoutData;
/** 뷰포트 너비 */ /** 뷰포트 너비 */
viewportWidth: number; viewportWidth: number;
/** 화면 ID (이벤트 버스용) */ /** 화면 ID (이벤트 버스용) */
@ -178,7 +178,7 @@ export default function PopViewerWithModals({
const closeOnOverlay = definition.frameConfig?.closeOnOverlay !== false; const closeOnOverlay = definition.frameConfig?.closeOnOverlay !== false;
const closeOnEsc = definition.frameConfig?.closeOnEsc !== false; const closeOnEsc = definition.frameConfig?.closeOnEsc !== false;
const modalLayout: PopLayoutDataV5 = { const modalLayout: PopLayoutData = {
...layout, ...layout,
gridConfig: definition.gridConfig, gridConfig: definition.gridConfig,
components: definition.components, components: definition.components,

View File

@ -49,8 +49,8 @@ import { usePopEvent } from "@/hooks/pop/usePopEvent";
import { useCartSync } from "@/hooks/pop/useCartSync"; import { useCartSync } from "@/hooks/pop/useCartSync";
import { NumberInputModal } from "../pop-card-list/NumberInputModal"; import { NumberInputModal } from "../pop-card-list/NumberInputModal";
import { renderCellV2 } from "./cell-renderers"; import { renderCellV2 } from "./cell-renderers";
import type { PopLayoutDataV5 } from "@/components/pop/designer/types/pop-layout"; import type { PopLayoutData } from "@/components/pop/designer/types/pop-layout";
import { isV5Layout, detectGridMode } from "@/components/pop/designer/types/pop-layout"; import { isPopLayout, detectGridMode } from "@/components/pop/designer/types/pop-layout";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
const PopViewerWithModals = dynamic(() => import("@/components/pop/viewer/PopViewerWithModals"), { ssr: false }); const PopViewerWithModals = dynamic(() => import("@/components/pop/viewer/PopViewerWithModals"), { ssr: false });
@ -216,7 +216,7 @@ export function PopCardListV2Component({
// ===== 모달 열기 (POP 화면) ===== // ===== 모달 열기 (POP 화면) =====
const [popModalOpen, setPopModalOpen] = useState(false); const [popModalOpen, setPopModalOpen] = useState(false);
const [popModalLayout, setPopModalLayout] = useState<PopLayoutDataV5 | null>(null); const [popModalLayout, setPopModalLayout] = useState<PopLayoutData | null>(null);
const [popModalScreenId, setPopModalScreenId] = useState<string>(""); const [popModalScreenId, setPopModalScreenId] = useState<string>("");
const [popModalRow, setPopModalRow] = useState<RowData | null>(null); const [popModalRow, setPopModalRow] = useState<RowData | null>(null);
@ -228,7 +228,7 @@ export function PopCardListV2Component({
return; return;
} }
const popLayout = await screenApi.getLayoutPop(sid); const popLayout = await screenApi.getLayoutPop(sid);
if (popLayout && isV5Layout(popLayout)) { if (popLayout && isPopLayout(popLayout)) {
setPopModalLayout(popLayout); setPopModalLayout(popLayout);
setPopModalScreenId(String(sid)); setPopModalScreenId(String(sid));
setPopModalRow(row); setPopModalRow(row);

View File

@ -19,7 +19,7 @@ import { usePopEvent } from "@/hooks/pop/usePopEvent";
import { BarcodeScanModal } from "@/components/common/BarcodeScanModal"; import { BarcodeScanModal } from "@/components/common/BarcodeScanModal";
import type { import type {
PopDataConnection, PopDataConnection,
PopComponentDefinitionV5, PopComponentDefinition,
} from "@/components/pop/designer/types/pop-layout"; } from "@/components/pop/designer/types/pop-layout";
// ======================================== // ========================================
@ -99,7 +99,7 @@ function parseScanResult(
function getConnectedFields( function getConnectedFields(
componentId?: string, componentId?: string,
connections?: PopDataConnection[], connections?: PopDataConnection[],
allComponents?: PopComponentDefinitionV5[], allComponents?: PopComponentDefinition[],
): ConnectedFieldInfo[] { ): ConnectedFieldInfo[] {
if (!componentId || !connections || !allComponents) return []; if (!componentId || !connections || !allComponents) return [];
@ -308,7 +308,7 @@ const PARSE_MODE_LABELS: Record<string, string> = {
interface PopScannerConfigPanelProps { interface PopScannerConfigPanelProps {
config: PopScannerConfig; config: PopScannerConfig;
onUpdate: (config: PopScannerConfig) => void; onUpdate: (config: PopScannerConfig) => void;
allComponents?: PopComponentDefinitionV5[]; allComponents?: PopComponentDefinition[];
connections?: PopDataConnection[]; connections?: PopDataConnection[];
componentId?: string; componentId?: string;
} }

View File

@ -72,7 +72,7 @@ const DEFAULT_CONFIG: PopSearchConfig = {
interface ConfigPanelProps { interface ConfigPanelProps {
config: PopSearchConfig | undefined; config: PopSearchConfig | undefined;
onUpdate: (config: PopSearchConfig) => void; onUpdate: (config: PopSearchConfig) => void;
allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinitionV5[]; allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinition[];
connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[]; connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[];
componentId?: string; componentId?: string;
} }
@ -151,7 +151,7 @@ export function PopSearchConfigPanel({ config, onUpdate, allComponents, connecti
interface StepProps { interface StepProps {
cfg: PopSearchConfig; cfg: PopSearchConfig;
update: (partial: Partial<PopSearchConfig>) => void; update: (partial: Partial<PopSearchConfig>) => void;
allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinitionV5[]; allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinition[];
connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[]; connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[];
componentId?: string; componentId?: string;
} }
@ -268,7 +268,7 @@ interface FilterConnectionSectionProps {
update: (partial: Partial<PopSearchConfig>) => void; update: (partial: Partial<PopSearchConfig>) => void;
showFieldName: boolean; showFieldName: boolean;
fixedFilterMode?: SearchFilterMode; fixedFilterMode?: SearchFilterMode;
allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinitionV5[]; allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinition[];
connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[]; connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[];
componentId?: string; componentId?: string;
} }
@ -284,7 +284,7 @@ interface ConnectedComponentInfo {
function getConnectedComponentInfo( function getConnectedComponentInfo(
componentId?: string, componentId?: string,
connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[], connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[],
allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinitionV5[], allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinition[],
): ConnectedComponentInfo { ): ConnectedComponentInfo {
const empty: ConnectedComponentInfo = { tableNames: [], displayedColumns: new Set() }; const empty: ConnectedComponentInfo = { tableNames: [], displayedColumns: new Set() };
if (!componentId || !connections || !allComponents) return empty; if (!componentId || !connections || !allComponents) return empty;

View File

@ -22,7 +22,7 @@ import { DEFAULT_STATUS_BAR_CONFIG, STATUS_CHIP_STYLE_LABELS } from "./types";
interface ConfigPanelProps { interface ConfigPanelProps {
config: StatusBarConfig | undefined; config: StatusBarConfig | undefined;
onUpdate: (config: StatusBarConfig) => void; onUpdate: (config: StatusBarConfig) => void;
allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinitionV5[]; allComponents?: import("@/components/pop/designer/types/pop-layout").PopComponentDefinition[];
connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[]; connections?: import("@/components/pop/designer/types/pop-layout").PopDataConnection[];
componentId?: string; componentId?: string;
} }