ERP-node/frontend/lib/registry/PopComponentRegistry.ts

268 lines
7.1 KiB
TypeScript

"use client";
import React from "react";
/**
* POP 컴포넌트 정의 인터페이스
*/
export interface PopComponentDefinition {
id: string;
name: string;
description: string;
category: PopComponentCategory;
icon?: string;
component: React.ComponentType<any>;
configPanel?: React.ComponentType<any>;
defaultProps?: Record<string, any>;
// POP 전용 속성
touchOptimized?: boolean;
minTouchArea?: number;
supportedDevices?: ("mobile" | "tablet")[];
createdAt?: Date;
updatedAt?: Date;
}
/**
* POP 컴포넌트 카테고리
*/
export type PopComponentCategory =
| "display" // 데이터 표시 (카드, 리스트, 배지)
| "input" // 입력 (스캐너, 터치 입력)
| "action" // 액션 (버튼, 스와이프)
| "layout" // 레이아웃 (컨테이너, 그리드)
| "feedback"; // 피드백 (토스트, 로딩)
/**
* POP 컴포넌트 레지스트리 이벤트
*/
export interface PopComponentRegistryEvent {
type: "component_registered" | "component_unregistered";
data: PopComponentDefinition;
timestamp: Date;
}
/**
* POP 컴포넌트 레지스트리 클래스
* 모바일/태블릿 전용 컴포넌트를 등록, 관리, 조회할 수 있는 중앙 레지스트리
*/
export class PopComponentRegistry {
private static components = new Map<string, PopComponentDefinition>();
private static eventListeners: Array<(event: PopComponentRegistryEvent) => void> = [];
/**
* 컴포넌트 등록
*/
static registerComponent(definition: PopComponentDefinition): void {
// 유효성 검사
if (!definition.id || !definition.name || !definition.component) {
throw new Error(
`POP 컴포넌트 등록 실패 (${definition.id || "unknown"}): 필수 필드 누락`
);
}
// 중복 등록 체크
if (this.components.has(definition.id)) {
console.warn(`[POP Registry] 컴포넌트 중복 등록: ${definition.id} - 기존 정의를 덮어씁니다.`);
}
// 타임스탬프 추가
const enhancedDefinition: PopComponentDefinition = {
...definition,
touchOptimized: definition.touchOptimized ?? true,
minTouchArea: definition.minTouchArea ?? 44,
supportedDevices: definition.supportedDevices ?? ["mobile", "tablet"],
createdAt: definition.createdAt || new Date(),
updatedAt: new Date(),
};
this.components.set(definition.id, enhancedDefinition);
// 이벤트 발생
this.emitEvent({
type: "component_registered",
data: enhancedDefinition,
timestamp: new Date(),
});
// 개발 모드에서만 로깅
if (process.env.NODE_ENV === "development") {
console.log(`[POP Registry] 컴포넌트 등록: ${definition.id}`);
}
}
/**
* 컴포넌트 등록 해제
*/
static unregisterComponent(id: string): void {
const definition = this.components.get(id);
if (!definition) {
console.warn(`[POP Registry] 등록되지 않은 컴포넌트 해제 시도: ${id}`);
return;
}
this.components.delete(id);
// 이벤트 발생
this.emitEvent({
type: "component_unregistered",
data: definition,
timestamp: new Date(),
});
console.log(`[POP Registry] 컴포넌트 해제: ${id}`);
}
/**
* 특정 컴포넌트 조회
*/
static getComponent(id: string): PopComponentDefinition | undefined {
return this.components.get(id);
}
/**
* URL로 컴포넌트 조회
*/
static getComponentByUrl(url: string): PopComponentDefinition | undefined {
// "@/lib/registry/pop-components/pop-card-list" → "pop-card-list"
const parts = url.split("/");
const componentId = parts[parts.length - 1];
return this.getComponent(componentId);
}
/**
* 모든 컴포넌트 조회
*/
static getAllComponents(): PopComponentDefinition[] {
return Array.from(this.components.values()).sort((a, b) => {
// 카테고리별 정렬, 그 다음 이름순
if (a.category !== b.category) {
return a.category.localeCompare(b.category);
}
return a.name.localeCompare(b.name);
});
}
/**
* 카테고리별 컴포넌트 조회
*/
static getComponentsByCategory(category: PopComponentCategory): PopComponentDefinition[] {
return Array.from(this.components.values())
.filter((def) => def.category === category)
.sort((a, b) => a.name.localeCompare(b.name));
}
/**
* 디바이스별 컴포넌트 조회
*/
static getComponentsByDevice(device: "mobile" | "tablet"): PopComponentDefinition[] {
return Array.from(this.components.values())
.filter((def) => def.supportedDevices?.includes(device))
.sort((a, b) => a.name.localeCompare(b.name));
}
/**
* 컴포넌트 검색
*/
static searchComponents(query: string): PopComponentDefinition[] {
const lowerQuery = query.toLowerCase();
return Array.from(this.components.values()).filter(
(def) =>
def.id.toLowerCase().includes(lowerQuery) ||
def.name.toLowerCase().includes(lowerQuery) ||
def.description?.toLowerCase().includes(lowerQuery)
);
}
/**
* 등록된 컴포넌트 개수
*/
static getComponentCount(): number {
return this.components.size;
}
/**
* 카테고리별 통계
*/
static getStatsByCategory(): Record<PopComponentCategory, number> {
const stats: Record<PopComponentCategory, number> = {
display: 0,
input: 0,
action: 0,
layout: 0,
feedback: 0,
};
for (const def of this.components.values()) {
stats[def.category]++;
}
return stats;
}
/**
* 이벤트 리스너 등록
*/
static addEventListener(callback: (event: PopComponentRegistryEvent) => void): void {
this.eventListeners.push(callback);
}
/**
* 이벤트 리스너 해제
*/
static removeEventListener(callback: (event: PopComponentRegistryEvent) => void): void {
const index = this.eventListeners.indexOf(callback);
if (index > -1) {
this.eventListeners.splice(index, 1);
}
}
/**
* 이벤트 발생
*/
private static emitEvent(event: PopComponentRegistryEvent): void {
for (const listener of this.eventListeners) {
try {
listener(event);
} catch (error) {
console.error("[POP Registry] 이벤트 리스너 오류:", error);
}
}
}
/**
* 레지스트리 초기화 (테스트용)
*/
static clear(): void {
this.components.clear();
console.log("[POP Registry] 레지스트리 초기화됨");
}
/**
* 컴포넌트 존재 여부 확인
*/
static hasComponent(id: string): boolean {
return this.components.has(id);
}
/**
* 디버그 정보 출력
*/
static debug(): void {
console.group("[POP Registry] 등록된 컴포넌트");
console.log(`${this.components.size}개 컴포넌트`);
console.table(
Array.from(this.components.values()).map((c) => ({
id: c.id,
name: c.name,
category: c.category,
touchOptimized: c.touchOptimized,
devices: c.supportedDevices?.join(", "),
}))
);
console.groupEnd();
}
}
// 기본 export
export default PopComponentRegistry;