Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management
This commit is contained in:
commit
efc9175fec
|
|
@ -197,6 +197,10 @@ export function UniversalFormModalComponent({
|
|||
// 로딩 상태
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// 채번규칙 원본 값 추적 (수동 모드 감지용)
|
||||
// key: columnName, value: 자동 생성된 원본 값
|
||||
const [numberingOriginalValues, setNumberingOriginalValues] = useState<Record<string, string>>({});
|
||||
|
||||
// 🆕 수정 모드: 원본 그룹 데이터 (INSERT/UPDATE/DELETE 추적용)
|
||||
const [originalGroupedData, setOriginalGroupedData] = useState<any[]>([]);
|
||||
const groupedDataInitializedRef = useRef(false);
|
||||
|
|
@ -221,15 +225,14 @@ export function UniversalFormModalComponent({
|
|||
// hasInitialized: hasInitialized.current,
|
||||
// lastInitializedId: lastInitializedId.current,
|
||||
// });
|
||||
|
||||
|
||||
// initialData에서 ID 값 추출 (id, ID, objid 등)
|
||||
const currentId = initialData?.id || initialData?.ID || initialData?.objid;
|
||||
const currentIdString = currentId !== undefined ? String(currentId) : undefined;
|
||||
|
||||
|
||||
// 생성 모드에서 부모로부터 전달받은 데이터 해시 (ID가 없을 때만)
|
||||
const createModeDataHash = !currentIdString && initialData && Object.keys(initialData).length > 0
|
||||
? JSON.stringify(initialData)
|
||||
: undefined;
|
||||
const createModeDataHash =
|
||||
!currentIdString && initialData && Object.keys(initialData).length > 0 ? JSON.stringify(initialData) : undefined;
|
||||
|
||||
// 이미 초기화되었고, ID가 동일하고, 생성 모드 데이터도 동일하면 스킵
|
||||
if (hasInitialized.current && lastInitializedId.current === currentIdString) {
|
||||
|
|
@ -241,7 +244,7 @@ export function UniversalFormModalComponent({
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 🆕 컴포넌트 remount 감지: hasInitialized가 true인데 formData가 비어있으면 재초기화
|
||||
// (React의 Strict Mode나 EmbeddedScreen 리렌더링으로 인한 remount)
|
||||
if (hasInitialized.current && !currentIdString) {
|
||||
|
|
@ -435,7 +438,7 @@ export function UniversalFormModalComponent({
|
|||
// console.log("[채번] 섹션 검사:", section.title, { type: section.type, repeatable: section.repeatable, fieldsCount: section.fields?.length });
|
||||
if (section.repeatable || section.type === "table") continue;
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
// generateOnOpen은 기본값 true (undefined일 경우 true로 처리)
|
||||
const shouldGenerateOnOpen = field.numberingRule?.generateOnOpen !== false;
|
||||
// console.log("[채번] 필드 검사:", field.columnName, {
|
||||
|
|
@ -457,12 +460,19 @@ export function UniversalFormModalComponent({
|
|||
// generateOnOpen: 미리보기만 표시 (DB 시퀀스 증가 안 함)
|
||||
const response = await previewNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
updatedData[field.columnName] = response.data.generatedCode;
|
||||
const generatedCode = response.data.generatedCode;
|
||||
updatedData[field.columnName] = generatedCode;
|
||||
|
||||
// 저장 시 실제 할당을 위해 ruleId 저장 (TextInput과 동일한 키 형식)
|
||||
const ruleIdKey = `${field.columnName}_numberingRuleId`;
|
||||
updatedData[ruleIdKey] = field.numberingRule.ruleId;
|
||||
|
||||
// 원본 채번 값 저장 (수동 모드 감지용)
|
||||
setNumberingOriginalValues((prev) => ({
|
||||
...prev,
|
||||
[field.columnName]: generatedCode,
|
||||
}));
|
||||
|
||||
hasChanges = true;
|
||||
numberingGeneratedRef.current = true; // 생성 완료 표시
|
||||
// console.log(
|
||||
|
|
@ -534,7 +544,7 @@ export function UniversalFormModalComponent({
|
|||
continue;
|
||||
} else {
|
||||
// 일반 섹션 필드 초기화
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
// 기본값 설정
|
||||
let value = field.defaultValue ?? "";
|
||||
|
||||
|
|
@ -556,14 +566,16 @@ export function UniversalFormModalComponent({
|
|||
if (section.optionalFieldGroups) {
|
||||
for (const group of section.optionalFieldGroups) {
|
||||
const key = `${section.id}-${group.id}`;
|
||||
|
||||
|
||||
// 수정 모드: triggerField 값이 triggerValueOnAdd와 일치하면 그룹 자동 활성화
|
||||
if (effectiveInitialData && group.triggerField && group.triggerValueOnAdd !== undefined) {
|
||||
const triggerValue = effectiveInitialData[group.triggerField];
|
||||
if (triggerValue === group.triggerValueOnAdd) {
|
||||
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 || []) {
|
||||
let value = field.defaultValue ?? "";
|
||||
|
|
@ -575,7 +587,7 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 신규 등록 모드: triggerValueOnRemove를 기본값으로 설정
|
||||
if (group.triggerField && group.triggerValueOnRemove !== undefined) {
|
||||
// effectiveInitialData에 해당 값이 없는 경우에만 기본값 설정
|
||||
|
|
@ -595,7 +607,7 @@ export function UniversalFormModalComponent({
|
|||
sectionsCount: config.sections.length,
|
||||
effectiveInitialDataKeys: Object.keys(effectiveInitialData),
|
||||
});
|
||||
|
||||
|
||||
for (const section of config.sections) {
|
||||
if (section.type !== "table" || !section.tableConfig) {
|
||||
continue;
|
||||
|
|
@ -634,67 +646,71 @@ export function UniversalFormModalComponent({
|
|||
// 마스터 테이블명 확인 (saveConfig에서)
|
||||
// 1. customApiSave.multiTable.mainTable.tableName (다중 테이블 저장)
|
||||
// 2. saveConfig.tableName (단일 테이블 저장)
|
||||
const masterTable = config.saveConfig?.customApiSave?.multiTable?.mainTable?.tableName
|
||||
|| config.saveConfig?.tableName;
|
||||
|
||||
const masterTable =
|
||||
config.saveConfig?.customApiSave?.multiTable?.mainTable?.tableName || config.saveConfig?.tableName;
|
||||
|
||||
// 디테일 테이블의 컬럼 목록 조회
|
||||
const columnsResponse = await apiClient.get(`/table-management/tables/${detailTable}/columns`);
|
||||
|
||||
|
||||
if (columnsResponse.data?.success && columnsResponse.data?.data) {
|
||||
// API 응답 구조: { success, data: { columns: [...], total, page, ... } }
|
||||
const columnsArray = columnsResponse.data.data.columns || columnsResponse.data.data || [];
|
||||
const detailColumnsData = Array.isArray(columnsArray) ? columnsArray : [];
|
||||
const detailColumns = detailColumnsData.map((col: any) => col.column_name || col.columnName);
|
||||
const masterKeys = Object.keys(effectiveInitialData);
|
||||
|
||||
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 연결 필드 자동 감지`, {
|
||||
masterTable,
|
||||
detailTable,
|
||||
detailColumnsCount: detailColumnsData.length,
|
||||
});
|
||||
|
||||
|
||||
// 방법 1: 엔티티 관계 기반 감지 (정확)
|
||||
// 디테일 테이블에서 마스터 테이블을 참조하는 엔티티 컬럼 찾기
|
||||
if (masterTable) {
|
||||
for (const col of detailColumnsData) {
|
||||
const colName = col.column_name || col.columnName;
|
||||
const inputType = col.input_type || col.inputType;
|
||||
|
||||
|
||||
// 엔티티 타입 컬럼 확인
|
||||
if (inputType === "entity") {
|
||||
// reference_table 또는 detail_settings에서 참조 테이블 확인
|
||||
let refTable = col.reference_table || col.referenceTable;
|
||||
|
||||
|
||||
// detail_settings에서 referenceTable 확인
|
||||
if (!refTable && col.detail_settings) {
|
||||
try {
|
||||
const settings = typeof col.detail_settings === "string"
|
||||
? JSON.parse(col.detail_settings)
|
||||
: col.detail_settings;
|
||||
const settings =
|
||||
typeof col.detail_settings === "string"
|
||||
? JSON.parse(col.detail_settings)
|
||||
: col.detail_settings;
|
||||
refTable = settings.referenceTable;
|
||||
} catch {
|
||||
// JSON 파싱 실패 무시
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 마스터 테이블을 참조하는 컬럼 발견
|
||||
if (refTable === masterTable) {
|
||||
// 참조 컬럼 확인 (마스터 테이블의 어떤 컬럼을 참조하는지)
|
||||
let refColumn = col.reference_column || col.referenceColumn;
|
||||
if (!refColumn && col.detail_settings) {
|
||||
try {
|
||||
const settings = typeof col.detail_settings === "string"
|
||||
? JSON.parse(col.detail_settings)
|
||||
: col.detail_settings;
|
||||
const settings =
|
||||
typeof col.detail_settings === "string"
|
||||
? JSON.parse(col.detail_settings)
|
||||
: col.detail_settings;
|
||||
refColumn = settings.referenceColumn;
|
||||
} catch {
|
||||
// JSON 파싱 실패 무시
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 마스터 데이터에 해당 컬럼 값이 있는지 확인
|
||||
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 };
|
||||
break;
|
||||
}
|
||||
|
|
@ -702,18 +718,21 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 방법 2: 공통 컬럼 패턴 기반 감지 (폴백)
|
||||
// 엔티티 관계가 없으면 공통 컬럼명 패턴으로 찾기
|
||||
if (!linkColumn) {
|
||||
const priorityPatterns = ["_no", "_number", "_code", "_id"];
|
||||
|
||||
|
||||
for (const pattern of priorityPatterns) {
|
||||
for (const masterKey of masterKeys) {
|
||||
if (masterKey.endsWith(pattern) &&
|
||||
detailColumns.includes(masterKey) &&
|
||||
effectiveInitialData[masterKey] &&
|
||||
masterKey !== "id" && masterKey !== "company_code") {
|
||||
if (
|
||||
masterKey.endsWith(pattern) &&
|
||||
detailColumns.includes(masterKey) &&
|
||||
effectiveInitialData[masterKey] &&
|
||||
masterKey !== "id" &&
|
||||
masterKey !== "company_code"
|
||||
) {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 공통 컬럼 패턴 감지 - ${masterKey}`);
|
||||
linkColumn = { masterField: masterKey, detailField: masterKey };
|
||||
break;
|
||||
|
|
@ -722,14 +741,17 @@ export function UniversalFormModalComponent({
|
|||
if (linkColumn) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 방법 3: 일반 공통 컬럼 (마지막 폴백)
|
||||
if (!linkColumn) {
|
||||
for (const masterKey of masterKeys) {
|
||||
if (detailColumns.includes(masterKey) &&
|
||||
effectiveInitialData[masterKey] &&
|
||||
masterKey !== "id" && masterKey !== "company_code" &&
|
||||
!masterKey.startsWith("__")) {
|
||||
if (
|
||||
detailColumns.includes(masterKey) &&
|
||||
effectiveInitialData[masterKey] &&
|
||||
masterKey !== "id" &&
|
||||
masterKey !== "company_code" &&
|
||||
!masterKey.startsWith("__")
|
||||
) {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 공통 컬럼 감지 - ${masterKey}`);
|
||||
linkColumn = { masterField: masterKey, detailField: masterKey };
|
||||
break;
|
||||
|
|
@ -750,7 +772,9 @@ export function UniversalFormModalComponent({
|
|||
// 마스터 테이블의 연결 필드 값 가져오기
|
||||
const masterValue = effectiveInitialData[linkColumn.masterField];
|
||||
if (!masterValue) {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: masterField(${linkColumn.masterField}) 값 없음, 스킵`);
|
||||
console.log(
|
||||
`[initializeForm] 테이블 섹션 ${section.id}: masterField(${linkColumn.masterField}) 값 없음, 스킵`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -767,23 +791,30 @@ export function UniversalFormModalComponent({
|
|||
[linkColumn.detailField]: { value: masterValue, operator: "equals" },
|
||||
};
|
||||
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: API 요청 - URL: /table-management/tables/${detailTable}/data`);
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: API 요청 - search:`, JSON.stringify(searchCondition));
|
||||
console.log(
|
||||
`[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`, {
|
||||
search: searchCondition, // filters가 아닌 search로 전달
|
||||
search: searchCondition, // filters가 아닌 search로 전달
|
||||
page: 1,
|
||||
size: 1000, // pageSize가 아닌 size로 전달
|
||||
autoFilter: { enabled: true }, // 멀티테넌시 필터 적용
|
||||
size: 1000, // pageSize가 아닌 size로 전달
|
||||
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) {
|
||||
// 다양한 응답 구조 처리
|
||||
let items: any[] = [];
|
||||
const data = response.data.data;
|
||||
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
items = data;
|
||||
} else if (data?.items && Array.isArray(data.items)) {
|
||||
|
|
@ -793,7 +824,7 @@ export function UniversalFormModalComponent({
|
|||
} else if (data?.data && Array.isArray(data.data)) {
|
||||
items = data.data;
|
||||
}
|
||||
|
||||
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: ${items.length}건 로드됨`, items);
|
||||
|
||||
// 테이블 섹션 데이터를 formData에 저장 (TableSectionRenderer에서 사용)
|
||||
|
|
@ -818,35 +849,35 @@ export function UniversalFormModalComponent({
|
|||
if (multiTable && effectiveInitialData) {
|
||||
const pkColumn = multiTable.mainTable?.primaryKeyColumn;
|
||||
const pkValue = effectiveInitialData[pkColumn];
|
||||
|
||||
|
||||
// PK 값이 있으면 수정 모드로 판단
|
||||
if (pkValue) {
|
||||
console.log("[initializeForm] 수정 모드 - 서브 테이블 데이터 로드 시작");
|
||||
|
||||
|
||||
for (const subTableConfig of multiTable.subTables || []) {
|
||||
// loadOnEdit 옵션이 활성화된 경우에만 로드
|
||||
if (!subTableConfig.enabled || !subTableConfig.options?.loadOnEdit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const { tableName, linkColumn, repeatSectionId, fieldMappings, options } = subTableConfig;
|
||||
if (!tableName || !linkColumn?.subColumn || !repeatSectionId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 서브 테이블에서 데이터 조회
|
||||
const filters: Record<string, any> = {
|
||||
[linkColumn.subColumn]: pkValue,
|
||||
};
|
||||
|
||||
|
||||
// 서브 항목만 로드 (메인 항목 제외)
|
||||
if (options?.loadOnlySubItems && options?.mainMarkerColumn) {
|
||||
filters[options.mainMarkerColumn] = options.subMarkerValue ?? false;
|
||||
}
|
||||
|
||||
|
||||
console.log(`[initializeForm] 서브 테이블 ${tableName} 조회:`, filters);
|
||||
|
||||
|
||||
const response = await apiClient.get(`/table-management/tables/${tableName}/data`, {
|
||||
params: {
|
||||
filters: JSON.stringify(filters),
|
||||
|
|
@ -854,11 +885,11 @@ export function UniversalFormModalComponent({
|
|||
pageSize: 100,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
if (response.data?.success && response.data?.data?.items) {
|
||||
const subItems = response.data.data.items;
|
||||
console.log(`[initializeForm] 서브 테이블 ${tableName} 데이터 ${subItems.length}건 로드됨`);
|
||||
|
||||
|
||||
// 역매핑: 서브 테이블 데이터 → 반복 섹션 데이터
|
||||
const repeatItems: RepeatSectionItem[] = subItems.map((item: any, index: number) => {
|
||||
const repeatItem: RepeatSectionItem = {
|
||||
|
|
@ -866,17 +897,17 @@ export function UniversalFormModalComponent({
|
|||
_index: index,
|
||||
_originalData: item, // 원본 데이터 보관 (수정 시 필요)
|
||||
};
|
||||
|
||||
|
||||
// 필드 매핑 역변환 (targetColumn → formField)
|
||||
for (const mapping of fieldMappings || []) {
|
||||
if (mapping.formField && mapping.targetColumn) {
|
||||
repeatItem[mapping.formField] = item[mapping.targetColumn];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return repeatItem;
|
||||
});
|
||||
|
||||
|
||||
// 반복 섹션에 데이터 설정
|
||||
newRepeatSections[repeatSectionId] = repeatItems;
|
||||
setRepeatSections({ ...newRepeatSections });
|
||||
|
|
@ -903,7 +934,7 @@ export function UniversalFormModalComponent({
|
|||
_index: index,
|
||||
};
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
item[field.columnName] = field.defaultValue ?? "";
|
||||
}
|
||||
|
||||
|
|
@ -913,8 +944,42 @@ export function UniversalFormModalComponent({
|
|||
// 필드 값 변경 핸들러
|
||||
const handleFieldChange = useCallback(
|
||||
(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) => {
|
||||
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 사용)
|
||||
if (onChange) {
|
||||
setTimeout(() => onChange(newData), 0);
|
||||
|
|
@ -922,7 +987,7 @@ export function UniversalFormModalComponent({
|
|||
return newData;
|
||||
});
|
||||
},
|
||||
[onChange],
|
||||
[onChange, numberingOriginalValues, config.sections],
|
||||
);
|
||||
|
||||
// 반복 섹션 필드 값 변경 핸들러
|
||||
|
|
@ -995,47 +1060,53 @@ export function UniversalFormModalComponent({
|
|||
}, []);
|
||||
|
||||
// 옵셔널 필드 그룹 활성화
|
||||
const activateOptionalFieldGroup = useCallback((sectionId: string, groupId: string) => {
|
||||
const section = config.sections.find((s) => s.id === sectionId);
|
||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||
if (!group) return;
|
||||
const activateOptionalFieldGroup = useCallback(
|
||||
(sectionId: string, groupId: string) => {
|
||||
const section = config.sections.find((s) => s.id === sectionId);
|
||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||
if (!group) return;
|
||||
|
||||
const key = `${sectionId}-${groupId}`;
|
||||
setActivatedOptionalFieldGroups((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(key);
|
||||
return newSet;
|
||||
});
|
||||
const key = `${sectionId}-${groupId}`;
|
||||
setActivatedOptionalFieldGroups((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(key);
|
||||
return newSet;
|
||||
});
|
||||
|
||||
// 연동 필드 값 변경 (추가 시)
|
||||
if (group.triggerField && group.triggerValueOnAdd !== undefined) {
|
||||
handleFieldChange(group.triggerField, group.triggerValueOnAdd);
|
||||
}
|
||||
}, [config, handleFieldChange]);
|
||||
// 연동 필드 값 변경 (추가 시)
|
||||
if (group.triggerField && group.triggerValueOnAdd !== undefined) {
|
||||
handleFieldChange(group.triggerField, group.triggerValueOnAdd);
|
||||
}
|
||||
},
|
||||
[config, handleFieldChange],
|
||||
);
|
||||
|
||||
// 옵셔널 필드 그룹 비활성화
|
||||
const deactivateOptionalFieldGroup = useCallback((sectionId: string, groupId: string) => {
|
||||
const section = config.sections.find((s) => s.id === sectionId);
|
||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||
if (!group) return;
|
||||
const deactivateOptionalFieldGroup = useCallback(
|
||||
(sectionId: string, groupId: string) => {
|
||||
const section = config.sections.find((s) => s.id === sectionId);
|
||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||
if (!group) return;
|
||||
|
||||
const key = `${sectionId}-${groupId}`;
|
||||
setActivatedOptionalFieldGroups((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(key);
|
||||
return newSet;
|
||||
});
|
||||
const key = `${sectionId}-${groupId}`;
|
||||
setActivatedOptionalFieldGroups((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(key);
|
||||
return newSet;
|
||||
});
|
||||
|
||||
// 연동 필드 값 변경 (제거 시)
|
||||
if (group.triggerField && group.triggerValueOnRemove !== undefined) {
|
||||
handleFieldChange(group.triggerField, group.triggerValueOnRemove);
|
||||
}
|
||||
// 연동 필드 값 변경 (제거 시)
|
||||
if (group.triggerField && group.triggerValueOnRemove !== undefined) {
|
||||
handleFieldChange(group.triggerField, group.triggerValueOnRemove);
|
||||
}
|
||||
|
||||
// 옵셔널 필드 그룹 필드 값 초기화
|
||||
(group.fields || []).forEach((field) => {
|
||||
handleFieldChange(field.columnName, field.defaultValue || "");
|
||||
});
|
||||
}, [config, handleFieldChange]);
|
||||
// 옵셔널 필드 그룹 필드 값 초기화
|
||||
(group.fields || []).forEach((field) => {
|
||||
handleFieldChange(field.columnName, field.defaultValue || "");
|
||||
});
|
||||
},
|
||||
[config, handleFieldChange],
|
||||
);
|
||||
|
||||
// Select 옵션 로드
|
||||
const loadSelectOptions = useCallback(
|
||||
|
|
@ -1081,13 +1152,11 @@ export function UniversalFormModalComponent({
|
|||
// categoryKey 형식: "tableName.columnName"
|
||||
const [categoryTable, categoryColumn] = optionConfig.categoryKey.split(".");
|
||||
if (categoryTable && categoryColumn) {
|
||||
const response = await apiClient.get(
|
||||
`/table-categories/${categoryTable}/${categoryColumn}/values`
|
||||
);
|
||||
const response = await apiClient.get(`/table-categories/${categoryTable}/${categoryColumn}/values`);
|
||||
if (response.data?.success && response.data?.data) {
|
||||
// 라벨값을 DB에 저장 (화면에 표시되는 값 그대로 저장)
|
||||
// 코드값을 DB에 저장하고 라벨값을 화면에 표시
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
|
@ -1162,7 +1231,7 @@ export function UniversalFormModalComponent({
|
|||
for (const section of config.sections) {
|
||||
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) {
|
||||
const value = formData[field.columnName];
|
||||
if (value === undefined || value === null || value === "") {
|
||||
|
|
@ -1178,7 +1247,7 @@ export function UniversalFormModalComponent({
|
|||
// 단일 행 저장
|
||||
const saveSingleRow = useCallback(async () => {
|
||||
const dataToSave = { ...formData };
|
||||
|
||||
|
||||
// 테이블 섹션 데이터 추출 (별도 저장용)
|
||||
const tableSectionData: Record<string, any[]> = {};
|
||||
|
||||
|
|
@ -1194,19 +1263,45 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
});
|
||||
|
||||
// 저장 시점 채번규칙 처리 (generateOnSave만 처리)
|
||||
// 저장 시점 채번규칙 처리
|
||||
for (const section of config.sections) {
|
||||
// 테이블 타입 섹션은 건너뛰기
|
||||
if (section.type === "table") continue;
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
if (field.numberingRule?.enabled && field.numberingRule?.generateOnSave && field.numberingRule?.ruleId) {
|
||||
const response = await allocateNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
dataToSave[field.columnName] = response.data.generatedCode;
|
||||
console.log(`[채번 할당] ${field.columnName} = ${response.data.generatedCode}`);
|
||||
|
||||
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.error(`[채번 실패] ${field.columnName}:`, response.error);
|
||||
console.log(
|
||||
`[채번 스킵] ${field.columnName}: 사용자가 직접 입력한 값 유지 = ${dataToSave[field.columnName]}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1214,22 +1309,30 @@ export function UniversalFormModalComponent({
|
|||
|
||||
// 별도 테이블에 저장해야 하는 테이블 섹션 목록
|
||||
const tableSectionsForSeparateTable = config.sections.filter(
|
||||
(s) => s.type === "table" &&
|
||||
s.tableConfig?.saveConfig?.targetTable &&
|
||||
s.tableConfig.saveConfig.targetTable !== config.saveConfig.tableName
|
||||
(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)
|
||||
(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] 메인 테이블에 저장할 테이블 섹션:",
|
||||
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));
|
||||
|
||||
|
|
@ -1237,58 +1340,58 @@ export function UniversalFormModalComponent({
|
|||
// 공통 저장 필드 수집 (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
|
||||
rowToSave,
|
||||
);
|
||||
|
||||
|
||||
if (!response.data?.success) {
|
||||
throw new Error(response.data?.message || "품목 저장 실패");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 이미 저장했으므로 아래 로직에서 다시 저장하지 않도록 제거
|
||||
delete tableSectionData[tableSection.id];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 품목이 없으면 공통 데이터만 저장하지 않음 (품목이 필요한 화면이므로)
|
||||
// 다른 테이블 섹션이 있는 경우에만 메인 데이터 저장
|
||||
const hasOtherTableSections = Object.keys(tableSectionData).length > 0;
|
||||
|
|
@ -1303,7 +1406,7 @@ export function UniversalFormModalComponent({
|
|||
if (!response.data?.success) {
|
||||
throw new Error(response.data?.message || "저장 실패");
|
||||
}
|
||||
|
||||
|
||||
// 테이블 섹션 데이터 저장 (별도 테이블에)
|
||||
for (const section of config.sections) {
|
||||
if (section.type === "table" && section.tableConfig?.saveConfig?.targetTable) {
|
||||
|
|
@ -1311,35 +1414,35 @@ export function UniversalFormModalComponent({
|
|||
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) {
|
||||
|
|
@ -1354,13 +1457,13 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
|
|
@ -1368,24 +1471,24 @@ export function UniversalFormModalComponent({
|
|||
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
|
||||
itemToSave,
|
||||
);
|
||||
|
||||
|
||||
if (!saveResponse.data?.success) {
|
||||
throw new Error(saveResponse.data?.message || `${section.title || "테이블 섹션"} 저장 실패`);
|
||||
}
|
||||
|
|
@ -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 () => {
|
||||
|
|
@ -1469,7 +1578,7 @@ export function UniversalFormModalComponent({
|
|||
for (const section of config.sections) {
|
||||
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) {
|
||||
// generateOnSave 또는 generateOnOpen 모두 저장 시 실제 순번 할당
|
||||
const shouldAllocate = field.numberingRule.generateOnSave || field.numberingRule.generateOnOpen;
|
||||
|
|
@ -1525,7 +1634,7 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 1-0. receiveFromParent 필드 값도 mainData에 추가 (서브 테이블 저장용)
|
||||
// 이 필드들은 메인 테이블에는 저장되지 않지만, 서브 테이블 저장 시 필요할 수 있음
|
||||
config.sections.forEach((section) => {
|
||||
|
|
@ -1544,7 +1653,7 @@ export function UniversalFormModalComponent({
|
|||
for (const section of config.sections) {
|
||||
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) {
|
||||
// 신규 생성이거나 값이 없는 경우에만 채번
|
||||
|
|
@ -1589,7 +1698,7 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
|
||||
const subItems: Record<string, any>[] = [];
|
||||
|
||||
|
||||
// 반복 섹션이 있는 경우에만 반복 데이터 처리
|
||||
if (subTableConfig.repeatSectionId) {
|
||||
const repeatData = repeatSections[subTableConfig.repeatSectionId] || [];
|
||||
|
|
@ -1902,10 +2011,8 @@ export function UniversalFormModalComponent({
|
|||
// 메인 표시 컬럼 (displayColumn)
|
||||
const mainDisplayVal = row[lfg.displayColumn || ""] || "";
|
||||
// 서브 표시 컬럼 (subDisplayColumn이 있으면 사용, 없으면 valueColumn 사용)
|
||||
const subDisplayVal = lfg.subDisplayColumn
|
||||
? (row[lfg.subDisplayColumn] || "")
|
||||
: (row[valueColumn] || "");
|
||||
|
||||
const subDisplayVal = lfg.subDisplayColumn ? row[lfg.subDisplayColumn] || "" : row[valueColumn] || "";
|
||||
|
||||
switch (lfg.displayFormat) {
|
||||
case "code_name":
|
||||
// 서브 - 메인 형식
|
||||
|
|
@ -1923,7 +2030,10 @@ export function UniversalFormModalComponent({
|
|||
matches.forEach((match) => {
|
||||
const columnName = match.slice(1, -1); // { } 제거
|
||||
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;
|
||||
|
|
@ -1980,7 +2090,12 @@ export function UniversalFormModalComponent({
|
|||
<SelectContent>
|
||||
{sourceData.length > 0 ? (
|
||||
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) => (
|
||||
<SelectItem key={`${row[valueColumn]}_${index}`} value={String(row[valueColumn])}>
|
||||
{getDisplayText(row)}
|
||||
|
|
@ -2240,13 +2355,11 @@ export function UniversalFormModalComponent({
|
|||
),
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* 옵셔널 필드 그룹 렌더링 */}
|
||||
{section.optionalFieldGroups && section.optionalFieldGroups.length > 0 && (
|
||||
<div className="mt-4 space-y-3">
|
||||
{section.optionalFieldGroups.map((group) =>
|
||||
renderOptionalFieldGroup(section, group, sectionColumns)
|
||||
)}
|
||||
{section.optionalFieldGroups.map((group) => renderOptionalFieldGroup(section, group, sectionColumns))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
|
@ -2274,7 +2387,7 @@ export function UniversalFormModalComponent({
|
|||
const renderOptionalFieldGroup = (
|
||||
section: FormSectionConfig,
|
||||
group: OptionalFieldGroupConfig,
|
||||
sectionColumns: number
|
||||
sectionColumns: number,
|
||||
) => {
|
||||
const key = `${section.id}-${group.id}`;
|
||||
const isActivated = activatedOptionalFieldGroups.has(key);
|
||||
|
|
@ -2293,9 +2406,7 @@ export function UniversalFormModalComponent({
|
|||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm font-medium">{group.title}</p>
|
||||
{group.description && (
|
||||
<p className="text-muted-foreground/70 mt-0.5 text-xs">{group.description}</p>
|
||||
)}
|
||||
{group.description && <p className="text-muted-foreground/70 mt-0.5 text-xs">{group.description}</p>}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -2334,16 +2445,10 @@ export function UniversalFormModalComponent({
|
|||
<div className="flex items-center justify-between p-3">
|
||||
<CollapsibleTrigger asChild>
|
||||
<button className="flex items-center gap-2 text-left hover:opacity-80">
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
)}
|
||||
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||
<div>
|
||||
<p className="text-sm font-medium">{group.title}</p>
|
||||
{group.description && (
|
||||
<p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>
|
||||
)}
|
||||
{group.description && <p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>}
|
||||
</div>
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
|
|
@ -2373,8 +2478,8 @@ export function UniversalFormModalComponent({
|
|||
formData[field.columnName],
|
||||
(value) => handleFieldChange(field.columnName, value),
|
||||
`${section.id}-${group.id}-${field.id}`,
|
||||
groupColumns
|
||||
)
|
||||
groupColumns,
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
|
|
@ -2388,9 +2493,7 @@ export function UniversalFormModalComponent({
|
|||
<div className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">{group.title}</p>
|
||||
{group.description && (
|
||||
<p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>
|
||||
)}
|
||||
{group.description && <p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -2417,8 +2520,8 @@ export function UniversalFormModalComponent({
|
|||
formData[field.columnName],
|
||||
(value) => handleFieldChange(field.columnName, value),
|
||||
`${section.id}-${group.id}-${field.id}`,
|
||||
groupColumns
|
||||
)
|
||||
groupColumns,
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2546,7 +2649,8 @@ export function UniversalFormModalComponent({
|
|||
<div className="text-muted-foreground text-center">
|
||||
<p className="font-medium">{config.modal.title || "범용 폼 모달"}</p>
|
||||
<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 className="mt-1 text-xs">저장 테이블: {config.saveConfig.tableName || "(미설정)"}</p>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue