ERP-node/frontend/lib/schemas/popComponentConfig.ts

232 lines
7.4 KiB
TypeScript

/**
* POP 컴포넌트 설정 스키마 및 유틸리티
*
* POP(모바일/태블릿) 컴포넌트의 overrides 스키마 및 기본값을 관리
* - 공통 요소는 componentConfig.ts에서 import하여 재사용
* - POP 전용 컴포넌트의 overrides 스키마만 새로 정의
*/
import { z } from "zod";
// ============================================
// 공통 요소 재사용 (componentConfig.ts에서 import)
// ============================================
export {
// 공통 스키마
customConfigSchema,
componentV2Schema,
layoutV2Schema,
// 공통 유틸리티 함수
deepMerge,
mergeComponentConfig,
extractCustomConfig,
isDeepEqual,
getComponentTypeFromUrl,
} from "./componentConfig";
// ============================================
// POP 전용 URL 생성 함수
// ============================================
export function getPopComponentUrl(componentType: string): string {
return `@/lib/registry/pop-components/${componentType}`;
}
// ============================================
// POP 전용 컴포넌트 기본값
// ============================================
// POP 카드 리스트 기본값
export const popCardListDefaults = {
displayMode: "card" as const,
cardStyle: "compact" as const,
showHeader: true,
showFooter: false,
pageSize: 10,
enablePullToRefresh: true,
enableInfiniteScroll: false,
cardColumns: 1,
gap: 8,
padding: 16,
// 터치 최적화
touchFeedback: true,
swipeActions: false,
};
// POP 터치 버튼 기본값
export const popTouchButtonDefaults = {
variant: "primary" as const,
size: "lg" as const,
text: "확인",
icon: null,
iconPosition: "left" as const,
fullWidth: true,
// 터치 최적화
minHeight: 48, // 최소 터치 영역 48px
hapticFeedback: true,
pressDelay: 0,
};
// POP 스캐너 입력 기본값
export const popScannerInputDefaults = {
placeholder: "바코드를 스캔하세요",
showKeyboard: false,
autoFocus: true,
autoSubmit: true,
submitDelay: 300,
// 스캐너 설정
scannerMode: "auto" as const,
beepOnScan: true,
vibrationOnScan: true,
clearOnSubmit: true,
};
// POP 상태 배지 기본값
export const popStatusBadgeDefaults = {
variant: "default" as const,
size: "md" as const,
text: "",
icon: null,
// 스타일
rounded: true,
pulse: false,
};
// ============================================
// POP 전용 overrides 스키마
// ============================================
// POP 카드 리스트 overrides 스키마
export const popCardListOverridesSchema = z
.object({
displayMode: z.enum(["card", "list", "grid"]).default("card"),
cardStyle: z.enum(["compact", "default", "expanded"]).default("compact"),
showHeader: z.boolean().default(true),
showFooter: z.boolean().default(false),
pageSize: z.number().default(10),
enablePullToRefresh: z.boolean().default(true),
enableInfiniteScroll: z.boolean().default(false),
cardColumns: z.number().default(1),
gap: z.number().default(8),
padding: z.number().default(16),
touchFeedback: z.boolean().default(true),
swipeActions: z.boolean().default(false),
// 데이터 바인딩
tableName: z.string().optional(),
columns: z.array(z.string()).optional(),
titleField: z.string().optional(),
subtitleField: z.string().optional(),
statusField: z.string().optional(),
})
.passthrough();
// POP 터치 버튼 overrides 스키마
export const popTouchButtonOverridesSchema = z
.object({
variant: z.enum(["primary", "secondary", "success", "warning", "danger", "ghost"]).default("primary"),
size: z.enum(["sm", "md", "lg", "xl"]).default("lg"),
text: z.string().default("확인"),
icon: z.string().nullable().default(null),
iconPosition: z.enum(["left", "right", "top", "bottom"]).default("left"),
fullWidth: z.boolean().default(true),
minHeight: z.number().default(48),
hapticFeedback: z.boolean().default(true),
pressDelay: z.number().default(0),
// 액션
actionType: z.string().optional(),
actionParams: z.record(z.string(), z.any()).optional(),
})
.passthrough();
// POP 스캐너 입력 overrides 스키마
export const popScannerInputOverridesSchema = z
.object({
placeholder: z.string().default("바코드를 스캔하세요"),
showKeyboard: z.boolean().default(false),
autoFocus: z.boolean().default(true),
autoSubmit: z.boolean().default(true),
submitDelay: z.number().default(300),
scannerMode: z.enum(["auto", "camera", "external"]).default("auto"),
beepOnScan: z.boolean().default(true),
vibrationOnScan: z.boolean().default(true),
clearOnSubmit: z.boolean().default(true),
// 데이터 바인딩
tableName: z.string().optional(),
columnName: z.string().optional(),
})
.passthrough();
// POP 상태 배지 overrides 스키마
export const popStatusBadgeOverridesSchema = z
.object({
variant: z.enum(["default", "success", "warning", "danger", "info"]).default("default"),
size: z.enum(["sm", "md", "lg"]).default("md"),
text: z.string().default(""),
icon: z.string().nullable().default(null),
rounded: z.boolean().default(true),
pulse: z.boolean().default(false),
// 조건부 스타일
conditionField: z.string().optional(),
conditionMapping: z.record(z.string(), z.string()).optional(),
})
.passthrough();
// ============================================
// POP 컴포넌트 overrides 스키마 레지스트리
// ============================================
export const popComponentOverridesSchemaRegistry: Record<string, z.ZodTypeAny> = {
"pop-card-list": popCardListOverridesSchema,
"pop-touch-button": popTouchButtonOverridesSchema,
"pop-scanner-input": popScannerInputOverridesSchema,
"pop-status-badge": popStatusBadgeOverridesSchema,
};
// ============================================
// POP 컴포넌트 기본값 레지스트리
// ============================================
export const popComponentDefaultsRegistry: Record<string, Record<string, any>> = {
"pop-card-list": popCardListDefaults,
"pop-touch-button": popTouchButtonDefaults,
"pop-scanner-input": popScannerInputDefaults,
"pop-status-badge": popStatusBadgeDefaults,
};
// ============================================
// POP 기본값 조회 함수
// ============================================
export function getPopComponentDefaults(componentType: string): Record<string, any> {
return popComponentDefaultsRegistry[componentType] || {};
}
// ============================================
// POP URL로 기본값 조회
// ============================================
export function getPopDefaultsByUrl(componentUrl: string): Record<string, any> {
// "@/lib/registry/pop-components/pop-card-list" → "pop-card-list"
const parts = componentUrl.split("/");
const componentType = parts[parts.length - 1];
return getPopComponentDefaults(componentType);
}
// ============================================
// POP overrides 파싱 및 검증
// ============================================
export function parsePopOverridesByUrl(
componentUrl: string,
overrides: Record<string, any>,
): Record<string, any> {
const parts = componentUrl.split("/");
const componentType = parts[parts.length - 1];
const schema = popComponentOverridesSchemaRegistry[componentType];
if (!schema) {
// 스키마 없으면 그대로 반환
return overrides || {};
}
try {
return schema.parse(overrides || {});
} catch {
// 파싱 실패 시 기본값 반환
return getPopComponentDefaults(componentType);
}
}