a5bbd1eb7c
- 옛 registry/numbering-rule, registry/v2-numbering-rule, V2NumberingRuleConfigPanel, NumberingRuleTemplate 폐기 — InvFieldConfigPanel + InputComponent 로 통합 - input 에 numbering-picker / select-pickers 추가, autonum 타입 흡수 - 채번 관리 전용 admin 페이지(systemMng/numberingRuleList) + CreateDialog + SequenceManagementPanel 신설 - backend NumberingRule controller/service/mapper 갱신 (시퀀스 관리 엔드포인트) - input canonical 진행 노트 + 채번 관리 mockup 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
164 lines
5.2 KiB
TypeScript
164 lines
5.2 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* BlockRenderer — 하나의 BlockV2 를 실제 React 컴포넌트로 렌더한다.
|
|
* ComponentRegistry 에서 componentId 로 정의를 찾아 위임.
|
|
*
|
|
* TemplateRenderer (line grid) 와 PopupTemplateRenderer (absolute) 양쪽에서
|
|
* 공유되는 최소 단위. 별도 파일로 분리한 이유는 팝업 번들 경량화 —
|
|
* PopupTemplateRenderer 가 TemplateRenderer 전체(line layout 알고리즘 800+줄)
|
|
* 를 딸려 로드하지 않도록.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import type { BlockV2, CanvasV2 } from '@/types/invyone-component';
|
|
import { ComponentRegistry } from '@/lib/registry/ComponentRegistry';
|
|
// side-effect: 컴포넌트 레지스트리 등록
|
|
import '@/lib/registry/components';
|
|
import type { TemplateRenderContext, ViewKey } from './TemplateRenderer';
|
|
|
|
export function BlockRenderer({
|
|
block,
|
|
context,
|
|
view,
|
|
canvas,
|
|
runtimeSize,
|
|
}: {
|
|
block: BlockV2;
|
|
context: TemplateRenderContext;
|
|
view: ViewKey;
|
|
/**
|
|
* 선택: 제공되면 block 의 %좌표를 px 로 환산해 컴포넌트에 전달한다.
|
|
* line grid 의 실제 runtime cell 크기를 component.size 로 전달한다.
|
|
*/
|
|
canvas?: CanvasV2;
|
|
runtimeSize?: {
|
|
width: number;
|
|
height: number;
|
|
};
|
|
}) {
|
|
const resolvedTableName =
|
|
block.config?.selectedTable ||
|
|
block.config?.tableName ||
|
|
context.primaryTable;
|
|
const resolvedColumnName =
|
|
block.config?.columnName ||
|
|
block.config?.column_name ||
|
|
block.config?.fieldKey ||
|
|
block.config?.bindField ||
|
|
block.config?.column;
|
|
const resolvedValue =
|
|
resolvedColumnName != null
|
|
? context.formRow?.[resolvedColumnName]
|
|
: undefined;
|
|
// 사용자가 ConfigPanel 에 설정한 defaultValue 를 form data 값이 없을 때 보존.
|
|
// formRow[col] 가 의미있는 값일 때만 defaultValue 로 hijack — undefined/null 은 무시.
|
|
const runtimeConfig =
|
|
resolvedColumnName != null && resolvedValue !== undefined && resolvedValue !== null
|
|
? { ...block.config, defaultValue: resolvedValue }
|
|
: block.config;
|
|
const handleFormValueChange = (
|
|
fieldNameOrPatch: string | Record<string, any>,
|
|
value?: any,
|
|
) => {
|
|
if (typeof fieldNameOrPatch === 'string') {
|
|
context.onFormRowChange?.({ [fieldNameOrPatch]: value });
|
|
return;
|
|
}
|
|
context.onFormRowChange?.(fieldNameOrPatch);
|
|
};
|
|
|
|
const def = ComponentRegistry.getComponent(block.componentId);
|
|
if (!def?.component) {
|
|
return (
|
|
<div className="flex h-full w-full items-center justify-center rounded border border-dashed border-slate-300 bg-slate-50 p-2 text-center text-[10px] text-slate-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
|
|
<div>
|
|
<div className="font-mono text-[11px] font-bold">
|
|
{block.componentId || '(empty)'}
|
|
</div>
|
|
<div className="opacity-70">미등록</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
const Cmp = def.component as React.ComponentType<any>;
|
|
|
|
const bw = canvas?.baseWidth ?? 0;
|
|
const bh = canvas?.baseHeight ?? 0;
|
|
const position = {
|
|
x: bw > 0 ? block.xPct * bw : 0,
|
|
y: bh > 0 ? block.yPct * bh : 0,
|
|
z: 1,
|
|
};
|
|
const size = {
|
|
width: bw > 0 ? block.wPct * bw : 0,
|
|
height: bh > 0 ? block.hPct * bh : 0,
|
|
};
|
|
const isButtonLike =
|
|
block.componentId === 'button' ||
|
|
block.componentId === 'button-bar' ||
|
|
block.componentId === 'pagination';
|
|
const effectiveSize =
|
|
isButtonLike && runtimeSize
|
|
? {
|
|
width:
|
|
runtimeSize.width > 0
|
|
? Math.min(size.width, Math.max(0, runtimeSize.width - 2))
|
|
: size.width,
|
|
height:
|
|
runtimeSize.height > 0
|
|
? Math.min(size.height, Math.max(0, runtimeSize.height - 2))
|
|
: size.height,
|
|
}
|
|
: size;
|
|
|
|
return (
|
|
<Cmp
|
|
component={{
|
|
id: block.id,
|
|
componentType: block.componentId,
|
|
tableName: resolvedTableName,
|
|
columnName: resolvedColumnName,
|
|
column_name: resolvedColumnName,
|
|
value: resolvedValue,
|
|
position,
|
|
size: effectiveSize,
|
|
componentConfig: runtimeConfig,
|
|
component_config: runtimeConfig,
|
|
style: {},
|
|
}}
|
|
componentConfig={runtimeConfig}
|
|
config={runtimeConfig}
|
|
tableName={resolvedTableName}
|
|
columnName={resolvedColumnName}
|
|
column_name={resolvedColumnName}
|
|
value={resolvedValue}
|
|
isDesignMode={false}
|
|
isPreview={true}
|
|
isInteractive={true}
|
|
formData={context.formRow}
|
|
form_data={context.formRow}
|
|
onFormDataChange={(fieldName: string, value: any) =>
|
|
handleFormValueChange(fieldName, value)
|
|
}
|
|
onChange={(value: any) =>
|
|
resolvedColumnName
|
|
? handleFormValueChange(resolvedColumnName, value)
|
|
: undefined
|
|
}
|
|
originalData={context.formRow}
|
|
_originalData={context.formRow}
|
|
onSearch={context.onSearch}
|
|
searchParams={context.searchParams}
|
|
onRowSelect={context.onRowSelect}
|
|
onAdd={context.onAdd}
|
|
onEdit={context.onEdit}
|
|
onDelete={context.onDelete}
|
|
onFormSubmit={context.onFormSubmit}
|
|
onFormCancel={context.onFormCancel}
|
|
selectedRow={context.selectedRow}
|
|
view={view}
|
|
/>
|
|
);
|
|
}
|