- 범용 컴포넌트 3종 개발 및 레지스트리 등록: * AutocompleteSearchInput: 자동완성 검색 입력 컴포넌트 * EntitySearchInput: 엔티티 검색 모달 컴포넌트 * ModalRepeaterTable: 모달 기반 반복 테이블 컴포넌트 - 수주등록 전용 컴포넌트: * OrderCustomerSearch: 거래처 검색 (AutocompleteSearchInput 래퍼) * OrderItemRepeaterTable: 품목 관리 (ModalRepeaterTable 래퍼) * OrderRegistrationModal: 수주등록 메인 모달 - 백엔드 API: * Entity 검색 API (멀티테넌시 지원) * 수주 등록 API (자동 채번) - 화면 편집기 통합: * 컴포넌트 레지스트리에 등록 * ConfigPanel을 통한 설정 기능 * 드래그앤드롭으로 배치 가능 - 개발 문서: * 수주등록_화면_개발_계획서.md (상세 설계 문서)
138 lines
4.6 KiB
TypeScript
138 lines
4.6 KiB
TypeScript
"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 } 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 || "";
|
|
const sourceColumns = config?.sourceColumns || propSourceColumns || [];
|
|
const sourceSearchFields = config?.sourceSearchFields || propSourceSearchFields || [];
|
|
const modalTitle = config?.modalTitle || propModalTitle || "항목 검색";
|
|
const modalButtonText = config?.modalButtonText || propModalButtonText || "품목 검색";
|
|
const multiSelect = config?.multiSelect ?? propMultiSelect ?? true;
|
|
const columns = config?.columns || propColumns || [];
|
|
const calculationRules = config?.calculationRules || propCalculationRules || [];
|
|
const value = config?.value || propValue || [];
|
|
const onChange = config?.onChange || propOnChange || (() => {});
|
|
const uniqueField = config?.uniqueField || propUniqueField;
|
|
const filterCondition = config?.filterCondition || propFilterCondition || {};
|
|
const companyCode = config?.companyCode || propCompanyCode;
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
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[]) => {
|
|
// 기본값 적용
|
|
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);
|
|
|
|
// 기존 데이터에 추가
|
|
onChange([...value, ...calculatedItems]);
|
|
};
|
|
|
|
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);
|
|
};
|
|
|
|
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}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|