feat(ui/ux): unify typography to Pretendard and enforce read-only view mode as default

- Set global font-family to Pretendard and letter-spacing to -0.02em.
- Standardized table header font-size to var(--fs-sm).
- Fixed table clipping and sticky header behavior at 1920x1080.
- Implemented dynamic select options in search filters.
- Enforced 'view' mode as default for all asset modals (PC, Server, SW, etc.).
- Improved Modal logic to ensure all fields (including dynamic rows) are correctly locked.
- Updated Location View detail button from 'Edit' to 'View'.
- Updated design_rule.md to reflect new typography standards.
This commit is contained in:
2026-06-18 11:13:16 +09:00
parent 2cb4b87c0a
commit 3db05f2939
14 changed files with 143 additions and 99 deletions

View File

@@ -9,6 +9,7 @@ export abstract class BaseModal {
protected title: string;
protected currentAsset: any | null = null;
protected isEditMode: boolean = false;
protected currentMode: 'view' | 'edit' | 'add' = 'view';
protected modalEl: HTMLElement | null = null;
protected formEl: HTMLFormElement | null = null;
@@ -53,16 +54,23 @@ export abstract class BaseModal {
*/
public open(asset: any, mode: 'view' | 'edit' | 'add' = 'view') {
this.currentAsset = asset;
this.currentMode = mode;
this.isEditMode = (mode === 'add' || mode === 'edit');
// 폼 초기화 추가
if (this.formEl) this.formEl.reset();
this.setEditLockMode(mode);
// fillFormData를 먼저 호출하여 동적 요소들을 생성한 후 잠금 처리
this.fillFormData(asset);
this.setEditLockMode(mode);
if (this.modalEl) {
this.modalEl.classList.remove('hidden');
const content = this.modalEl.querySelector('.modal-content');
if (content) {
if (mode === 'view') content.classList.add('is-view-mode');
else content.classList.remove('is-view-mode');
}
}
this.onAfterOpen(asset, mode);

View File

@@ -237,14 +237,15 @@ class HwAssetModal extends BaseModal {
</div>
</div>
</div>
<div class="modal-footer">
<button id="btn-delete-hw-asset" class="btn btn-outline btn-danger">삭제</button>
<div class="footer-actions">
<button id="btn-cancel-hw-modal" class="btn btn-outline">닫기</button>
<button id="btn-save-hw-asset" class="btn btn-primary">저장</button>
</div>
<div class="modal-footer">
<button id="btn-delete-hw-asset" class="btn btn-outline btn-danger">삭제</button>
<div class="footer-actions">
<button id="btn-revert-hw-edit" class="btn btn-outline hidden">수정 취소</button>
<button id="btn-cancel-hw-modal" class="btn btn-outline">닫기</button>
<button id="btn-save-hw-asset" class="btn btn-primary">저장</button>
</div>
</div>
</div>
</div>
<style>
.hidden {
@@ -370,6 +371,12 @@ class HwAssetModal extends BaseModal {
saveBtn.addEventListener('click', async () => {
if (!this.currentAsset) return;
// [추가] 조회 모드인 경우 수정 모드로 전환
if (!this.isEditMode) {
this.open(this.currentAsset, 'edit');
return;
}
// 동적 볼륨 데이터 수집
const vols: any[] = [];
document.querySelectorAll('#hw-volume-container .volume-row').forEach((row, idx) => {

View File

@@ -255,19 +255,13 @@ class JobSpecModal extends BaseModal {
deleteBtn.style.display = (mode === 'add') ? 'none' : 'block';
if (mode === 'add') {
this.setEditLockMode('edit');
this.isEditMode = true;
saveBtn.textContent = '등록';
if (mode === 'add' || mode === 'edit') {
saveBtn.textContent = (mode === 'add') ? '등록' : '저장';
saveBtn.style.display = 'block';
} else {
this.setEditLockMode('view');
this.isEditMode = false;
saveBtn.textContent = '수정';
saveBtn.style.display = 'block';
}
this.updateMinScore();
this.updateHeaderIdentity(asset);
}

View File

@@ -110,42 +110,43 @@ export function setEditLock(
const generateBtn = options.generateBtnId ? document.getElementById(options.generateBtnId) : null;
const addLogBtn = options.addLogBtnId ? document.getElementById(options.addLogBtnId) : null;
if (!form || !saveBtn || !revertBtn) return;
if (!form) return;
if (mode === 'add' || mode === 'edit') {
const isEdit = (mode === 'add' || mode === 'edit');
if (isEdit) {
// 편집 모드 활성화
form.classList.remove('is-view-mode');
form.classList.add('is-edit-mode');
saveBtn.textContent = '저장';
revertBtn.classList.toggle('hidden', mode === 'add');
if (saveBtn) saveBtn.textContent = (mode === 'add' ? '등록' : '저장');
if (revertBtn) revertBtn.classList.toggle('hidden', mode === 'add');
// 모든 필드 활성화
const inputs = form.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
const el = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
if (el.name !== 'asset_code' && !el.id.includes('asset-id')) { // 자산번호 등 일부는 편집 모드에서도 잠금 유지
// 자산번호 및 ID 필드는 편집 모드에서도 잠금 유지
if (el.name !== 'asset_code' && !el.id.includes('asset-id') && !el.id.includes('id-hidden')) {
el.disabled = false;
if ('readOnly' in el) (el as HTMLInputElement).readOnly = false;
}
});
// 번호 생성 버튼은 '추가(add)' 시에만 노출
if (generateBtn) {
generateBtn.style.display = mode === 'add' ? 'flex' : 'none';
}
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 (saveBtn) saveBtn.textContent = '수정';
if (revertBtn) revertBtn.classList.add('hidden');
// 모든 필드 잠금
const inputs = form.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
const el = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
el.disabled = true; // select의 경우 disabled 필요
el.disabled = true;
if ('readOnly' in el) (el as HTMLInputElement).readOnly = true;
});
if (generateBtn) generateBtn.style.display = 'none';

View File

@@ -137,14 +137,10 @@ class PartsMasterModal extends BaseModal {
deleteBtn.style.display = (mode === 'add') ? 'none' : 'block';
if (mode === 'add') {
this.setEditLockMode('edit');
this.isEditMode = true;
saveBtn.textContent = '등록';
if (mode === 'add' || mode === 'edit') {
saveBtn.textContent = (mode === 'add') ? '등록' : '저장';
saveBtn.style.display = 'block';
} else {
this.setEditLockMode('view');
this.isEditMode = false;
saveBtn.textContent = '수정';
saveBtn.style.display = 'block';
}

View File

@@ -144,14 +144,10 @@ class UserModal extends BaseModal {
deleteBtn.style.display = (mode === 'add') ? 'none' : 'block';
if (mode === 'add') {
this.setEditLockMode('edit');
this.isEditMode = true;
saveBtn.textContent = '등록';
if (mode === 'add' || mode === 'edit') {
saveBtn.textContent = mode === 'add' ? '등록' : '저장';
saveBtn.style.display = 'block';
} else {
this.setEditLockMode('view');
this.isEditMode = false;
saveBtn.textContent = '수정';
saveBtn.style.display = 'block';
}