diff --git a/frontend/app/(main)/admin/builder/page.tsx b/frontend/app/(main)/admin/builder/page.tsx index 630567e7..eb9795b7 100644 --- a/frontend/app/(main)/admin/builder/page.tsx +++ b/frontend/app/(main)/admin/builder/page.tsx @@ -1,11 +1,108 @@ "use client"; -import TemplateBuilder from "@/components/template-builder/TemplateBuilder"; +// VEX 화면 디자이너 직접 연결. /admin/builder 접속 시 곧바로 ScreenDesigner 를 +// 전체 화면으로 표시. URL 쿼리 `?id=xxx` 로 특정 화면 지정 가능, 없으면 첫 번째 +// 화면 자동 선택. +// Phase 2.1 의 TemplateBuilder 는 카드 내부 모델 시도 실패 후 포기 (2026-04-11). +import { Suspense, useState, useEffect } from "react"; +import { useSearchParams, useRouter } from "next/navigation"; +import ScreenDesigner from "@/components/screen/ScreenDesigner"; +import type { ScreenDefinition } from "@/types/screen"; +import { screenApi } from "@/lib/api/screen"; -export default function BuilderPage() { +function BuilderInner() { + const router = useRouter(); + const searchParams = useSearchParams(); + const screenIdParam = searchParams.get("id"); + const [selectedScreen, setSelectedScreen] = useState(null); + const [loading, setLoading] = useState(true); + const [loadError, setLoadError] = useState(null); + + useEffect(() => { + let alive = true; + (async () => { + try { + setLoading(true); + setLoadError(null); + const result: any = await screenApi.getScreens({ + page: 1, + size: 1000, + searchTerm: "", + excludePop: true, + }); + if (!alive) return; + const list: ScreenDefinition[] = result?.data ?? []; + let target: ScreenDefinition | null = null; + if (screenIdParam) { + const id = parseInt(screenIdParam, 10); + target = list.find((s: any) => s.screen_id === id) ?? null; + } + if (!target && list.length > 0) { + target = list[0]; + } + setSelectedScreen(target); + } catch (err: any) { + console.error("[BuilderPage] 화면 로드 실패:", err); + if (alive) setLoadError(err?.message ?? "화면 로드 실패"); + } finally { + if (alive) setLoading(false); + } + })(); + return () => { + alive = false; + }; + }, [screenIdParam]); + + if (loading) { + return ( +
+ 빌더 로딩 중... +
+ ); + } + + if (loadError) { + return ( +
+
⚠ {loadError}
+ +
+ ); + } + + // AppLayout 의 헤더/탭 아래 영역에만 배치. fixed 덮어쓰기 대신 일반 flow 로 + // 헤더와 안 겹치게. return ( -
- +
+ router.push("/admin/screenMng/screenMngList")} + onScreenUpdate={(updatedFields) => { + if (selectedScreen) { + setSelectedScreen({ ...selectedScreen, ...updatedFields }); + } + }} + />
); } + +export default function BuilderPage() { + return ( + + 빌더 로딩 중... +
+ } + > + + + ); +} diff --git a/frontend/components/dash/DashboardCard.tsx b/frontend/components/dash/DashboardCard.tsx index 73666f20..de68d7da 100644 --- a/frontend/components/dash/DashboardCard.tsx +++ b/frontend/components/dash/DashboardCard.tsx @@ -2,18 +2,13 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { RefreshCw, ChevronDown, X, Settings } from 'lucide-react'; +import { toast } from 'sonner'; import { getTemplateInfo } from '@/lib/api/template'; -import { getMetaFields } from '@/lib/api/meta'; -import { fcList } from '@/lib/api/fcData'; -import { FcTable, FcForm, FcSearch, FcButton, FcButtonBar, FcPagination } from '@/components/fc'; -import type { - FieldConfig, - GridPosition, - AbsolutePosition, - TemplateKind, -} from '@/types/invyone-component'; -import { isGridPosition } from '@/types/invyone-component'; +import { fcList, fcInsert, fcUpdate, fcDelete } from '@/lib/api/fcData'; +import { FcForm } from '@/components/fc'; +import type { FieldConfig, Template } from '@/types/invyone-component'; import { CardMiniView } from './CardMiniView'; +import { TemplateRenderer, type TemplateRenderContext } from './TemplateRenderer'; interface DashboardCardProps { card: Record; @@ -23,12 +18,6 @@ interface DashboardCardProps { onOpenSettings?: (cardId: string) => void; } -/** - * DashboardCard — Template 기반 렌더러 (2026-04-10 재설계) - * - kind: 'business' → 12-col grid + @container 카드 너비 반응형 - * - kind: 'canvas' → absolute 자유배치 (control/flow 등 예외) - * - 반응형 분기는 GridComponent가 CSS 변수로 주입, @container 쿼리가 처리 - */ export function DashboardCard({ card, editMode, @@ -43,15 +32,11 @@ export function DashboardCard({ const primaryTable = card.primary_table ?? card.PRIMARY_TABLE ?? ''; const isCollapsed = card.is_collapsed ?? card.IS_COLLAPSED ?? false; - // ─── Template 상태 ─── const [fields, setFields] = useState([]); - const [components, setComponents] = useState[]>([]); - const [connections, setConnections] = useState[]>([]); - const [templateKind, setTemplateKind] = useState(null); + const [template, setTemplate] = useState