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

165 lines
5.2 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="flex h-full w-full flex-col items-center justify-center gap-1 rounded border border-dashed border-muted-foreground/30 bg-muted/20 p-2">
<span className="text-muted-foreground">
{SUBTYPE_ICONS[subType]}
</span>
<span className="truncate text-[10px] text-muted-foreground">
{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="h-6 w-6 text-muted-foreground/50" />
<span className="text-[10px] text-muted-foreground"></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="rounded bg-primary/10 px-1 py-0.5 text-[8px] font-medium text-primary">
{MODE_LABELS[migrated.displayMode] ?? migrated.displayMode}
</span>
{hasPages && (
<span className="rounded bg-muted px-1 py-0.5 text-[8px] font-medium text-muted-foreground">
{pages.length}
</span>
)}
<span className="text-[8px] text-muted-foreground">
{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="h-full rounded border border-dashed border-muted-foreground/20" />
)}
</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="absolute bottom-1 right-1 rounded-full bg-primary/80 px-1.5 py-0.5 text-[8px] font-medium text-primary-foreground">
+{visibleItems.length - 1}
</div>
)}
</div>
)}
</div>
</div>
);
}