feat(pop): POP 화면 카테고리 관리 시스템 구현 및 저장/로드 안정화

POP 전용 카테고리 트리 UI 구현 (계층적 폴더 구조)
카테고리 CRUD API 추가 (hierarchy_path LIKE 'POP/%' 필터)
화면 이동 기능 (기존 연결 삭제 후 새 연결 추가 방식)
카테고리/화면 순서 변경 기능 (display_order 교환)
이동 UI를 서브메뉴에서 검색 가능한 모달로 개선
POP 레이아웃 버전 통일 (pop-1.0) 및 로드 로직 수정
DB 스키마 호환성 수정 (writer 컬럼, is_active VARCHAR)
This commit is contained in:
SeongHyun Kim
2026-02-02 18:01:05 +09:00
parent 8c045acab3
commit d9b7ef9ad4
13 changed files with 3104 additions and 209 deletions

View File

@@ -78,18 +78,24 @@ function PopScreenViewPage() {
setScreen(screenData);
// POP 레이아웃 로드 (screen_layouts_pop 테이블에서)
// POP 레이아웃은 sections[] 구조 사용 (데스크톱의 components[]와 다름)
try {
const popLayout = await screenApi.getLayoutPop(screenId);
if (popLayout && popLayout.components && popLayout.components.length > 0) {
// POP 레이아웃이 있으면 사용
console.log("POP 레이아웃 로드:", popLayout.components?.length || 0, "개 컴포넌트");
if (popLayout && popLayout.sections && popLayout.sections.length > 0) {
// POP 레이아웃 (sections 구조) - 그대로 저장
console.log("POP 레이아웃 로드:", popLayout.sections?.length || 0, "개 섹션");
setLayout(popLayout as any); // sections 구조 그대로 사용
} else if (popLayout && popLayout.components && popLayout.components.length > 0) {
// 이전 형식 (components 구조) - 호환성 유지
console.log("POP 레이아웃 로드 (이전 형식):", popLayout.components?.length || 0, "개 컴포넌트");
setLayout(popLayout as LayoutData);
} else {
// POP 레이아웃이 비어있으면 빈 레이아웃
console.log("POP 레이아웃 없음, 빈 화면 표시");
setLayout({
screenId,
sections: [],
components: [],
gridSettings: {
columns: 12,
@@ -101,12 +107,13 @@ function PopScreenViewPage() {
opacity: 0.5,
snapToGrid: true,
},
});
} as any);
}
} catch (layoutError) {
console.warn("POP 레이아웃 로드 실패:", layoutError);
setLayout({
screenId,
sections: [],
components: [],
gridSettings: {
columns: 12,
@@ -118,7 +125,7 @@ function PopScreenViewPage() {
opacity: 0.5,
snapToGrid: true,
},
});
} as any);
}
} catch (error) {
console.error("POP 화면 로드 실패:", error);
@@ -222,8 +229,58 @@ function PopScreenViewPage() {
maxWidth: isPreviewMode ? currentDevice.width : "100%",
}}
>
{/* 화면 컨텐츠 */}
{layout && layout.components && layout.components.length > 0 ? (
{/* POP 레이아웃: sections 구조 렌더링 */}
{layout && (layout as any).sections && (layout as any).sections.length > 0 ? (
<div className="w-full min-h-full p-2">
{/* 그리드 레이아웃으로 섹션 배치 */}
<div
className="grid gap-1"
style={{
gridTemplateColumns: `repeat(${(layout as any).canvasGrid?.columns || 24}, 1fr)`,
}}
>
{(layout as any).sections.map((section: any) => (
<div
key={section.id}
className="bg-gray-50 border border-gray-200 rounded-lg p-2"
style={{
gridColumn: `${section.grid?.col || 1} / span ${section.grid?.colSpan || 6}`,
gridRow: `${section.grid?.row || 1} / span ${section.grid?.rowSpan || 4}`,
minHeight: `${(section.grid?.rowSpan || 4) * 20}px`,
}}
>
{/* 섹션 라벨 */}
{section.label && (
<div className="text-xs font-medium text-gray-500 mb-1">
{section.label}
</div>
)}
{/* 섹션 내 컴포넌트들 */}
{section.components && section.components.length > 0 ? (
<div className="space-y-1">
{section.components.map((comp: any) => (
<div
key={comp.id}
className="bg-white border border-gray-100 rounded p-2 text-sm"
>
{/* TODO: POP 전용 컴포넌트 렌더러 구현 필요 */}
<span className="text-gray-600">
{comp.label || comp.type || comp.id}
</span>
</div>
))}
</div>
) : (
<div className="text-xs text-gray-400 text-center py-2">
</div>
)}
</div>
))}
</div>
</div>
) : layout && layout.components && layout.components.length > 0 ? (
// 이전 형식 (components 구조) - 호환성 유지
<ScreenMultiLangProvider components={layout.components} companyCode={companyCode}>
<div className="relative w-full min-h-full p-4">
{layout.components