Files
vexplor_dev/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx
SeongHyun Kim 8eccdd0b4c fix: 수주등록 항목추가 시 정보출력 가능하게 수정
문제:
- 조건부 컨테이너 내부의 modal-repeater-table 컴포넌트가 데이터 업데이트 불가
- ConditionalSectionViewer가 RealtimePreview에 formData/onFormDataChange 미전달

해결:
- ConditionalSectionViewer.tsx: RealtimePreview에 formData, onFormDataChange props 추가
- DynamicComponentRenderer.tsx: 디버깅 로그 정리
- ScreenModal.tsx: 디버깅 로그 정리

영향:
- 수주 등록 화면 품목 추가 기능 정상 작동
- 조건부 컨테이너 내부 모든 폼 컴포넌트 데이터 바인딩 정상화

Refs: #수주관리 #modal-repeater-table #ConditionalContainer
2025-11-19 11:48:00 +09:00

218 lines
7.4 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, "개 항목");
// 기본값 적용
const itemsWithDefaults = items.map((item) => {
const newItem = { ...item };
columns.forEach((col) => {
if (col.defaultValue !== undefined && newItem[col.field] === undefined) {
newItem[col.field] = col.defaultValue;
}
});
return newItem;
});
// 계산 필드 업데이트
const calculatedItems = calculateAll(itemsWithDefaults);
// 기존 데이터에 추가
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>
);
}