ERP-node/frontend/lib/registry/pop-components/pop-card-list-v2/PopCardListV2Preview.tsx

105 lines
4.0 KiB
TypeScript
Raw Normal View History

feat(pop): pop-card-list-v2 슬롯 기반 카드 컴포넌트 신규 + 타임라인 범용화 + 액션 인라인 설정 CSS Grid 기반 슬롯 구조의 pop-card-list-v2 컴포넌트를 추가한다. 기존 pop-card-list의 데이터 로딩/필터링/장바구니 로직을 재활용하되, 카드 내부는 12종 셀 타입(text/field/image/badge/button/number-input/ cart-button/package-summary/status-badge/timeline/action-buttons/ footer-status)의 조합으로 자유롭게 구성할 수 있다. [신규 컴포넌트: pop-card-list-v2] - PopCardListV2Component: 런타임 렌더링 (데이터 조회 + CSS Grid 카드) - PopCardListV2Config: 3탭 설정 패널 (데이터/카드 디자인/동작) - PopCardListV2Preview: 디자이너 미리보기 - cell-renderers: 셀 타입별 독립 렌더러 12종 - migrate: v1 -> v2 설정 마이그레이션 함수 - index: PopComponentRegistry 자동 등록 [타임라인 데이터 소스 범용화] - TimelineDataSource 인터페이스로 공정 테이블/FK/컬럼/상태값 매핑 설정 - 하드코딩(work_orders+work_order_process) 제거 -> 설정 기반 동적 조회 - injectProcessFlow: 설정 기반 공정 데이터 조회 + __processFlow__ 가상 컬럼 주입 - 상태값 정규화(DB값 -> waiting/accepted/in_progress/completed) [액션 버튼 인라인 설정] - actionRules 내 updates 배열로 동작 정의 (별도 DB 테이블 불필요) - execute-action API 재활용 (targetTable/column/valueType) - 백엔드 __CURRENT_USER__/__CURRENT_TIME__ 특수값 치환 [디자이너 통합] - PopComponentType에 "pop-card-list-v2" 추가 - ComponentEditorPanel/ComponentPalette/PopRenderer 등록 - PopDesigner loadLayout: components 존재 확인 null 체크 추가 [기타] - .gitignore: .gradle/ 추가
2026-03-10 16:56:14 +09:00
"use client";
/**
* pop-card-list-v2
*
* .
* CSS Grid .
*/
import React from "react";
import { LayoutGrid, Package } from "lucide-react";
import type { PopCardListV2Config } from "../types";
import { CARD_SCROLL_DIRECTION_LABELS, CARD_SIZE_LABELS } from "../types";
interface PopCardListV2PreviewProps {
config?: PopCardListV2Config;
}
export function PopCardListV2PreviewComponent({ config }: PopCardListV2PreviewProps) {
const scrollDirection = config?.scrollDirection || "vertical";
const cardSize = config?.cardSize || "medium";
const dataSource = config?.dataSource;
const cardGrid = config?.cardGrid;
const hasTable = !!dataSource?.tableName;
const cellCount = cardGrid?.cells?.length || 0;
return (
<div className="flex h-full w-full flex-col bg-muted/30 p-3">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2 text-muted-foreground">
<LayoutGrid className="h-4 w-4" />
<span className="text-xs font-medium"> V2</span>
</div>
<div className="flex gap-1">
<span className="rounded bg-primary/10 px-1.5 py-0.5 text-[9px] text-primary">
{CARD_SCROLL_DIRECTION_LABELS[scrollDirection]}
</span>
<span className="rounded bg-secondary px-1.5 py-0.5 text-[9px] text-secondary-foreground">
{CARD_SIZE_LABELS[cardSize]}
</span>
</div>
</div>
{!hasTable ? (
<div className="flex flex-1 items-center justify-center">
<div className="text-center">
<Package className="mx-auto mb-2 h-8 w-8 text-muted-foreground/50" />
<p className="text-xs text-muted-foreground"> </p>
</div>
</div>
) : (
<>
<div className="mb-2 text-center">
<span className="rounded bg-muted px-2 py-0.5 text-[10px] text-muted-foreground">
{dataSource!.tableName}
</span>
<span className="ml-1 text-[10px] text-muted-foreground/60">
({cellCount})
</span>
</div>
<div className="flex flex-1 flex-col gap-2">
{[0, 1].map((cardIdx) => (
<div key={cardIdx} className="rounded-md border bg-card p-2">
{cellCount === 0 ? (
<div className="flex h-12 items-center justify-center">
<span className="text-[10px] text-muted-foreground"> </span>
</div>
) : (
<div
style={{
display: "grid",
gridTemplateColumns: cardGrid!.colWidths?.length
? cardGrid!.colWidths.map((w) => w || "1fr").join(" ")
: `repeat(${cardGrid!.cols || 1}, 1fr)`,
gridTemplateRows: `repeat(${cardGrid!.rows || 1}, minmax(16px, auto))`,
gap: "2px",
}}
>
{cardGrid!.cells.map((cell) => (
<div
key={cell.id}
className="rounded border border-dashed border-border/50 bg-muted/20 px-1 py-0.5"
style={{
gridColumn: `${cell.col} / span ${cell.colSpan || 1}`,
gridRow: `${cell.row} / span ${cell.rowSpan || 1}`,
}}
>
<span className="text-[8px] text-muted-foreground">
{cell.type}
{cell.columnName ? `: ${cell.columnName}` : ""}
</span>
</div>
))}
</div>
)}
</div>
))}
</div>
</>
)}
</div>
);
}