- Published on
4단계 Creation Wizard UX 구현 회고 — 상태·모드 분리와 완전 종료 문제
- Authors

- Name
- Hyo814
4단계 Creation Wizard UX 구현 회고 — 상태·모드 분리와 완전 종료 문제
단일 폼에 입력 칸을 잔뜩 쌓아두는 대신, 4단계 Wizard로 나눠서 받는 UX로 개편했습니다. 겉보기에는 "Next 버튼 넣으면 되는 거 아닌가" 싶지만, 일반 편집 모드와의 공존·취소 시 완전 종료·트리 상태 동기화가 한 덩어리가 되면서 까다로워졌습니다. 그 과정에서 나온 회고를 남깁니다.
1. 요구사항 — 복잡한 입력을 4단계로 분해
객체식별자(OID) 등록 폼은 입력 항목이 많았습니다.
- 기본 정보 — 이름, 번호, 상위 OID 선택
- 등록자 정보 — 기관, 담당자, 연락처
- 연관 OID / 표준데이터 — 트리에서 선택
- 표기법 확인 — 자동 계산 결과 검토 후 제출
처음에는 한 페이지에 전부 넣었는데, 사용자가 "어디까지 작성했는지 모르겠다" "틀린 부분이 어디인지 한눈에 안 보인다" 는 피드백이 왔습니다.
2. 두 가지 모드 — Wizard vs 일반 편집
단순히 "Wizard 전환"만 하면 될 줄 알았는데, 이미 저장된 OID를 수정하는 경우에는 단계별로 가는 게 오히려 불편했습니다. 그래서 두 모드를 분리했습니다.
| 모드 | 상황 | UX |
|---|---|---|
| Creation Wizard | 신규 등록 | 1→2→3→4단계 순차 진행, 뒤로가기 허용 |
| 일반 편집 | 기존 OID 수정 | 전체 폼 한 화면, 필요한 필드만 수정 후 저장 |
// static/js/oidClassTree.js
const WizardMode = {
CREATION: 'creation', // Wizard 단계별
EDIT: 'edit', // 일반 편집
};
let currentMode = WizardMode.EDIT;
let currentStep = 1;
function enterCreationWizard() {
currentMode = WizardMode.CREATION;
currentStep = 1;
showStep(1);
updateButtonGroup(); // 버튼 집합 갱신
}
function enterEditMode(oidId) {
currentMode = WizardMode.EDIT;
currentStep = 0; // 단계 개념 없음
showFullForm(oidId);
updateButtonGroup();
}
3. 상위 OID 선택 — 트리 모달로 UX 해결
"상위 OID는 무엇인가" 라는 질문에 사용자가 이름만 보고 고르기는 어려웠습니다. 계층 구조를 보여주는 트리 모달을 띄워서 선택하게 했습니다.
function openParentOidModal() {
$('#parent-oid-modal').modal('show');
// 모달 내부 트리 초기화 — jstree 기반
$('#parent-oid-tree').jstree({
core: {
data: {
url: '/api/oid/tree/',
dataType: 'json'
}
},
plugins: ['types', 'state']
});
$('#parent-oid-tree').on('select_node.jstree', (e, data) => {
selectedParent = data.node;
});
}
function confirmParentSelection() {
if (!selectedParent) {
alert('상위 OID를 선택해야 합니다.');
return;
}
$('#parent-oid-display').text(selectedParent.text);
$('#parent-oid-id').val(selectedParent.id);
$('#parent-oid-modal').modal('hide');
// 선택된 상위 OID를 기반으로 표기법 자동 계산 트리거
recalculateNotations();
}
4. 표기법 자동 계산 — 4가지 표기법 동시 생성
OID는 1.2.3.4처럼 점 구분뿐만 아니라 ISO 표기, URN 형식 등 4가지 표기법을 동시에 표시해야 합니다. 상위 OID가 정해지면 자동으로 파생합니다.
function recalculateNotations() {
const parent = $('#parent-oid-id').val();
const number = $('#oid-number').val();
const name = $('#oid-name').val();
if (!parent || !number) return;
fetch(`/api/oid/notations/?parent=${parent}&number=${number}&name=${encodeURIComponent(name)}`)
.then(r => r.json())
.then(data => {
$('#notation-dot').text(data.dot); // 1.2.3.4
$('#notation-iso').text(data.iso); // { iso(1) ... }
$('#notation-urn').text(data.urn); // urn:oid:1.2.3.4
$('#notation-oid-iri').text(data.oid_iri); // /ISO/...
});
}
5. "완전 종료" 버그 — 취소했는데 편집 폼이 남는다
가장 골치 아팠던 버그는 Wizard 취소가 "완전히" 안 되는 문제였습니다.
증상
1단계에서 입력하고 2~4단계에서 "취소" 버튼을 누르면, 모달은 닫히는데 편집 폼의 alert와 일부 입력값이 화면에 그대로 남아 있었습니다.
원인
Wizard 내부에서 띄운 편집 폼의 상태(선택된 트리 노드, alert 매크로의 표시 여부, 버튼 그룹의 모드 플래그)가 모달 단위가 아니라 전역 상태로 존재하고 있었습니다. 모달만 닫아도 전역 상태는 살아 있었습니다.
해결 — cancelCreationWizard() 도입
function cancelCreationWizard() {
// 1. 모달 닫기
$('#creation-wizard-modal').modal('hide');
// 2. 전역 상태 초기화
currentMode = WizardMode.EDIT;
currentStep = 0;
selectedParent = null;
// 3. 편집 폼 DOM 초기화
$('#oid-form')[0].reset();
hideOidEditButtons();
// 4. 트리 선택 상태 해제
$('#oid-tree').jstree('deselect_all');
// 5. alert 매크로 숨김
$('.wizard-alert').hide();
// 6. 버튼 그룹을 "조회" 기본 상태로 복원
updateButtonGroup();
}
// 2~4단계 취소 버튼에 바인딩
$('.btn-wizard-cancel').on('click', cancelCreationWizard);
포인트: "상태가 6곳에 흩어져 있다면 종료 함수도 6곳을 닫아줘야" 합니다. 당연한 말 같지만 한 군데 빠뜨리면 바로 "왜 안 닫히지?" 가 됩니다.
6. 트리 구조에 등록 상태 표시
Wizard로 등록된 OID는 심사 상태(신청 중 / 승인 / 반려)에 따라 트리에서 시각적으로 구분돼야 합니다.
// 노드 텍스트에 상태 표시 — 초기에는 이렇게 했다가 철회
function renderNodeText(node) {
let text = node.name;
const state = node.data.current_state;
if (state) {
text += ` [${state}]`; // "샘플 OID [신청중]"
}
return text;
}
나중에는 텍스트에 상태를 끼워넣는 대신 아이콘 색상과 타입으로 구분하는 방식으로 바꿨습니다. 텍스트에 상태를 섞으면 노드 이름 검색/정렬 시 간섭이 생겼기 때문입니다.
// 최종 — types 플러그인으로 분리
$('#oid-tree').jstree({
types: {
pending: { icon: 'icon-pending' },
approved: { icon: 'icon-approved' },
rejected: { icon: 'icon-rejected' },
},
// 노드 로드 시 state를 type으로 매핑
});
7. 저장 상태인데 alert가 뜨는 이슈
신규 노드를 추가한 직후에 저장 완료 상태인데, 다른 노드를 클릭하면 "저장하지 않은 변경사항이 있습니다" alert가 떴습니다.
원인은 "더티 플래그가 저장 후에 리셋되지 않음" 이었고, 저장 성공 콜백에서 명시적으로 초기화했습니다.
function saveOid(payload) {
return fetch('/api/oid/', { method: 'POST', body: JSON.stringify(payload) })
.then(r => r.json())
.then(data => {
isDirty = false; // 리셋
lastSavedSnapshot = payload; // 스냅샷 갱신
return data;
});
}
function checkBeforeLeave() {
if (isDirty) {
return confirm('저장하지 않은 변경사항이 있습니다. 계속하시겠습니까?');
}
return true;
}
정리
- Wizard와 일반 편집을 같은 화면에서 공존시키려면 모드 플래그와 버튼 그룹을 명시적으로 분리해야 합니다.
- 완전 종료 함수를 초기부터 설계하세요. 상태가 분산될수록 "그냥 모달 닫기"로는 안 됩니다.
- 트리에 상태를 표시할 때는 텍스트 대신 아이콘/타입이 장기적으로 안전합니다.
- 더티 플래그는 저장 성공 콜백에서 반드시 리셋하세요.
- 표기법 자동 계산 같은 파생 로직은 서버에 두고, 클라이언트는 요청만 하도록 분리하는 게 유지보수에 유리합니다.
Wizard UX는 사용자 입장에서 "작성해야 할 항목이 적어 보이는" 착시를 주지만, 개발자 입장에서는 상태 관리 복잡도가 2배가 됩니다. 모드 분리를 처음부터 설계도에 넣고 시작하는 것을 권장합니다.