135 lines
4.9 KiB
TypeScript
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>
|
|
);
|
|
}
|