ERP-node/frontend/components/pop/designer/panels/ComponentPalette.tsx

165 lines
4.3 KiB
TypeScript
Raw Normal View History

"use client";
import { useDrag } from "react-dnd";
import { cn } from "@/lib/utils";
import { PopComponentType } from "../types/pop-layout";
import { Square, FileText, MousePointer, BarChart3, LayoutGrid, MousePointerClick, List, Search, TextCursorInput, ScanLine, UserCircle, BarChart2 } from "lucide-react";
import { DND_ITEM_TYPES } from "../constants";
// 컴포넌트 정의
interface PaletteItem {
type: PopComponentType;
label: string;
icon: React.ElementType;
description: string;
}
const PALETTE_ITEMS: PaletteItem[] = [
{
type: "pop-sample",
label: "샘플 박스",
icon: Square,
description: "크기 조정 테스트용",
},
{
type: "pop-text",
label: "텍스트",
icon: FileText,
description: "텍스트, 시간, 이미지 표시",
},
{
type: "pop-icon",
label: "아이콘",
icon: MousePointer,
description: "네비게이션 아이콘 (화면 이동, URL, 뒤로가기)",
},
{
type: "pop-dashboard",
label: "대시보드",
icon: BarChart3,
description: "KPI, 차트, 게이지, 통계 집계",
},
{
type: "pop-card-list",
label: "카드 목록",
icon: LayoutGrid,
description: "테이블 데이터를 카드 형태로 표시",
},
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
{
type: "pop-card-list-v2",
label: "카드 목록 V2",
icon: LayoutGrid,
description: "슬롯 기반 카드 (CSS Grid + 셀 타입별 렌더링)",
},
{
type: "pop-button",
label: "버튼",
icon: MousePointerClick,
description: "액션 버튼 (저장/삭제/API/모달)",
},
{
type: "pop-string-list",
label: "리스트 목록",
icon: List,
description: "테이블 데이터를 리스트/카드로 표시",
},
{
type: "pop-search",
label: "검색",
icon: Search,
description: "조건 입력 (텍스트/날짜/선택/모달)",
},
{
type: "pop-status-bar",
label: "상태 바",
icon: BarChart2,
description: "상태별 건수 대시보드 + 필터",
},
{
type: "pop-field",
label: "입력 필드",
icon: TextCursorInput,
description: "저장용 값 입력 (섹션별 멀티필드)",
},
feat(pop-scanner): 바코드/QR 스캐너 컴포넌트 + 멀티필드 파싱 + 반자동 매핑 모바일/태블릿 환경에서 바코드·QR을 카메라로 스캔하여 검색·입력 필드에 값을 자동 전달하는 pop-scanner 컴포넌트를 추가하고, JSON 형태의 멀티필드 데이터를 여러 컴포넌트에 분배하는 파싱 체계를 구현한다. [pop-scanner 신규] - 카메라 스캔 UI (BarcodeScanModal) + 아이콘 전용 버튼 - parseMode 3모드: none(단일값), auto(전역 자동매칭), json(반자동 매핑) - auto: scan_auto_fill 전역 이벤트로 fieldName 기준 자동 입력 - json: 연결된 컴포넌트 필드를 체크박스 목록으로 표시, fieldName==JSON키 자동 매칭 + 관리자 override(enabled/sourceKey) - getDynamicConnectionMeta로 parseMode별 sendable 동적 생성 [pop-field 연동] - scan_auto_fill 구독: sections.fields의 fieldName과 JSON 키 매칭 - columnMapping 키를 fieldName 기준으로 통일 (fieldId→fieldName) - targetColumn 선택 시 fieldName 자동 동기화 - 새 필드 fieldName 기본값을 빈 문자열로 변경 [pop-search 연동] - scan_auto_fill 구독: filterColumns 전체 키 매칭 - set_value 수신 시 모달 타입이면 modalDisplayText도 갱신 [BarcodeScanModal 개선] - 모달 열릴 때 상태 리셋 (scannedCode/error/isScanning) - "다시 스캔" 버튼 추가 - 스캔 가이드 영역 확대 (h-3/5 w-4/5) [getConnectedFields 필드 추출] - filterColumns(복수) > modalConfig.valueField > fieldName 우선순위 - pop-field sections.fields[].fieldName 추출
2026-03-06 19:52:18 +09:00
{
type: "pop-scanner",
label: "스캐너",
icon: ScanLine,
description: "바코드/QR 카메라 스캔",
},
{
type: "pop-profile",
label: "프로필",
icon: UserCircle,
description: "사용자 프로필 / PC 전환 / 로그아웃",
},
];
// 드래그 가능한 컴포넌트 아이템
function DraggablePaletteItem({ item }: { item: PaletteItem }) {
const [{ isDragging }, drag] = useDrag(
() => ({
type: DND_ITEM_TYPES.COMPONENT,
item: { type: DND_ITEM_TYPES.COMPONENT, componentType: item.type },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}),
[item.type]
);
const Icon = item.icon;
return (
<div
ref={drag}
className={cn(
"flex cursor-grab items-center gap-3 rounded-md border bg-white p-3",
"transition-all hover:border-primary hover:shadow-sm",
isDragging && "opacity-50 cursor-grabbing"
)}
>
<div className="flex h-9 w-9 items-center justify-center rounded bg-muted">
<Icon className="h-4 w-4 text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium">{item.label}</div>
<div className="text-xs text-muted-foreground truncate">
{item.description}
</div>
</div>
</div>
);
}
// 컴포넌트 팔레트 패널
export default function ComponentPalette() {
return (
<div className="flex h-full flex-col bg-muted">
{/* 헤더 */}
<div className="border-b bg-white px-4 py-3">
<h3 className="text-sm font-semibold"></h3>
<p className="text-xs text-muted-foreground">
</p>
</div>
{/* 컴포넌트 목록 */}
<div className="flex-1 overflow-y-auto p-3">
<div className="space-y-2">
{PALETTE_ITEMS.map((item) => (
<DraggablePaletteItem key={item.type} item={item} />
))}
</div>
</div>
{/* 하단 안내 */}
<div className="border-t bg-white px-4 py-3">
<p className="text-xs text-muted-foreground">
Tip: 캔버스의
</p>
</div>
</div>
);
}