|
|
|
|
@ -146,53 +146,62 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
|
|
|
|
|
const newOptions: Record<string, Array<{ label: string; value: string }>> = { ...codeOptions };
|
|
|
|
|
|
|
|
|
|
// 🆕 대상 테이블의 컬럼 메타데이터에서 codeCategory 가져오기
|
|
|
|
|
const targetTable = componentConfig.targetTable;
|
|
|
|
|
let targetTableColumns: any[] = [];
|
|
|
|
|
// 🆕 그룹별 sourceTable 매핑 구성
|
|
|
|
|
const groups = componentConfig.fieldGroups || [];
|
|
|
|
|
const groupSourceTableMap: Record<string, string> = {};
|
|
|
|
|
groups.forEach((g) => {
|
|
|
|
|
if (g.sourceTable) {
|
|
|
|
|
groupSourceTableMap[g.id] = g.sourceTable;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const defaultTargetTable = componentConfig.targetTable;
|
|
|
|
|
|
|
|
|
|
if (targetTable) {
|
|
|
|
|
// 테이블별 컬럼 메타데이터 캐시
|
|
|
|
|
const tableColumnsCache: Record<string, any[]> = {};
|
|
|
|
|
const getTableColumns = async (tableName: string) => {
|
|
|
|
|
if (tableColumnsCache[tableName]) return tableColumnsCache[tableName];
|
|
|
|
|
try {
|
|
|
|
|
const { tableTypeApi } = await import("@/lib/api/screen");
|
|
|
|
|
const columnsResponse = await tableTypeApi.getColumns(targetTable);
|
|
|
|
|
targetTableColumns = columnsResponse || [];
|
|
|
|
|
const columnsResponse = await tableTypeApi.getColumns(tableName);
|
|
|
|
|
tableColumnsCache[tableName] = columnsResponse || [];
|
|
|
|
|
return tableColumnsCache[tableName];
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("❌ 대상 테이블 컬럼 조회 실패:", error);
|
|
|
|
|
}
|
|
|
|
|
console.error(`❌ 테이블 컬럼 조회 실패 (${tableName}):`, error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const field of codeFields) {
|
|
|
|
|
// 이미 로드된 옵션이면 스킵
|
|
|
|
|
if (newOptions[field.name]) {
|
|
|
|
|
console.log(`⏭️ 이미 로드된 옵션 (${field.name})`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🆕 필드의 그룹 sourceTable 결정
|
|
|
|
|
const fieldSourceTable = (field.groupId && groupSourceTableMap[field.groupId]) || defaultTargetTable;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 🆕 category 타입이면 table_column_category_values에서 로드
|
|
|
|
|
if (field.inputType === "category" && targetTable) {
|
|
|
|
|
console.log(`🔄 카테고리 옵션 로드 시도 (${targetTable}.${field.name})`);
|
|
|
|
|
if (field.inputType === "category" && fieldSourceTable) {
|
|
|
|
|
console.log(`🔄 카테고리 옵션 로드 (${fieldSourceTable}.${field.name})`);
|
|
|
|
|
|
|
|
|
|
const { getCategoryValues } = await import("@/lib/api/tableCategoryValue");
|
|
|
|
|
const response = await getCategoryValues(targetTable, field.name, false);
|
|
|
|
|
const response = await getCategoryValues(fieldSourceTable, field.name, false);
|
|
|
|
|
|
|
|
|
|
console.log("📥 getCategoryValues 응답:", response);
|
|
|
|
|
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
if (response.success && response.data && response.data.length > 0) {
|
|
|
|
|
newOptions[field.name] = response.data.map((item: any) => ({
|
|
|
|
|
label: item.value_label || item.valueLabel,
|
|
|
|
|
value: item.value_code || item.valueCode,
|
|
|
|
|
}));
|
|
|
|
|
console.log(`✅ 카테고리 옵션 로드 완료 (${field.name}):`, newOptions[field.name]);
|
|
|
|
|
console.log(`✅ 카테고리 옵션 로드 완료 (${fieldSourceTable}.${field.name}):`, newOptions[field.name]);
|
|
|
|
|
} else {
|
|
|
|
|
console.error(`❌ 카테고리 옵션 로드 실패 (${field.name}):`, response.error || "응답 없음");
|
|
|
|
|
console.warn(`⚠️ 카테고리 옵션 없음 (${fieldSourceTable}.${field.name})`);
|
|
|
|
|
}
|
|
|
|
|
} else if (field.inputType === "code") {
|
|
|
|
|
// code 타입이면 기존대로 code_info에서 로드
|
|
|
|
|
// 이미 codeCategory가 있으면 사용
|
|
|
|
|
let codeCategory = field.codeCategory;
|
|
|
|
|
|
|
|
|
|
// 🆕 codeCategory가 없으면 대상 테이블 컬럼에서 찾기
|
|
|
|
|
if (!codeCategory && targetTableColumns.length > 0) {
|
|
|
|
|
if (!codeCategory && fieldSourceTable) {
|
|
|
|
|
const targetTableColumns = await getTableColumns(fieldSourceTable);
|
|
|
|
|
if (targetTableColumns.length > 0) {
|
|
|
|
|
const columnMeta = targetTableColumns.find(
|
|
|
|
|
(col: any) => (col.columnName || col.column_name) === field.name,
|
|
|
|
|
);
|
|
|
|
|
@ -201,6 +210,7 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
console.log(`🔍 필드 "${field.name}"의 codeCategory를 메타데이터에서 찾음:`, codeCategory);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!codeCategory) {
|
|
|
|
|
console.warn(`⚠️ 필드 "${field.name}"의 codeCategory를 찾을 수 없습니다`);
|
|
|
|
|
@ -784,13 +794,22 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
onClick?.();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 🆕 실시간 단가 계산 함수 (설정 기반 + 카테고리 매핑)
|
|
|
|
|
// 🆕 카테고리 코드 → 라벨명 변환 헬퍼
|
|
|
|
|
const getOptionLabel = useCallback(
|
|
|
|
|
(fieldName: string, valueCode: string): string => {
|
|
|
|
|
const options = codeOptions[fieldName] || [];
|
|
|
|
|
const matched = options.find((opt) => opt.value === valueCode);
|
|
|
|
|
return matched?.label || valueCode || "";
|
|
|
|
|
},
|
|
|
|
|
[codeOptions],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 🆕 실시간 단가 계산 함수 (라벨명 기반 - 회사별 코드 무관)
|
|
|
|
|
const calculatePrice = useCallback(
|
|
|
|
|
(entry: GroupEntry): number => {
|
|
|
|
|
// 자동 계산 설정이 없으면 계산하지 않음
|
|
|
|
|
if (!componentConfig.autoCalculation) return 0;
|
|
|
|
|
|
|
|
|
|
const { inputFields, valueMapping } = componentConfig.autoCalculation;
|
|
|
|
|
const { inputFields } = componentConfig.autoCalculation;
|
|
|
|
|
|
|
|
|
|
// 기본 단가
|
|
|
|
|
const basePrice = parseFloat(entry[inputFields.basePrice] || "0");
|
|
|
|
|
@ -798,38 +817,46 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
|
|
|
|
|
let price = basePrice;
|
|
|
|
|
|
|
|
|
|
// 1단계: 할인 적용
|
|
|
|
|
const discountTypeValue = entry[inputFields.discountType];
|
|
|
|
|
// 1단계: 할인 적용 (라벨명으로 판단)
|
|
|
|
|
const discountTypeCode = entry[inputFields.discountType];
|
|
|
|
|
const discountTypeLabel = getOptionLabel("discount_type", discountTypeCode);
|
|
|
|
|
const discountValue = parseFloat(entry[inputFields.discountValue] || "0");
|
|
|
|
|
|
|
|
|
|
// 매핑을 통해 실제 연산 타입 결정
|
|
|
|
|
const discountOperation = valueMapping?.discountType?.[discountTypeValue] || "none";
|
|
|
|
|
|
|
|
|
|
if (discountOperation === "rate") {
|
|
|
|
|
if (discountTypeLabel.includes("할인율") || discountTypeLabel.includes("%")) {
|
|
|
|
|
// 할인율(%)
|
|
|
|
|
price = price * (1 - discountValue / 100);
|
|
|
|
|
} else if (discountOperation === "amount") {
|
|
|
|
|
} else if (discountTypeLabel.includes("할인금액") || discountTypeLabel.includes("금액")) {
|
|
|
|
|
// 할인금액
|
|
|
|
|
price = price - discountValue;
|
|
|
|
|
}
|
|
|
|
|
// "할인없음"이면 그대로
|
|
|
|
|
|
|
|
|
|
// 2단계: 반올림 적용
|
|
|
|
|
const roundingTypeValue = entry[inputFields.roundingType];
|
|
|
|
|
const roundingUnitValue = entry[inputFields.roundingUnit];
|
|
|
|
|
// rounding_type = 단위 (10원, 100원, 1000원)
|
|
|
|
|
// rounding_unit_value = 방법 (반올림, 절삭, 올림, 반올림없음)
|
|
|
|
|
const roundingTypeCode = entry[inputFields.roundingType];
|
|
|
|
|
const roundingTypeLabel = getOptionLabel("rounding_type", roundingTypeCode);
|
|
|
|
|
|
|
|
|
|
// 매핑을 통해 실제 연산 타입 결정
|
|
|
|
|
const roundingOperation = valueMapping?.roundingType?.[roundingTypeValue] || "none";
|
|
|
|
|
const unit = valueMapping?.roundingUnit?.[roundingUnitValue] || parseFloat(roundingUnitValue) || 1;
|
|
|
|
|
const roundingUnitCode = entry[inputFields.roundingUnit];
|
|
|
|
|
const roundingUnitLabel = getOptionLabel("rounding_unit_value", roundingUnitCode);
|
|
|
|
|
|
|
|
|
|
if (roundingOperation === "round") {
|
|
|
|
|
// roundingType 라벨에서 단위 숫자 추출 (예: "10원" → 10, "1000원" → 1000)
|
|
|
|
|
const unitMatch = roundingTypeLabel.match(/(\d+)/);
|
|
|
|
|
const unit = unitMatch ? parseInt(unitMatch[1]) : parseFloat(roundingTypeCode) || 1;
|
|
|
|
|
|
|
|
|
|
// roundingUnit 라벨로 반올림 방법 결정
|
|
|
|
|
if (roundingUnitLabel.includes("반올림") && !roundingUnitLabel.includes("없음")) {
|
|
|
|
|
price = Math.round(price / unit) * unit;
|
|
|
|
|
} else if (roundingOperation === "floor") {
|
|
|
|
|
} else if (roundingUnitLabel.includes("절삭")) {
|
|
|
|
|
price = Math.floor(price / unit) * unit;
|
|
|
|
|
} else if (roundingOperation === "ceil") {
|
|
|
|
|
} else if (roundingUnitLabel.includes("올림")) {
|
|
|
|
|
price = Math.ceil(price / unit) * unit;
|
|
|
|
|
}
|
|
|
|
|
// "반올림없음"이면 그대로
|
|
|
|
|
|
|
|
|
|
return price;
|
|
|
|
|
},
|
|
|
|
|
[componentConfig.autoCalculation],
|
|
|
|
|
[componentConfig.autoCalculation, getOptionLabel],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 🆕 그룹별 필드 변경 핸들러: itemId + groupId + entryId + fieldName
|
|
|
|
|
@ -851,7 +878,23 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
const existingEntryIndex = groupEntries.findIndex((e) => e.id === entryId);
|
|
|
|
|
|
|
|
|
|
if (existingEntryIndex >= 0) {
|
|
|
|
|
// 기존 entry 업데이트 (항상 이 경로로만 진입)
|
|
|
|
|
const currentEntry = groupEntries[existingEntryIndex];
|
|
|
|
|
|
|
|
|
|
// 날짜 검증: 종료일이 시작일보다 앞서면 차단
|
|
|
|
|
if (fieldName === "end_date" && value && currentEntry.start_date) {
|
|
|
|
|
if (new Date(value) < new Date(currentEntry.start_date as string)) {
|
|
|
|
|
alert("종료일은 시작일보다 이후여야 합니다.");
|
|
|
|
|
return item; // 변경 취소
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (fieldName === "start_date" && value && currentEntry.end_date) {
|
|
|
|
|
if (new Date(value) > new Date(currentEntry.end_date as string)) {
|
|
|
|
|
alert("시작일은 종료일보다 이전이어야 합니다.");
|
|
|
|
|
return item; // 변경 취소
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 기존 entry 업데이트
|
|
|
|
|
const updatedEntries = [...groupEntries];
|
|
|
|
|
const updatedEntry = {
|
|
|
|
|
...updatedEntries[existingEntryIndex],
|
|
|
|
|
@ -1099,16 +1142,19 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
case "integer":
|
|
|
|
|
case "bigint":
|
|
|
|
|
case "decimal":
|
|
|
|
|
case "numeric":
|
|
|
|
|
// 🆕 계산된 단가는 천 단위 구분 및 강조 표시
|
|
|
|
|
if (isCalculatedField) {
|
|
|
|
|
const numericValue = parseFloat(value) || 0;
|
|
|
|
|
const formattedValue = new Intl.NumberFormat("ko-KR").format(numericValue);
|
|
|
|
|
case "numeric": {
|
|
|
|
|
// 숫자 포맷팅 헬퍼: 콤마 표시 + 실제 값은 숫자만 저장
|
|
|
|
|
const rawNum = value ? String(value).replace(/,/g, "") : "";
|
|
|
|
|
const displayNum = rawNum && !isNaN(Number(rawNum))
|
|
|
|
|
? new Intl.NumberFormat("ko-KR").format(Number(rawNum))
|
|
|
|
|
: rawNum;
|
|
|
|
|
|
|
|
|
|
// 계산된 단가는 읽기 전용 + 강조 표시
|
|
|
|
|
if (isCalculatedField) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Input
|
|
|
|
|
value={formattedValue}
|
|
|
|
|
value={displayNum}
|
|
|
|
|
readOnly
|
|
|
|
|
disabled
|
|
|
|
|
className={cn(
|
|
|
|
|
@ -1124,14 +1170,20 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Input
|
|
|
|
|
{...commonProps}
|
|
|
|
|
type="number"
|
|
|
|
|
onChange={(e) => handleFieldChange(itemId, groupId, entryId, field.name, e.target.value)}
|
|
|
|
|
min={field.validation?.min}
|
|
|
|
|
max={field.validation?.max}
|
|
|
|
|
value={displayNum}
|
|
|
|
|
placeholder={field.placeholder}
|
|
|
|
|
disabled={componentConfig.disabled || componentConfig.readonly}
|
|
|
|
|
type="text"
|
|
|
|
|
inputMode="numeric"
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
// 콤마 제거 후 숫자만 저장
|
|
|
|
|
const cleaned = e.target.value.replace(/,/g, "").replace(/[^0-9.\-]/g, "");
|
|
|
|
|
handleFieldChange(itemId, groupId, entryId, field.name, cleaned);
|
|
|
|
|
}}
|
|
|
|
|
className="h-7 text-xs"
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "date":
|
|
|
|
|
case "timestamp":
|
|
|
|
|
@ -1194,7 +1246,7 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
onValueChange={(val) => handleFieldChange(itemId, groupId, entryId, field.name, val)}
|
|
|
|
|
disabled={componentConfig.disabled || componentConfig.readonly}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger size="default" className="h-7 w-full text-xs">
|
|
|
|
|
<SelectTrigger className="h-7 w-full text-xs">
|
|
|
|
|
<SelectValue placeholder={field.placeholder || "선택하세요"} />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
@ -1220,7 +1272,7 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
{...commonProps}
|
|
|
|
|
type="text"
|
|
|
|
|
onChange={(e) => handleFieldChange(itemId, groupId, entryId, field.name, e.target.value)}
|
|
|
|
|
className="h-8 text-xs sm:h-10 sm:text-sm"
|
|
|
|
|
className="h-7 text-xs"
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
@ -1231,7 +1283,7 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
onValueChange={(val) => handleFieldChange(itemId, groupId, entryId, field.name, val)}
|
|
|
|
|
disabled={componentConfig.disabled || componentConfig.readonly}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger className="h-8 w-full text-xs sm:h-10 sm:text-sm">
|
|
|
|
|
<SelectTrigger className="h-7 w-full text-xs">
|
|
|
|
|
<SelectValue placeholder={field.placeholder || "선택하세요"} />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
@ -1254,7 +1306,7 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
type="text"
|
|
|
|
|
onChange={(e) => handleFieldChange(itemId, groupId, entryId, field.name, e.target.value)}
|
|
|
|
|
maxLength={field.validation?.maxLength}
|
|
|
|
|
className="h-8 text-xs sm:h-10 sm:text-sm"
|
|
|
|
|
className="h-7 text-xs"
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@ -1295,42 +1347,91 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
const displayItems = group?.displayItems || [];
|
|
|
|
|
|
|
|
|
|
if (displayItems.length === 0) {
|
|
|
|
|
// displayItems가 없으면 기본 방식 (해당 그룹의 visible 필드만 나열)
|
|
|
|
|
const fields = (componentConfig.additionalFields || []).filter((f) => {
|
|
|
|
|
// 그룹 필터
|
|
|
|
|
const matchGroup = componentConfig.fieldGroups && componentConfig.fieldGroups.length > 0
|
|
|
|
|
? f.groupId === groupId
|
|
|
|
|
: true;
|
|
|
|
|
// hidden 필드 제외 (width: "0px"인 필드)
|
|
|
|
|
const isVisible = f.width !== "0px";
|
|
|
|
|
return matchGroup && isVisible;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 값이 있는 필드만 "라벨: 값" 형식으로 표시
|
|
|
|
|
const displayParts = fields
|
|
|
|
|
.map((f) => {
|
|
|
|
|
const value = entry[f.name];
|
|
|
|
|
if (!value && value !== 0) return null;
|
|
|
|
|
|
|
|
|
|
// 헬퍼: 값을 사람이 읽기 좋은 형태로 변환
|
|
|
|
|
const formatValue = (f: any, value: any): string => {
|
|
|
|
|
if (!value && value !== 0) return "";
|
|
|
|
|
const strValue = String(value);
|
|
|
|
|
|
|
|
|
|
// ISO 날짜 형식 자동 포맷팅
|
|
|
|
|
// 날짜 포맷
|
|
|
|
|
const isoDateMatch = strValue.match(/^(\d{4})-(\d{2})-(\d{2})(T|\s|$)/);
|
|
|
|
|
if (isoDateMatch) {
|
|
|
|
|
const [, year, month, day] = isoDateMatch;
|
|
|
|
|
return `${f.label}: ${year}.${month}.${day}`;
|
|
|
|
|
return `${year}.${month}.${day}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return `${f.label}: ${strValue}`;
|
|
|
|
|
})
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
// 카테고리/코드 -> 라벨명
|
|
|
|
|
const renderType = f.inputType || f.type;
|
|
|
|
|
if (renderType === "category" || renderType === "code" || renderType === "select") {
|
|
|
|
|
const options = codeOptions[f.name] || f.options || [];
|
|
|
|
|
const matched = options.find((opt: any) => opt.value === strValue);
|
|
|
|
|
if (matched) return matched.label;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 빈 항목 표시: 그룹 필드명으로 안내 메시지 생성
|
|
|
|
|
if (displayParts.length === 0) {
|
|
|
|
|
// 숫자는 천 단위 구분
|
|
|
|
|
if (renderType === "number" && !isNaN(Number(strValue))) {
|
|
|
|
|
return new Intl.NumberFormat("ko-KR").format(Number(strValue));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strValue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 간결한 요약 생성 (그룹별 핵심 정보만)
|
|
|
|
|
const hasAnyValue = fields.some((f) => {
|
|
|
|
|
const v = entry[f.name];
|
|
|
|
|
return v !== undefined && v !== null && v !== "";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!hasAnyValue) {
|
|
|
|
|
const fieldLabels = fields.slice(0, 2).map(f => f.label).join("/");
|
|
|
|
|
return `신규 ${fieldLabels} 입력`;
|
|
|
|
|
}
|
|
|
|
|
return displayParts.join(" ");
|
|
|
|
|
|
|
|
|
|
// 날짜 범위가 있으면 우선 표시
|
|
|
|
|
const startDate = entry["start_date"] ? formatValue({ inputType: "date" }, entry["start_date"]) : "";
|
|
|
|
|
const endDate = entry["end_date"] ? formatValue({ inputType: "date" }, entry["end_date"]) : "";
|
|
|
|
|
|
|
|
|
|
// 기준단가(calculated_price) 또는 기준가(base_price) 표시
|
|
|
|
|
const calcPrice = entry["calculated_price"] ? formatValue({ inputType: "number" }, entry["calculated_price"]) : "";
|
|
|
|
|
const basePrice = entry["base_price"] ? formatValue({ inputType: "number" }, entry["base_price"]) : "";
|
|
|
|
|
|
|
|
|
|
// 통화코드
|
|
|
|
|
const currencyCode = entry["currency_code"] ? formatValue(
|
|
|
|
|
fields.find(f => f.name === "currency_code") || { inputType: "category", name: "currency_code" },
|
|
|
|
|
entry["currency_code"]
|
|
|
|
|
) : "";
|
|
|
|
|
|
|
|
|
|
if (startDate || calcPrice || basePrice) {
|
|
|
|
|
// 날짜 + 단가 간결 표시
|
|
|
|
|
const parts: string[] = [];
|
|
|
|
|
if (startDate) {
|
|
|
|
|
parts.push(endDate ? `${startDate} ~ ${endDate}` : `${startDate} ~`);
|
|
|
|
|
}
|
|
|
|
|
if (calcPrice) {
|
|
|
|
|
parts.push(`${currencyCode || ""} ${calcPrice}`.trim());
|
|
|
|
|
} else if (basePrice) {
|
|
|
|
|
parts.push(`${currencyCode || ""} ${basePrice}`.trim());
|
|
|
|
|
}
|
|
|
|
|
return parts.join(" | ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 그 외 그룹 (거래처 품번 등): 첫 2개 필드만 표시
|
|
|
|
|
const summaryParts = fields
|
|
|
|
|
.slice(0, 3)
|
|
|
|
|
.map((f) => {
|
|
|
|
|
const value = entry[f.name];
|
|
|
|
|
if (!value && value !== 0) return null;
|
|
|
|
|
return `${f.label}: ${formatValue(f, value)}`;
|
|
|
|
|
})
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
return summaryParts.join(" ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// displayItems 설정대로 렌더링
|
|
|
|
|
@ -1499,7 +1600,7 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
[componentConfig.fieldGroups, componentConfig.additionalFields],
|
|
|
|
|
[componentConfig.fieldGroups, componentConfig.additionalFields, codeOptions],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 빈 상태 렌더링
|
|
|
|
|
|