Sync main → gbpark-node: AI 모듈 JSONB 파싱 + audit-log fix #1
@@ -3,7 +3,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { getTemplateList } from '@/lib/api/template';
|
||||
import { TemplateThumbnail } from './TemplateThumbnail';
|
||||
import { TemplateMiniPreview } from './TemplateMiniPreview';
|
||||
|
||||
interface TemplateLibraryModalProps {
|
||||
open: boolean;
|
||||
@@ -143,7 +143,7 @@ export function TemplateLibraryModal({ open, onClose, onSelectTemplate }: Templa
|
||||
className="dash-lib-card"
|
||||
onClick={() => onSelectTemplate(t)}
|
||||
>
|
||||
<TemplateThumbnail views={t.views ?? t.VIEWS} />
|
||||
<TemplateMiniPreview template={t} />
|
||||
<div className="dash-lib-card-name">{name}</div>
|
||||
{desc && <div className="dash-lib-card-desc">{desc}</div>}
|
||||
<div style={{ display: 'flex', gap: '.2rem', marginTop: 'auto', flexWrap: 'wrap' }}>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 라이브러리 카드용 라이브 미니 프리뷰.
|
||||
*
|
||||
* 실제 TemplateRenderer 를 mock empty context 로 띄운 뒤 transform: scale 로
|
||||
* 카드 썸네일 사이즈에 맞춤. 사용자가 빌더에서 그린 그대로의 레이아웃이
|
||||
* 읽힘 (테이블/폼/버튼/구분선 등 실제 컴포넌트 모양). 데이터는 비어있으니
|
||||
* 추가 API 호출이나 회사 전환 부담 없음.
|
||||
*
|
||||
* - pointer-events: none — 카드 클릭 영역은 부모가 처리
|
||||
* - overflow: hidden — scale 후 박스 밖 픽셀 클립
|
||||
* - ResizeObserver 로 카드 폭에 따라 scale 자동 조정
|
||||
*
|
||||
* 빈 템플릿이면 TemplateThumbnail 폴백.
|
||||
*/
|
||||
|
||||
import { useLayoutEffect, useRef, useState, useMemo } from 'react';
|
||||
import { TemplateRenderer, type TemplateRenderContext } from './TemplateRenderer';
|
||||
import { TemplateThumbnail } from './TemplateThumbnail';
|
||||
|
||||
interface TemplateMiniPreviewProps {
|
||||
template: any;
|
||||
}
|
||||
|
||||
const EMPTY_CTX: TemplateRenderContext = {
|
||||
fields: [],
|
||||
data: [],
|
||||
loading: false,
|
||||
selectedRow: null,
|
||||
totalCount: 0,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
searchParams: {},
|
||||
onSearch: () => {},
|
||||
onRowSelect: () => {},
|
||||
onPageChange: () => {},
|
||||
onAdd: () => {},
|
||||
onEdit: () => {},
|
||||
onDelete: () => {},
|
||||
};
|
||||
|
||||
const BASE_WIDTH = 1200;
|
||||
const BASE_HEIGHT = 750; // 16:10
|
||||
|
||||
function hasContent(template: any): boolean {
|
||||
const v = template?.views ?? template?.VIEWS;
|
||||
if (!v) return false;
|
||||
const lst = v?.list;
|
||||
if (Array.isArray(lst) && lst.length > 0) return true;
|
||||
if (Array.isArray(lst?.blocks) && lst.blocks.length > 0) return true;
|
||||
if (Array.isArray(lst?.components) && lst.components.length > 0) return true;
|
||||
if (Array.isArray(lst?.layers) &&
|
||||
lst.layers.some((l: any) => Array.isArray(l?.components) && l.components.length > 0)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function TemplateMiniPreview({ template }: TemplateMiniPreviewProps) {
|
||||
const wrapRef = useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(0.15);
|
||||
|
||||
const empty = useMemo(() => !hasContent(template), [template]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (empty || !wrapRef.current) return;
|
||||
const el = wrapRef.current;
|
||||
const update = () => {
|
||||
const w = el.clientWidth;
|
||||
if (w > 0) setScale(w / BASE_WIDTH);
|
||||
};
|
||||
update();
|
||||
const ro = new ResizeObserver(update);
|
||||
ro.observe(el);
|
||||
return () => ro.disconnect();
|
||||
}, [empty]);
|
||||
|
||||
if (empty) {
|
||||
return <TemplateThumbnail views={template?.views ?? template?.VIEWS} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={wrapRef} className="dash-lib-card-thumb dash-lib-card-thumb--live" aria-hidden="true">
|
||||
<div
|
||||
className="dash-lib-card-thumb-stage"
|
||||
style={{
|
||||
width: BASE_WIDTH,
|
||||
height: BASE_HEIGHT,
|
||||
transform: `scale(${scale})`,
|
||||
}}
|
||||
>
|
||||
<TemplateRenderer template={template} context={EMPTY_CTX} view="list" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -487,6 +487,15 @@
|
||||
.dash-lib-card-thumb--empty {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.dash-lib-card-thumb--live {
|
||||
padding: 0;
|
||||
background: var(--v5-surface-solid);
|
||||
}
|
||||
.dash-lib-card-thumb-stage {
|
||||
transform-origin: top left;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
.dash-lib-card-thumb-canvas {
|
||||
position: relative; width: 100%; height: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user