diff --git a/.gitignore b/.gitignore index 2566257f..972957ba 100644 --- a/.gitignore +++ b/.gitignore @@ -194,4 +194,6 @@ mcp-task-queue/ # 파이프라인 회고록 (자동 생성) docs/retrospectives/ -mes-architecture-guide.md \ No newline at end of file +mes-architecture-guide.md +# MES Reference Documents +docs/mes-reference/ diff --git a/frontend/components/v2/V2Repeater.tsx b/frontend/components/v2/V2Repeater.tsx index f2f46f82..31a80c18 100644 --- a/frontend/components/v2/V2Repeater.tsx +++ b/frontend/components/v2/V2Repeater.tsx @@ -367,6 +367,20 @@ export const V2Repeater: React.FC = ({ }); try { + // 🆕 필수값 검증 + const requiredColumns = repeaterColumnsRef.current.filter(col => col.required); + for (let i = 0; i < currentData.length; i++) { + const row = currentData[i]; + for (const col of requiredColumns) { + const val = row[col.field]; + if (val === undefined || val === null || val === "") { + toast.error(`${i + 1}번째 행의 '${col.label}'은(는) 필수 입력 항목입니다.`); + window.dispatchEvent(new CustomEvent("repeaterSaveComplete")); + return; + } + } + } + let validColumns: Set = new Set(); try { const columnsResponse = await apiClient.get(`/table-management/tables/${tableName}/columns`); @@ -706,6 +720,7 @@ export const V2Repeater: React.FC = ({ displayName: col.displayName || col.display_name || col.label || name, detailSettings: col.detailSettings || col.detail_settings, categoryRef: typeInfo?.categoryRef || null, + isRequired: col.isNullable === 'NO' || col.is_nullable === 'NO' || col.isRequired || col.is_required || col.notNull || col.not_null === true || col.not_null === 'Y' || col.not_null === 'y', }; }); setCurrentTableColumnInfo(columnMap); @@ -808,10 +823,12 @@ export const V2Repeater: React.FC = ({ loadSourceColumnLabels(); }, [resolvedSourceTable, isModalMode]); + const repeaterColumnsRef = useRef([]); + // V2ColumnConfig → RepeaterColumnConfig 변환 // 🆕 모든 컬럼을 columns 배열의 순서대로 처리 (isSourceDisplay 플래그로 구분) const repeaterColumns: RepeaterColumnConfig[] = useMemo(() => { - return config.columns + const cols = config.columns .filter((col: V2ColumnConfig) => col.visible !== false) .map((col: V2ColumnConfig): RepeaterColumnConfig => { const colInfo = currentTableColumnInfo[col.key]; @@ -858,12 +875,15 @@ export const V2Repeater: React.FC = ({ type, editable: col.editable !== false, width: col.width === "auto" ? undefined : col.width, - required: false, + required: colInfo?.isRequired || false, categoryRef, // 🆕 카테고리 참조 ID 전달 hidden: col.hidden, // 🆕 히든 처리 autoFill: col.autoFill, // 🆕 자동 입력 설정 }; }); + + repeaterColumnsRef.current = cols; + return cols; }, [config.columns, sourceColumnLabels, currentTableColumnInfo, resolvedSourceTable, config.dataSource?.tableName]); // 리피터 컬럼 설정에서 카테고리 타입 컬럼 자동 감지 diff --git a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx index 4b8cb23d..e9df58d5 100644 --- a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx @@ -792,6 +792,7 @@ export function RepeaterTable({ {/* 컬럼명 - 선택된 옵션라벨 형식으로 표시 */} {activeOption?.headerLabel || `${col.label} - ${activeOption?.label || ""}`} + {col.required && *} diff --git a/frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx b/frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx index 2baa6887..8c42b957 100644 --- a/frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx +++ b/frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx @@ -207,9 +207,6 @@ const FileUploadComponent: React.FC = ({ return; } - // 등록 모드(새 레코드)일 때는 이전 파일을 로드하지 않음 - if (!isRecordMode) return; - const rawValue = String(imageObjidFromFormData); // 콤마 구분 다중 objid 또는 단일 objid 모두 처리 const objids = rawValue.split(',').map(s => s.trim()).filter(s => /^\d+$/.test(s)); @@ -265,7 +262,7 @@ const FileUploadComponent: React.FC = ({ console.error("🖼️ [FileUploadComponent] 파일 정보 조회 오류:", error); } })(); - }, [imageObjidFromFormData, columnName, component.id, isRecordMode]); + }, [imageObjidFromFormData, columnName, component.id]); // 🎯 화면설계 모드에서 실제 화면으로의 실시간 동기화 이벤트 리스너 // 🆕 columnName도 체크하여 같은 화면의 다른 파일 업로드 컴포넌트와 구분