feat: 수주관리 품목 추가/수정/삭제 기능 구현
- EditModal의 handleSave가 button-primary까지 전달되도록 수정 - ConditionalContainer/ConditionalSectionViewer에 onSave prop 추가 - DynamicComponentRenderer와 InteractiveScreenViewerDynamic에 onSave 전달 로직 추가 - ButtonActionExecutor에서 context.onSave 콜백 우선 실행 로직 구현 - 신규 품목 추가 시 groupByColumns 값 자동 포함 처리 기능: - 품목 추가: order_no 자동 설정 - 품목 수정: 변경 필드만 부분 업데이트 - 품목 삭제: originalGroupData 비교 후 제거
This commit is contained in:
@@ -305,84 +305,173 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// 🆕 그룹 데이터가 있는 경우: 모든 품목 일괄 수정
|
||||
if (groupData.length > 0) {
|
||||
console.log("🔄 그룹 데이터 일괄 수정 시작:", {
|
||||
// 🆕 그룹 데이터가 있는 경우: 모든 품목 일괄 처리 (추가/수정/삭제)
|
||||
if (groupData.length > 0 || originalGroupData.length > 0) {
|
||||
console.log("🔄 그룹 데이터 일괄 처리 시작:", {
|
||||
groupDataLength: groupData.length,
|
||||
originalGroupDataLength: originalGroupData.length,
|
||||
groupData,
|
||||
originalGroupData,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
screenId: modalState.screenId,
|
||||
});
|
||||
|
||||
let insertedCount = 0;
|
||||
let updatedCount = 0;
|
||||
let deletedCount = 0;
|
||||
|
||||
for (let i = 0; i < groupData.length; i++) {
|
||||
const currentData = groupData[i];
|
||||
const originalItemData = originalGroupData[i];
|
||||
// 🆕 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",
|
||||
];
|
||||
|
||||
if (!originalItemData) {
|
||||
console.warn(`원본 데이터가 없습니다 (index: ${i})`);
|
||||
continue;
|
||||
}
|
||||
// 1️⃣ 신규 품목 추가 (id가 없는 항목)
|
||||
for (const currentData of groupData) {
|
||||
if (!currentData.id) {
|
||||
console.log("➕ 신규 품목 추가:", currentData);
|
||||
|
||||
// 변경된 필드만 추출
|
||||
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;
|
||||
// 실제 테이블 컬럼만 추출
|
||||
const insertData: Record<string, any> = {};
|
||||
Object.keys(currentData).forEach((key) => {
|
||||
if (salesOrderColumns.includes(key) && key !== "id") {
|
||||
insertData[key] = currentData[key];
|
||||
}
|
||||
});
|
||||
|
||||
// 🆕 groupByColumns의 값을 강제로 포함 (order_no 등)
|
||||
if (modalState.groupByColumns && modalState.groupByColumns.length > 0) {
|
||||
modalState.groupByColumns.forEach((colName) => {
|
||||
// 기존 품목(groupData[0])에서 groupByColumns 값 가져오기
|
||||
const referenceData = originalGroupData[0] || groupData.find(item => item.id);
|
||||
if (referenceData && referenceData[colName]) {
|
||||
insertData[colName] = referenceData[colName];
|
||||
console.log(`🔑 [신규 품목] ${colName} 값 추가:`, referenceData[colName]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (currentData[key] !== originalItemData[key]) {
|
||||
changedData[key] = currentData[key];
|
||||
|
||||
console.log("📦 [신규 품목] 최종 insertData:", insertData);
|
||||
|
||||
try {
|
||||
const response = await dynamicFormApi.saveFormData({
|
||||
screenId: modalState.screenId || 0,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
data: insertData,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
insertedCount++;
|
||||
console.log("✅ 신규 품목 추가 성공:", response.data);
|
||||
} else {
|
||||
console.error("❌ 신규 품목 추가 실패:", response.message);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("❌ 신규 품목 추가 오류:", error);
|
||||
}
|
||||
});
|
||||
|
||||
// 변경사항이 없으면 스킵
|
||||
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}개의 품목이 수정되었습니다.`);
|
||||
// 2️⃣ 기존 품목 수정 (id가 있는 항목)
|
||||
for (const currentData of groupData) {
|
||||
if (currentData.id) {
|
||||
// id 기반 매칭 (인덱스 기반 X)
|
||||
const originalItemData = originalGroupData.find(
|
||||
(orig) => orig.id === currentData.id
|
||||
);
|
||||
|
||||
if (!originalItemData) {
|
||||
console.warn(`원본 데이터를 찾을 수 없습니다 (id: ${currentData.id})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 변경된 필드만 추출
|
||||
const changedData: Record<string, any> = {};
|
||||
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(`변경사항 없음 (id: ${currentData.id})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// UPDATE 실행
|
||||
try {
|
||||
const response = await dynamicFormApi.updateFormDataPartial(
|
||||
currentData.id,
|
||||
originalItemData,
|
||||
changedData,
|
||||
screenData.screenInfo.tableName,
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
updatedCount++;
|
||||
console.log(`✅ 품목 수정 성공 (id: ${currentData.id})`);
|
||||
} else {
|
||||
console.error(`❌ 품목 수정 실패 (id: ${currentData.id}):`, response.message);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`❌ 품목 수정 오류 (id: ${currentData.id}):`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3️⃣ 삭제된 품목 제거 (원본에는 있지만 현재 데이터에는 없는 항목)
|
||||
const currentIds = new Set(groupData.map((item) => item.id).filter(Boolean));
|
||||
const deletedItems = originalGroupData.filter(
|
||||
(orig) => orig.id && !currentIds.has(orig.id)
|
||||
);
|
||||
|
||||
for (const deletedItem of deletedItems) {
|
||||
console.log("🗑️ 품목 삭제:", deletedItem);
|
||||
|
||||
try {
|
||||
const response = await dynamicFormApi.deleteFormDataFromTable(
|
||||
deletedItem.id,
|
||||
screenData.screenInfo.tableName
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
deletedCount++;
|
||||
console.log(`✅ 품목 삭제 성공 (id: ${deletedItem.id})`);
|
||||
} else {
|
||||
console.error(`❌ 품목 삭제 실패 (id: ${deletedItem.id}):`, response.message);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`❌ 품목 삭제 오류 (id: ${deletedItem.id}):`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 결과 메시지
|
||||
const messages: string[] = [];
|
||||
if (insertedCount > 0) messages.push(`${insertedCount}개 추가`);
|
||||
if (updatedCount > 0) messages.push(`${updatedCount}개 수정`);
|
||||
if (deletedCount > 0) messages.push(`${deletedCount}개 삭제`);
|
||||
|
||||
if (messages.length > 0) {
|
||||
toast.success(`품목이 저장되었습니다 (${messages.join(", ")})`);
|
||||
|
||||
// 부모 컴포넌트의 onSave 콜백 실행 (테이블 새로고침)
|
||||
if (modalState.onSave) {
|
||||
@@ -585,6 +674,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||
tableName: screenData.screenInfo?.tableName,
|
||||
}}
|
||||
onSave={handleSave}
|
||||
isInModal={true}
|
||||
// 🆕 그룹 데이터를 ModalRepeaterTable에 전달
|
||||
groupedData={groupData.length > 0 ? groupData : undefined}
|
||||
/>
|
||||
|
||||
@@ -48,6 +48,8 @@ interface InteractiveScreenViewerProps {
|
||||
companyCode?: string;
|
||||
// 🆕 그룹 데이터 (EditModal에서 전달)
|
||||
groupedData?: Record<string, any>[];
|
||||
// 🆕 EditModal 내부인지 여부 (button-primary가 EditModal의 handleSave 사용하도록)
|
||||
isInModal?: boolean;
|
||||
}
|
||||
|
||||
export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerProps> = ({
|
||||
@@ -64,6 +66,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
userName: externalUserName,
|
||||
companyCode: externalCompanyCode,
|
||||
groupedData,
|
||||
isInModal = false,
|
||||
}) => {
|
||||
const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
|
||||
const { userName: authUserName, user: authUser } = useAuth();
|
||||
@@ -329,6 +332,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
userId={user?.userId} // ✅ 사용자 ID 전달
|
||||
userName={user?.userName} // ✅ 사용자 이름 전달
|
||||
companyCode={user?.companyCode} // ✅ 회사 코드 전달
|
||||
onSave={onSave} // 🆕 EditModal의 handleSave 콜백 전달
|
||||
allComponents={allComponents} // 🆕 같은 화면의 모든 컴포넌트 전달 (TableList 자동 감지용)
|
||||
selectedRowsData={selectedRowsData}
|
||||
onSelectedRowsChange={(selectedRows, selectedData) => {
|
||||
@@ -401,6 +405,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||
required: required,
|
||||
placeholder: placeholder,
|
||||
className: "w-full h-full",
|
||||
isInModal: isInModal, // 🆕 EditModal 내부 여부 전달
|
||||
onSave: onSave, // 🆕 EditModal의 handleSave 콜백 전달
|
||||
}}
|
||||
config={widget.webTypeConfig}
|
||||
onEvent={(event: string, data: any) => {
|
||||
|
||||
Reference in New Issue
Block a user