refactor: UniversalFormModalComponent 자체 저장 기능 제거
saveSingleRow, saveWithCustomApi, handleSave, handleReset 함수 삭제 saving 상태 및 저장/초기화 버튼 UI 삭제 설정 패널에서 저장 버튼 관련 설정 UI 삭제 ModalConfig 타입에서 버튼 관련 속성 삭제 저장 처리는 button-primary (action: save)로 위임 약 468줄 코드 삭제
This commit is contained in:
parent
384106dd95
commit
17498b1b2b
|
|
@ -19,11 +19,11 @@ import {
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
import { ChevronDown, ChevronUp, ChevronRight, Plus, Trash2, RefreshCw, Loader2 } from "lucide-react";
|
import { ChevronDown, ChevronUp, ChevronRight, Plus, Trash2, Loader2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
import { generateNumberingCode, allocateNumberingCode, previewNumberingCode } from "@/lib/api/numberingRule";
|
import { allocateNumberingCode, previewNumberingCode } from "@/lib/api/numberingRule";
|
||||||
import { useCascadingDropdown } from "@/hooks/useCascadingDropdown";
|
import { useCascadingDropdown } from "@/hooks/useCascadingDropdown";
|
||||||
import { CascadingDropdownConfig } from "@/types/screen-management";
|
import { CascadingDropdownConfig } from "@/types/screen-management";
|
||||||
|
|
||||||
|
|
@ -190,9 +190,6 @@ export function UniversalFormModalComponent({
|
||||||
[tableKey: string]: Record<string, any>[];
|
[tableKey: string]: Record<string, any>[];
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
// 로딩 상태
|
|
||||||
const [saving, setSaving] = useState(false);
|
|
||||||
|
|
||||||
// 채번규칙 원본 값 추적 (수동 모드 감지용)
|
// 채번규칙 원본 값 추적 (수동 모드 감지용)
|
||||||
// key: columnName, value: 자동 생성된 원본 값
|
// key: columnName, value: 자동 생성된 원본 값
|
||||||
const [numberingOriginalValues, setNumberingOriginalValues] = useState<Record<string, string>>({});
|
const [numberingOriginalValues, setNumberingOriginalValues] = useState<Record<string, string>>({});
|
||||||
|
|
@ -610,7 +607,8 @@ export function UniversalFormModalComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableConfig = section.tableConfig;
|
const tableConfig = section.tableConfig;
|
||||||
const editConfig = tableConfig.editConfig;
|
// editConfig는 타입에 정의되지 않았지만 런타임에 존재할 수 있음
|
||||||
|
const editConfig = (tableConfig as any).editConfig;
|
||||||
const saveConfig = tableConfig.saveConfig;
|
const saveConfig = tableConfig.saveConfig;
|
||||||
|
|
||||||
console.log(`[initializeForm] 테이블 섹션 ${section.id} 검사:`, {
|
console.log(`[initializeForm] 테이블 섹션 ${section.id} 검사:`, {
|
||||||
|
|
@ -1240,266 +1238,6 @@ export function UniversalFormModalComponent({
|
||||||
return { valid: missingFields.length === 0, missingFields };
|
return { valid: missingFields.length === 0, missingFields };
|
||||||
}, [config.sections, formData]);
|
}, [config.sections, formData]);
|
||||||
|
|
||||||
// 단일 행 저장
|
|
||||||
const saveSingleRow = useCallback(async () => {
|
|
||||||
const dataToSave = { ...formData };
|
|
||||||
|
|
||||||
// 테이블 섹션 데이터 추출 (별도 저장용)
|
|
||||||
const tableSectionData: Record<string, any[]> = {};
|
|
||||||
|
|
||||||
// 메타데이터 필드 제거 (채번 규칙 ID는 유지 - buttonActions.ts에서 사용)
|
|
||||||
Object.keys(dataToSave).forEach((key) => {
|
|
||||||
if (key.startsWith("_tableSection_")) {
|
|
||||||
// 테이블 섹션 데이터는 별도로 저장
|
|
||||||
const sectionId = key.replace("_tableSection_", "");
|
|
||||||
tableSectionData[sectionId] = dataToSave[key] || [];
|
|
||||||
delete dataToSave[key];
|
|
||||||
} else if (key.startsWith("_") && !key.includes("_numberingRuleId")) {
|
|
||||||
delete dataToSave[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 저장 시점 채번규칙 처리
|
|
||||||
for (const section of config.sections) {
|
|
||||||
// 테이블 타입 섹션은 건너뛰기
|
|
||||||
if (section.type === "table") continue;
|
|
||||||
|
|
||||||
for (const field of section.fields || []) {
|
|
||||||
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
|
||||||
const ruleIdKey = `${field.columnName}_numberingRuleId`;
|
|
||||||
const hasRuleId = dataToSave[ruleIdKey]; // 사용자가 수정하지 않았으면 ruleId 유지됨
|
|
||||||
|
|
||||||
// 채번 규칙 할당 조건
|
|
||||||
const shouldAllocate =
|
|
||||||
// 1. generateOnSave가 ON인 경우: 항상 저장 시점에 할당
|
|
||||||
field.numberingRule.generateOnSave ||
|
|
||||||
// 2. editable이 OFF인 경우: 사용자 입력 무시하고 채번 규칙으로 덮어씌움
|
|
||||||
!field.numberingRule.editable ||
|
|
||||||
// 3. editable이 ON이고 사용자가 수정하지 않은 경우 (ruleId 유지됨): 실제 번호 할당
|
|
||||||
(field.numberingRule.editable && hasRuleId);
|
|
||||||
|
|
||||||
if (shouldAllocate) {
|
|
||||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
|
||||||
if (response.success && response.data?.generatedCode) {
|
|
||||||
dataToSave[field.columnName] = response.data.generatedCode;
|
|
||||||
let reason = "(알 수 없음)";
|
|
||||||
if (field.numberingRule.generateOnSave) {
|
|
||||||
reason = "(generateOnSave)";
|
|
||||||
} else if (!field.numberingRule.editable) {
|
|
||||||
reason = "(editable=OFF, 강제 덮어씌움)";
|
|
||||||
} else if (hasRuleId) {
|
|
||||||
reason = "(editable=ON, 사용자 미수정)";
|
|
||||||
}
|
|
||||||
console.log(`[채번 할당] ${field.columnName} = ${response.data.generatedCode} ${reason}`);
|
|
||||||
} else {
|
|
||||||
console.error(`[채번 실패] ${field.columnName}:`, response.error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`[채번 스킵] ${field.columnName}: 사용자가 직접 입력한 값 유지 = ${dataToSave[field.columnName]}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 별도 테이블에 저장해야 하는 테이블 섹션 목록
|
|
||||||
const tableSectionsForSeparateTable = config.sections.filter(
|
|
||||||
(s) =>
|
|
||||||
s.type === "table" &&
|
|
||||||
s.tableConfig?.saveConfig?.targetTable &&
|
|
||||||
s.tableConfig.saveConfig.targetTable !== config.saveConfig.tableName,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 테이블 섹션이 있고 메인 테이블에 품목별로 저장하는 경우 (공통 + 개별 병합 저장)
|
|
||||||
// targetTable이 없거나 메인 테이블과 같은 경우
|
|
||||||
const tableSectionsForMainTable = config.sections.filter(
|
|
||||||
(s) =>
|
|
||||||
s.type === "table" &&
|
|
||||||
(!s.tableConfig?.saveConfig?.targetTable ||
|
|
||||||
s.tableConfig.saveConfig.targetTable === config.saveConfig.tableName),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("[saveSingleRow] 메인 테이블:", config.saveConfig.tableName);
|
|
||||||
console.log(
|
|
||||||
"[saveSingleRow] 메인 테이블에 저장할 테이블 섹션:",
|
|
||||||
tableSectionsForMainTable.map((s) => s.id),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
"[saveSingleRow] 별도 테이블에 저장할 테이블 섹션:",
|
|
||||||
tableSectionsForSeparateTable.map((s) => s.id),
|
|
||||||
);
|
|
||||||
console.log("[saveSingleRow] 테이블 섹션 데이터 키:", Object.keys(tableSectionData));
|
|
||||||
console.log("[saveSingleRow] dataToSave 키:", Object.keys(dataToSave));
|
|
||||||
|
|
||||||
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) {
|
|
||||||
throw new Error(response.data?.message || "저장 실패");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 테이블 섹션 데이터 저장 (별도 테이블에)
|
|
||||||
for (const section of config.sections) {
|
|
||||||
if (section.type === "table" && section.tableConfig?.saveConfig?.targetTable) {
|
|
||||||
const sectionData = tableSectionData[section.id];
|
|
||||||
if (sectionData && sectionData.length > 0) {
|
|
||||||
// 메인 레코드 ID가 필요한 경우 (response.data에서 가져오기)
|
|
||||||
const mainRecordId = response.data?.data?.id;
|
|
||||||
|
|
||||||
// 공통 저장 필드 수집: 다른 섹션(필드 타입)에서 공통 저장으로 설정된 필드 값
|
|
||||||
// 기본값: 필드 타입 섹션은 'common', 테이블 타입 섹션은 'individual'
|
|
||||||
const commonFieldsData: Record<string, any> = {};
|
|
||||||
const { sectionSaveModes } = config.saveConfig;
|
|
||||||
|
|
||||||
// 다른 섹션에서 공통 저장으로 설정된 필드 값 수집
|
|
||||||
for (const otherSection of config.sections) {
|
|
||||||
if (otherSection.id === section.id) continue; // 현재 테이블 섹션은 건너뛰기
|
|
||||||
|
|
||||||
const sectionMode = sectionSaveModes?.find((s) => s.sectionId === otherSection.id);
|
|
||||||
// 기본값: 필드 타입 섹션은 'common', 테이블 타입 섹션은 'individual'
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🆕 선택적 필드 그룹 (optionalFieldGroups)도 처리
|
|
||||||
if (otherSection.optionalFieldGroups && otherSection.optionalFieldGroups.length > 0) {
|
|
||||||
for (const optGroup of otherSection.optionalFieldGroups) {
|
|
||||||
if (optGroup.fields) {
|
|
||||||
for (const field of optGroup.fields) {
|
|
||||||
// 선택적 필드 그룹은 기본적으로 common 저장
|
|
||||||
if (formData[field.columnName] !== undefined) {
|
|
||||||
commonFieldsData[field.columnName] = formData[field.columnName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[saveSingleRow] 별도 테이블 저장 - 공통 필드:", Object.keys(commonFieldsData));
|
|
||||||
|
|
||||||
for (const item of sectionData) {
|
|
||||||
// 공통 필드 병합 + 개별 품목 데이터
|
|
||||||
const itemToSave = { ...commonFieldsData, ...item };
|
|
||||||
|
|
||||||
// saveToTarget: false인 컬럼은 저장에서 제외
|
|
||||||
const columns = section.tableConfig?.columns || [];
|
|
||||||
for (const col of columns) {
|
|
||||||
if (col.saveConfig?.saveToTarget === false && col.field in itemToSave) {
|
|
||||||
delete itemToSave[col.field];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// _sourceData 등 내부 메타데이터 제거
|
|
||||||
Object.keys(itemToSave).forEach((key) => {
|
|
||||||
if (key.startsWith("_")) {
|
|
||||||
delete itemToSave[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 메인 레코드와 연결이 필요한 경우
|
|
||||||
if (mainRecordId && config.saveConfig.primaryKeyColumn) {
|
|
||||||
itemToSave[config.saveConfig.primaryKeyColumn] = mainRecordId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveResponse = await apiClient.post(
|
|
||||||
`/table-management/tables/${section.tableConfig.saveConfig.targetTable}/add`,
|
|
||||||
itemToSave,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!saveResponse.data?.success) {
|
|
||||||
throw new Error(saveResponse.data?.message || `${section.title || "테이블 섹션"} 저장 실패`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
config.sections,
|
|
||||||
config.saveConfig.tableName,
|
|
||||||
config.saveConfig.primaryKeyColumn,
|
|
||||||
config.saveConfig.sectionSaveModes,
|
|
||||||
formData,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 다중 테이블 저장 (범용)
|
// 다중 테이블 저장 (범용)
|
||||||
const saveWithMultiTable = useCallback(async () => {
|
const saveWithMultiTable = useCallback(async () => {
|
||||||
const { customApiSave } = config.saveConfig;
|
const { customApiSave } = config.saveConfig;
|
||||||
|
|
@ -1682,130 +1420,6 @@ export function UniversalFormModalComponent({
|
||||||
}
|
}
|
||||||
}, [config.sections, config.saveConfig, formData, repeatSections, initialData]);
|
}, [config.sections, config.saveConfig, formData, repeatSections, initialData]);
|
||||||
|
|
||||||
// 커스텀 API 저장
|
|
||||||
const saveWithCustomApi = useCallback(async () => {
|
|
||||||
const { customApiSave } = config.saveConfig;
|
|
||||||
if (!customApiSave) return;
|
|
||||||
|
|
||||||
const saveWithGenericCustomApi = async () => {
|
|
||||||
if (!customApiSave.customEndpoint) {
|
|
||||||
throw new Error("커스텀 API 엔드포인트가 설정되지 않았습니다.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataToSave = { ...formData };
|
|
||||||
|
|
||||||
// 메타데이터 필드 제거
|
|
||||||
Object.keys(dataToSave).forEach((key) => {
|
|
||||||
if (key.startsWith("_")) {
|
|
||||||
delete dataToSave[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 반복 섹션 데이터 포함
|
|
||||||
if (Object.keys(repeatSections).length > 0) {
|
|
||||||
dataToSave._repeatSections = repeatSections;
|
|
||||||
}
|
|
||||||
|
|
||||||
const method = customApiSave.customMethod || "POST";
|
|
||||||
const response =
|
|
||||||
method === "PUT"
|
|
||||||
? await apiClient.put(customApiSave.customEndpoint, dataToSave)
|
|
||||||
: await apiClient.post(customApiSave.customEndpoint, dataToSave);
|
|
||||||
|
|
||||||
if (!response.data?.success) {
|
|
||||||
throw new Error(response.data?.message || "저장 실패");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (customApiSave.apiType) {
|
|
||||||
case "multi-table":
|
|
||||||
await saveWithMultiTable();
|
|
||||||
break;
|
|
||||||
case "custom":
|
|
||||||
await saveWithGenericCustomApi();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`지원하지 않는 API 타입: ${customApiSave.apiType}`);
|
|
||||||
}
|
|
||||||
}, [config.saveConfig, formData, repeatSections, saveWithMultiTable]);
|
|
||||||
|
|
||||||
// 저장 처리
|
|
||||||
const handleSave = useCallback(async () => {
|
|
||||||
// 커스텀 API 저장 모드가 아닌 경우에만 테이블명 체크
|
|
||||||
if (!config.saveConfig.customApiSave?.enabled && !config.saveConfig.tableName) {
|
|
||||||
toast.error("저장할 테이블이 설정되지 않았습니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 필수 필드 검증
|
|
||||||
const { valid, missingFields } = validateRequiredFields();
|
|
||||||
if (!valid) {
|
|
||||||
toast.error(`필수 항목을 입력해주세요: ${missingFields.join(", ")}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSaving(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { customApiSave } = config.saveConfig;
|
|
||||||
|
|
||||||
// 커스텀 API 저장 모드 (다중 테이블)
|
|
||||||
if (customApiSave?.enabled) {
|
|
||||||
await saveWithCustomApi();
|
|
||||||
} else {
|
|
||||||
// 단일 테이블 저장
|
|
||||||
await saveSingleRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 저장 후 동작
|
|
||||||
if (config.saveConfig.afterSave?.showToast) {
|
|
||||||
toast.success("저장되었습니다.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.saveConfig.afterSave?.refreshParent) {
|
|
||||||
window.dispatchEvent(new CustomEvent("refreshParentData"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// onSave 콜백은 저장 완료 알림용으로만 사용
|
|
||||||
// 실제 저장은 이미 위에서 완료됨
|
|
||||||
// EditModal 등 부모 컴포넌트의 저장 로직이 다시 실행되지 않도록
|
|
||||||
// _saveCompleted 플래그를 포함하여 전달
|
|
||||||
if (onSave) {
|
|
||||||
onSave({ ...formData, _saveCompleted: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 저장 완료 후 모달 닫기 이벤트 발생
|
|
||||||
if (config.saveConfig.afterSave?.closeModal !== false) {
|
|
||||||
window.dispatchEvent(new CustomEvent("closeEditModal"));
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("저장 실패:", error);
|
|
||||||
// axios 에러의 경우 서버 응답 메시지 추출
|
|
||||||
const errorMessage =
|
|
||||||
error.response?.data?.message ||
|
|
||||||
error.response?.data?.error?.details ||
|
|
||||||
error.message ||
|
|
||||||
"저장에 실패했습니다.";
|
|
||||||
toast.error(errorMessage);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
config,
|
|
||||||
formData,
|
|
||||||
repeatSections,
|
|
||||||
onSave,
|
|
||||||
validateRequiredFields,
|
|
||||||
saveSingleRow,
|
|
||||||
saveWithCustomApi,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 폼 초기화
|
|
||||||
const handleReset = useCallback(() => {
|
|
||||||
initializeForm();
|
|
||||||
toast.info("폼이 초기화되었습니다.");
|
|
||||||
}, [initializeForm]);
|
|
||||||
|
|
||||||
// 필드 요소 렌더링 (입력 컴포넌트만)
|
// 필드 요소 렌더링 (입력 컴포넌트만)
|
||||||
// repeatContext: 반복 섹션인 경우 { sectionId, itemId }를 전달
|
// repeatContext: 반복 섹션인 경우 { sectionId, itemId }를 전달
|
||||||
const renderFieldElement = (
|
const renderFieldElement = (
|
||||||
|
|
@ -2544,38 +2158,6 @@ export function UniversalFormModalComponent({
|
||||||
{/* 섹션들 */}
|
{/* 섹션들 */}
|
||||||
<div className="space-y-4">{config.sections.map((section) => renderSection(section))}</div>
|
<div className="space-y-4">{config.sections.map((section) => renderSection(section))}</div>
|
||||||
|
|
||||||
{/* 버튼 영역 - 저장 버튼이 표시될 때만 렌더링 */}
|
|
||||||
{config.modal.showSaveButton !== false && (
|
|
||||||
<div className="mt-6 flex justify-end gap-2 border-t pt-4">
|
|
||||||
{config.modal.showResetButton && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
handleReset();
|
|
||||||
}}
|
|
||||||
disabled={saving}
|
|
||||||
>
|
|
||||||
<RefreshCw className="mr-1 h-4 w-4" />
|
|
||||||
{config.modal.resetButtonText || "초기화"}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
handleSave();
|
|
||||||
}}
|
|
||||||
disabled={saving || !config.saveConfig.tableName}
|
|
||||||
>
|
|
||||||
{saving ? "저장 중..." : config.modal.saveButtonText || "저장"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 삭제 확인 다이얼로그 */}
|
{/* 삭제 확인 다이얼로그 */}
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
open={deleteDialog.open}
|
open={deleteDialog.open}
|
||||||
|
|
|
||||||
|
|
@ -530,40 +530,6 @@ export function UniversalFormModalConfigPanel({
|
||||||
</Select>
|
</Select>
|
||||||
<HelpText>모달 창의 크기를 선택하세요</HelpText>
|
<HelpText>모달 창의 크기를 선택하세요</HelpText>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 저장 버튼 표시 설정 */}
|
|
||||||
<div className="w-full min-w-0">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
id="show-save-button"
|
|
||||||
checked={config.modal.showSaveButton !== false}
|
|
||||||
onCheckedChange={(checked) => updateModalConfig({ showSaveButton: checked === true })}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="show-save-button" className="cursor-pointer text-xs font-medium">
|
|
||||||
저장 버튼 표시
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<HelpText>체크 해제 시 모달 하단의 저장 버튼이 숨겨집니다</HelpText>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full min-w-0 space-y-3">
|
|
||||||
<div className="w-full min-w-0">
|
|
||||||
<Label className="mb-1.5 block text-xs font-medium">저장 버튼 텍스트</Label>
|
|
||||||
<Input
|
|
||||||
value={config.modal.saveButtonText || "저장"}
|
|
||||||
onChange={(e) => updateModalConfig({ saveButtonText: e.target.value })}
|
|
||||||
className="h-9 w-full max-w-full text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full min-w-0">
|
|
||||||
<Label className="mb-1.5 block text-xs font-medium">취소 버튼 텍스트</Label>
|
|
||||||
<Input
|
|
||||||
value={config.modal.cancelButtonText || "취소"}
|
|
||||||
onChange={(e) => updateModalConfig({ cancelButtonText: e.target.value })}
|
|
||||||
className="h-9 w-full max-w-full text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,6 @@ export const defaultConfig: UniversalFormModalConfig = {
|
||||||
size: "lg",
|
size: "lg",
|
||||||
closeOnOutsideClick: false,
|
closeOnOutsideClick: false,
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
showSaveButton: true,
|
|
||||||
saveButtonText: "저장",
|
|
||||||
cancelButtonText: "취소",
|
|
||||||
showResetButton: false,
|
|
||||||
resetButtonText: "초기화",
|
|
||||||
},
|
},
|
||||||
sections: [
|
sections: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -786,13 +786,6 @@ export interface ModalConfig {
|
||||||
size: "sm" | "md" | "lg" | "xl" | "full";
|
size: "sm" | "md" | "lg" | "xl" | "full";
|
||||||
closeOnOutsideClick?: boolean;
|
closeOnOutsideClick?: boolean;
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
|
|
||||||
// 버튼 설정
|
|
||||||
showSaveButton?: boolean; // 저장 버튼 표시 (기본: true)
|
|
||||||
saveButtonText?: string; // 저장 버튼 텍스트 (기본: "저장")
|
|
||||||
cancelButtonText?: string; // 취소 버튼 텍스트 (기본: "취소")
|
|
||||||
showResetButton?: boolean; // 초기화 버튼 표시
|
|
||||||
resetButtonText?: string; // 초기화 버튼 텍스트
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 전체 설정
|
// 전체 설정
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue