157 lines
4.8 KiB
TypeScript
157 lines
4.8 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* pop-dashboard 디자이너 미리보기 컴포넌트
|
||
|
|
*
|
||
|
|
* 실제 데이터 없이 더미 레이아웃으로 미리보기 표시
|
||
|
|
* 디자이너가 설정 변경 시 즉시 미리보기 확인 가능
|
||
|
|
*/
|
||
|
|
|
||
|
|
import React from "react";
|
||
|
|
import { BarChart3, PieChart, Gauge, LayoutList } from "lucide-react";
|
||
|
|
import type { PopDashboardConfig, DashboardSubType } from "../types";
|
||
|
|
|
||
|
|
// ===== 서브타입별 아이콘 매핑 =====
|
||
|
|
|
||
|
|
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": "자동 슬라이드",
|
||
|
|
grid: "그리드",
|
||
|
|
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;
|
||
|
|
}) {
|
||
|
|
if (!config || !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 mode = config.displayMode;
|
||
|
|
|
||
|
|
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[mode] ?? mode}
|
||
|
|
</span>
|
||
|
|
<span className="text-[8px] text-muted-foreground">
|
||
|
|
{visibleItems.length}개
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 모드별 미리보기 */}
|
||
|
|
<div className="min-h-0 flex-1">
|
||
|
|
{mode === "grid" ? (
|
||
|
|
// 그리드: 셀 구조 시각화
|
||
|
|
<div
|
||
|
|
className="h-full w-full gap-1"
|
||
|
|
style={{
|
||
|
|
display: "grid",
|
||
|
|
gridTemplateColumns: `repeat(${config.gridColumns ?? 2}, 1fr)`,
|
||
|
|
gridTemplateRows: `repeat(${config.gridRows ?? 2}, 1fr)`,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{config.gridCells?.length
|
||
|
|
? config.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>
|
||
|
|
);
|
||
|
|
}
|