From fca5e37d43543ed1e6e680a779d340b1cc4427cd Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Fri, 3 Oct 2025 13:55:08 +0900 Subject: (김준회) spreadjs dialog client error 처리 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/form-data/spreadJS-dialog.tsx | 99 +++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 34 deletions(-) (limited to 'components/form-data') diff --git a/components/form-data/spreadJS-dialog.tsx b/components/form-data/spreadJS-dialog.tsx index 375c097c..272d99be 100644 --- a/components/form-data/spreadJS-dialog.tsx +++ b/components/form-data/spreadJS-dialog.tsx @@ -189,6 +189,11 @@ export function TemplateViewDialog({ }, [columnsJSON]); const isValidTemplate = React.useCallback((template: TemplateItem): boolean => { + // 🔍 TMPL_ID 필수 검증 추가 + if (!template || !template.TMPL_ID || typeof template.TMPL_ID !== 'string') { + console.warn('⚠️ Invalid template: missing or invalid TMPL_ID', template); + return false; + } return determineTemplateType(template) !== null; }, [determineTemplateType]); @@ -270,29 +275,43 @@ export function TemplateViewDialog({ if (validTemplates.length > 0 && !selectedTemplateId) { const firstTemplate = validTemplates[0]; - const templateTypeToSet = determineTemplateType(firstTemplate); - setSelectedTemplateId(firstTemplate.TMPL_ID); - setTemplateType(templateTypeToSet); + // 🔍 TMPL_ID 검증 (isValidTemplate로 필터링했으므로 존재해야 하지만 안전장치) + if (firstTemplate?.TMPL_ID) { + const templateTypeToSet = determineTemplateType(firstTemplate); + setSelectedTemplateId(firstTemplate.TMPL_ID); + setTemplateType(templateTypeToSet); + } else { + console.error('❌ First valid template has no TMPL_ID:', firstTemplate); + } } }, [templateData, selectedTemplateId, isValidTemplate, determineTemplateType, columnsJSON]); const handleTemplateChange = (templateId: string) => { - const template = availableTemplates.find(t => t.TMPL_ID === templateId); - if (template) { - const templateTypeToSet = determineTemplateType(template); - setSelectedTemplateId(templateId); - setTemplateType(templateTypeToSet); - setHasChanges(false); - setValidationErrors([]); - - if (currentSpread && template) { - initSpread(currentSpread, template); - } + const template = availableTemplates.find(t => t?.TMPL_ID === templateId); + + // 🔍 템플릿과 TMPL_ID 검증 + if (!template || !template.TMPL_ID) { + console.error('❌ Template not found or invalid TMPL_ID:', templateId); + return; + } + + const templateTypeToSet = determineTemplateType(template); + setSelectedTemplateId(templateId); + setTemplateType(templateTypeToSet); + setHasChanges(false); + setValidationErrors([]); + + if (currentSpread) { + initSpread(currentSpread, template); } }; const selectedTemplate = React.useMemo(() => { - return availableTemplates.find(t => t.TMPL_ID === selectedTemplateId); + const found = availableTemplates.find(t => t?.TMPL_ID === selectedTemplateId); + if (!found && selectedTemplateId) { + console.warn('⚠️ Selected template not found:', selectedTemplateId); + } + return found; }, [availableTemplates, selectedTemplateId]); const editableFields = React.useMemo(() => { @@ -1293,17 +1312,29 @@ export function TemplateViewDialog({ }, [selectedTemplate, templateType, selectedRow, tableData, updateProgress, getSafeActiveSheet, createGrdListTableOptimized, setBatchValues, setBatchStyles, setupSheetProtectionAndEvents, setCellMappings, createCellStyle, isFieldEditable, columnsJSON, setupOptimizedListValidation, parseCellAddress, ensureRowCapacity, getCellAddress]); React.useEffect(() => { - if (!selectedTemplateId) { + // 🔍 안전성 검증: availableTemplates가 있고, selectedTemplateId가 없을 때만 실행 + if (!selectedTemplateId && availableTemplates.length > 0) { const only = availableTemplates[0]; + + // 🔍 TMPL_ID 검증 + if (!only || !only.TMPL_ID) { + console.error('❌ First template has no TMPL_ID:', only); + return; + } + const type = determineTemplateType(only); + + // 🔍 type이 null이 아닐 때만 진행 + if (!type) { + console.warn('⚠️ Could not determine template type for:', only); + return; + } // 선택되어 있지 않다면 자동 선택 - if (selectedTemplateId !== only.TMPL_ID) { - setSelectedTemplateId(only.TMPL_ID); - setTemplateType(type); - } + setSelectedTemplateId(only.TMPL_ID); + setTemplateType(type); - // 이미 스프레드가 마운트되어 있다면 즉시 초기화(선택 변경만으로도 리렌더되지만 안전하게 보강) + // 이미 스프레드가 마운트되어 있다면 즉시 초기화 if (currentSpread) { initSpread(currentSpread, only); } @@ -1313,9 +1344,7 @@ export function TemplateViewDialog({ selectedTemplateId, currentSpread, determineTemplateType, - initSpread, - setTemplateType, - setSelectedTemplateId + initSpread ]); const handleSaveChanges = React.useCallback(async () => { @@ -1491,8 +1520,8 @@ export function TemplateViewDialog({ SEDP Template - {formCode}
- {availableTemplates.length > 0 ? ( - // 템플릿이 2개 이상일 때: Select 박스 표시 + {availableTemplates.length > 1 ? ( + // 🔍 템플릿이 2개 이상일 때: Select 박스 표시
Template:
) : availableTemplates.length === 1 ? ( - // 템플릿이 정확히 1개일 때: 템플릿 이름을 텍스트로 표시 + // 🔍 템플릿이 정확히 1개일 때: 템플릿 이름을 텍스트로 표시
Template: - {availableTemplates[0].NAME} ({availableTemplates[0].TMPL_TYPE}) + {availableTemplates[0]?.NAME || 'Unnamed'} ({availableTemplates[0]?.TMPL_TYPE || 'Unknown'})
) : null} @@ -1581,7 +1612,7 @@ export function TemplateViewDialog({ {selectedTemplate && isClient && isDataValid ? ( -- cgit v1.2.3