diff --git a/index.html b/index.html index d6f5132..08a1f5e 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,7 @@ + @@ -19,7 +20,7 @@
`).join(''); +======= +export function openHwModal(asset: HardwareAsset, mode: 'view' | 'add' | 'edit' = 'view') { + currentAsset = asset; + const modal = document.getElementById('hw-asset-modal')!; + + // 1. 잠금 상태 통합 제어 (데이터 유무가 아닌 호출 mode에만 의존) + setEditLock('hw-asset-form', mode, { + saveBtnId: 'btn-save-hw-asset', + revertBtnId: 'btn-revert-hw-edit', + generateBtnId: 'btn-generate-hw-code' + }); + + isEditMode = (mode === 'add' || mode === 'edit'); + + // 2. 데이터 바인딩 + fillHwFormData(asset); + + modal.classList.remove('hidden'); + applyTypeSpecificUI(asset.type); + createIcons({ icons: { Paperclip } }); +>>>>>>> origin/SW_Table } function applyTypeSpecificUI(type: string) { diff --git a/src/components/Modal/ModalUtils.ts b/src/components/Modal/ModalUtils.ts index cd39927..a448e1a 100644 --- a/src/components/Modal/ModalUtils.ts +++ b/src/components/Modal/ModalUtils.ts @@ -1,213 +1,180 @@ -import { LOCATION_DATA } from './SharedData'; +import { createIcons, Save, Edit2, RotateCcw } from 'lucide'; -/** - * 모달 조작 및 UI 생성을 위한 공통 유틸리티 - */ +// 공통 옵션 생성 함수 +export const generateOptionsHTML = (options: string[]) => + options.map(opt => ``).join(''); -// 1. Select 박스의 Option HTML 생성 -export function generateOptionsHTML(list: string[], defaultValue: string = '', includeSelectHint: boolean = true): string { - let html = includeSelectHint ? '' : ''; - html += list.map(item => ``).join(''); - return html; -} - -// 2. 안전하게 폼 필드 값 설정 (Null 에러 방지) +// 필드 값 설정 유틸리티 export function setFieldValue(id: string, value: any) { const el = document.getElementById(id) as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; if (el) { - el.value = value || ''; + if (el.type === 'checkbox') (el as HTMLInputElement).checked = !!value; + else el.value = value || ''; } } -// 3. 안전하게 폼 필드 값 읽기 +// 필드 값 가져오기 유틸리티 export function getFieldValue(id: string): string { const el = document.getElementById(id) as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; - return el ? el.value : ''; + if (!el) return ''; + if (el.type === 'checkbox') return (el as HTMLInputElement).checked ? 'Y' : 'N'; + return el.value || ''; } -// 4. 위치 정보 파싱 및 UI 세팅 +// 폼 자동 채우기 +export function autoFillForm(prefix: string, data: any, fieldMap: Record) { + Object.keys(fieldMap).forEach(fieldId => { + const dataKey = fieldMap[fieldId]; + setFieldValue(`${prefix}-${fieldId}`, data[dataKey]); + }); +} + +// 폼 데이터 자동 추출 +export function autoExtractForm(prefix: string, fieldMap: Record): any { + const extracted: any = {}; + Object.keys(fieldMap).forEach(fieldId => { + const dataKey = fieldMap[fieldId]; + extracted[dataKey] = getFieldValue(`${prefix}-${fieldId}`); + }); + return extracted; +} + +// 모달 편집 잠금/해제 유틸리티 +export function setEditLock(formId: string, mode: 'view' | 'edit' | 'add', options: { + saveBtnId: string, + revertBtnId: string, + generateBtnId?: string, + addLogBtnId?: string +}) { + const form = document.getElementById(formId) as HTMLFormElement; + if (!form) return; + + const isView = mode === 'view'; + const inputs = form.querySelectorAll('input, select, textarea'); + + inputs.forEach(input => { + const el = input as HTMLInputElement; + if (el.id.includes('자산코드') || el.id.includes('asset-id') || el.classList.contains('is-readonly-field')) { + el.readOnly = true; + el.disabled = false; + } else { + if (el.tagName === 'SELECT') (el as HTMLSelectElement).disabled = isView; + else (el as HTMLInputElement).readOnly = isView; + } + }); + + const saveBtn = document.getElementById(options.saveBtnId); + const revertBtn = document.getElementById(options.revertBtnId); + const generateBtn = options.generateBtnId ? document.getElementById(options.generateBtnId) : null; + const addLogBtn = options.addLogBtnId ? document.getElementById(options.addLogBtnId) : null; + + if (saveBtn) { + saveBtn.innerHTML = isView + ? ` 수정` + : ` 저장`; + saveBtn.className = isView ? 'btn btn-primary' : 'btn btn-success'; + } + + if (revertBtn) revertBtn.classList.toggle('hidden', isView); + if (generateBtn) generateBtn.classList.toggle('hidden', isView); + if (addLogBtn) addLogBtn.classList.toggle('hidden', isView); + + createIcons({ icons: { Save, Edit2, RotateCcw } }); +} + +// 위치 정보 파싱 및 설정 export function parseAndSetLocation(locationStr: string, bldgId: string, detailId: string, etcGroupId: string, etcInputId: string) { const bldgSelect = document.getElementById(bldgId) as HTMLSelectElement; const detailSelect = document.getElementById(detailId) as HTMLSelectElement; const etcGroup = document.getElementById(etcGroupId); const etcInput = document.getElementById(etcInputId) as HTMLInputElement; - if (!bldgSelect || !detailSelect) return; - - // 초기화 - bldgSelect.value = ''; - detailSelect.innerHTML = ''; - if (etcGroup) etcGroup.style.display = 'none'; - if (!locationStr) return; - const parts = locationStr.split(' '); - const bldg = parts[0]; - - if (LOCATION_DATA[bldg]) { - bldgSelect.value = bldg; - // 상세 목록 갱신 - detailSelect.innerHTML = generateOptionsHTML(LOCATION_DATA[bldg]); - - const detail = parts[1]; - if (detail) { - detailSelect.value = detail; - if (detail === '기타' && etcGroup && etcInput) { - etcGroup.style.display = 'flex'; - etcInput.value = parts.slice(2).join(' '); - } + const parts = locationStr.split(' > '); + if (parts.length >= 1) { + bldgSelect.value = parts[0]; + bldgSelect.dispatchEvent(new Event('change')); + if (parts.length >= 2) { + setTimeout(() => { + detailSelect.value = parts[1]; + if (parts[1] === '기타' && parts[2]) { + if (etcGroup) etcGroup.style.display = 'flex'; + etcInput.value = parts[2]; + } + }, 0); } } } -// 5. 위치 종속성(Cascade) 이벤트 바인딩 +// 위치 정보 취합 +export function getCombinedLocation(bldgId: string, detailId: string, etcId: string): string { + const bldg = getFieldValue(bldgId); + const detail = getFieldValue(detailId); + const etc = getFieldValue(etcId); + + if (!bldg) return ''; + if (detail === '기타') return `${bldg} > 기타 > ${etc}`; + return detail ? `${bldg} > ${detail}` : bldg; +} + +// 위치 이벤트 바인딩 +import { LOCATION_DATA } from './SharedData'; export function bindLocationEvents(bldgId: string, detailId: string, etcGroupId: string, etcInputId: string) { const bldgSelect = document.getElementById(bldgId) as HTMLSelectElement; const detailSelect = document.getElementById(detailId) as HTMLSelectElement; const etcGroup = document.getElementById(etcGroupId); - const etcInput = document.getElementById(etcInputId) as HTMLInputElement; - if (!bldgSelect || !detailSelect) return; - - bldgSelect.addEventListener('change', () => { + bldgSelect?.addEventListener('change', () => { const bldg = bldgSelect.value; - detailSelect.innerHTML = generateOptionsHTML(LOCATION_DATA[bldg] || []); + const details = LOCATION_DATA[bldg] || []; + detailSelect.innerHTML = `` + generateOptionsHTML(details) + ``; if (etcGroup) etcGroup.style.display = 'none'; - if (etcInput) etcInput.value = ''; }); - detailSelect.addEventListener('change', () => { - if (etcGroup) { - etcGroup.style.display = detailSelect.value === '기타' ? 'flex' : 'none'; - } + detailSelect?.addEventListener('change', () => { + if (etcGroup) etcGroup.style.display = detailSelect.value === '기타' ? 'flex' : 'none'; }); } -// 6. 위치 문자열 조합 (저장용) -export function getCombinedLocation(bldgId: string, detailId: string, etcInputId: string): string { - const bldg = getFieldValue(bldgId); - const detail = getFieldValue(detailId); - const etc = getFieldValue(etcInputId); - - let combined = bldg; - if (detail) combined += ` ${detail}`; - if (detail === '기타' && etc) combined += ` ${etc}`; - - return combined.trim(); -} - -// 7. 조회/수정 모드 UI 통합 제어 -export function setEditLock( - formId: string, - mode: 'view' | 'add' | 'edit', - options: { - saveBtnId: string, - revertBtnId: string, - generateBtnId?: string, - addLogBtnId?: string - } -) { - const form = document.getElementById(formId) as HTMLFormElement; - const saveBtn = document.getElementById(options.saveBtnId); - const revertBtn = document.getElementById(options.revertBtnId); - const generateBtn = options.generateBtnId ? document.getElementById(options.generateBtnId) : null; - const addLogBtn = options.addLogBtnId ? document.getElementById(options.addLogBtnId) : null; - - if (!form || !saveBtn || !revertBtn) return; - - if (mode === 'add' || mode === 'edit') { - // 편집 모드 활성화 - form.classList.remove('is-view-mode'); - form.classList.add('is-edit-mode'); - saveBtn.textContent = '저장'; - revertBtn.classList.toggle('hidden', mode === 'add'); // 신규 추가 시에는 취소 버튼 숨김 - - // 번호 생성 버튼은 '추가(add)' 시에만 노출 - if (generateBtn) { - generateBtn.style.display = mode === 'add' ? 'flex' : 'none'; - } - // 내역 추가 버튼 노출 - if (addLogBtn) addLogBtn.style.display = 'flex'; - } else { - // 조회 모드 (잠금) - form.classList.remove('is-edit-mode'); - form.classList.add('is-view-mode'); - saveBtn.textContent = '수정'; - revertBtn.classList.add('hidden'); - - // 조회 모드에서는 버튼들 숨김 - if (generateBtn) generateBtn.style.display = 'none'; - if (addLogBtn) addLogBtn.style.display = 'none'; - } -} - -/** - * 8. 공통 모달 프레임 템플릿 생성 - * @param idPrefix 필드 ID의 접두사 (예: 'hw', 'sw', 'pc') - * @param title 모달 제목 - * @param formContent 각 모달마다 다른 폼 본문 HTML - * @param options 설정 (이력 영역 제목 등) - */ -export function createModalFrameHTML( - idPrefix: string, - title: string, - formContent: string, - options: { historyTitle: string, addLogBtnId: string } -): string { +// 모달 프레임 HTML 생성 (2열 그리드 표준 레이아웃) +export function createModalFrameHTML(id: string, title: string, formHTML: string, options: { historyTitle?: string, addLogBtnId?: string }) { return ` -