ERP-node/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent...

254 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useState, useEffect } from "react";
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 { useCalculation } from "./useCalculation";
import { cn } from "@/lib/utils";
interface ModalRepeaterTableComponentProps extends Partial<ModalRepeaterTableProps> {
config?: ModalRepeaterTableProps;
}
export function ModalRepeaterTableComponent({
config,
sourceTable: propSourceTable,
sourceColumns: propSourceColumns,
sourceSearchFields: propSourceSearchFields,
modalTitle: propModalTitle,
modalButtonText: propModalButtonText,
multiSelect: propMultiSelect,
columns: propColumns,
calculationRules: propCalculationRules,
value: propValue,
onChange: propOnChange,
uniqueField: propUniqueField,
filterCondition: propFilterCondition,
companyCode: propCompanyCode,
className,
}: ModalRepeaterTableComponentProps) {
// config prop 우선, 없으면 개별 prop 사용
const sourceTable = config?.sourceTable || propSourceTable || "";
// sourceColumns에서 빈 문자열 필터링
const rawSourceColumns = config?.sourceColumns || propSourceColumns || [];
const sourceColumns = rawSourceColumns.filter((col) => col && col.trim() !== "");
const sourceSearchFields = config?.sourceSearchFields || propSourceSearchFields || [];
const modalTitle = config?.modalTitle || propModalTitle || "항목 검색";
const modalButtonText = config?.modalButtonText || propModalButtonText || "품목 검색";
const multiSelect = config?.multiSelect ?? propMultiSelect ?? true;
const calculationRules = config?.calculationRules || propCalculationRules || [];
const value = config?.value || propValue || [];
const onChange = config?.onChange || propOnChange || (() => {});
// uniqueField 자동 보정: order_no는 item_info 테이블에 없으므로 item_number로 변경
const rawUniqueField = config?.uniqueField || propUniqueField;
const uniqueField = rawUniqueField === "order_no" && sourceTable === "item_info"
? "item_number"
: rawUniqueField;
const filterCondition = config?.filterCondition || propFilterCondition || {};
const companyCode = config?.companyCode || propCompanyCode;
const [modalOpen, setModalOpen] = useState(false);
// columns가 비어있으면 sourceColumns로부터 자동 생성
const columns = React.useMemo((): RepeaterColumnConfig[] => {
const configuredColumns = config?.columns || propColumns || [];
if (configuredColumns.length > 0) {
console.log("✅ 설정된 columns 사용:", configuredColumns);
return configuredColumns;
}
// columns가 비어있으면 sourceColumns로부터 자동 생성
if (sourceColumns.length > 0) {
console.log("🔄 sourceColumns로부터 자동 생성:", sourceColumns);
const autoColumns: RepeaterColumnConfig[] = sourceColumns.map((field) => ({
field: field,
label: field, // 필드명을 라벨로 사용 (나중에 설정에서 변경 가능)
editable: false, // 기본적으로 읽기 전용
type: "text" as const,
width: "150px",
}));
console.log("📋 자동 생성된 columns:", autoColumns);
return autoColumns;
}
console.warn("⚠️ columns와 sourceColumns 모두 비어있음!");
return [];
}, [config?.columns, propColumns, sourceColumns]);
// 초기 props 로깅
useEffect(() => {
if (rawSourceColumns.length !== sourceColumns.length) {
console.warn(`⚠️ sourceColumns 필터링: ${rawSourceColumns.length}개 → ${sourceColumns.length}개 (빈 문자열 제거)`);
}
if (rawUniqueField !== uniqueField) {
console.warn(`⚠️ uniqueField 자동 보정: "${rawUniqueField}" → "${uniqueField}"`);
}
console.log("🎬 ModalRepeaterTableComponent 마운트:", {
columnsLength: columns.length,
sourceTable,
sourceColumns,
uniqueField,
});
if (columns.length === 0) {
console.error("❌ columns가 비어있습니다! sourceColumns:", sourceColumns);
} else {
console.log("✅ columns 설정 완료:", columns.map(c => c.label || c.field).join(", "));
}
}, []);
// value 변경 감지
useEffect(() => {
console.log("📦 ModalRepeaterTableComponent value 변경:", {
valueLength: value.length,
});
}, [value]);
const { calculateRow, calculateAll } = useCalculation(calculationRules);
// 초기 데이터에 계산 필드 적용
useEffect(() => {
if (value.length > 0 && calculationRules.length > 0) {
const calculated = calculateAll(value);
// 값이 실제로 변경된 경우만 업데이트
if (JSON.stringify(calculated) !== JSON.stringify(value)) {
onChange(calculated);
}
}
}, []);
const handleAddItems = (items: any[]) => {
console.log(" handleAddItems 호출:", items.length, "개 항목");
console.log("📋 소스 데이터:", items);
// 매핑 규칙에 따라 데이터 변환
const mappedItems = items.map((sourceItem) => {
const newItem: any = {};
columns.forEach((col) => {
console.log(`🔄 컬럼 "${col.field}" 매핑 처리:`, col.mapping);
// 1. 매핑 규칙이 있는 경우
if (col.mapping) {
if (col.mapping.type === "source") {
// 소스 테이블 컬럼에서 복사
const sourceField = col.mapping.sourceField;
if (sourceField && sourceItem[sourceField] !== undefined) {
newItem[col.field] = sourceItem[sourceField];
console.log(` ✅ 소스 복사: ${sourceField}${col.field}:`, newItem[col.field]);
} 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;
console.log(` ✏️ 수동 입력 필드`);
}
}
// 2. 매핑 규칙이 없는 경우 - 소스 데이터에서 같은 필드명으로 복사
else if (sourceItem[col.field] !== undefined) {
newItem[col.field] = sourceItem[col.field];
console.log(` 📝 직접 복사: ${col.field}:`, newItem[col.field]);
}
// 3. 기본값 적용
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);
// 기존 데이터에 추가
const newData = [...value, ...calculatedItems];
console.log("✅ 최종 데이터:", newData.length, "개 항목");
onChange(newData);
};
const handleRowChange = (index: number, newRow: any) => {
// 계산 필드 업데이트
const calculatedRow = calculateRow(newRow);
// 데이터 업데이트
const newData = [...value];
newData[index] = calculatedRow;
onChange(newData);
};
const handleRowDelete = (index: number) => {
const newData = value.filter((_, i) => i !== index);
onChange(newData);
};
// 컬럼명 -> 라벨명 매핑 생성
const columnLabels = columns.reduce((acc, col) => {
acc[col.field] = col.label;
return acc;
}, {} as Record<string, string>);
return (
<div className={cn("space-y-4", className)}>
{/* 추가 버튼 */}
<div className="flex justify-between items-center">
<div className="text-sm text-muted-foreground">
{value.length > 0 && `${value.length}개 항목`}
</div>
<Button
onClick={() => setModalOpen(true)}
className="h-8 text-xs sm:h-10 sm:text-sm"
>
<Plus className="h-4 w-4 mr-2" />
{modalButtonText}
</Button>
</div>
{/* Repeater 테이블 */}
<RepeaterTable
columns={columns}
data={value}
onDataChange={onChange}
onRowChange={handleRowChange}
onRowDelete={handleRowDelete}
/>
{/* 항목 선택 모달 */}
<ItemSelectionModal
open={modalOpen}
onOpenChange={setModalOpen}
sourceTable={sourceTable}
sourceColumns={sourceColumns}
sourceSearchFields={sourceSearchFields}
multiSelect={multiSelect}
filterCondition={filterCondition}
modalTitle={modalTitle}
alreadySelected={value}
uniqueField={uniqueField}
onSelect={handleAddItems}
columnLabels={columnLabels}
/>
</div>
);
}