Compare commits
5 Commits
eb61506acd
...
efc9175fec
| Author | SHA1 | Date |
|---|---|---|
|
|
efc9175fec | |
|
|
75b5530d04 | |
|
|
40fd5f9055 | |
|
|
0709b8df25 | |
|
|
b3ee2b50e8 |
|
|
@ -197,6 +197,10 @@ export function UniversalFormModalComponent({
|
||||||
// 로딩 상태
|
// 로딩 상태
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
|
// 채번규칙 원본 값 추적 (수동 모드 감지용)
|
||||||
|
// key: columnName, value: 자동 생성된 원본 값
|
||||||
|
const [numberingOriginalValues, setNumberingOriginalValues] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
// 🆕 수정 모드: 원본 그룹 데이터 (INSERT/UPDATE/DELETE 추적용)
|
// 🆕 수정 모드: 원본 그룹 데이터 (INSERT/UPDATE/DELETE 추적용)
|
||||||
const [originalGroupedData, setOriginalGroupedData] = useState<any[]>([]);
|
const [originalGroupedData, setOriginalGroupedData] = useState<any[]>([]);
|
||||||
const groupedDataInitializedRef = useRef(false);
|
const groupedDataInitializedRef = useRef(false);
|
||||||
|
|
@ -227,9 +231,8 @@ export function UniversalFormModalComponent({
|
||||||
const currentIdString = currentId !== undefined ? String(currentId) : undefined;
|
const currentIdString = currentId !== undefined ? String(currentId) : undefined;
|
||||||
|
|
||||||
// 생성 모드에서 부모로부터 전달받은 데이터 해시 (ID가 없을 때만)
|
// 생성 모드에서 부모로부터 전달받은 데이터 해시 (ID가 없을 때만)
|
||||||
const createModeDataHash = !currentIdString && initialData && Object.keys(initialData).length > 0
|
const createModeDataHash =
|
||||||
? JSON.stringify(initialData)
|
!currentIdString && initialData && Object.keys(initialData).length > 0 ? JSON.stringify(initialData) : undefined;
|
||||||
: undefined;
|
|
||||||
|
|
||||||
// 이미 초기화되었고, ID가 동일하고, 생성 모드 데이터도 동일하면 스킵
|
// 이미 초기화되었고, ID가 동일하고, 생성 모드 데이터도 동일하면 스킵
|
||||||
if (hasInitialized.current && lastInitializedId.current === currentIdString) {
|
if (hasInitialized.current && lastInitializedId.current === currentIdString) {
|
||||||
|
|
@ -435,7 +438,7 @@ export function UniversalFormModalComponent({
|
||||||
// console.log("[채번] 섹션 검사:", section.title, { type: section.type, repeatable: section.repeatable, fieldsCount: section.fields?.length });
|
// console.log("[채번] 섹션 검사:", section.title, { type: section.type, repeatable: section.repeatable, fieldsCount: section.fields?.length });
|
||||||
if (section.repeatable || section.type === "table") continue;
|
if (section.repeatable || section.type === "table") continue;
|
||||||
|
|
||||||
for (const field of (section.fields || [])) {
|
for (const field of section.fields || []) {
|
||||||
// generateOnOpen은 기본값 true (undefined일 경우 true로 처리)
|
// generateOnOpen은 기본값 true (undefined일 경우 true로 처리)
|
||||||
const shouldGenerateOnOpen = field.numberingRule?.generateOnOpen !== false;
|
const shouldGenerateOnOpen = field.numberingRule?.generateOnOpen !== false;
|
||||||
// console.log("[채번] 필드 검사:", field.columnName, {
|
// console.log("[채번] 필드 검사:", field.columnName, {
|
||||||
|
|
@ -457,12 +460,19 @@ export function UniversalFormModalComponent({
|
||||||
// generateOnOpen: 미리보기만 표시 (DB 시퀀스 증가 안 함)
|
// generateOnOpen: 미리보기만 표시 (DB 시퀀스 증가 안 함)
|
||||||
const response = await previewNumberingCode(field.numberingRule.ruleId);
|
const response = await previewNumberingCode(field.numberingRule.ruleId);
|
||||||
if (response.success && response.data?.generatedCode) {
|
if (response.success && response.data?.generatedCode) {
|
||||||
updatedData[field.columnName] = response.data.generatedCode;
|
const generatedCode = response.data.generatedCode;
|
||||||
|
updatedData[field.columnName] = generatedCode;
|
||||||
|
|
||||||
// 저장 시 실제 할당을 위해 ruleId 저장 (TextInput과 동일한 키 형식)
|
// 저장 시 실제 할당을 위해 ruleId 저장 (TextInput과 동일한 키 형식)
|
||||||
const ruleIdKey = `${field.columnName}_numberingRuleId`;
|
const ruleIdKey = `${field.columnName}_numberingRuleId`;
|
||||||
updatedData[ruleIdKey] = field.numberingRule.ruleId;
|
updatedData[ruleIdKey] = field.numberingRule.ruleId;
|
||||||
|
|
||||||
|
// 원본 채번 값 저장 (수동 모드 감지용)
|
||||||
|
setNumberingOriginalValues((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field.columnName]: generatedCode,
|
||||||
|
}));
|
||||||
|
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
numberingGeneratedRef.current = true; // 생성 완료 표시
|
numberingGeneratedRef.current = true; // 생성 완료 표시
|
||||||
// console.log(
|
// console.log(
|
||||||
|
|
@ -534,7 +544,7 @@ export function UniversalFormModalComponent({
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
// 일반 섹션 필드 초기화
|
// 일반 섹션 필드 초기화
|
||||||
for (const field of (section.fields || [])) {
|
for (const field of section.fields || []) {
|
||||||
// 기본값 설정
|
// 기본값 설정
|
||||||
let value = field.defaultValue ?? "";
|
let value = field.defaultValue ?? "";
|
||||||
|
|
||||||
|
|
@ -562,7 +572,9 @@ export function UniversalFormModalComponent({
|
||||||
const triggerValue = effectiveInitialData[group.triggerField];
|
const triggerValue = effectiveInitialData[group.triggerField];
|
||||||
if (triggerValue === group.triggerValueOnAdd) {
|
if (triggerValue === group.triggerValueOnAdd) {
|
||||||
newActivatedGroups.add(key);
|
newActivatedGroups.add(key);
|
||||||
console.log(`[initializeForm] 옵셔널 그룹 자동 활성화: ${key}, triggerField=${group.triggerField}, value=${triggerValue}`);
|
console.log(
|
||||||
|
`[initializeForm] 옵셔널 그룹 자동 활성화: ${key}, triggerField=${group.triggerField}, value=${triggerValue}`,
|
||||||
|
);
|
||||||
|
|
||||||
// 활성화된 그룹의 필드값도 초기화
|
// 활성화된 그룹의 필드값도 초기화
|
||||||
for (const field of group.fields || []) {
|
for (const field of group.fields || []) {
|
||||||
|
|
@ -634,8 +646,8 @@ export function UniversalFormModalComponent({
|
||||||
// 마스터 테이블명 확인 (saveConfig에서)
|
// 마스터 테이블명 확인 (saveConfig에서)
|
||||||
// 1. customApiSave.multiTable.mainTable.tableName (다중 테이블 저장)
|
// 1. customApiSave.multiTable.mainTable.tableName (다중 테이블 저장)
|
||||||
// 2. saveConfig.tableName (단일 테이블 저장)
|
// 2. saveConfig.tableName (단일 테이블 저장)
|
||||||
const masterTable = config.saveConfig?.customApiSave?.multiTable?.mainTable?.tableName
|
const masterTable =
|
||||||
|| config.saveConfig?.tableName;
|
config.saveConfig?.customApiSave?.multiTable?.mainTable?.tableName || config.saveConfig?.tableName;
|
||||||
|
|
||||||
// 디테일 테이블의 컬럼 목록 조회
|
// 디테일 테이블의 컬럼 목록 조회
|
||||||
const columnsResponse = await apiClient.get(`/table-management/tables/${detailTable}/columns`);
|
const columnsResponse = await apiClient.get(`/table-management/tables/${detailTable}/columns`);
|
||||||
|
|
@ -668,9 +680,10 @@ export function UniversalFormModalComponent({
|
||||||
// detail_settings에서 referenceTable 확인
|
// detail_settings에서 referenceTable 확인
|
||||||
if (!refTable && col.detail_settings) {
|
if (!refTable && col.detail_settings) {
|
||||||
try {
|
try {
|
||||||
const settings = typeof col.detail_settings === "string"
|
const settings =
|
||||||
? JSON.parse(col.detail_settings)
|
typeof col.detail_settings === "string"
|
||||||
: col.detail_settings;
|
? JSON.parse(col.detail_settings)
|
||||||
|
: col.detail_settings;
|
||||||
refTable = settings.referenceTable;
|
refTable = settings.referenceTable;
|
||||||
} catch {
|
} catch {
|
||||||
// JSON 파싱 실패 무시
|
// JSON 파싱 실패 무시
|
||||||
|
|
@ -683,9 +696,10 @@ export function UniversalFormModalComponent({
|
||||||
let refColumn = col.reference_column || col.referenceColumn;
|
let refColumn = col.reference_column || col.referenceColumn;
|
||||||
if (!refColumn && col.detail_settings) {
|
if (!refColumn && col.detail_settings) {
|
||||||
try {
|
try {
|
||||||
const settings = typeof col.detail_settings === "string"
|
const settings =
|
||||||
? JSON.parse(col.detail_settings)
|
typeof col.detail_settings === "string"
|
||||||
: col.detail_settings;
|
? JSON.parse(col.detail_settings)
|
||||||
|
: col.detail_settings;
|
||||||
refColumn = settings.referenceColumn;
|
refColumn = settings.referenceColumn;
|
||||||
} catch {
|
} catch {
|
||||||
// JSON 파싱 실패 무시
|
// JSON 파싱 실패 무시
|
||||||
|
|
@ -694,7 +708,9 @@ export function UniversalFormModalComponent({
|
||||||
|
|
||||||
// 마스터 데이터에 해당 컬럼 값이 있는지 확인
|
// 마스터 데이터에 해당 컬럼 값이 있는지 확인
|
||||||
if (refColumn && effectiveInitialData[refColumn]) {
|
if (refColumn && effectiveInitialData[refColumn]) {
|
||||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 엔티티 관계 감지 - ${colName} → ${masterTable}.${refColumn}`);
|
console.log(
|
||||||
|
`[initializeForm] 테이블 섹션 ${section.id}: 엔티티 관계 감지 - ${colName} → ${masterTable}.${refColumn}`,
|
||||||
|
);
|
||||||
linkColumn = { masterField: refColumn, detailField: colName };
|
linkColumn = { masterField: refColumn, detailField: colName };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -710,10 +726,13 @@ export function UniversalFormModalComponent({
|
||||||
|
|
||||||
for (const pattern of priorityPatterns) {
|
for (const pattern of priorityPatterns) {
|
||||||
for (const masterKey of masterKeys) {
|
for (const masterKey of masterKeys) {
|
||||||
if (masterKey.endsWith(pattern) &&
|
if (
|
||||||
detailColumns.includes(masterKey) &&
|
masterKey.endsWith(pattern) &&
|
||||||
effectiveInitialData[masterKey] &&
|
detailColumns.includes(masterKey) &&
|
||||||
masterKey !== "id" && masterKey !== "company_code") {
|
effectiveInitialData[masterKey] &&
|
||||||
|
masterKey !== "id" &&
|
||||||
|
masterKey !== "company_code"
|
||||||
|
) {
|
||||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 공통 컬럼 패턴 감지 - ${masterKey}`);
|
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 공통 컬럼 패턴 감지 - ${masterKey}`);
|
||||||
linkColumn = { masterField: masterKey, detailField: masterKey };
|
linkColumn = { masterField: masterKey, detailField: masterKey };
|
||||||
break;
|
break;
|
||||||
|
|
@ -726,10 +745,13 @@ export function UniversalFormModalComponent({
|
||||||
// 방법 3: 일반 공통 컬럼 (마지막 폴백)
|
// 방법 3: 일반 공통 컬럼 (마지막 폴백)
|
||||||
if (!linkColumn) {
|
if (!linkColumn) {
|
||||||
for (const masterKey of masterKeys) {
|
for (const masterKey of masterKeys) {
|
||||||
if (detailColumns.includes(masterKey) &&
|
if (
|
||||||
effectiveInitialData[masterKey] &&
|
detailColumns.includes(masterKey) &&
|
||||||
masterKey !== "id" && masterKey !== "company_code" &&
|
effectiveInitialData[masterKey] &&
|
||||||
!masterKey.startsWith("__")) {
|
masterKey !== "id" &&
|
||||||
|
masterKey !== "company_code" &&
|
||||||
|
!masterKey.startsWith("__")
|
||||||
|
) {
|
||||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 공통 컬럼 감지 - ${masterKey}`);
|
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 공통 컬럼 감지 - ${masterKey}`);
|
||||||
linkColumn = { masterField: masterKey, detailField: masterKey };
|
linkColumn = { masterField: masterKey, detailField: masterKey };
|
||||||
break;
|
break;
|
||||||
|
|
@ -750,7 +772,9 @@ export function UniversalFormModalComponent({
|
||||||
// 마스터 테이블의 연결 필드 값 가져오기
|
// 마스터 테이블의 연결 필드 값 가져오기
|
||||||
const masterValue = effectiveInitialData[linkColumn.masterField];
|
const masterValue = effectiveInitialData[linkColumn.masterField];
|
||||||
if (!masterValue) {
|
if (!masterValue) {
|
||||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: masterField(${linkColumn.masterField}) 값 없음, 스킵`);
|
console.log(
|
||||||
|
`[initializeForm] 테이블 섹션 ${section.id}: masterField(${linkColumn.masterField}) 값 없음, 스킵`,
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -767,17 +791,24 @@ export function UniversalFormModalComponent({
|
||||||
[linkColumn.detailField]: { value: masterValue, operator: "equals" },
|
[linkColumn.detailField]: { value: masterValue, operator: "equals" },
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: API 요청 - URL: /table-management/tables/${detailTable}/data`);
|
console.log(
|
||||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: API 요청 - search:`, JSON.stringify(searchCondition));
|
`[initializeForm] 테이블 섹션 ${section.id}: API 요청 - URL: /table-management/tables/${detailTable}/data`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`[initializeForm] 테이블 섹션 ${section.id}: API 요청 - search:`,
|
||||||
|
JSON.stringify(searchCondition),
|
||||||
|
);
|
||||||
|
|
||||||
const response = await apiClient.post(`/table-management/tables/${detailTable}/data`, {
|
const response = await apiClient.post(`/table-management/tables/${detailTable}/data`, {
|
||||||
search: searchCondition, // filters가 아닌 search로 전달
|
search: searchCondition, // filters가 아닌 search로 전달
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 1000, // pageSize가 아닌 size로 전달
|
size: 1000, // pageSize가 아닌 size로 전달
|
||||||
autoFilter: { enabled: true }, // 멀티테넌시 필터 적용
|
autoFilter: { enabled: true }, // 멀티테넌시 필터 적용
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: API 응답 - success: ${response.data?.success}, total: ${response.data?.data?.total}, dataLength: ${response.data?.data?.data?.length}`);
|
console.log(
|
||||||
|
`[initializeForm] 테이블 섹션 ${section.id}: API 응답 - success: ${response.data?.success}, total: ${response.data?.data?.total}, dataLength: ${response.data?.data?.data?.length}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (response.data?.success) {
|
if (response.data?.success) {
|
||||||
// 다양한 응답 구조 처리
|
// 다양한 응답 구조 처리
|
||||||
|
|
@ -903,7 +934,7 @@ export function UniversalFormModalComponent({
|
||||||
_index: index,
|
_index: index,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const field of (section.fields || [])) {
|
for (const field of section.fields || []) {
|
||||||
item[field.columnName] = field.defaultValue ?? "";
|
item[field.columnName] = field.defaultValue ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -913,8 +944,42 @@ export function UniversalFormModalComponent({
|
||||||
// 필드 값 변경 핸들러
|
// 필드 값 변경 핸들러
|
||||||
const handleFieldChange = useCallback(
|
const handleFieldChange = useCallback(
|
||||||
(columnName: string, value: any) => {
|
(columnName: string, value: any) => {
|
||||||
|
// 채번규칙 필드의 수동 모드 감지
|
||||||
|
const originalNumberingValue = numberingOriginalValues[columnName];
|
||||||
|
const ruleIdKey = `${columnName}_numberingRuleId`;
|
||||||
|
|
||||||
|
// 해당 필드의 채번규칙 설정 찾기
|
||||||
|
let fieldConfig: FormFieldConfig | undefined;
|
||||||
|
for (const section of config.sections) {
|
||||||
|
if (section.type === "table" || section.repeatable) continue;
|
||||||
|
fieldConfig = section.fields?.find((f) => f.columnName === columnName);
|
||||||
|
if (fieldConfig) break;
|
||||||
|
// 옵셔널 필드 그룹에서도 찾기
|
||||||
|
for (const group of section.optionalFieldGroups || []) {
|
||||||
|
fieldConfig = group.fields?.find((f) => f.columnName === columnName);
|
||||||
|
if (fieldConfig) break;
|
||||||
|
}
|
||||||
|
if (fieldConfig) break;
|
||||||
|
}
|
||||||
|
|
||||||
setFormData((prev) => {
|
setFormData((prev) => {
|
||||||
const newData = { ...prev, [columnName]: value };
|
const newData = { ...prev, [columnName]: value };
|
||||||
|
|
||||||
|
// 채번규칙이 활성화된 필드이고, "사용자 수정 가능"이 ON인 경우
|
||||||
|
if (fieldConfig?.numberingRule?.enabled && fieldConfig?.numberingRule?.editable && originalNumberingValue) {
|
||||||
|
// 사용자가 값을 수정했으면 (원본과 다르면) ruleId 제거 → 수동 모드
|
||||||
|
if (value !== originalNumberingValue) {
|
||||||
|
delete newData[ruleIdKey];
|
||||||
|
console.log(`[채번 수동 모드] ${columnName}: 사용자가 값 수정 → ruleId 제거`);
|
||||||
|
} else {
|
||||||
|
// 원본 값으로 복구하면 ruleId 복구 → 자동 모드
|
||||||
|
if (fieldConfig.numberingRule.ruleId) {
|
||||||
|
newData[ruleIdKey] = fieldConfig.numberingRule.ruleId;
|
||||||
|
console.log(`[채번 자동 모드] ${columnName}: 원본 값 복구 → ruleId 복구`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// onChange는 렌더링 외부에서 호출해야 함 (setTimeout 사용)
|
// onChange는 렌더링 외부에서 호출해야 함 (setTimeout 사용)
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
setTimeout(() => onChange(newData), 0);
|
setTimeout(() => onChange(newData), 0);
|
||||||
|
|
@ -922,7 +987,7 @@ export function UniversalFormModalComponent({
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onChange],
|
[onChange, numberingOriginalValues, config.sections],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 반복 섹션 필드 값 변경 핸들러
|
// 반복 섹션 필드 값 변경 핸들러
|
||||||
|
|
@ -995,47 +1060,53 @@ export function UniversalFormModalComponent({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 옵셔널 필드 그룹 활성화
|
// 옵셔널 필드 그룹 활성화
|
||||||
const activateOptionalFieldGroup = useCallback((sectionId: string, groupId: string) => {
|
const activateOptionalFieldGroup = useCallback(
|
||||||
const section = config.sections.find((s) => s.id === sectionId);
|
(sectionId: string, groupId: string) => {
|
||||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
const section = config.sections.find((s) => s.id === sectionId);
|
||||||
if (!group) return;
|
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||||
|
if (!group) return;
|
||||||
|
|
||||||
const key = `${sectionId}-${groupId}`;
|
const key = `${sectionId}-${groupId}`;
|
||||||
setActivatedOptionalFieldGroups((prev) => {
|
setActivatedOptionalFieldGroups((prev) => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
newSet.add(key);
|
newSet.add(key);
|
||||||
return newSet;
|
return newSet;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 연동 필드 값 변경 (추가 시)
|
// 연동 필드 값 변경 (추가 시)
|
||||||
if (group.triggerField && group.triggerValueOnAdd !== undefined) {
|
if (group.triggerField && group.triggerValueOnAdd !== undefined) {
|
||||||
handleFieldChange(group.triggerField, group.triggerValueOnAdd);
|
handleFieldChange(group.triggerField, group.triggerValueOnAdd);
|
||||||
}
|
}
|
||||||
}, [config, handleFieldChange]);
|
},
|
||||||
|
[config, handleFieldChange],
|
||||||
|
);
|
||||||
|
|
||||||
// 옵셔널 필드 그룹 비활성화
|
// 옵셔널 필드 그룹 비활성화
|
||||||
const deactivateOptionalFieldGroup = useCallback((sectionId: string, groupId: string) => {
|
const deactivateOptionalFieldGroup = useCallback(
|
||||||
const section = config.sections.find((s) => s.id === sectionId);
|
(sectionId: string, groupId: string) => {
|
||||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
const section = config.sections.find((s) => s.id === sectionId);
|
||||||
if (!group) return;
|
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||||
|
if (!group) return;
|
||||||
|
|
||||||
const key = `${sectionId}-${groupId}`;
|
const key = `${sectionId}-${groupId}`;
|
||||||
setActivatedOptionalFieldGroups((prev) => {
|
setActivatedOptionalFieldGroups((prev) => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
newSet.delete(key);
|
newSet.delete(key);
|
||||||
return newSet;
|
return newSet;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 연동 필드 값 변경 (제거 시)
|
// 연동 필드 값 변경 (제거 시)
|
||||||
if (group.triggerField && group.triggerValueOnRemove !== undefined) {
|
if (group.triggerField && group.triggerValueOnRemove !== undefined) {
|
||||||
handleFieldChange(group.triggerField, group.triggerValueOnRemove);
|
handleFieldChange(group.triggerField, group.triggerValueOnRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 옵셔널 필드 그룹 필드 값 초기화
|
// 옵셔널 필드 그룹 필드 값 초기화
|
||||||
(group.fields || []).forEach((field) => {
|
(group.fields || []).forEach((field) => {
|
||||||
handleFieldChange(field.columnName, field.defaultValue || "");
|
handleFieldChange(field.columnName, field.defaultValue || "");
|
||||||
});
|
});
|
||||||
}, [config, handleFieldChange]);
|
},
|
||||||
|
[config, handleFieldChange],
|
||||||
|
);
|
||||||
|
|
||||||
// Select 옵션 로드
|
// Select 옵션 로드
|
||||||
const loadSelectOptions = useCallback(
|
const loadSelectOptions = useCallback(
|
||||||
|
|
@ -1081,13 +1152,11 @@ export function UniversalFormModalComponent({
|
||||||
// categoryKey 형식: "tableName.columnName"
|
// categoryKey 형식: "tableName.columnName"
|
||||||
const [categoryTable, categoryColumn] = optionConfig.categoryKey.split(".");
|
const [categoryTable, categoryColumn] = optionConfig.categoryKey.split(".");
|
||||||
if (categoryTable && categoryColumn) {
|
if (categoryTable && categoryColumn) {
|
||||||
const response = await apiClient.get(
|
const response = await apiClient.get(`/table-categories/${categoryTable}/${categoryColumn}/values`);
|
||||||
`/table-categories/${categoryTable}/${categoryColumn}/values`
|
|
||||||
);
|
|
||||||
if (response.data?.success && response.data?.data) {
|
if (response.data?.success && response.data?.data) {
|
||||||
// 라벨값을 DB에 저장 (화면에 표시되는 값 그대로 저장)
|
// 코드값을 DB에 저장하고 라벨값을 화면에 표시
|
||||||
options = response.data.data.map((item: any) => ({
|
options = response.data.data.map((item: any) => ({
|
||||||
value: item.valueLabel || item.value_label,
|
value: item.valueCode || item.value_code,
|
||||||
label: item.valueLabel || item.value_label,
|
label: item.valueLabel || item.value_label,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -1162,7 +1231,7 @@ export function UniversalFormModalComponent({
|
||||||
for (const section of config.sections) {
|
for (const section of config.sections) {
|
||||||
if (section.repeatable || section.type === "table") continue; // 반복 섹션 및 테이블 섹션은 별도 검증
|
if (section.repeatable || section.type === "table") continue; // 반복 섹션 및 테이블 섹션은 별도 검증
|
||||||
|
|
||||||
for (const field of (section.fields || [])) {
|
for (const field of section.fields || []) {
|
||||||
if (field.required && !field.hidden && !field.numberingRule?.hidden) {
|
if (field.required && !field.hidden && !field.numberingRule?.hidden) {
|
||||||
const value = formData[field.columnName];
|
const value = formData[field.columnName];
|
||||||
if (value === undefined || value === null || value === "") {
|
if (value === undefined || value === null || value === "") {
|
||||||
|
|
@ -1194,19 +1263,45 @@ export function UniversalFormModalComponent({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 저장 시점 채번규칙 처리 (generateOnSave만 처리)
|
// 저장 시점 채번규칙 처리
|
||||||
for (const section of config.sections) {
|
for (const section of config.sections) {
|
||||||
// 테이블 타입 섹션은 건너뛰기
|
// 테이블 타입 섹션은 건너뛰기
|
||||||
if (section.type === "table") continue;
|
if (section.type === "table") continue;
|
||||||
|
|
||||||
for (const field of (section.fields || [])) {
|
for (const field of section.fields || []) {
|
||||||
if (field.numberingRule?.enabled && field.numberingRule?.generateOnSave && field.numberingRule?.ruleId) {
|
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
||||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
const ruleIdKey = `${field.columnName}_numberingRuleId`;
|
||||||
if (response.success && response.data?.generatedCode) {
|
const hasRuleId = dataToSave[ruleIdKey]; // 사용자가 수정하지 않았으면 ruleId 유지됨
|
||||||
dataToSave[field.columnName] = response.data.generatedCode;
|
|
||||||
console.log(`[채번 할당] ${field.columnName} = ${response.data.generatedCode}`);
|
// 채번 규칙 할당 조건
|
||||||
|
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 {
|
} else {
|
||||||
console.error(`[채번 실패] ${field.columnName}:`, response.error);
|
console.log(
|
||||||
|
`[채번 스킵] ${field.columnName}: 사용자가 직접 입력한 값 유지 = ${dataToSave[field.columnName]}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1214,22 +1309,30 @@ export function UniversalFormModalComponent({
|
||||||
|
|
||||||
// 별도 테이블에 저장해야 하는 테이블 섹션 목록
|
// 별도 테이블에 저장해야 하는 테이블 섹션 목록
|
||||||
const tableSectionsForSeparateTable = config.sections.filter(
|
const tableSectionsForSeparateTable = config.sections.filter(
|
||||||
(s) => s.type === "table" &&
|
(s) =>
|
||||||
|
s.type === "table" &&
|
||||||
s.tableConfig?.saveConfig?.targetTable &&
|
s.tableConfig?.saveConfig?.targetTable &&
|
||||||
s.tableConfig.saveConfig.targetTable !== config.saveConfig.tableName
|
s.tableConfig.saveConfig.targetTable !== config.saveConfig.tableName,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 테이블 섹션이 있고 메인 테이블에 품목별로 저장하는 경우 (공통 + 개별 병합 저장)
|
// 테이블 섹션이 있고 메인 테이블에 품목별로 저장하는 경우 (공통 + 개별 병합 저장)
|
||||||
// targetTable이 없거나 메인 테이블과 같은 경우
|
// targetTable이 없거나 메인 테이블과 같은 경우
|
||||||
const tableSectionsForMainTable = config.sections.filter(
|
const tableSectionsForMainTable = config.sections.filter(
|
||||||
(s) => s.type === "table" &&
|
(s) =>
|
||||||
|
s.type === "table" &&
|
||||||
(!s.tableConfig?.saveConfig?.targetTable ||
|
(!s.tableConfig?.saveConfig?.targetTable ||
|
||||||
s.tableConfig.saveConfig.targetTable === config.saveConfig.tableName)
|
s.tableConfig.saveConfig.targetTable === config.saveConfig.tableName),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("[saveSingleRow] 메인 테이블:", config.saveConfig.tableName);
|
console.log("[saveSingleRow] 메인 테이블:", config.saveConfig.tableName);
|
||||||
console.log("[saveSingleRow] 메인 테이블에 저장할 테이블 섹션:", tableSectionsForMainTable.map(s => s.id));
|
console.log(
|
||||||
console.log("[saveSingleRow] 별도 테이블에 저장할 테이블 섹션:", tableSectionsForSeparateTable.map(s => s.id));
|
"[saveSingleRow] 메인 테이블에 저장할 테이블 섹션:",
|
||||||
|
tableSectionsForMainTable.map((s) => s.id),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"[saveSingleRow] 별도 테이블에 저장할 테이블 섹션:",
|
||||||
|
tableSectionsForSeparateTable.map((s) => s.id),
|
||||||
|
);
|
||||||
console.log("[saveSingleRow] 테이블 섹션 데이터 키:", Object.keys(tableSectionData));
|
console.log("[saveSingleRow] 테이블 섹션 데이터 키:", Object.keys(tableSectionData));
|
||||||
console.log("[saveSingleRow] dataToSave 키:", Object.keys(dataToSave));
|
console.log("[saveSingleRow] dataToSave 키:", Object.keys(dataToSave));
|
||||||
|
|
||||||
|
|
@ -1276,7 +1379,7 @@ export function UniversalFormModalComponent({
|
||||||
|
|
||||||
const response = await apiClient.post(
|
const response = await apiClient.post(
|
||||||
`/table-management/tables/${config.saveConfig.tableName}/add`,
|
`/table-management/tables/${config.saveConfig.tableName}/add`,
|
||||||
rowToSave
|
rowToSave,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.data?.success) {
|
if (!response.data?.success) {
|
||||||
|
|
@ -1383,7 +1486,7 @@ export function UniversalFormModalComponent({
|
||||||
|
|
||||||
const saveResponse = await apiClient.post(
|
const saveResponse = await apiClient.post(
|
||||||
`/table-management/tables/${section.tableConfig.saveConfig.targetTable}/add`,
|
`/table-management/tables/${section.tableConfig.saveConfig.targetTable}/add`,
|
||||||
itemToSave
|
itemToSave,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!saveResponse.data?.success) {
|
if (!saveResponse.data?.success) {
|
||||||
|
|
@ -1393,7 +1496,13 @@ export function UniversalFormModalComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [config.sections, config.saveConfig.tableName, config.saveConfig.primaryKeyColumn, config.saveConfig.sectionSaveModes, formData]);
|
}, [
|
||||||
|
config.sections,
|
||||||
|
config.saveConfig.tableName,
|
||||||
|
config.saveConfig.primaryKeyColumn,
|
||||||
|
config.saveConfig.sectionSaveModes,
|
||||||
|
formData,
|
||||||
|
]);
|
||||||
|
|
||||||
// 다중 행 저장 (겸직 등)
|
// 다중 행 저장 (겸직 등)
|
||||||
const saveMultipleRows = useCallback(async () => {
|
const saveMultipleRows = useCallback(async () => {
|
||||||
|
|
@ -1469,7 +1578,7 @@ export function UniversalFormModalComponent({
|
||||||
for (const section of config.sections) {
|
for (const section of config.sections) {
|
||||||
if (section.repeatable || section.type === "table") continue;
|
if (section.repeatable || section.type === "table") continue;
|
||||||
|
|
||||||
for (const field of (section.fields || [])) {
|
for (const field of section.fields || []) {
|
||||||
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
||||||
// generateOnSave 또는 generateOnOpen 모두 저장 시 실제 순번 할당
|
// generateOnSave 또는 generateOnOpen 모두 저장 시 실제 순번 할당
|
||||||
const shouldAllocate = field.numberingRule.generateOnSave || field.numberingRule.generateOnOpen;
|
const shouldAllocate = field.numberingRule.generateOnSave || field.numberingRule.generateOnOpen;
|
||||||
|
|
@ -1544,7 +1653,7 @@ export function UniversalFormModalComponent({
|
||||||
for (const section of config.sections) {
|
for (const section of config.sections) {
|
||||||
if (section.repeatable || section.type === "table") continue;
|
if (section.repeatable || section.type === "table") continue;
|
||||||
|
|
||||||
for (const field of (section.fields || [])) {
|
for (const field of section.fields || []) {
|
||||||
// 채번규칙이 활성화된 필드 처리
|
// 채번규칙이 활성화된 필드 처리
|
||||||
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
||||||
// 신규 생성이거나 값이 없는 경우에만 채번
|
// 신규 생성이거나 값이 없는 경우에만 채번
|
||||||
|
|
@ -1902,9 +2011,7 @@ export function UniversalFormModalComponent({
|
||||||
// 메인 표시 컬럼 (displayColumn)
|
// 메인 표시 컬럼 (displayColumn)
|
||||||
const mainDisplayVal = row[lfg.displayColumn || ""] || "";
|
const mainDisplayVal = row[lfg.displayColumn || ""] || "";
|
||||||
// 서브 표시 컬럼 (subDisplayColumn이 있으면 사용, 없으면 valueColumn 사용)
|
// 서브 표시 컬럼 (subDisplayColumn이 있으면 사용, 없으면 valueColumn 사용)
|
||||||
const subDisplayVal = lfg.subDisplayColumn
|
const subDisplayVal = lfg.subDisplayColumn ? row[lfg.subDisplayColumn] || "" : row[valueColumn] || "";
|
||||||
? (row[lfg.subDisplayColumn] || "")
|
|
||||||
: (row[valueColumn] || "");
|
|
||||||
|
|
||||||
switch (lfg.displayFormat) {
|
switch (lfg.displayFormat) {
|
||||||
case "code_name":
|
case "code_name":
|
||||||
|
|
@ -1923,7 +2030,10 @@ export function UniversalFormModalComponent({
|
||||||
matches.forEach((match) => {
|
matches.forEach((match) => {
|
||||||
const columnName = match.slice(1, -1); // { } 제거
|
const columnName = match.slice(1, -1); // { } 제거
|
||||||
const columnValue = row[columnName];
|
const columnValue = row[columnName];
|
||||||
result = result.replace(match, columnValue !== undefined && columnValue !== null ? String(columnValue) : "");
|
result = result.replace(
|
||||||
|
match,
|
||||||
|
columnValue !== undefined && columnValue !== null ? String(columnValue) : "",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -1980,7 +2090,12 @@ export function UniversalFormModalComponent({
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{sourceData.length > 0 ? (
|
{sourceData.length > 0 ? (
|
||||||
sourceData
|
sourceData
|
||||||
.filter((row) => row[valueColumn] !== null && row[valueColumn] !== undefined && String(row[valueColumn]) !== "")
|
.filter(
|
||||||
|
(row) =>
|
||||||
|
row[valueColumn] !== null &&
|
||||||
|
row[valueColumn] !== undefined &&
|
||||||
|
String(row[valueColumn]) !== "",
|
||||||
|
)
|
||||||
.map((row, index) => (
|
.map((row, index) => (
|
||||||
<SelectItem key={`${row[valueColumn]}_${index}`} value={String(row[valueColumn])}>
|
<SelectItem key={`${row[valueColumn]}_${index}`} value={String(row[valueColumn])}>
|
||||||
{getDisplayText(row)}
|
{getDisplayText(row)}
|
||||||
|
|
@ -2244,9 +2359,7 @@ export function UniversalFormModalComponent({
|
||||||
{/* 옵셔널 필드 그룹 렌더링 */}
|
{/* 옵셔널 필드 그룹 렌더링 */}
|
||||||
{section.optionalFieldGroups && section.optionalFieldGroups.length > 0 && (
|
{section.optionalFieldGroups && section.optionalFieldGroups.length > 0 && (
|
||||||
<div className="mt-4 space-y-3">
|
<div className="mt-4 space-y-3">
|
||||||
{section.optionalFieldGroups.map((group) =>
|
{section.optionalFieldGroups.map((group) => renderOptionalFieldGroup(section, group, sectionColumns))}
|
||||||
renderOptionalFieldGroup(section, group, sectionColumns)
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -2274,7 +2387,7 @@ export function UniversalFormModalComponent({
|
||||||
const renderOptionalFieldGroup = (
|
const renderOptionalFieldGroup = (
|
||||||
section: FormSectionConfig,
|
section: FormSectionConfig,
|
||||||
group: OptionalFieldGroupConfig,
|
group: OptionalFieldGroupConfig,
|
||||||
sectionColumns: number
|
sectionColumns: number,
|
||||||
) => {
|
) => {
|
||||||
const key = `${section.id}-${group.id}`;
|
const key = `${section.id}-${group.id}`;
|
||||||
const isActivated = activatedOptionalFieldGroups.has(key);
|
const isActivated = activatedOptionalFieldGroups.has(key);
|
||||||
|
|
@ -2293,9 +2406,7 @@ export function UniversalFormModalComponent({
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">{group.title}</p>
|
<p className="text-muted-foreground text-sm font-medium">{group.title}</p>
|
||||||
{group.description && (
|
{group.description && <p className="text-muted-foreground/70 mt-0.5 text-xs">{group.description}</p>}
|
||||||
<p className="text-muted-foreground/70 mt-0.5 text-xs">{group.description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -2334,16 +2445,10 @@ export function UniversalFormModalComponent({
|
||||||
<div className="flex items-center justify-between p-3">
|
<div className="flex items-center justify-between p-3">
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<button className="flex items-center gap-2 text-left hover:opacity-80">
|
<button className="flex items-center gap-2 text-left hover:opacity-80">
|
||||||
{isCollapsed ? (
|
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||||
<ChevronRight className="h-4 w-4" />
|
|
||||||
) : (
|
|
||||||
<ChevronDown className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium">{group.title}</p>
|
<p className="text-sm font-medium">{group.title}</p>
|
||||||
{group.description && (
|
{group.description && <p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>}
|
||||||
<p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
@ -2373,8 +2478,8 @@ export function UniversalFormModalComponent({
|
||||||
formData[field.columnName],
|
formData[field.columnName],
|
||||||
(value) => handleFieldChange(field.columnName, value),
|
(value) => handleFieldChange(field.columnName, value),
|
||||||
`${section.id}-${group.id}-${field.id}`,
|
`${section.id}-${group.id}-${field.id}`,
|
||||||
groupColumns
|
groupColumns,
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
|
|
@ -2388,9 +2493,7 @@ export function UniversalFormModalComponent({
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium">{group.title}</p>
|
<p className="text-sm font-medium">{group.title}</p>
|
||||||
{group.description && (
|
{group.description && <p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>}
|
||||||
<p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -2417,8 +2520,8 @@ export function UniversalFormModalComponent({
|
||||||
formData[field.columnName],
|
formData[field.columnName],
|
||||||
(value) => handleFieldChange(field.columnName, value),
|
(value) => handleFieldChange(field.columnName, value),
|
||||||
`${section.id}-${group.id}-${field.id}`,
|
`${section.id}-${group.id}-${field.id}`,
|
||||||
groupColumns
|
groupColumns,
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -2546,7 +2649,8 @@ export function UniversalFormModalComponent({
|
||||||
<div className="text-muted-foreground text-center">
|
<div className="text-muted-foreground text-center">
|
||||||
<p className="font-medium">{config.modal.title || "범용 폼 모달"}</p>
|
<p className="font-medium">{config.modal.title || "범용 폼 모달"}</p>
|
||||||
<p className="mt-1 text-xs">
|
<p className="mt-1 text-xs">
|
||||||
{config.sections.length}개 섹션 |{config.sections.reduce((acc, s) => acc + (s.fields?.length || 0), 0)}개 필드
|
{config.sections.length}개 섹션 |{config.sections.reduce((acc, s) => acc + (s.fields?.length || 0), 0)}개
|
||||||
|
필드
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-xs">저장 테이블: {config.saveConfig.tableName || "(미설정)"}</p>
|
<p className="mt-1 text-xs">저장 테이블: {config.saveConfig.tableName || "(미설정)"}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue