Files
invyone/frontend/lib/fieldConfig/adapters.ts
T
DDD1542 5f945363b2 refactor: V2Date 일괄 폐기 + InvField type=date 통일
V2DateConfigPanel(262줄) + V2Date 본체(1046줄) + V2DateConfig 타입
+ lib/registry/components/v2-date/ + DynamicComponentRenderer 의
LEGACY alias 모두 삭제. 솔루션 정의 단계 정확한 1안.

데이터 모델
- FieldType / InputFieldType union 에 time, daterange 2종 추가하여 12종 확장
- canonical 키: dateFormat / minDate / maxDate / showToday / weekStart /
  dateDefault / rangePresets / maxRangeDays
- 옛 v2-date 의 snake_case (min_date / max_date / show_today) 와 dateType /
  type / range / format 키 충돌 종결

런타임 (InputComponent + pickers.tsx)
- V2Date 본체에 있던 SingleDatePicker / DateTimePicker / TimePicker /
  RangeDatePicker 4 picker 를 input/pickers.tsx 로 통합
- InputComponent 의 case "date"/"datetime" 의 native input 분기를 datepicker
  로 교체하고 case "time"/"daterange" 신규 추가
- type 결정 로직에 inputType prop 인식 (DB input_type 매핑) → date/time
  입력이 text 로 fallback 되던 silent breakage 해결

FC 계층
- FieldRenderer / CellRenderer / FcSearch 에 time / daterange 분기 추가
- TimeField, DateRangeField 신규 컴포넌트
- adapters.normalizeType allowed 배열 확장

ConfigPanel
- InvFieldConfigPanel.DateOptions 에 showToday CPRow + CPSwitch 신규
- 옛 호환 코드 (showSeconds:ss 보정 / dateType-format 격상 등) 모두 제거
- InvInputConfigPanel.TYPE_OPTIONS / 날짜 옵션 분기에 time/daterange 추가

dead code 삭제 14곳 + 잔존 정리
- V2Date / V2DateConfigPanel / V2DateConfig / lib/registry/v2-date/ 폴더
- LEGACY_TO_UNIFIED / CONFIG_PANEL_MAP / CONFIG_PANEL_ALIAS / register /
  V2PropertiesPanel hardcoded require / config-panels barrel / hidden 목록
- componentConfig 스키마, templateMigrate, webTypeMapping, DynamicConfigPanel
- withContainerQuery.css 의 v2-date 컨테이너 룰
- db/migrations/so_modal_layout(_kr).json 의 v2-date → input + type=date

37 files, +287 / -854.

Codex GO 판정 기준 (2회 NO-GO 후 FC 계층 / inputType prop / FieldType union /
잔존 v2-date / console.log 모두 처리 후 GO).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:22:55 +09:00

180 lines
5.5 KiB
TypeScript

/**
* FieldConfig Adapter — INVYONE FieldConfig[] 를 기존 v2-* 컴포넌트 내부 포맷
* (ColumnConfig / SearchField / FormField) 으로 변환한다.
*
* 역할:
* 화면 디자이너에서 Screen 수준으로 정의한 fields 배열을 각 컴포넌트가
* 소비할 수 있는 기존 포맷으로 풀어주는 브리지. 컴포넌트 자체를 FieldConfig
* 네이티브로 리팩토링하기 전까지의 호환 레이어.
*
* 원칙:
* - 호환성 우선. 기존 포맷에 없는 필드는 버리고 기본값은 보존.
* - 타입은 Record<string, any>. v2-* 포맷이 컴포넌트마다 달라 강타입 못 씀.
* - fields 가 없으면 호출부는 fallback 으로 기존 config.columns 사용 (이 파일
* 호출 자체를 건너뛰어야 함).
*/
import type { FieldConfig } from "@/types/invyone-component";
/**
* FieldConfig[] → v2-table-list 의 ColumnConfig[] 호환 배열.
*
* 상세 매핑 규칙은 v2-table-list 내부 포맷 확정 후 보강한다. 현재는 공통 필드
* (column_name / column_label / visible / display_order / width / align / sortable)
* 만 매핑.
*/
export function fieldsToColumns(
fields: FieldConfig[],
): Record<string, any>[] {
return [...fields]
.filter((f) => f.visible !== false && !f.system)
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
.map((f) => ({
column_name: f.column,
column_label: f.label,
label: f.label,
visible: f.visible !== false,
display_order: f.order ?? 0,
width: f.width,
align: f.align ?? (f.type === "number" ? "right" : "left"),
sortable: f.sortable ?? true,
data_type: f.type,
format: f.format,
pk: f.pk ?? false,
editable: f.editable ?? true,
}));
}
/**
* FieldConfig[] → 검색 위젯의 SearchField[] 호환 배열.
*
* searchable: true 인 것만 포함. 타입별 기본 검색 모드 매핑:
* date/datetime/number → 'range'
* select → 'multi'
* entity → 'single' (팝업)
* code → 'exact'
* text/textarea → 'partial'
* checkbox → 'tri' (전체/✓/✗)
*/
export function fieldsToSearchFields(
fields: FieldConfig[],
): Record<string, any>[] {
return fields
.filter((f) => f.searchable && !f.system)
.map((f) => ({
column_name: f.column,
column_label: f.label,
label: f.label,
data_type: f.type,
search_mode: getDefaultSearchMode(f.type),
options: f.options,
ref: f.ref,
default_value: f.defaultValue,
}));
}
/**
* FieldConfig[] → 폼 컴포넌트의 FormField[] 호환 배열.
*
* system 필드는 자동 제외. required / editable / options / ref / computed 는
* 그대로 전달. pk 이고 code 타입이면 readonly 자동 설정 (자동채번).
*/
export function fieldsToFormFields(
fields: FieldConfig[],
): Record<string, any>[] {
return [...fields]
.filter((f) => f.visible !== false && !f.system)
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
.map((f) => ({
column_name: f.column,
column_label: f.label,
label: f.label,
data_type: f.type,
required: f.required ?? false,
editable: f.editable ?? true,
readonly: f.editable === false || f.type === "code",
default_value: f.defaultValue,
placeholder: f.placeholder,
options: f.options,
ref: f.ref,
format: f.format,
computed: f.computed,
pk: f.pk ?? false,
}));
}
/**
* 역방향: 기존 컬럼 포맷 → FieldConfig[] 추정.
*
* 마이그레이션 도우미. 완벽하지 않음 (검색 모드 역추정 불가, computed 등 누락
* 가능). 화면 디자이너에서 기존 Screen 을 "FieldConfig 로 자동 채우기" 할 때
* 초기값 생성용.
*/
export function columnsToFields(
columns: Record<string, any>[],
): FieldConfig[] {
return columns.map((col, idx) => ({
column: col.column_name ?? col.column ?? `col_${idx}`,
label:
col.column_label ?? col.label ?? col.column_name ?? `컬럼 ${idx + 1}`,
type: normalizeType(col.data_type ?? col.type ?? "text"),
visible: col.visible !== false,
order: col.display_order ?? idx,
required: col.required === true,
editable: col.editable !== false,
width: col.width,
align: col.align,
sortable: col.sortable !== false,
searchable: col.searchable === true,
format: col.format,
options: col.options,
ref: col.ref,
pk: col.pk === true,
system: col.system === true,
}));
}
// ─── 내부 헬퍼 ──────────────────────────────────────────────────────────
type SearchMode = "exact" | "partial" | "range" | "multi" | "single" | "tri";
function getDefaultSearchMode(type: FieldConfig["type"]): SearchMode {
switch (type) {
case "date":
case "datetime":
case "number":
return "range";
case "select":
return "multi";
case "entity":
return "single";
case "code":
return "exact";
case "checkbox":
return "tri";
case "text":
case "textarea":
default:
return "partial";
}
}
function normalizeType(raw: string): FieldConfig["type"] {
const allowed: FieldConfig["type"][] = [
"text",
"number",
"date",
"datetime",
"time",
"daterange",
"select",
"entity",
"checkbox",
"textarea",
"file",
"code",
];
return (allowed as string[]).includes(raw)
? (raw as FieldConfig["type"])
: "text";
}