fix: ModalRepeaterTable reference 매핑 처리 순서 및 API 파라미터 수정
문제: - reference 매핑 시 조인 조건의 소스 필드 값이 undefined - API 호출 시 filters 파라미터를 백엔드가 인식 못함 해결: - 컬럼 처리를 2단계로 분리 (source/manual → reference) - API 파라미터 변경 (filters→search, limit/offset→size/page) - 응답 경로 수정 (data.data → data.data.data) 결과: - 외부 테이블 참조 매핑 정상 작동 - 품목 선택 시 customer_item_mapping에서 단가 자동 조회 성공
This commit is contained in:
parent
d5d267e63a
commit
6d0acdd1ec
|
|
@ -5,14 +5,122 @@ import { Button } from "@/components/ui/button";
|
|||
import { Plus } from "lucide-react";
|
||||
import { ItemSelectionModal } from "./ItemSelectionModal";
|
||||
import { RepeaterTable } from "./RepeaterTable";
|
||||
import { ModalRepeaterTableProps, RepeaterColumnConfig } from "./types";
|
||||
import { ModalRepeaterTableProps, RepeaterColumnConfig, JoinCondition } from "./types";
|
||||
import { useCalculation } from "./useCalculation";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
|
||||
interface ModalRepeaterTableComponentProps extends Partial<ModalRepeaterTableProps> {
|
||||
config?: ModalRepeaterTableProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 외부 테이블에서 참조 값을 조회하는 함수
|
||||
* @param referenceTable 참조 테이블명 (예: "customer_item_mapping")
|
||||
* @param referenceField 참조할 컬럼명 (예: "basic_price")
|
||||
* @param joinConditions 조인 조건 배열
|
||||
* @param sourceItem 소스 데이터 (모달에서 선택한 항목)
|
||||
* @param currentItem 현재 빌드 중인 항목 (이미 설정된 필드들)
|
||||
* @returns 참조된 값 또는 undefined
|
||||
*/
|
||||
async function fetchReferenceValue(
|
||||
referenceTable: string,
|
||||
referenceField: string,
|
||||
joinConditions: JoinCondition[],
|
||||
sourceItem: any,
|
||||
currentItem: any
|
||||
): Promise<any> {
|
||||
if (joinConditions.length === 0) {
|
||||
console.warn("⚠️ 조인 조건이 없습니다. 참조 조회를 건너뜁니다.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
// 조인 조건을 WHERE 절로 변환
|
||||
const whereConditions: Record<string, any> = {};
|
||||
|
||||
for (const condition of joinConditions) {
|
||||
const { sourceTable = "target", sourceField, targetField, operator = "=" } = condition;
|
||||
|
||||
// 소스 테이블에 따라 값을 가져오기
|
||||
let value: any;
|
||||
if (sourceTable === "source") {
|
||||
// 소스 테이블 (item_info 등): 모달에서 선택한 원본 데이터
|
||||
value = sourceItem[sourceField];
|
||||
console.log(` 📘 소스 테이블에서 값 가져오기: ${sourceField} =`, value);
|
||||
} else {
|
||||
// 저장 테이블 (sales_order_mng 등): 반복 테이블에 이미 복사된 값
|
||||
value = currentItem[sourceField];
|
||||
console.log(` 📗 저장 테이블(반복테이블)에서 값 가져오기: ${sourceField} =`, value);
|
||||
}
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
console.warn(`⚠️ 조인 조건의 소스 필드 "${sourceField}" 값이 없습니다. (sourceTable: ${sourceTable})`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 연산자가 "=" 인 경우만 지원 (확장 가능)
|
||||
if (operator === "=") {
|
||||
whereConditions[targetField] = value;
|
||||
} else {
|
||||
console.warn(`⚠️ 연산자 "${operator}"는 아직 지원되지 않습니다.`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`🔍 참조 조회 API 호출:`, {
|
||||
table: referenceTable,
|
||||
field: referenceField,
|
||||
where: whereConditions,
|
||||
});
|
||||
|
||||
// API 호출: 테이블 데이터 조회 (POST 방식)
|
||||
const requestBody = {
|
||||
search: whereConditions, // ✅ filters → search 변경 (백엔드 파라미터명)
|
||||
size: 1, // 첫 번째 결과만 가져오기
|
||||
page: 1,
|
||||
};
|
||||
|
||||
console.log("📤 API 요청 Body:", JSON.stringify(requestBody, null, 2));
|
||||
|
||||
const response = await apiClient.post(
|
||||
`/table-management/tables/${referenceTable}/data`,
|
||||
requestBody
|
||||
);
|
||||
|
||||
console.log("📥 API 전체 응답:", {
|
||||
success: response.data.success,
|
||||
dataLength: response.data.data?.data?.length, // ✅ data.data.data 구조
|
||||
total: response.data.data?.total, // ✅ data.data.total
|
||||
firstRow: response.data.data?.data?.[0], // ✅ data.data.data[0]
|
||||
});
|
||||
|
||||
if (response.data.success && response.data.data?.data?.length > 0) {
|
||||
const firstRow = response.data.data.data[0]; // ✅ data.data.data[0]
|
||||
const value = firstRow[referenceField];
|
||||
|
||||
console.log(`✅ 참조 조회 성공:`, {
|
||||
table: referenceTable,
|
||||
field: referenceField,
|
||||
value,
|
||||
fullRow: firstRow,
|
||||
});
|
||||
|
||||
return value;
|
||||
} else {
|
||||
console.warn(`⚠️ 참조 조회 결과 없음:`, {
|
||||
table: referenceTable,
|
||||
where: whereConditions,
|
||||
responseData: response.data.data,
|
||||
total: response.data.total,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ 참조 조회 API 오류:`, error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function ModalRepeaterTableComponent({
|
||||
config,
|
||||
sourceTable: propSourceTable,
|
||||
|
|
@ -126,15 +234,31 @@ export function ModalRepeaterTableComponent({
|
|||
}
|
||||
}, []);
|
||||
|
||||
const handleAddItems = (items: any[]) => {
|
||||
const handleAddItems = async (items: any[]) => {
|
||||
console.log("➕ handleAddItems 호출:", items.length, "개 항목");
|
||||
console.log("📋 소스 데이터:", items);
|
||||
|
||||
// 매핑 규칙에 따라 데이터 변환
|
||||
const mappedItems = items.map((sourceItem) => {
|
||||
// 매핑 규칙에 따라 데이터 변환 (비동기 처리)
|
||||
const mappedItems = await Promise.all(items.map(async (sourceItem) => {
|
||||
const newItem: any = {};
|
||||
|
||||
columns.forEach((col) => {
|
||||
// ⚠️ 중요: reference 매핑은 다른 컬럼에 의존할 수 있으므로
|
||||
// 1단계: source/manual 매핑을 먼저 처리
|
||||
// 2단계: reference 매핑을 나중에 처리
|
||||
|
||||
const referenceColumns: typeof columns = [];
|
||||
const otherColumns: typeof columns = [];
|
||||
|
||||
for (const col of columns) {
|
||||
if (col.mapping?.type === "reference") {
|
||||
referenceColumns.push(col);
|
||||
} else {
|
||||
otherColumns.push(col);
|
||||
}
|
||||
}
|
||||
|
||||
// 1단계: source/manual 컬럼 먼저 처리
|
||||
for (const col of otherColumns) {
|
||||
console.log(`🔄 컬럼 "${col.field}" 매핑 처리:`, col.mapping);
|
||||
|
||||
// 1. 매핑 규칙이 있는 경우
|
||||
|
|
@ -148,11 +272,6 @@ export function ModalRepeaterTableComponent({
|
|||
} else {
|
||||
console.warn(` ⚠️ 소스 필드 "${sourceField}" 값이 없음`);
|
||||
}
|
||||
} else if (col.mapping.type === "reference") {
|
||||
// 외부 테이블 참조 (TODO: API 호출 필요)
|
||||
console.log(` ⏳ 참조 조회 필요: ${col.mapping.referenceTable}.${col.mapping.referenceField}`);
|
||||
// 현재는 빈 값으로 설정 (나중에 API 호출로 구현)
|
||||
newItem[col.field] = undefined;
|
||||
} else if (col.mapping.type === "manual") {
|
||||
// 사용자 입력 (빈 값)
|
||||
newItem[col.field] = undefined;
|
||||
|
|
@ -170,11 +289,47 @@ export function ModalRepeaterTableComponent({
|
|||
newItem[col.field] = col.defaultValue;
|
||||
console.log(` 🎯 기본값 적용: ${col.field}:`, col.defaultValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2단계: reference 컬럼 처리 (다른 컬럼들이 모두 설정된 후)
|
||||
console.log("🔗 2단계: reference 컬럼 처리 시작");
|
||||
for (const col of referenceColumns) {
|
||||
console.log(`🔄 컬럼 "${col.field}" 참조 매핑 처리:`, col.mapping);
|
||||
|
||||
// 외부 테이블 참조 (API 호출)
|
||||
console.log(` ⏳ 참조 조회 시작: ${col.mapping?.referenceTable}.${col.mapping?.referenceField}`);
|
||||
|
||||
try {
|
||||
const referenceValue = await fetchReferenceValue(
|
||||
col.mapping!.referenceTable!,
|
||||
col.mapping!.referenceField!,
|
||||
col.mapping!.joinCondition || [],
|
||||
sourceItem,
|
||||
newItem
|
||||
);
|
||||
|
||||
if (referenceValue !== null && referenceValue !== undefined) {
|
||||
newItem[col.field] = referenceValue;
|
||||
console.log(` ✅ 참조 조회 성공: ${col.field}:`, referenceValue);
|
||||
} else {
|
||||
newItem[col.field] = undefined;
|
||||
console.warn(` ⚠️ 참조 조회 결과 없음`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(` ❌ 참조 조회 오류:`, error);
|
||||
newItem[col.field] = undefined;
|
||||
}
|
||||
|
||||
// 기본값 적용
|
||||
if (col.defaultValue !== undefined && newItem[col.field] === undefined) {
|
||||
newItem[col.field] = col.defaultValue;
|
||||
console.log(` 🎯 기본값 적용: ${col.field}:`, col.defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("📦 변환된 항목:", newItem);
|
||||
return newItem;
|
||||
});
|
||||
}));
|
||||
|
||||
// 계산 필드 업데이트
|
||||
const calculatedItems = calculateAll(mappedItems);
|
||||
|
|
|
|||
|
|
@ -131,7 +131,8 @@ export function ModalRepeaterTableConfigPanel({
|
|||
// 계산 규칙이 없으면 모든 컬럼의 calculated 속성 제거
|
||||
if (!initialConfig.calculationRules || initialConfig.calculationRules.length === 0) {
|
||||
const cleanedColumns = (initialConfig.columns || []).map((col) => {
|
||||
const { calculated: _calc, ...rest } = col;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { calculated, ...rest } = col;
|
||||
return { ...rest, editable: true };
|
||||
});
|
||||
return { ...initialConfig, columns: cleanedColumns };
|
||||
|
|
@ -145,7 +146,8 @@ export function ModalRepeaterTableConfigPanel({
|
|||
return { ...col, calculated: true, editable: false };
|
||||
} else {
|
||||
// 나머지 필드는 calculated 제거, editable=true
|
||||
const { calculated: _calc, ...rest } = col;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { calculated, ...rest } = col;
|
||||
return { ...rest, editable: true };
|
||||
}
|
||||
});
|
||||
|
|
@ -281,12 +283,12 @@ export function ModalRepeaterTableConfigPanel({
|
|||
const columns = localConfig.columns || [];
|
||||
|
||||
// 이미 존재하는 컬럼인지 확인
|
||||
if (columns.some(col => col.field === columnName)) {
|
||||
if (columns.some((col) => col.field === columnName)) {
|
||||
alert("이미 추가된 컬럼입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const targetCol = targetTableColumns.find(c => c.columnName === columnName);
|
||||
const targetCol = targetTableColumns.find((c) => c.columnName === columnName);
|
||||
|
||||
const newColumn: RepeaterColumnConfig = {
|
||||
field: columnName,
|
||||
|
|
@ -340,16 +342,17 @@ export function ModalRepeaterTableConfigPanel({
|
|||
|
||||
// 이전 결과 필드의 calculated 속성 제거
|
||||
if (oldRule.result) {
|
||||
const oldResultIndex = columns.findIndex(c => c.field === oldRule.result);
|
||||
const oldResultIndex = columns.findIndex((c) => c.field === oldRule.result);
|
||||
if (oldResultIndex !== -1) {
|
||||
const { calculated: _calc, ...rest } = columns[oldResultIndex];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { calculated, ...rest } = columns[oldResultIndex];
|
||||
columns[oldResultIndex] = { ...rest, editable: true };
|
||||
}
|
||||
}
|
||||
|
||||
// 새 결과 필드를 calculated=true, editable=false로 설정
|
||||
if (updates.result) {
|
||||
const newResultIndex = columns.findIndex(c => c.field === updates.result);
|
||||
const newResultIndex = columns.findIndex((c) => c.field === updates.result);
|
||||
if (newResultIndex !== -1) {
|
||||
columns[newResultIndex] = {
|
||||
...columns[newResultIndex],
|
||||
|
|
@ -375,9 +378,10 @@ export function ModalRepeaterTableConfigPanel({
|
|||
// 결과 필드의 calculated 속성 제거
|
||||
if (removedRule.result) {
|
||||
const columns = [...(localConfig.columns || [])];
|
||||
const resultIndex = columns.findIndex(c => c.field === removedRule.result);
|
||||
const resultIndex = columns.findIndex((c) => c.field === removedRule.result);
|
||||
if (resultIndex !== -1) {
|
||||
const { calculated: _calc, ...rest } = columns[resultIndex];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { calculated, ...rest } = columns[resultIndex];
|
||||
columns[resultIndex] = { ...rest, editable: true };
|
||||
}
|
||||
rules.splice(index, 1);
|
||||
|
|
@ -978,6 +982,296 @@ export function ModalRepeaterTableConfigPanel({
|
|||
가져올 컬럼명
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 조인 조건 설정 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-[11px]">조인 조건 *</Label>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const currentConditions = col.mapping?.joinCondition || [];
|
||||
updateRepeaterColumn(index, {
|
||||
mapping: {
|
||||
...col.mapping,
|
||||
type: "reference",
|
||||
joinCondition: [
|
||||
...currentConditions,
|
||||
{ sourceField: "", targetField: "", operator: "=" }
|
||||
]
|
||||
} as ColumnMapping
|
||||
});
|
||||
}}
|
||||
className="h-6 text-[10px] px-2"
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
조인 조건 추가
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
소스 테이블과 참조 테이블을 어떻게 매칭할지 설정
|
||||
</p>
|
||||
|
||||
{/* 조인 조건 목록 */}
|
||||
<div className="space-y-2">
|
||||
{(col.mapping?.joinCondition || []).map((condition, condIndex) => (
|
||||
<div key={condIndex} className="p-3 border rounded-md bg-background space-y-2">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-[10px] font-medium text-muted-foreground">
|
||||
조인 조건 {condIndex + 1}
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
const currentConditions = [...(col.mapping?.joinCondition || [])];
|
||||
currentConditions.splice(condIndex, 1);
|
||||
updateRepeaterColumn(index, {
|
||||
mapping: {
|
||||
...col.mapping,
|
||||
type: "reference",
|
||||
joinCondition: currentConditions
|
||||
} as ColumnMapping
|
||||
});
|
||||
}}
|
||||
className="h-5 w-5 p-0"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 소스 테이블 선택 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px] text-muted-foreground">소스 테이블 (어느 테이블)</Label>
|
||||
<Select
|
||||
value={condition.sourceTable || "target"}
|
||||
onValueChange={(value) => {
|
||||
const currentConditions = [...(col.mapping?.joinCondition || [])];
|
||||
currentConditions[condIndex] = {
|
||||
...currentConditions[condIndex],
|
||||
sourceTable: value as "source" | "target"
|
||||
};
|
||||
updateRepeaterColumn(index, {
|
||||
mapping: {
|
||||
...col.mapping,
|
||||
type: "reference",
|
||||
joinCondition: currentConditions
|
||||
} as ColumnMapping
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-xs w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="target">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">저장 테이블 (권장)</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{localConfig.targetTable || "sales_order_mng"}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="source">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">소스 테이블</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{localConfig.sourceTable || "item_info"}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
반복 테이블 = 저장 테이블 컬럼 사용
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 소스 필드 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px] text-muted-foreground">소스 필드 (어느 컬럼)</Label>
|
||||
<Select
|
||||
value={condition.sourceField || ""}
|
||||
onValueChange={(value) => {
|
||||
const currentConditions = [...(col.mapping?.joinCondition || [])];
|
||||
currentConditions[condIndex] = {
|
||||
...currentConditions[condIndex],
|
||||
sourceField: value
|
||||
};
|
||||
updateRepeaterColumn(index, {
|
||||
mapping: {
|
||||
...col.mapping,
|
||||
type: "reference",
|
||||
joinCondition: currentConditions
|
||||
} as ColumnMapping
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-xs w-full">
|
||||
<SelectValue placeholder="컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{/* 저장 테이블 선택 시 */}
|
||||
{(!condition.sourceTable || condition.sourceTable === "target") && (
|
||||
<>
|
||||
{/* 반복 테이블에 추가된 컬럼 */}
|
||||
{(localConfig.columns || []).length > 0 && (
|
||||
<>
|
||||
<div className="px-2 py-1.5 text-[10px] font-semibold text-muted-foreground">
|
||||
반복 테이블 컬럼 (권장)
|
||||
</div>
|
||||
{localConfig.columns
|
||||
.filter((c) => c.field !== col.field)
|
||||
.map((repeaterCol) => (
|
||||
<SelectItem key={`repeater-${repeaterCol.field}`} value={repeaterCol.field}>
|
||||
<span className="font-medium">{repeaterCol.label}</span>
|
||||
<span className="text-[10px] text-muted-foreground ml-1">({repeaterCol.field})</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 저장 테이블의 모든 컬럼 */}
|
||||
{targetTableColumns.length > 0 && (
|
||||
<>
|
||||
<div className="px-2 py-1.5 text-[10px] font-semibold text-muted-foreground border-t mt-1 pt-2">
|
||||
저장 테이블 전체 컬럼 ({localConfig.targetTable || "sales_order_mng"})
|
||||
</div>
|
||||
{targetTableColumns.map((targetCol) => (
|
||||
<SelectItem key={`target-${targetCol.columnName}`} value={targetCol.columnName}>
|
||||
{targetCol.displayName || targetCol.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 소스 테이블 선택 시 */}
|
||||
{condition.sourceTable === "source" && (
|
||||
<>
|
||||
{tableColumns.length > 0 && (
|
||||
<>
|
||||
<div className="px-2 py-1.5 text-[10px] font-semibold text-muted-foreground">
|
||||
소스 테이블 컬럼 ({localConfig.sourceTable || "item_info"})
|
||||
</div>
|
||||
{tableColumns.map((sourceCol) => (
|
||||
<SelectItem key={`source-${sourceCol.columnName}`} value={sourceCol.columnName}>
|
||||
{sourceCol.displayName || sourceCol.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
{(!condition.sourceTable || condition.sourceTable === "target")
|
||||
? "반복 테이블에 이미 추가된 컬럼"
|
||||
: "모달에서 선택한 원본 데이터의 컬럼"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 연산자 */}
|
||||
<div className="flex items-center justify-center">
|
||||
<Select
|
||||
value={condition.operator || "="}
|
||||
onValueChange={(value) => {
|
||||
const currentConditions = [...(col.mapping?.joinCondition || [])];
|
||||
currentConditions[condIndex] = {
|
||||
...currentConditions[condIndex],
|
||||
operator: value as "=" | "!=" | ">" | "<" | ">=" | "<="
|
||||
};
|
||||
updateRepeaterColumn(index, {
|
||||
mapping: {
|
||||
...col.mapping,
|
||||
type: "reference",
|
||||
joinCondition: currentConditions
|
||||
} as ColumnMapping
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-xs w-20">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="=">=</SelectItem>
|
||||
<SelectItem value="!=">!=</SelectItem>
|
||||
<SelectItem value=">">></SelectItem>
|
||||
<SelectItem value="<"><</SelectItem>
|
||||
<SelectItem value=">=">>=</SelectItem>
|
||||
<SelectItem value="<="><=</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 대상 필드 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px] text-muted-foreground">대상 필드 (참조 테이블)</Label>
|
||||
<ReferenceColumnSelector
|
||||
referenceTable={col.mapping?.referenceTable || ""}
|
||||
value={condition.targetField || ""}
|
||||
onChange={(value) => {
|
||||
const currentConditions = [...(col.mapping?.joinCondition || [])];
|
||||
currentConditions[condIndex] = {
|
||||
...currentConditions[condIndex],
|
||||
targetField: value
|
||||
};
|
||||
updateRepeaterColumn(index, {
|
||||
mapping: {
|
||||
...col.mapping,
|
||||
type: "reference",
|
||||
joinCondition: currentConditions
|
||||
} as ColumnMapping
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 조인 조건 미리보기 */}
|
||||
{condition.sourceField && condition.targetField && (
|
||||
<div className="mt-2 p-2 bg-muted/50 rounded text-[10px] font-mono">
|
||||
<span className="text-blue-600 dark:text-blue-400">
|
||||
{condition.sourceTable === "source"
|
||||
? localConfig.sourceTable
|
||||
: localConfig.targetTable || "저장테이블"}
|
||||
</span>
|
||||
<span className="text-muted-foreground">.{condition.sourceField}</span>
|
||||
<span className="mx-2 text-purple-600 dark:text-purple-400">{condition.operator || "="}</span>
|
||||
<span className="text-purple-600 dark:text-purple-400">{col.mapping?.referenceTable}</span>
|
||||
<span className="text-muted-foreground">.{condition.targetField}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 조인 조건 없을 때 안내 */}
|
||||
{(!col.mapping?.joinCondition || col.mapping.joinCondition.length === 0) && (
|
||||
<div className="p-4 border-2 border-dashed rounded-lg text-center">
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
조인 조건이 없습니다
|
||||
</p>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
"조인 조건 추가" 버튼을 클릭하여 매칭 조건을 설정하세요
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 조인 조건 예시 */}
|
||||
{col.mapping?.referenceTable && (
|
||||
<div className="p-3 bg-blue-50 dark:bg-blue-950 rounded-md border border-blue-200 dark:border-blue-800">
|
||||
<p className="text-xs font-medium mb-2">조인 조건 예시</p>
|
||||
<div className="space-y-1 text-[10px] text-muted-foreground">
|
||||
<p>예) 거래처별 품목 단가 조회:</p>
|
||||
<p className="ml-2 font-mono">• item_code = item_code</p>
|
||||
<p className="ml-2 font-mono">• customer_code = customer_code</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ export interface ColumnMapping {
|
|||
* 조인 조건 정의
|
||||
*/
|
||||
export interface JoinCondition {
|
||||
/** 소스 테이블 (어느 테이블의 컬럼인지) */
|
||||
sourceTable?: string; // "source" (item_info) 또는 "target" (sales_order_mng)
|
||||
/** 현재 테이블의 컬럼 (소스 테이블 또는 반복 테이블) */
|
||||
sourceField: string;
|
||||
/** 참조 테이블의 컬럼 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue