Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node
This commit is contained in:
commit
f10ceb5f7c
|
|
@ -496,16 +496,27 @@ export class DynamicFormService {
|
|||
for (const repeater of mergedRepeaterData) {
|
||||
for (const item of repeater.data) {
|
||||
// 헤더 + 품목을 병합
|
||||
const mergedData = { ...dataToInsert, ...item };
|
||||
const rawMergedData = { ...dataToInsert, ...item };
|
||||
|
||||
// 타입 변환
|
||||
Object.keys(mergedData).forEach((columnName) => {
|
||||
// 🆕 실제 테이블 컬럼만 필터링 (조인/계산 컬럼 제외)
|
||||
const validColumnNames = columnInfo.map((col) => col.column_name);
|
||||
const mergedData: Record<string, any> = {};
|
||||
|
||||
Object.keys(rawMergedData).forEach((columnName) => {
|
||||
// 실제 테이블 컬럼인지 확인
|
||||
if (validColumnNames.includes(columnName)) {
|
||||
const column = columnInfo.find((col) => col.column_name === columnName);
|
||||
if (column) {
|
||||
// 타입 변환
|
||||
mergedData[columnName] = this.convertValueForPostgreSQL(
|
||||
mergedData[columnName],
|
||||
rawMergedData[columnName],
|
||||
column.data_type
|
||||
);
|
||||
} else {
|
||||
mergedData[columnName] = rawMergedData[columnName];
|
||||
}
|
||||
} else {
|
||||
console.log(`⚠️ 조인/계산 컬럼 제외: ${columnName} (값: ${rawMergedData[columnName]})`);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ interface EditModalState {
|
|||
modalSize: "sm" | "md" | "lg" | "xl";
|
||||
editData: Record<string, any>;
|
||||
onSave?: () => void;
|
||||
groupByColumns?: string[]; // 🆕 그룹핑 컬럼 (예: ["order_no"])
|
||||
tableName?: string; // 🆕 테이블명 (그룹 조회용)
|
||||
}
|
||||
|
||||
interface EditModalProps {
|
||||
|
|
@ -40,6 +42,8 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
modalSize: "md",
|
||||
editData: {},
|
||||
onSave: undefined,
|
||||
groupByColumns: undefined,
|
||||
tableName: undefined,
|
||||
});
|
||||
|
||||
const [screenData, setScreenData] = useState<{
|
||||
|
|
@ -58,6 +62,10 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
// 폼 데이터 상태 (편집 데이터로 초기화됨)
|
||||
const [formData, setFormData] = useState<Record<string, any>>({});
|
||||
const [originalData, setOriginalData] = useState<Record<string, any>>({});
|
||||
|
||||
// 🆕 그룹 데이터 상태 (같은 order_no의 모든 품목)
|
||||
const [groupData, setGroupData] = useState<Record<string, any>[]>([]);
|
||||
const [originalGroupData, setOriginalGroupData] = useState<Record<string, any>[]>([]);
|
||||
|
||||
// 화면의 실제 크기 계산 함수 (ScreenModal과 동일)
|
||||
const calculateScreenDimensions = (components: ComponentData[]) => {
|
||||
|
|
@ -110,7 +118,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
// 전역 모달 이벤트 리스너
|
||||
useEffect(() => {
|
||||
const handleOpenEditModal = (event: CustomEvent) => {
|
||||
const { screenId, title, description, modalSize, editData, onSave } = event.detail;
|
||||
const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName } = event.detail;
|
||||
|
||||
setModalState({
|
||||
isOpen: true,
|
||||
|
|
@ -120,6 +128,8 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
modalSize: modalSize || "lg",
|
||||
editData: editData || {},
|
||||
onSave,
|
||||
groupByColumns, // 🆕 그룹핑 컬럼
|
||||
tableName, // 🆕 테이블명
|
||||
});
|
||||
|
||||
// 편집 데이터로 폼 데이터 초기화
|
||||
|
|
@ -154,9 +164,78 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
useEffect(() => {
|
||||
if (modalState.isOpen && modalState.screenId) {
|
||||
loadScreenData(modalState.screenId);
|
||||
|
||||
// 🆕 그룹 데이터 조회 (groupByColumns가 있는 경우)
|
||||
if (modalState.groupByColumns && modalState.groupByColumns.length > 0 && modalState.tableName) {
|
||||
loadGroupData();
|
||||
}
|
||||
}
|
||||
}, [modalState.isOpen, modalState.screenId]);
|
||||
|
||||
// 🆕 그룹 데이터 조회 함수
|
||||
const loadGroupData = async () => {
|
||||
if (!modalState.tableName || !modalState.groupByColumns || modalState.groupByColumns.length === 0) {
|
||||
console.warn("테이블명 또는 그룹핑 컬럼이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("🔍 그룹 데이터 조회 시작:", {
|
||||
tableName: modalState.tableName,
|
||||
groupByColumns: modalState.groupByColumns,
|
||||
editData: modalState.editData,
|
||||
});
|
||||
|
||||
// 그룹핑 컬럼 값 추출 (예: order_no = "ORD-20251124-001")
|
||||
const groupValues: Record<string, any> = {};
|
||||
modalState.groupByColumns.forEach((column) => {
|
||||
if (modalState.editData[column]) {
|
||||
groupValues[column] = modalState.editData[column];
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(groupValues).length === 0) {
|
||||
console.warn("그룹핑 컬럼 값이 없습니다:", modalState.groupByColumns);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🔍 그룹 조회 요청:", {
|
||||
tableName: modalState.tableName,
|
||||
groupValues,
|
||||
});
|
||||
|
||||
// 같은 그룹의 모든 레코드 조회 (entityJoinApi 사용)
|
||||
const { entityJoinApi } = await import("@/lib/api/entityJoin");
|
||||
const response = await entityJoinApi.getTableDataWithJoins(modalState.tableName, {
|
||||
page: 1,
|
||||
size: 1000,
|
||||
search: groupValues, // search 파라미터로 전달 (백엔드에서 WHERE 조건으로 처리)
|
||||
enableEntityJoin: true,
|
||||
});
|
||||
|
||||
console.log("🔍 그룹 조회 응답:", response);
|
||||
|
||||
// entityJoinApi는 배열 또는 { data: [] } 형식으로 반환
|
||||
const dataArray = Array.isArray(response) ? response : response?.data || [];
|
||||
|
||||
if (dataArray.length > 0) {
|
||||
console.log("✅ 그룹 데이터 조회 성공:", dataArray);
|
||||
setGroupData(dataArray);
|
||||
setOriginalGroupData(JSON.parse(JSON.stringify(dataArray))); // Deep copy
|
||||
toast.info(`${dataArray.length}개의 관련 품목을 불러왔습니다.`);
|
||||
} else {
|
||||
console.warn("그룹 데이터가 없습니다:", response);
|
||||
setGroupData([modalState.editData]); // 기본값: 선택된 행만
|
||||
setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("❌ 그룹 데이터 조회 오류:", error);
|
||||
toast.error("관련 데이터를 불러오는 중 오류가 발생했습니다.");
|
||||
setGroupData([modalState.editData]); // 기본값: 선택된 행만
|
||||
setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]);
|
||||
}
|
||||
};
|
||||
|
||||
const loadScreenData = async (screenId: number) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
|
@ -208,10 +287,14 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
modalSize: "md",
|
||||
editData: {},
|
||||
onSave: undefined,
|
||||
groupByColumns: undefined,
|
||||
tableName: undefined,
|
||||
});
|
||||
setScreenData(null);
|
||||
setFormData({});
|
||||
setOriginalData({});
|
||||
setGroupData([]); // 🆕
|
||||
setOriginalGroupData([]); // 🆕
|
||||
};
|
||||
|
||||
// 저장 버튼 클릭 시 - UPDATE 액션 실행
|
||||
|
|
@ -222,7 +305,104 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
}
|
||||
|
||||
try {
|
||||
// 🆕 그룹 데이터가 있는 경우: 모든 품목 일괄 수정
|
||||
if (groupData.length > 0) {
|
||||
console.log("🔄 그룹 데이터 일괄 수정 시작:", {
|
||||
groupDataLength: groupData.length,
|
||||
originalGroupDataLength: originalGroupData.length,
|
||||
});
|
||||
|
||||
let updatedCount = 0;
|
||||
|
||||
for (let i = 0; i < groupData.length; i++) {
|
||||
const currentData = groupData[i];
|
||||
const originalItemData = originalGroupData[i];
|
||||
|
||||
if (!originalItemData) {
|
||||
console.warn(`원본 데이터가 없습니다 (index: ${i})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 변경된 필드만 추출
|
||||
const changedData: Record<string, any> = {};
|
||||
|
||||
// 🆕 sales_order_mng 테이블의 실제 컬럼만 포함 (조인된 컬럼 제외)
|
||||
const salesOrderColumns = [
|
||||
"id",
|
||||
"order_no",
|
||||
"customer_code",
|
||||
"customer_name",
|
||||
"order_date",
|
||||
"delivery_date",
|
||||
"item_code",
|
||||
"quantity",
|
||||
"unit_price",
|
||||
"amount",
|
||||
"status",
|
||||
"notes",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"company_code",
|
||||
];
|
||||
|
||||
Object.keys(currentData).forEach((key) => {
|
||||
// sales_order_mng 테이블의 컬럼만 처리 (조인 컬럼 제외)
|
||||
if (!salesOrderColumns.includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentData[key] !== originalItemData[key]) {
|
||||
changedData[key] = currentData[key];
|
||||
}
|
||||
});
|
||||
|
||||
// 변경사항이 없으면 스킵
|
||||
if (Object.keys(changedData).length === 0) {
|
||||
console.log(`변경사항 없음 (index: ${i})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 기본키 확인
|
||||
const recordId = originalItemData.id || Object.values(originalItemData)[0];
|
||||
|
||||
// UPDATE 실행
|
||||
const response = await dynamicFormApi.updateFormDataPartial(
|
||||
recordId,
|
||||
originalItemData,
|
||||
changedData,
|
||||
screenData.screenInfo.tableName,
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
updatedCount++;
|
||||
console.log(`✅ 품목 ${i + 1} 수정 성공 (id: ${recordId})`);
|
||||
} else {
|
||||
console.error(`❌ 품목 ${i + 1} 수정 실패 (id: ${recordId}):`, response.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedCount > 0) {
|
||||
toast.success(`${updatedCount}개의 품목이 수정되었습니다.`);
|
||||
|
||||
// 부모 컴포넌트의 onSave 콜백 실행 (테이블 새로고침)
|
||||
if (modalState.onSave) {
|
||||
try {
|
||||
modalState.onSave();
|
||||
} catch (callbackError) {
|
||||
console.error("⚠️ onSave 콜백 에러:", callbackError);
|
||||
}
|
||||
}
|
||||
|
||||
handleClose();
|
||||
} else {
|
||||
toast.info("변경된 내용이 없습니다.");
|
||||
handleClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 기존 로직: 단일 레코드 수정
|
||||
const changedData: Record<string, any> = {};
|
||||
Object.keys(formData).forEach((key) => {
|
||||
if (formData[key] !== originalData[key]) {
|
||||
|
|
@ -341,6 +521,13 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
maxHeight: "100%",
|
||||
}}
|
||||
>
|
||||
{/* 🆕 그룹 데이터가 있으면 안내 메시지 표시 */}
|
||||
{groupData.length > 1 && (
|
||||
<div className="absolute left-4 top-4 z-10 rounded-md bg-blue-50 px-3 py-2 text-xs text-blue-700 shadow-sm">
|
||||
{groupData.length}개의 관련 품목을 함께 수정합니다
|
||||
</div>
|
||||
)}
|
||||
|
||||
{screenData.components.map((component) => {
|
||||
// 컴포넌트 위치를 offset만큼 조정
|
||||
const offsetX = screenDimensions?.offsetX || 0;
|
||||
|
|
@ -355,23 +542,51 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
},
|
||||
};
|
||||
|
||||
// 🔍 디버깅: 컴포넌트 렌더링 시점의 groupData 확인
|
||||
if (component.id === screenData.components[0]?.id) {
|
||||
console.log("🔍 [EditModal] InteractiveScreenViewerDynamic props:", {
|
||||
componentId: component.id,
|
||||
groupDataLength: groupData.length,
|
||||
groupData: groupData,
|
||||
formData: groupData.length > 0 ? groupData[0] : formData,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<InteractiveScreenViewerDynamic
|
||||
key={component.id}
|
||||
component={adjustedComponent}
|
||||
allComponents={screenData.components}
|
||||
formData={formData}
|
||||
formData={groupData.length > 0 ? groupData[0] : formData}
|
||||
onFormDataChange={(fieldName, value) => {
|
||||
// 🆕 그룹 데이터가 있으면 처리
|
||||
if (groupData.length > 0) {
|
||||
// ModalRepeaterTable의 경우 배열 전체를 받음
|
||||
if (Array.isArray(value)) {
|
||||
setGroupData(value);
|
||||
} else {
|
||||
// 일반 필드는 모든 항목에 동일하게 적용
|
||||
setGroupData((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
[fieldName]: value,
|
||||
}))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[fieldName]: value,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
screenInfo={{
|
||||
id: modalState.screenId!,
|
||||
tableName: screenData.screenInfo?.tableName,
|
||||
}}
|
||||
onSave={handleSave}
|
||||
// 🆕 그룹 데이터를 ModalRepeaterTable에 전달
|
||||
groupedData={groupData.length > 0 ? groupData : undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ interface InteractiveScreenViewerProps {
|
|||
userId?: string;
|
||||
userName?: string;
|
||||
companyCode?: string;
|
||||
// 🆕 그룹 데이터 (EditModal에서 전달)
|
||||
groupedData?: Record<string, any>[];
|
||||
}
|
||||
|
||||
export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerProps> = ({
|
||||
|
|
@ -61,6 +63,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
userId: externalUserId,
|
||||
userName: externalUserName,
|
||||
companyCode: externalCompanyCode,
|
||||
groupedData,
|
||||
}) => {
|
||||
const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
|
||||
const { userName: authUserName, user: authUser } = useAuth();
|
||||
|
|
@ -332,6 +335,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
console.log("🔍 테이블에서 선택된 행 데이터:", selectedData);
|
||||
setSelectedRowsData(selectedData);
|
||||
}}
|
||||
// 🆕 그룹 데이터 전달 (EditModal → ModalRepeaterTable)
|
||||
groupedData={groupedData}
|
||||
flowSelectedData={flowSelectedData}
|
||||
flowSelectedStepId={flowSelectedStepId}
|
||||
onFlowSelectedDataChange={(selectedData, stepId) => {
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ export interface DynamicComponentRendererProps {
|
|||
onClose?: () => void;
|
||||
// 테이블 선택된 행 정보 (다중 선택 액션용)
|
||||
selectedRows?: any[];
|
||||
// 🆕 그룹 데이터 (EditModal → ModalRepeaterTable)
|
||||
groupedData?: Record<string, any>[];
|
||||
selectedRowsData?: any[];
|
||||
onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[], sortBy?: string, sortOrder?: "asc" | "desc", columnOrder?: string[], tableDisplayData?: any[]) => void;
|
||||
// 테이블 정렬 정보 (엑셀 다운로드용)
|
||||
|
|
@ -279,7 +281,17 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
// modal-repeater-table은 배열 데이터를 다루므로 빈 배열로 초기화
|
||||
let currentValue;
|
||||
if (componentType === "modal-repeater-table") {
|
||||
currentValue = formData?.[fieldName] || [];
|
||||
// 🆕 EditModal에서 전달된 groupedData가 있으면 우선 사용
|
||||
currentValue = props.groupedData || formData?.[fieldName] || [];
|
||||
|
||||
// 디버깅 로그
|
||||
console.log("🔍 [DynamicComponentRenderer] ModalRepeaterTable value 설정:", {
|
||||
hasGroupedData: !!props.groupedData,
|
||||
groupedDataLength: props.groupedData?.length || 0,
|
||||
fieldName,
|
||||
formDataValue: formData?.[fieldName],
|
||||
finalValueLength: Array.isArray(currentValue) ? currentValue.length : 0,
|
||||
});
|
||||
} else {
|
||||
currentValue = formData?.[fieldName] || "";
|
||||
}
|
||||
|
|
@ -380,6 +392,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
|||
isPreview,
|
||||
// 디자인 모드 플래그 전달 - isPreview와 명확히 구분
|
||||
isDesignMode: props.isDesignMode !== undefined ? props.isDesignMode : false,
|
||||
// 🆕 그룹 데이터 전달 (EditModal → ConditionalContainer → ModalRepeaterTable)
|
||||
groupedData: props.groupedData,
|
||||
};
|
||||
|
||||
// 렌더러가 클래스인지 함수인지 확인
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export function ConditionalContainerComponent({
|
|||
componentId,
|
||||
style,
|
||||
className,
|
||||
groupedData, // 🆕 그룹 데이터
|
||||
}: ConditionalContainerProps) {
|
||||
console.log("🎯 ConditionalContainerComponent 렌더링!", {
|
||||
isDesignMode,
|
||||
|
|
@ -177,6 +178,7 @@ export function ConditionalContainerComponent({
|
|||
showBorder={showBorder}
|
||||
formData={formData}
|
||||
onFormDataChange={onFormDataChange}
|
||||
groupedData={groupedData}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -196,6 +198,7 @@ export function ConditionalContainerComponent({
|
|||
showBorder={showBorder}
|
||||
formData={formData}
|
||||
onFormDataChange={onFormDataChange}
|
||||
groupedData={groupedData}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { ConditionalSectionViewerProps } from "./types";
|
||||
import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic";
|
||||
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
|
|
@ -24,6 +25,7 @@ export function ConditionalSectionViewer({
|
|||
showBorder = true,
|
||||
formData,
|
||||
onFormDataChange,
|
||||
groupedData, // 🆕 그룹 데이터
|
||||
}: ConditionalSectionViewerProps) {
|
||||
const { userId, userName, user } = useAuth();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
|
@ -135,13 +137,24 @@ export function ConditionalSectionViewer({
|
|||
minHeight: "200px",
|
||||
}}
|
||||
>
|
||||
{components.map((component) => (
|
||||
<RealtimePreview
|
||||
{components.map((component) => {
|
||||
const { position = { x: 0, y: 0, z: 1 }, size = { width: 200, height: 40 } } = component;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={component.id}
|
||||
className="absolute"
|
||||
style={{
|
||||
left: position.x || 0,
|
||||
top: position.y || 0,
|
||||
width: size.width || 200,
|
||||
height: size.height || 40,
|
||||
zIndex: position.z || 1,
|
||||
}}
|
||||
>
|
||||
<DynamicComponentRenderer
|
||||
component={component}
|
||||
isSelected={false}
|
||||
isDesignMode={false}
|
||||
onClick={() => {}}
|
||||
isInteractive={true}
|
||||
screenId={screenInfo?.id}
|
||||
tableName={screenInfo?.tableName}
|
||||
userId={userId}
|
||||
|
|
@ -149,8 +162,11 @@ export function ConditionalSectionViewer({
|
|||
companyCode={user?.companyCode}
|
||||
formData={formData}
|
||||
onFormDataChange={onFormDataChange}
|
||||
groupedData={groupedData}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export interface ConditionalContainerProps {
|
|||
onChange?: (value: string) => void;
|
||||
formData?: Record<string, any>;
|
||||
onFormDataChange?: (fieldName: string, value: any) => void;
|
||||
groupedData?: Record<string, any>[]; // 🆕 그룹 데이터 (EditModal → ModalRepeaterTable)
|
||||
|
||||
// 화면 편집기 관련
|
||||
isDesignMode?: boolean; // 디자인 모드 여부
|
||||
|
|
@ -75,5 +76,6 @@ export interface ConditionalSectionViewerProps {
|
|||
// 폼 데이터 전달
|
||||
formData?: Record<string, any>;
|
||||
onFormDataChange?: (fieldName: string, value: any) => void;
|
||||
groupedData?: Record<string, any>[]; // 🆕 그룹 데이터
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -291,13 +291,47 @@ export function ModalRepeaterTableComponent({
|
|||
return;
|
||||
}
|
||||
|
||||
// 🔥 sourceColumns에 포함된 컬럼 제외 (조인된 컬럼 제거)
|
||||
console.log("🔍 [ModalRepeaterTable] 필터링 전 데이터:", {
|
||||
sourceColumns,
|
||||
sourceTable,
|
||||
targetTable,
|
||||
sampleItem: value[0],
|
||||
itemKeys: value[0] ? Object.keys(value[0]) : [],
|
||||
});
|
||||
|
||||
const filteredData = value.map((item: any) => {
|
||||
const filtered: Record<string, any> = {};
|
||||
|
||||
Object.keys(item).forEach((key) => {
|
||||
// sourceColumns에 포함된 컬럼은 제외 (item_info 테이블의 컬럼)
|
||||
if (sourceColumns.includes(key)) {
|
||||
console.log(` ⛔ ${key} 제외 (sourceColumn)`);
|
||||
return;
|
||||
}
|
||||
// 메타데이터 필드도 제외
|
||||
if (key.startsWith("_")) {
|
||||
console.log(` ⛔ ${key} 제외 (메타데이터)`);
|
||||
return;
|
||||
}
|
||||
filtered[key] = item[key];
|
||||
});
|
||||
|
||||
return filtered;
|
||||
});
|
||||
|
||||
console.log("✅ [ModalRepeaterTable] 필터링 후 데이터:", {
|
||||
filteredItemKeys: filteredData[0] ? Object.keys(filteredData[0]) : [],
|
||||
sampleFilteredItem: filteredData[0],
|
||||
});
|
||||
|
||||
// 🔥 targetTable 메타데이터를 배열 항목에 추가
|
||||
const dataWithTargetTable = targetTable
|
||||
? value.map(item => ({
|
||||
? filteredData.map((item: any) => ({
|
||||
...item,
|
||||
_targetTable: targetTable, // 백엔드가 인식할 메타데이터
|
||||
}))
|
||||
: value;
|
||||
: filteredData;
|
||||
|
||||
// ✅ CustomEvent의 detail에 데이터 추가
|
||||
if (event instanceof CustomEvent && event.detail) {
|
||||
|
|
@ -333,9 +367,10 @@ export function ModalRepeaterTableComponent({
|
|||
const calculated = calculateAll(value);
|
||||
// 값이 실제로 변경된 경우만 업데이트
|
||||
if (JSON.stringify(calculated) !== JSON.stringify(value)) {
|
||||
onChange(calculated);
|
||||
handleChange(calculated);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleAddItems = async (items: any[]) => {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ export interface ButtonActionConfig {
|
|||
|
||||
// 모달/팝업 관련
|
||||
modalTitle?: string;
|
||||
modalTitleBlocks?: Array<{ // 🆕 블록 기반 제목 (우선순위 높음)
|
||||
modalTitleBlocks?: Array<{
|
||||
// 🆕 블록 기반 제목 (우선순위 높음)
|
||||
id: string;
|
||||
type: "text" | "field";
|
||||
value: string; // type=text: 텍스트 내용, type=field: 컬럼명
|
||||
|
|
@ -88,6 +89,12 @@ export interface ButtonActionConfig {
|
|||
// 코드 병합 관련
|
||||
mergeColumnName?: string; // 병합할 컬럼명 (예: "item_code")
|
||||
mergeShowPreview?: boolean; // 병합 전 미리보기 표시 여부 (기본: true)
|
||||
|
||||
// 편집 관련 (수주관리 등 그룹별 다중 레코드 편집)
|
||||
editMode?: "modal" | "navigate" | "inline"; // 편집 모드
|
||||
editModalTitle?: string; // 편집 모달 제목
|
||||
editModalDescription?: string; // 편집 모달 설명
|
||||
groupByColumns?: string[]; // 같은 그룹의 여러 행을 함께 편집 (예: ["order_no"])
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1256,14 +1263,6 @@ export class ButtonActionExecutor {
|
|||
// 플로우 선택 데이터 우선 사용
|
||||
let dataToEdit = flowSelectedData && flowSelectedData.length > 0 ? flowSelectedData : selectedRowsData;
|
||||
|
||||
console.log("🔍 handleEdit - 데이터 소스 확인:", {
|
||||
hasFlowSelectedData: !!(flowSelectedData && flowSelectedData.length > 0),
|
||||
flowSelectedDataLength: flowSelectedData?.length || 0,
|
||||
hasSelectedRowsData: !!(selectedRowsData && selectedRowsData.length > 0),
|
||||
selectedRowsDataLength: selectedRowsData?.length || 0,
|
||||
dataToEditLength: dataToEdit?.length || 0,
|
||||
});
|
||||
|
||||
// 선택된 데이터가 없는 경우
|
||||
if (!dataToEdit || dataToEdit.length === 0) {
|
||||
toast.error("수정할 항목을 선택해주세요.");
|
||||
|
|
@ -1276,26 +1275,15 @@ export class ButtonActionExecutor {
|
|||
return false;
|
||||
}
|
||||
|
||||
console.log(`📝 편집 액션 실행: ${dataToEdit.length}개 항목`, {
|
||||
dataToEdit,
|
||||
targetScreenId: config.targetScreenId,
|
||||
editMode: config.editMode,
|
||||
});
|
||||
|
||||
if (dataToEdit.length === 1) {
|
||||
// 단일 항목 편집
|
||||
const rowData = dataToEdit[0];
|
||||
console.log("📝 단일 항목 편집:", rowData);
|
||||
|
||||
await this.openEditForm(config, rowData, context);
|
||||
} else {
|
||||
// 다중 항목 편집 - 현재는 단일 편집만 지원
|
||||
toast.error("현재 단일 항목 편집만 지원됩니다. 하나의 항목만 선택해주세요.");
|
||||
return false;
|
||||
|
||||
// TODO: 향후 다중 편집 지원
|
||||
// console.log("📝 다중 항목 편집:", selectedRowsData);
|
||||
// this.openBulkEditForm(config, selectedRowsData, context);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -1329,7 +1317,7 @@ export class ButtonActionExecutor {
|
|||
|
||||
default:
|
||||
// 기본값: 모달
|
||||
this.openEditModal(config, rowData, context);
|
||||
await this.openEditModal(config, rowData, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1341,11 +1329,17 @@ export class ButtonActionExecutor {
|
|||
rowData: any,
|
||||
context: ButtonActionContext,
|
||||
): Promise<void> {
|
||||
console.log("🎭 편집 모달 열기:", {
|
||||
targetScreenId: config.targetScreenId,
|
||||
modalSize: config.modalSize,
|
||||
rowData,
|
||||
});
|
||||
const { groupByColumns = [] } = config;
|
||||
|
||||
// PK 값 추출 (우선순위: id > ID > 첫 번째 필드)
|
||||
let primaryKeyValue: any;
|
||||
if (rowData.id !== undefined && rowData.id !== null) {
|
||||
primaryKeyValue = rowData.id;
|
||||
} else if (rowData.ID !== undefined && rowData.ID !== null) {
|
||||
primaryKeyValue = rowData.ID;
|
||||
} else {
|
||||
primaryKeyValue = Object.values(rowData)[0];
|
||||
}
|
||||
|
||||
// 1. config에 editModalDescription이 있으면 우선 사용
|
||||
let description = config.editModalDescription || "";
|
||||
|
|
@ -1360,7 +1354,7 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
// 모달 열기 이벤트 발생
|
||||
// 🔧 항상 EditModal 사용 (groupByColumns는 EditModal에서 처리)
|
||||
const modalEvent = new CustomEvent("openEditModal", {
|
||||
detail: {
|
||||
screenId: config.targetScreenId,
|
||||
|
|
@ -1368,16 +1362,15 @@ export class ButtonActionExecutor {
|
|||
description: description,
|
||||
modalSize: config.modalSize || "lg",
|
||||
editData: rowData,
|
||||
groupByColumns: groupByColumns.length > 0 ? groupByColumns : undefined, // 🆕 그룹핑 컬럼 전달
|
||||
tableName: context.tableName, // 🆕 테이블명 전달
|
||||
onSave: () => {
|
||||
// 저장 후 테이블 새로고침
|
||||
console.log("💾 편집 저장 완료 - 테이블 새로고침");
|
||||
context.onRefresh?.();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
window.dispatchEvent(modalEvent);
|
||||
// 편집 모달 열기는 조용히 처리 (토스트 없음)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue