feat(UniversalFormModal): 섹션별 저장 방식 설정 기능 추가
SectionSaveMode 타입 추가 (공통 저장/개별 저장) SaveSettingsModal에 섹션별/필드별 저장 방식 설정 UI 추가 saveSingleRow()에 공통 필드 + 품목 병합 저장 로직 구현 buttonActions.ts에 외부 저장 버튼용 병합 저장 처리 추가
This commit is contained in:
parent
9684a83f37
commit
9fb94da493
|
|
@ -837,7 +837,79 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
}
|
||||
|
||||
// 메인 데이터 저장
|
||||
// 테이블 섹션이 있고 메인 테이블에 품목별로 저장하는 경우 (공통 + 개별 병합 저장)
|
||||
// targetTable이 없거나 메인 테이블과 같은 경우
|
||||
const tableSectionsForMainTable = config.sections.filter(
|
||||
(s) => s.type === "table" &&
|
||||
(!s.tableConfig?.saveConfig?.targetTable ||
|
||||
s.tableConfig.saveConfig.targetTable === config.saveConfig.tableName)
|
||||
);
|
||||
|
||||
if (tableSectionsForMainTable.length > 0) {
|
||||
// 공통 저장 필드 수집 (sectionSaveModes 설정에 따라)
|
||||
const commonFieldsData: Record<string, any> = {};
|
||||
const { sectionSaveModes } = config.saveConfig;
|
||||
|
||||
// 필드 타입 섹션에서 공통 저장 필드 수집
|
||||
for (const section of config.sections) {
|
||||
if (section.type === "table") continue;
|
||||
|
||||
const sectionMode = sectionSaveModes?.find((s) => s.sectionId === section.id);
|
||||
const defaultMode = "common"; // 필드 타입 섹션의 기본값은 공통 저장
|
||||
const sectionSaveMode = sectionMode?.saveMode || defaultMode;
|
||||
|
||||
if (section.fields) {
|
||||
for (const field of section.fields) {
|
||||
const fieldOverride = sectionMode?.fieldOverrides?.find((f) => f.fieldName === field.columnName);
|
||||
const fieldSaveMode = fieldOverride?.saveMode || sectionSaveMode;
|
||||
|
||||
if (fieldSaveMode === "common" && dataToSave[field.columnName] !== undefined) {
|
||||
commonFieldsData[field.columnName] = dataToSave[field.columnName];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 각 테이블 섹션의 품목 데이터에 공통 필드 병합하여 저장
|
||||
for (const tableSection of tableSectionsForMainTable) {
|
||||
const sectionData = tableSectionData[tableSection.id] || [];
|
||||
|
||||
if (sectionData.length > 0) {
|
||||
// 품목별로 행 저장
|
||||
for (const item of sectionData) {
|
||||
const rowToSave = { ...commonFieldsData, ...item };
|
||||
|
||||
// _sourceData 등 내부 메타데이터 제거
|
||||
Object.keys(rowToSave).forEach((key) => {
|
||||
if (key.startsWith("_")) {
|
||||
delete rowToSave[key];
|
||||
}
|
||||
});
|
||||
|
||||
const response = await apiClient.post(
|
||||
`/table-management/tables/${config.saveConfig.tableName}/add`,
|
||||
rowToSave
|
||||
);
|
||||
|
||||
if (!response.data?.success) {
|
||||
throw new Error(response.data?.message || "품목 저장 실패");
|
||||
}
|
||||
}
|
||||
|
||||
// 이미 저장했으므로 아래 로직에서 다시 저장하지 않도록 제거
|
||||
delete tableSectionData[tableSection.id];
|
||||
}
|
||||
}
|
||||
|
||||
// 품목이 없으면 공통 데이터만 저장하지 않음 (품목이 필요한 화면이므로)
|
||||
// 다른 테이블 섹션이 있는 경우에만 메인 데이터 저장
|
||||
const hasOtherTableSections = Object.keys(tableSectionData).length > 0;
|
||||
if (!hasOtherTableSections) {
|
||||
return; // 메인 테이블에 저장할 품목이 없으면 종료
|
||||
}
|
||||
}
|
||||
|
||||
// 메인 데이터 저장 (테이블 섹션이 없거나 별도 테이블에 저장하는 경우)
|
||||
const response = await apiClient.post(`/table-management/tables/${config.saveConfig.tableName}/add`, dataToSave);
|
||||
|
||||
if (!response.data?.success) {
|
||||
|
|
@ -852,8 +924,39 @@ export function UniversalFormModalComponent({
|
|||
// 메인 레코드 ID가 필요한 경우 (response.data에서 가져오기)
|
||||
const mainRecordId = response.data?.data?.id;
|
||||
|
||||
// 공통 저장 필드 수집 (sectionSaveModes 설정에 따라)
|
||||
const commonFieldsData: Record<string, any> = {};
|
||||
const { sectionSaveModes } = config.saveConfig;
|
||||
|
||||
if (sectionSaveModes && sectionSaveModes.length > 0) {
|
||||
// 다른 섹션에서 공통 저장으로 설정된 필드 값 수집
|
||||
for (const otherSection of config.sections) {
|
||||
if (otherSection.id === section.id) continue; // 현재 테이블 섹션은 건너뛰기
|
||||
|
||||
const sectionMode = sectionSaveModes.find((s) => s.sectionId === otherSection.id);
|
||||
const defaultMode = otherSection.type === "table" ? "individual" : "common";
|
||||
const sectionSaveMode = sectionMode?.saveMode || defaultMode;
|
||||
|
||||
// 필드 타입 섹션의 필드들 처리
|
||||
if (otherSection.type !== "table" && otherSection.fields) {
|
||||
for (const field of otherSection.fields) {
|
||||
// 필드별 오버라이드 확인
|
||||
const fieldOverride = sectionMode?.fieldOverrides?.find((f) => f.fieldName === field.columnName);
|
||||
const fieldSaveMode = fieldOverride?.saveMode || sectionSaveMode;
|
||||
|
||||
// 공통 저장이면 formData에서 값을 가져와 모든 품목에 적용
|
||||
if (fieldSaveMode === "common" && formData[field.columnName] !== undefined) {
|
||||
commonFieldsData[field.columnName] = formData[field.columnName];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of sectionData) {
|
||||
const itemToSave = { ...item };
|
||||
// 공통 필드 병합 + 개별 품목 데이터
|
||||
const itemToSave = { ...commonFieldsData, ...item };
|
||||
|
||||
// 메인 레코드와 연결이 필요한 경우
|
||||
if (mainRecordId && config.saveConfig.primaryKeyColumn) {
|
||||
itemToSave[config.saveConfig.primaryKeyColumn] = mainRecordId;
|
||||
|
|
@ -867,7 +970,7 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
}, [config.sections, config.saveConfig.tableName, config.saveConfig.primaryKeyColumn, formData]);
|
||||
}, [config.sections, config.saveConfig.tableName, config.saveConfig.primaryKeyColumn, config.saveConfig.sectionSaveModes, formData]);
|
||||
|
||||
// 다중 행 저장 (겸직 등)
|
||||
const saveMultipleRows = useCallback(async () => {
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Di
|
|||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Plus, Trash2, Database, Layers } from "lucide-react";
|
||||
import { Plus, Trash2, Database, Layers, Info } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SaveConfig, SubTableSaveConfig, SubTableFieldMapping, FormSectionConfig, FormFieldConfig } from "../types";
|
||||
import { SaveConfig, SubTableSaveConfig, SubTableFieldMapping, FormSectionConfig, FormFieldConfig, SectionSaveMode } from "../types";
|
||||
|
||||
// 도움말 텍스트 컴포넌트
|
||||
const HelpText = ({ children }: { children: React.ReactNode }) => (
|
||||
|
|
@ -235,6 +236,96 @@ export function SaveSettingsModal({
|
|||
|
||||
const allFields = getAllFields();
|
||||
|
||||
// 섹션별 저장 방식 조회 (없으면 기본값 반환)
|
||||
const getSectionSaveMode = (sectionId: string, sectionType: "fields" | "table"): "common" | "individual" => {
|
||||
const sectionMode = localSaveConfig.sectionSaveModes?.find((s) => s.sectionId === sectionId);
|
||||
if (sectionMode) {
|
||||
return sectionMode.saveMode;
|
||||
}
|
||||
// 기본값: fields 타입은 공통 저장, table 타입은 개별 저장
|
||||
return sectionType === "fields" ? "common" : "individual";
|
||||
};
|
||||
|
||||
// 필드별 저장 방식 조회 (오버라이드 확인)
|
||||
const getFieldSaveMode = (sectionId: string, fieldName: string, sectionType: "fields" | "table"): "common" | "individual" => {
|
||||
const sectionMode = localSaveConfig.sectionSaveModes?.find((s) => s.sectionId === sectionId);
|
||||
if (sectionMode) {
|
||||
// 필드별 오버라이드 확인
|
||||
const fieldOverride = sectionMode.fieldOverrides?.find((f) => f.fieldName === fieldName);
|
||||
if (fieldOverride) {
|
||||
return fieldOverride.saveMode;
|
||||
}
|
||||
return sectionMode.saveMode;
|
||||
}
|
||||
// 기본값
|
||||
return sectionType === "fields" ? "common" : "individual";
|
||||
};
|
||||
|
||||
// 섹션별 저장 방식 업데이트
|
||||
const updateSectionSaveMode = (sectionId: string, mode: "common" | "individual") => {
|
||||
const currentModes = localSaveConfig.sectionSaveModes || [];
|
||||
const existingIndex = currentModes.findIndex((s) => s.sectionId === sectionId);
|
||||
|
||||
let newModes: SectionSaveMode[];
|
||||
if (existingIndex >= 0) {
|
||||
newModes = [...currentModes];
|
||||
newModes[existingIndex] = { ...newModes[existingIndex], saveMode: mode };
|
||||
} else {
|
||||
newModes = [...currentModes, { sectionId, saveMode: mode }];
|
||||
}
|
||||
|
||||
updateSaveConfig({ sectionSaveModes: newModes });
|
||||
};
|
||||
|
||||
// 필드별 오버라이드 토글
|
||||
const toggleFieldOverride = (sectionId: string, fieldName: string, sectionType: "fields" | "table") => {
|
||||
const currentModes = localSaveConfig.sectionSaveModes || [];
|
||||
const sectionIndex = currentModes.findIndex((s) => s.sectionId === sectionId);
|
||||
|
||||
// 섹션 설정이 없으면 먼저 생성
|
||||
let newModes = [...currentModes];
|
||||
if (sectionIndex < 0) {
|
||||
const defaultMode = sectionType === "fields" ? "common" : "individual";
|
||||
newModes.push({ sectionId, saveMode: defaultMode, fieldOverrides: [] });
|
||||
}
|
||||
|
||||
const targetIndex = newModes.findIndex((s) => s.sectionId === sectionId);
|
||||
const sectionMode = newModes[targetIndex];
|
||||
const currentFieldOverrides = sectionMode.fieldOverrides || [];
|
||||
const fieldOverrideIndex = currentFieldOverrides.findIndex((f) => f.fieldName === fieldName);
|
||||
|
||||
let newFieldOverrides;
|
||||
if (fieldOverrideIndex >= 0) {
|
||||
// 이미 오버라이드가 있으면 제거 (섹션 기본값으로 돌아감)
|
||||
newFieldOverrides = currentFieldOverrides.filter((f) => f.fieldName !== fieldName);
|
||||
} else {
|
||||
// 오버라이드 추가 (섹션 기본값의 반대)
|
||||
const oppositeMode = sectionMode.saveMode === "common" ? "individual" : "common";
|
||||
newFieldOverrides = [...currentFieldOverrides, { fieldName, saveMode: oppositeMode }];
|
||||
}
|
||||
|
||||
newModes[targetIndex] = { ...sectionMode, fieldOverrides: newFieldOverrides };
|
||||
updateSaveConfig({ sectionSaveModes: newModes });
|
||||
};
|
||||
|
||||
// 섹션의 필드 목록 가져오기
|
||||
const getSectionFields = (section: FormSectionConfig): { fieldName: string; label: string }[] => {
|
||||
if (section.type === "table" && section.tableConfig) {
|
||||
// 테이블 타입: tableConfig.columns에서 필드 목록 가져오기
|
||||
return (section.tableConfig.columns || []).map((col) => ({
|
||||
fieldName: col.field,
|
||||
label: col.label,
|
||||
}));
|
||||
} else if (section.fields) {
|
||||
// 필드 타입: fields에서 목록 가져오기
|
||||
return section.fields.map((field) => ({
|
||||
fieldName: field.columnName,
|
||||
label: field.label,
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[800px] max-h-[90vh] flex flex-col p-0">
|
||||
|
|
@ -724,6 +815,150 @@ export function SaveSettingsModal({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* 섹션별 저장 방식 */}
|
||||
<div className="space-y-3 border rounded-lg p-3 bg-card">
|
||||
<div className="flex items-center gap-2">
|
||||
<Layers className="h-4 w-4 text-green-600" />
|
||||
<h3 className="text-xs font-semibold">섹션별 저장 방식</h3>
|
||||
</div>
|
||||
|
||||
{/* 설명 */}
|
||||
<div className="bg-muted/50 rounded-lg p-2.5 space-y-1.5">
|
||||
<div className="flex items-start gap-2">
|
||||
<Info className="h-3.5 w-3.5 text-blue-500 mt-0.5 shrink-0" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
<span className="font-medium text-foreground">공통 저장:</span> 이 섹션의 필드 값이 모든 품목 행에 <span className="font-medium">동일하게</span> 저장됩니다
|
||||
<br />
|
||||
<span className="text-[9px] text-muted-foreground/80">예: 수주번호, 거래처, 수주일 - 품목이 3개면 3개 행 모두 같은 값</span>
|
||||
</p>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
<span className="font-medium text-foreground">개별 저장:</span> 이 섹션의 필드 값이 각 품목마다 <span className="font-medium">다르게</span> 저장됩니다
|
||||
<br />
|
||||
<span className="text-[9px] text-muted-foreground/80">예: 품목코드, 수량, 단가 - 품목마다 다른 값</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 섹션 목록 */}
|
||||
{sections.length === 0 ? (
|
||||
<div className="text-center py-4 border border-dashed rounded-lg">
|
||||
<p className="text-[10px] text-muted-foreground">섹션이 없습니다</p>
|
||||
</div>
|
||||
) : (
|
||||
<Accordion type="multiple" className="space-y-2">
|
||||
{sections.map((section) => {
|
||||
const sectionType = section.type || "fields";
|
||||
const currentMode = getSectionSaveMode(section.id, sectionType);
|
||||
const sectionFields = getSectionFields(section);
|
||||
|
||||
return (
|
||||
<AccordionItem
|
||||
key={section.id}
|
||||
value={section.id}
|
||||
className={cn(
|
||||
"border rounded-lg",
|
||||
currentMode === "common" ? "bg-blue-50/30" : "bg-orange-50/30"
|
||||
)}
|
||||
>
|
||||
<AccordionTrigger className="px-3 py-2 text-xs hover:no-underline">
|
||||
<div className="flex items-center justify-between flex-1 mr-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{section.title}</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"text-[8px] h-4",
|
||||
sectionType === "table" ? "border-orange-300 text-orange-600" : "border-blue-300 text-blue-600"
|
||||
)}
|
||||
>
|
||||
{sectionType === "table" ? "테이블" : "필드"}
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge
|
||||
variant={currentMode === "common" ? "default" : "secondary"}
|
||||
className="text-[8px] h-4"
|
||||
>
|
||||
{currentMode === "common" ? "공통 저장" : "개별 저장"}
|
||||
</Badge>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="px-3 pb-3 space-y-3">
|
||||
{/* 저장 방식 선택 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-[10px] font-medium">저장 방식</Label>
|
||||
<RadioGroup
|
||||
value={currentMode}
|
||||
onValueChange={(value) => updateSectionSaveMode(section.id, value as "common" | "individual")}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<RadioGroupItem value="common" id={`${section.id}-common`} className="h-3 w-3" />
|
||||
<Label htmlFor={`${section.id}-common`} className="text-[10px] cursor-pointer">
|
||||
공통 저장
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<RadioGroupItem value="individual" id={`${section.id}-individual`} className="h-3 w-3" />
|
||||
<Label htmlFor={`${section.id}-individual`} className="text-[10px] cursor-pointer">
|
||||
개별 저장
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{/* 필드 목록 */}
|
||||
{sectionFields.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<Label className="text-[10px] font-medium">필드 목록 ({sectionFields.length}개)</Label>
|
||||
<HelpText>필드를 클릭하면 섹션 기본값과 다르게 설정할 수 있습니다</HelpText>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
{sectionFields.map((field) => {
|
||||
const fieldMode = getFieldSaveMode(section.id, field.fieldName, sectionType);
|
||||
const isOverridden = fieldMode !== currentMode;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={field.fieldName}
|
||||
onClick={() => toggleFieldOverride(section.id, field.fieldName, sectionType)}
|
||||
className={cn(
|
||||
"flex items-center justify-between px-2 py-1.5 rounded border text-left transition-colors",
|
||||
isOverridden
|
||||
? "border-amber-300 bg-amber-50"
|
||||
: "border-gray-200 bg-white hover:bg-gray-50"
|
||||
)}
|
||||
>
|
||||
<span className="text-[9px] truncate flex-1">
|
||||
{field.label}
|
||||
<span className="text-muted-foreground ml-1">({field.fieldName})</span>
|
||||
</span>
|
||||
<Badge
|
||||
variant={fieldMode === "common" ? "default" : "secondary"}
|
||||
className={cn(
|
||||
"text-[7px] h-3.5 ml-1 shrink-0",
|
||||
isOverridden && "ring-1 ring-amber-400"
|
||||
)}
|
||||
>
|
||||
{fieldMode === "common" ? "공통" : "개별"}
|
||||
</Badge>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
);
|
||||
})}
|
||||
</Accordion>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 저장 후 동작 */}
|
||||
<div className="space-y-2 border rounded-lg p-3 bg-card">
|
||||
<h3 className="text-xs font-semibold">저장 후 동작</h3>
|
||||
|
|
|
|||
|
|
@ -507,6 +507,21 @@ export interface MultiRowSaveConfig {
|
|||
mainSectionFields?: string[]; // 메인 행에만 저장할 필드
|
||||
}
|
||||
|
||||
/**
|
||||
* 섹션별 저장 방식 설정
|
||||
* 공통 저장: 해당 섹션의 필드 값이 모든 품목 행에 동일하게 저장됩니다 (예: 수주번호, 거래처)
|
||||
* 개별 저장: 해당 섹션의 필드 값이 각 품목마다 다르게 저장됩니다 (예: 품목코드, 수량, 단가)
|
||||
*/
|
||||
export interface SectionSaveMode {
|
||||
sectionId: string;
|
||||
saveMode: "common" | "individual"; // 공통 저장 / 개별 저장
|
||||
// 필드별 세부 설정 (선택사항 - 섹션 기본값과 다르게 설정할 필드)
|
||||
fieldOverrides?: {
|
||||
fieldName: string;
|
||||
saveMode: "common" | "individual";
|
||||
}[];
|
||||
}
|
||||
|
||||
// 저장 설정
|
||||
export interface SaveConfig {
|
||||
tableName: string;
|
||||
|
|
@ -518,6 +533,9 @@ export interface SaveConfig {
|
|||
// 커스텀 API 저장 설정 (테이블 직접 저장 대신 전용 API 사용)
|
||||
customApiSave?: CustomApiSaveConfig;
|
||||
|
||||
// 섹션별 저장 방식 설정
|
||||
sectionSaveModes?: SectionSaveMode[];
|
||||
|
||||
// 저장 후 동작 (간편 설정)
|
||||
showToast?: boolean; // 토스트 메시지 (기본: true)
|
||||
refreshParent?: boolean; // 부모 새로고침 (기본: true)
|
||||
|
|
|
|||
|
|
@ -675,6 +675,14 @@ export class ButtonActionExecutor {
|
|||
console.log("⚠️ [handleSave] formData 전체 내용:", context.formData);
|
||||
}
|
||||
|
||||
// 🆕 Universal Form Modal 테이블 섹션 병합 저장 처리
|
||||
// 범용_폼_모달 내부에 _tableSection_ 데이터가 있는 경우 공통 필드 + 개별 품목 병합 저장
|
||||
const universalFormModalResult = await this.handleUniversalFormModalTableSectionSave(config, context, formData);
|
||||
if (universalFormModalResult.handled) {
|
||||
console.log("✅ [handleSave] Universal Form Modal 테이블 섹션 저장 완료");
|
||||
return universalFormModalResult.success;
|
||||
}
|
||||
|
||||
// 폼 유효성 검사
|
||||
if (config.validateForm) {
|
||||
const validation = this.validateFormData(formData);
|
||||
|
|
@ -1479,6 +1487,122 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 Universal Form Modal 테이블 섹션 병합 저장 처리
|
||||
* 범용_폼_모달 내부의 공통 필드 + _tableSection_ 데이터를 병합하여 품목별로 저장
|
||||
*/
|
||||
private static async handleUniversalFormModalTableSectionSave(
|
||||
config: ButtonActionConfig,
|
||||
context: ButtonActionContext,
|
||||
formData: Record<string, any>,
|
||||
): Promise<{ handled: boolean; success: boolean }> {
|
||||
const { tableName, screenId } = context;
|
||||
|
||||
// 범용_폼_모달 키 찾기 (컬럼명에 따라 다를 수 있음)
|
||||
const universalFormModalKey = Object.keys(formData).find((key) => {
|
||||
const value = formData[key];
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
||||
// _tableSection_ 키가 있는지 확인
|
||||
return Object.keys(value).some((k) => k.startsWith("_tableSection_"));
|
||||
});
|
||||
|
||||
if (!universalFormModalKey) {
|
||||
return { handled: false, success: false };
|
||||
}
|
||||
|
||||
console.log("🎯 [handleUniversalFormModalTableSectionSave] Universal Form Modal 감지:", universalFormModalKey);
|
||||
|
||||
const modalData = formData[universalFormModalKey];
|
||||
|
||||
// _tableSection_ 데이터 추출
|
||||
const tableSectionData: Record<string, any[]> = {};
|
||||
const commonFieldsData: Record<string, any> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(modalData)) {
|
||||
if (key.startsWith("_tableSection_")) {
|
||||
const sectionId = key.replace("_tableSection_", "");
|
||||
tableSectionData[sectionId] = value as any[];
|
||||
} else if (!key.startsWith("_")) {
|
||||
// _로 시작하지 않는 필드는 공통 필드로 처리
|
||||
commonFieldsData[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("🎯 [handleUniversalFormModalTableSectionSave] 데이터 분리:", {
|
||||
commonFields: Object.keys(commonFieldsData),
|
||||
tableSections: Object.keys(tableSectionData),
|
||||
tableSectionCounts: Object.entries(tableSectionData).map(([k, v]) => ({ [k]: v.length })),
|
||||
});
|
||||
|
||||
// 테이블 섹션 데이터가 없으면 처리하지 않음
|
||||
const hasTableSectionData = Object.values(tableSectionData).some((arr) => arr.length > 0);
|
||||
if (!hasTableSectionData) {
|
||||
console.log("⚠️ [handleUniversalFormModalTableSectionSave] 테이블 섹션 데이터 없음 - 일반 저장으로 전환");
|
||||
return { handled: false, success: false };
|
||||
}
|
||||
|
||||
try {
|
||||
// 사용자 정보 추가
|
||||
if (!context.userId) {
|
||||
throw new Error("사용자 정보를 불러올 수 없습니다. 다시 로그인해주세요.");
|
||||
}
|
||||
|
||||
const userInfo = {
|
||||
writer: context.userId,
|
||||
created_by: context.userId,
|
||||
updated_by: context.userId,
|
||||
company_code: context.companyCode || "",
|
||||
};
|
||||
|
||||
let totalSaved = 0;
|
||||
|
||||
// 각 테이블 섹션의 품목별로 저장
|
||||
for (const [sectionId, items] of Object.entries(tableSectionData)) {
|
||||
console.log(`🔄 [handleUniversalFormModalTableSectionSave] 섹션 ${sectionId} 저장 시작: ${items.length}개 품목`);
|
||||
|
||||
for (const item of items) {
|
||||
// 공통 필드 + 품목 데이터 병합
|
||||
const rowToSave = { ...commonFieldsData, ...item, ...userInfo };
|
||||
|
||||
// 내부 메타데이터 제거
|
||||
Object.keys(rowToSave).forEach((key) => {
|
||||
if (key.startsWith("_")) {
|
||||
delete rowToSave[key];
|
||||
}
|
||||
});
|
||||
|
||||
console.log("📝 [handleUniversalFormModalTableSectionSave] 저장할 행:", rowToSave);
|
||||
|
||||
// INSERT 실행
|
||||
const saveResult = await DynamicFormApi.saveFormData({
|
||||
screenId: screenId!,
|
||||
tableName: tableName!,
|
||||
data: rowToSave,
|
||||
});
|
||||
|
||||
if (!saveResult.success) {
|
||||
throw new Error(saveResult.message || "품목 저장 실패");
|
||||
}
|
||||
|
||||
totalSaved++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ [handleUniversalFormModalTableSectionSave] 총 ${totalSaved}개 행 저장 완료`);
|
||||
toast.success(`${totalSaved}개 항목이 저장되었습니다.`);
|
||||
|
||||
// 저장 성공 이벤트 발생
|
||||
window.dispatchEvent(new CustomEvent("saveSuccess"));
|
||||
window.dispatchEvent(new CustomEvent("refreshTable"));
|
||||
|
||||
return { handled: true, success: true };
|
||||
} catch (error: any) {
|
||||
console.error("❌ [handleUniversalFormModalTableSectionSave] 저장 오류:", error);
|
||||
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||
return { handled: true, success: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 배치 저장 액션 처리 (SelectedItemsDetailInput용 - 새로운 데이터 구조)
|
||||
* ItemData[] → 각 품목의 details 배열을 개별 레코드로 저장
|
||||
|
|
|
|||
Loading…
Reference in New Issue