- beforeFormSave 이벤트에 skipDefaultSave 플래그 추가 - UPSERT 처리 시 기본 저장 로직 건너뛰기 - 빈 엔트리 필터링으로 불필요한 레코드 생성 방지 |
||
|---|---|---|
| .. | ||
| CalculationBuilder.tsx | ||
| README.md | ||
| SelectedItemsDetailInputComponent.tsx | ||
| SelectedItemsDetailInputConfigPanel.tsx | ||
| SelectedItemsDetailInputRenderer.tsx | ||
| index.ts | ||
| types.ts | ||
README.md
SelectedItemsDetailInput 컴포넌트
선택된 항목들의 상세 정보를 입력하는 컴포넌트입니다.
개요
이 컴포넌트는 다음과 같은 흐름에서 사용됩니다:
- 첫 번째 모달: TableList에서 여러 항목 선택 (체크박스)
- 버튼 클릭: "다음" 버튼 클릭 → 선택된 데이터를 modalDataStore에 저장
- 두 번째 모달: SelectedItemsDetailInput이 자동으로 데이터를 읽어와서 표시
- 추가 입력: 각 항목별로 추가 정보 입력 (거래처 품번, 단가 등)
- 저장: 모든 데이터를 백엔드로 일괄 전송
주요 기능
- ✅ 전달받은 원본 데이터 표시 (읽기 전용)
- ✅ 각 항목별 추가 입력 필드 제공
- ✅ Grid/Table 레이아웃 또는 Card 레이아웃 지원
- ✅ 필드별 타입 지정 (text, number, date, select, checkbox, textarea)
- ✅ 필수 입력 검증
- ✅ 항목 삭제 기능 (선택적)
사용 방법
1단계: 첫 번째 모달 (품목 선택)
// TableList 컴포넌트 설정
{
type: "table-list",
config: {
selectedTable: "item_info",
multiSelect: true, // 다중 선택 활성화
columns: [
{ columnName: "item_code", label: "품목코드" },
{ columnName: "item_name", label: "품목명" },
{ columnName: "spec", label: "규격" },
{ columnName: "unit", label: "단위" },
{ columnName: "price", label: "단가" }
]
}
}
// "다음" 버튼 설정
{
type: "button-primary",
config: {
text: "다음 (상세정보 입력)",
action: {
type: "openModalWithData", // 새 액션 타입
targetScreenId: "123", // 두 번째 모달 화면 ID
dataSourceId: "table-list-456" // TableList 컴포넌트 ID
}
}
}
2단계: 두 번째 모달 (상세 입력)
// SelectedItemsDetailInput 컴포넌트 설정
{
type: "selected-items-detail-input",
config: {
dataSourceId: "table-list-456", // 첫 번째 모달의 TableList ID
targetTable: "sales_detail", // 최종 저장 테이블
layout: "grid", // 테이블 형식
// 전달받은 원본 데이터 중 표시할 컬럼
displayColumns: ["item_code", "item_name", "spec", "unit"],
// 추가 입력 필드 정의
additionalFields: [
{
name: "customer_item_code",
label: "거래처 품번",
type: "text",
required: false
},
{
name: "customer_item_name",
label: "거래처 품명",
type: "text",
required: false
},
{
name: "year",
label: "연도",
type: "select",
required: true,
options: [
{ value: "2024", label: "2024년" },
{ value: "2025", label: "2025년" }
]
},
{
name: "currency",
label: "통화단위",
type: "select",
required: true,
options: [
{ value: "KRW", label: "KRW (원)" },
{ value: "USD", label: "USD (달러)" }
]
},
{
name: "unit_price",
label: "단가",
type: "number",
required: true
},
{
name: "quantity",
label: "수량",
type: "number",
required: true
}
],
showIndex: true,
allowRemove: true // 항목 삭제 허용
}
}
3단계: 저장 버튼
{
type: "button-primary",
config: {
text: "저장",
action: {
type: "save",
targetTable: "sales_detail",
// formData에 selected_items 데이터가 자동으로 포함됨
}
}
}
데이터 구조
전달되는 데이터 형식
const modalData: ModalDataItem[] = [
{
id: "SALE-003", // 항목 ID
originalData: { // 원본 데이터 (TableList에서 선택한 행)
item_code: "SALE-003",
item_name: "와셔 M8",
spec: "M8",
unit: "EA",
price: 50
},
additionalData: { // 사용자가 입력한 추가 데이터
customer_item_code: "ABC-001",
customer_item_name: "와셔",
year: "2025",
currency: "KRW",
unit_price: 50,
quantity: 100
}
},
// ... 더 많은 항목들
];
설정 옵션
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
dataSourceId |
string | - | 데이터를 전달하는 컴포넌트 ID (필수) |
displayColumns |
string[] | [] | 표시할 원본 데이터 컬럼명 |
additionalFields |
AdditionalFieldDefinition[] | [] | 추가 입력 필드 정의 |
targetTable |
string | - | 최종 저장 대상 테이블 |
layout |
"grid" | "card" | "grid" | 레이아웃 모드 |
showIndex |
boolean | true | 항목 번호 표시 여부 |
allowRemove |
boolean | false | 항목 삭제 허용 여부 |
emptyMessage |
string | "전달받은 데이터가 없습니다." | 빈 상태 메시지 |
disabled |
boolean | false | 비활성화 여부 |
readonly |
boolean | false | 읽기 전용 여부 |
추가 필드 정의
interface AdditionalFieldDefinition {
name: string; // 필드명 (컬럼명)
label: string; // 필드 라벨
type: "text" | "number" | "date" | "select" | "checkbox" | "textarea";
required?: boolean; // 필수 입력 여부
placeholder?: string; // 플레이스홀더
defaultValue?: any; // 기본값
options?: Array<{ label: string; value: string }>; // 선택 옵션 (select 타입일 때)
validation?: { // 검증 규칙
min?: number;
max?: number;
minLength?: number;
maxLength?: number;
pattern?: string;
};
}
실전 예시: 수주 등록 화면
시나리오
- 품목 선택 모달에서 여러 품목 선택
- "다음" 버튼 클릭
- 각 품목별로 거래처 정보, 단가, 수량 입력
- "저장" 버튼으로 일괄 저장
구현
// [모달 1] 품목 선택
<TableList id="item-selection-table" multiSelect={true} />
<Button action="openModalWithData" targetScreenId="detail-input-modal" dataSourceId="item-selection-table" />
// [모달 2] 상세 입력
<SelectedItemsDetailInput
dataSourceId="item-selection-table"
displayColumns={["item_code", "item_name", "spec"]}
additionalFields={[
{ name: "customer_item_code", label: "거래처 품번", type: "text" },
{ name: "unit_price", label: "단가", type: "number", required: true },
{ name: "quantity", label: "수량", type: "number", required: true }
]}
targetTable="sales_detail"
/>
<Button action="save" />
주의사항
- dataSourceId 일치: 첫 번째 모달의 TableList ID와 두 번째 모달의 dataSourceId가 정확히 일치해야 합니다.
- 컬럼명 정확성: displayColumns와 additionalFields의 name은 실제 데이터베이스 컬럼명과 일치해야 합니다.
- 필수 필드 검증: required=true인 필드는 반드시 입력해야 저장이 가능합니다.
- 데이터 정리: 모달이 닫힐 때 modalDataStore의 데이터가 자동으로 정리됩니다.
향후 개선 사항
- 일괄 수정 기능 (모든 항목에 같은 값 적용)
- 엑셀 업로드로 일괄 입력
- 조건부 필드 표시 (특정 조건에서만 필드 활성화)
- 커스텀 검증 규칙
- 실시간 계산 필드 (단가 × 수량 = 금액)