ERP-node/frontend/lib/registry/pop-components/pop-dashboard/PopDashboardPreview.tsx

135 lines
4.9 KiB
TypeScript

"use client";
/**
* pop-dashboard 디자이너 미리보기 컴포넌트
*
* 실제 데이터 없이 더미 레이아웃으로 미리보기 표시
* 디자이너가 설정 변경 시 즉시 미리보기 확인 가능
*/
import React from "react";
import { BarChart3, PieChart, Gauge, LayoutList } from "lucide-react";
import type { PopDashboardConfig, DashboardSubType } from "../types";
import { migrateConfig } from "./PopDashboardComponent";
// ===== 서브타입별 아이콘 매핑 =====
const SUBTYPE_ICONS: Record<DashboardSubType, React.ReactNode> = {
"kpi-card": <BarChart3 className="h-4 w-4" />,
chart: <PieChart className="h-4 w-4" />,
gauge: <Gauge className="h-4 w-4" />,
"stat-card": <LayoutList className="h-4 w-4" />,
};
const SUBTYPE_LABELS: Record<DashboardSubType, string> = {
"kpi-card": "KPI",
chart: "차트",
gauge: "게이지",
"stat-card": "통계",
};
// ===== 모드 라벨 =====
const MODE_LABELS: Record<string, string> = {
arrows: "좌우 버튼",
"auto-slide": "자동 슬라이드",
scroll: "스크롤",
};
// ===== 더미 아이템 프리뷰 =====
function DummyItemPreview({ subType, label }: { subType: DashboardSubType; label: string }) {
return (
<div className="border-muted-foreground/30 bg-muted/20 flex h-full w-full flex-col items-center justify-center gap-1 rounded border border-dashed p-2">
<span className="text-muted-foreground">{SUBTYPE_ICONS[subType]}</span>
<span className="text-muted-foreground truncate text-[10px]">{label || SUBTYPE_LABELS[subType]}</span>
</div>
);
}
// ===== 메인 미리보기 =====
export function PopDashboardPreviewComponent({ config }: { config?: PopDashboardConfig }) {
// config가 빈 객체 {} 또는 items가 없는 경우 방어
if (!config || !Array.isArray(config.items) || !config.items.length) {
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-1 overflow-hidden">
<BarChart3 className="text-muted-foreground/50 h-6 w-6" />
<span className="text-muted-foreground text-[10px]"></span>
</div>
);
}
const visibleItems = config.items.filter((i) => i.visible);
// 마이그레이션 적용
const migrated = migrateConfig(config as unknown as Record<string, unknown>);
const pages = migrated.pages ?? [];
const hasPages = pages.length > 0;
return (
<div className="flex h-full w-full flex-col overflow-hidden p-1">
{/* 모드 + 페이지 뱃지 */}
<div className="mb-1 flex items-center gap-1">
<span className="bg-primary/10 text-primary rounded px-1 py-0.5 text-[8px] font-medium">
{MODE_LABELS[migrated.displayMode] ?? migrated.displayMode}
</span>
{hasPages && (
<span className="bg-muted text-muted-foreground rounded px-1 py-0.5 text-[8px] font-medium">
{pages.length}
</span>
)}
<span className="text-muted-foreground text-[8px]">{visibleItems.length}</span>
</div>
{/* 미리보기 */}
<div className="min-h-0 flex-1">
{hasPages ? (
// 첫 번째 페이지 그리드 미리보기
<div
className="h-full w-full gap-1"
style={{
display: "grid",
gridTemplateColumns: `repeat(${pages[0].gridColumns}, 1fr)`,
gridTemplateRows: `repeat(${pages[0].gridRows}, 1fr)`,
}}
>
{pages[0].gridCells.length > 0
? pages[0].gridCells.map((cell) => {
const item = visibleItems.find((i) => i.id === cell.itemId);
return (
<div
key={cell.id}
style={{
gridColumn: cell.gridColumn,
gridRow: cell.gridRow,
}}
>
{item ? (
<DummyItemPreview subType={item.subType} label={item.label} />
) : (
<div className="border-muted-foreground/20 h-full rounded border border-dashed" />
)}
</div>
);
})
: visibleItems
.slice(0, 4)
.map((item) => <DummyItemPreview key={item.id} subType={item.subType} label={item.label} />)}
</div>
) : (
// 페이지 미설정: 첫 번째 아이템만 크게 표시
<div className="relative h-full">
{visibleItems[0] && <DummyItemPreview subType={visibleItems[0].subType} label={visibleItems[0].label} />}
{visibleItems.length > 1 && (
<div className="bg-primary/80 text-primary-foreground absolute right-1 bottom-1 rounded-full px-1.5 py-0.5 text-[8px] font-medium">
+{visibleItems.length - 1}
</div>
)}
</div>
)}
</div>
</div>
);
}