# 현재 구현 계획: pop-card-list 입력 필드/계산 필드 구조 개편

> **작성일**: 2026-02-24
> **상태**: 계획 완료, 코딩 대기
> **목적**: 입력 필드 설정 단순화 + 본문 필드에 계산식 통합 + 기존 계산 필드 섹션 제거

---

## 1. 변경 개요

### 배경
- 기존: "입력 필드", "계산 필드", "담기 버튼" 3개가 별도 섹션으로 분리
- 문제: 계산 필드가 본문 필드와 동일한 위치에 표시되어야 하는데 별도 영역에 있음
- 문제: 입력 필드의 min/max 고정값은 비실용적 (실제로는 DB 컬럼 기준 제한이 필요)
- 문제: step, columnName, sourceColumns, resultColumn 등 죽은 코드 존재

### 목표
1. **본문 필드에 계산식 지원 추가** - 필드별로 "DB 컬럼" 또는 "계산식" 선택
2. **입력 필드 설정 단순화** - 고정 min/max 제거, 제한 기준 컬럼 방식으로 변경
3. **기존 "계산 필드" 섹션 제거** - 본문 필드에 통합되므로 불필요
4. **죽은 코드 정리**

---

## 2. 수정 대상 파일 (3개)

### 파일 A: `frontend/lib/registry/pop-components/types.ts`

#### 변경 A-1: CardFieldBinding 타입 확장

**현재 코드** (라인 367~372):
```typescript
export interface CardFieldBinding {
  id: string;
  columnName: string;
  label: string;
  textColor?: string;
}
```

**변경 코드**:
```typescript
export interface CardFieldBinding {
  id: string;
  label: string;
  textColor?: string;
  valueType: "column" | "formula";  // 값 유형: DB 컬럼 또는 계산식
  columnName?: string;              // valueType === "column"일 때 사용
  formula?: string;                 // valueType === "formula"일 때 사용 (예: "$input - received_qty")
  unit?: string;                    // 계산식일 때 단위 표시 (예: "EA")
}
```

**주의**: `columnName`이 required에서 optional로 변경됨. 기존 저장 데이터와의 하위 호환 필요.

#### 변경 A-2: CardInputFieldConfig 단순화

**현재 코드** (라인 443~453):
```typescript
export interface CardInputFieldConfig {
  enabled: boolean;
  columnName?: string;
  label?: string;
  unit?: string;
  defaultValue?: number;
  min?: number;
  max?: number;
  maxColumn?: string;
  step?: number;
}
```

**변경 코드**:
```typescript
export interface CardInputFieldConfig {
  enabled: boolean;
  label?: string;
  unit?: string;
  limitColumn?: string;           // 제한 기준 컬럼 (해당 행의 이 컬럼 값이 최대값)
  saveTable?: string;             // 저장 대상 테이블
  saveColumn?: string;            // 저장 대상 컬럼
  showPackageUnit?: boolean;      // 포장등록 버튼 표시 여부
}
```

**제거 항목**:
- `columnName` -> `saveTable` + `saveColumn`으로 대체 (명확한 네이밍)
- `defaultValue` -> 제거 (제한 기준 컬럼 값으로 대체)
- `min` -> 제거 (항상 0)
- `max` -> 제거 (`limitColumn`으로 대체)
- `maxColumn` -> `limitColumn`으로 이름 변경
- `step` -> 제거 (키패드 방식에서 미사용)

#### 변경 A-3: CardCalculatedFieldConfig 제거

**삭제**: `CardCalculatedFieldConfig` 인터페이스 전체 (라인 457~464)
**삭제**: `PopCardListConfig`에서 `calculatedField?: CardCalculatedFieldConfig;` 제거

---

### 파일 B: `frontend/lib/registry/pop-components/pop-card-list/PopCardListConfig.tsx`

#### 변경 B-1: 본문 필드 편집기(FieldEditor)에 값 유형 선택 추가

**현재**: 필드 편집 시 라벨, 컬럼, 텍스트색상만 설정 가능

**변경**: 값 유형 라디오("DB 컬럼" / "계산식") 추가
- "DB 컬럼" 선택 시: 기존 컬럼 Select 표시
- "계산식" 선택 시: 수식 입력란 + 사용 가능한 컬럼/변수 칩 목록 표시
- 사용 가능한 변수: DB 컬럼명들 + `$input` (입력 필드 활성화 시)

**하위 호환**: 기존 저장 데이터에 `valueType`이 없으면 `"column"`으로 기본 처리

#### 변경 B-2: 입력 필드 설정 섹션 개편

**현재 설정 항목**: 라벨, 단위, 기본값, 최소/최대, 최대값 컬럼, 저장 컬럼

**변경 설정 항목**:
```
라벨           [입고 수량       ]
단위           [EA             ]
제한 기준 컬럼  [ order_qty     v ]
저장 대상 테이블 [ 선택          v ]
저장 대상 컬럼   [ 선택          v ]
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
포장등록 버튼                [on/off]
```

#### 변경 B-3: "계산 필드" 섹션 제거

**삭제**: `CalculatedFieldSettingsSection` 함수 전체
**삭제**: 카드 템플릿 탭에서 "계산 필드" CollapsibleSection 제거

#### 변경 B-4: import 정리

**삭제**: `CardCalculatedFieldConfig` import
**추가**: 없음 (기존 import 재사용)

---

### 파일 C: `frontend/lib/registry/pop-components/pop-card-list/PopCardListComponent.tsx`

#### 변경 C-1: FieldRow에서 계산식 필드 지원

**현재**: `const value = row[field.columnName]` 로 DB 값만 표시

**변경**:
```typescript
function FieldRow({ field, row, scaled, inputValue }: {
  field: CardFieldBinding;
  row: RowData;
  scaled: ScaledConfig;
  inputValue?: number;  // 입력 필드 값 (계산식에서 $input으로 참조)
}) {
  const value = field.valueType === "formula" && field.formula
    ? evaluateFormula(field.formula, row, inputValue ?? 0)
    : row[field.columnName ?? ""];
  // ...
}
```

**주의**: `inputValue`를 FieldRow까지 전달해야 하므로 CardItem -> FieldRow 경로에 prop 추가 필요

#### 변경 C-2: 계산식 필드 실시간 갱신

**현재**: 별도 `calculatedValue` useMemo가 `[calculatedField, row, inputValue]`에 반응

**변경**: FieldRow가 `inputValue` prop을 받으므로, `inputValue`가 변경될 때 계산식 필드가 자동으로 리렌더링됨. 별도 useMemo 불필요.

#### 변경 C-3: 기존 calculatedField 관련 코드 제거

**삭제 대상**:
- `calculatedField` prop 전달 (CardItem)
- `calculatedValue` useMemo
- 계산 필드 렌더링 블록 (`{calculatedField?.enabled && calculatedValue !== null && (...)}`

#### 변경 C-4: 입력 필드 로직 단순화

**변경 대상**:
- `effectiveMax`: `limitColumn` 사용, 미설정 시 999999 폴백
- `defaultValue` 자동 초기화 로직 제거 (불필요)
- `NumberInputModal`에 포장등록 on/off 전달

#### 변경 C-5: NumberInputModal에 포장등록 on/off 전달

**현재**: 포장등록 버튼 항상 표시
**변경**: `showPackageUnit` prop 추가, false이면 포장등록 버튼 숨김

---

### 파일 D: `frontend/lib/registry/pop-components/pop-card-list/NumberInputModal.tsx`

#### 변경 D-1: showPackageUnit prop 추가

**현재 props**: open, onOpenChange, unit, initialValue, initialPackageUnit, min, maxValue, onConfirm

**추가 prop**: `showPackageUnit?: boolean` (기본값 true)

**변경**: `showPackageUnit === false`이면 포장등록 버튼 숨김

---

## 3. 구현 순서 (의존성 기반)

| 순서 | 작업 | 파일 | 의존성 | 상태 |
|------|------|------|--------|------|
| 1 | A-1: CardFieldBinding 타입 확장 | types.ts | 없음 | [ ] |
| 2 | A-2: CardInputFieldConfig 단순화 | types.ts | 없음 | [ ] |
| 3 | A-3: CardCalculatedFieldConfig 제거 | types.ts | 없음 | [ ] |
| 4 | B-1: FieldEditor에 값 유형 선택 추가 | PopCardListConfig.tsx | 순서 1 | [ ] |
| 5 | B-2: 입력 필드 설정 섹션 개편 | PopCardListConfig.tsx | 순서 2 | [ ] |
| 6 | B-3: 계산 필드 섹션 제거 | PopCardListConfig.tsx | 순서 3 | [ ] |
| 7 | B-4: import 정리 | PopCardListConfig.tsx | 순서 6 | [ ] |
| 8 | D-1: NumberInputModal showPackageUnit 추가 | NumberInputModal.tsx | 없음 | [ ] |
| 9 | C-1: FieldRow 계산식 지원 | PopCardListComponent.tsx | 순서 1 | [ ] |
| 10 | C-3: calculatedField 관련 코드 제거 | PopCardListComponent.tsx | 순서 9 | [ ] |
| 11 | C-4: 입력 필드 로직 단순화 | PopCardListComponent.tsx | 순서 2, 8 | [ ] |
| 12 | 린트 검사 | 전체 | 순서 1~11 | [ ] |

순서 1, 2, 3은 독립이므로 병렬 가능.
순서 8은 독립이므로 병렬 가능.

---

## 4. 사전 충돌 검사 결과

### 새로 추가할 식별자 목록

| 식별자 | 타입 | 정의 파일 | 사용 파일 | 충돌 여부 |
|--------|------|-----------|-----------|-----------|
| `valueType` | CardFieldBinding 속성 | types.ts | PopCardListConfig.tsx, PopCardListComponent.tsx | 충돌 없음 |
| `formula` | CardFieldBinding 속성 | types.ts | PopCardListConfig.tsx, PopCardListComponent.tsx | 충돌 없음 (기존 CardCalculatedFieldConfig.formula와 다른 인터페이스) |
| `limitColumn` | CardInputFieldConfig 속성 | types.ts | PopCardListConfig.tsx, PopCardListComponent.tsx | 충돌 없음 |
| `saveTable` | CardInputFieldConfig 속성 | types.ts | PopCardListConfig.tsx | 충돌 없음 |
| `saveColumn` | CardInputFieldConfig 속성 | types.ts | PopCardListConfig.tsx | 충돌 없음 |
| `showPackageUnit` | CardInputFieldConfig 속성 / NumberInputModal prop | types.ts, NumberInputModal.tsx | PopCardListComponent.tsx | 충돌 없음 |

### 기존 타입/함수 재사용 목록

| 기존 식별자 | 정의 위치 | 이번 수정에서 사용하는 곳 |
|------------|-----------|------------------------|
| `evaluateFormula()` | PopCardListComponent.tsx 라인 1026 | C-1: FieldRow에서 호출 (기존 함수 그대로 재사용) |
| `CardFieldBinding` | types.ts 라인 367 | A-1에서 수정, B-1/C-1에서 사용 |
| `CardInputFieldConfig` | types.ts 라인 443 | A-2에서 수정, B-2/C-4에서 사용 |
| `GroupedColumnSelect` | PopCardListConfig.tsx | B-1: 계산식 모드에서 컬럼 칩 표시에 재사용 가능 |

**사용처 있는데 정의 누락된 항목: 없음**

---

## 5. 에러 함정 경고

### 함정 1: 기존 저장 데이터 하위 호환
기존에 저장된 `CardFieldBinding`에는 `valueType`이 없고 `columnName`이 필수였음.
**반드시** 런타임에서 `field.valueType || "column"` 폴백 처리해야 함.
Config UI에서도 `valueType` 미존재 시 `"column"` 기본값 적용 필요.

### 함정 2: CardInputFieldConfig 하위 호환
기존 `maxColumn`이 `limitColumn`으로 이름 변경됨.
기존 저장 데이터의 `maxColumn`을 `limitColumn`으로 읽어야 함.
런타임: `inputField?.limitColumn || (inputField as any)?.maxColumn` 폴백 필요.

### 함정 3: evaluateFormula의 inputValue 전달
FieldRow에 `inputValue`를 전달하려면, CardItem -> body.fields.map -> FieldRow 경로에서 `inputValue` prop을 추가해야 함.
입력 필드가 비활성화된 경우 `inputValue`는 0으로 전달.

### 함정 4: calculatedField 제거 시 기존 데이터
기존 config에 `calculatedField` 데이터가 남아 있을 수 있음.
타입에서 제거하더라도 런타임 에러는 나지 않음 (unknown 속성은 무시됨).
다만 이전에 계산 필드로 설정한 내용은 사라짐 - 마이그레이션 없이 제거.

### 함정 5: columnName optional 변경
`CardFieldBinding.columnName`이 optional이 됨.
기존에 `row[field.columnName]`으로 직접 접근하던 코드 전부 수정 필요.
`field.columnName ?? ""` 또는 valueType 분기 처리.

---

## 6. 검증 방법

### 시나리오 1: 기존 본문 필드 (하위 호환)
1. 기존 저장된 카드리스트 열기
2. 본문 필드에 기존 DB 컬럼 필드가 정상 표시되는지 확인
3. 설정 패널에서 기존 필드가 "DB 컬럼" 유형으로 표시되는지 확인

### 시나리오 2: 계산식 본문 필드 추가
1. 본문 필드 추가 -> 값 유형 "계산식" 선택
2. 수식: `order_qty - received_qty` 입력
3. 카드에서 계산 결과가 정상 표시되는지 확인

### 시나리오 3: $input 참조 계산식
1. 입력 필드 활성화
2. 본문 필드 추가 -> 값 유형 "계산식" -> 수식: `$input - received_qty`
3. 키패드에서 수량 입력 시 계산 결과가 실시간 갱신되는지 확인

### 시나리오 4: 제한 기준 컬럼
1. 입력 필드 -> 제한 기준 컬럼: `order_qty`
2. order_qty=1000인 카드에서 키패드 열기
3. MAX 버튼 클릭 시 1000이 입력되고, 1001 이상 입력 불가 확인

### 시나리오 5: 포장등록 on/off
1. 입력 필드 -> 포장등록 버튼: off
2. 키패드 모달에서 포장등록 버튼이 숨겨지는지 확인

---

## 이전 완료 계획 (아카이브)

<details>
<summary>pop-dashboard 4가지 아이템 모드 완성 (완료)</summary>

- [x] groupBy UI 추가
- [x] xAxisColumn 입력 UI 추가
- [x] 통계카드 카테고리 설정 UI 추가
- [x] 차트 xAxisColumn 자동 보정 로직
- [x] 통계카드 카테고리별 필터 적용
- [x] SQL 빌더 방어 로직
- [x] refreshInterval 최소값 강제

</details>

<details>
<summary>POP 뷰어 스크롤 수정 (완료)</summary>

- [x] overflow-hidden 제거
- [x] overflow-auto 공통 적용
- [x] 일반 모드 min-h-full 추가

</details>

<details>
<summary>POP 뷰어 실제 컴포넌트 렌더링 (완료)</summary>

- [x] 뷰어 페이지에 레지스트리 초기화 import 추가
- [x] renderActualComponent() 실제 컴포넌트 렌더링으로 교체

</details>
