직원 추가 패널 (AddEmployeePanel)
직원을 새로 만드는 폼입니다. 좌측 카테고리를 자유롭게 오가며 채우고, 빨간 점이 남아 있으면 저장이 막힙니다. 화면은 패널 모달 위에 뜹니다.
Alpine 컴포넌트 · Web · 진행 중(WIP) · 문서 포맷 시범 적용
Interactive playground
기본정보
이 자리에 해당 카테고리의 입력 폼이 옵니다. 지금 카테고리에 필수값이 비어 있습니다.
<!-- 위저드는 앱이 마운트하고, 좌측 메뉴 마크업은 파셜이 그린다 -->
<div x-data="addEmployeePanel({ onComplete: (id) => loadEmployees() })">
<!-- include "../../packages/components/html/add-employee-panel.html" -->
</div>
// 빨간 점은 이 한 줄이 정한다 — 카테고리별 필수값 검사
addCatValid(id) {
if (id === "basic") return this.addBasicValid();
if (id === "hr") return this.addHrValid();
if (id === "cert") return this.addCertValid();
return this.addContractValid();
}
// 지금 상태: basic · hr 에 점이 뜨고, 저장 버튼이 막힌다막 열었을 때 — 기본정보·인사정보에 점이 있고 저장이 막혀 있습니다
기본정보
이 자리에 해당 카테고리의 입력 폼이 옵니다. 지금 카테고리에 필수값이 비어 있습니다.
기본정보를 채운 뒤 — 그 점만 사라집니다. 다른 카테고리 점은 그대로
인사정보
이 자리에 해당 카테고리의 입력 폼이 옵니다. 지금 카테고리에 필수값이 비어 있습니다.
⚠️ 자격증 등록을 켜고 유형을 안 고른 상태 — 보고 있지 않은 카테고리에서 저장이 막힙니다
기본정보
이 자리에 해당 카테고리의 입력 폼이 옵니다.
전부 채운 상태 — 점이 하나도 없고 저장할 수 있습니다
근로계약
이 자리에 해당 카테고리의 입력 폼이 옵니다.
폼 전체는 재현하지 않았습니다
입력 마크업만 999줄입니다. 문서가 정해야 할 것은 입력 칸이 아니라 좌측 메뉴의 계약 — 자유 이동과 필수 누락 표시 — 이라서 그쪽만 실제 마크업으로 옮겼습니다.
규칙 (강제) · 코딩 에이전트용
AddEmployeePanel 강제 규칙. 상세는 아래 본문.
마크업 / 마운트
x-data="addEmployeePanel({ onComplete })"+packages/components/html/add-employee-panel.htmlinclude. 폼 마크업을 손으로 짓지 마세요.- 카드는
.ds-modal ds-modal--panel— 모달 3종 중 패널 모달입니다.
⚠️ 트랩 ① 단계 게이트가 없습니다 — 순서를 강제하지 마세요 네 카테고리(기본정보 · 인사정보 · 자격증·면허 · 근로계약)는 자유 이동입니다. 「다음」으로 넘기는 단계형이 아니에요. 대신 각 카테고리에 빨간 점으로 미완성을 표시하고, 하나라도 남으면 저장을 막습니다. 이 방식을 단계형으로 「정리」하지 마세요.
⚠️ 트랩 ② 저장이 막히는 이유가 화면 밖에 있을 수 있습니다attemptSaveEmployee() 는 네 카테고리 전부를 봅니다. 그래서 지금 보고 있는 화면은 멀쩡한데 저장이 안 되는 상황이 정상적으로 생깁니다 — 점이 어디 떠 있는지가 유일한 단서이므로, 저장 버튼 옆에 이유를 적지 않는 한 점을 없애면 안 됩니다.
⚠️ 트랩 ③ 빈 값은 빈 문자열로 보내지 말고 빼세요 선택 항목을 안 채우면 "" 를 보내는 게 아니라 키 자체를 payload 에서 뺍니다(테스트가 이걸 고정하고 있습니다). 주민번호도 13자리가 유효할 때만 보냅니다. 근태 방식은 조직 기본값 그대로면 빼고, 사용자가 바꿨을 때만 넣습니다.
⚠️ 트랩 ④ ESC 는 위에서부터 하나씩만패널 폼 패턴 → ESC 층의 공유 규칙입니다 — 맨 앞에서 권한 팝업에 양보하고, 레이어를 하나씩 닫고, 막 닫은 뒤 500ms 는 패널을 닫지 않습니다. 레이어 순서도 그 페이지에 있습니다.
⚠️ 트랩 ⑤ 입력한 걸 조용히 날리지 마세요 X · 취소 · ESC 는 전부 attemptClosePanel() 를 지나갑니다. 손대지 않은 폼은 바로 닫히고, 뭔가 입력했으면 저장확인이 뜹니다(열 때 찍어 둔 closeBaseline 스냅샷과 비교). 성공 화면에서는 잃을 게 없으므로 바로 닫힙니다.
- 다크 모드(2026-10-19) — 공용
add-employee-panel.html의 팔레트 유틸 45곳을 시멘틱으로 옮겼다(카테고리 내비 면 · 글자 · 필수 점 · 입력 라벨). 남은 1곳은 의도된bg-neutral-900(어두운 띠). 이 파셜은 관리자 앱이 쓰므로 다크는 개발 · QA 때 localStoragehereby.theme.admin-qa=dark로만 본다. 확인: Playground 「미리보기 테마 → 다크」.
무엇을 만드나
기존 조직에 직원 한 명을 추가합니다. 기관과 첫 계정을 함께 만드는 RegisterForm 과 다릅니다.
네 카테고리로 나뉩니다.
| 카테고리 | 담는 것 |
|---|---|
| 기본정보 | 성·이름 · 사번 · 이메일 · 전화 · 생년월일 · 주민번호 · 주소 |
| 인사정보 | 소속(팀·역할) · 입사일 · 고용형태 · 근무 그룹 · 근태 방식 등 |
| 자격증·면허 등록 | 자격 유형별 행 (선택) |
| 근로계약 | 급여·호봉 등 계약 항목 |
단계형이 아닙니다
「다음 → 다음」으로 넘기는 위저드가 아니라 아무 카테고리로나 바로 갈 수 있습니다. 대신 미완성을 빨간 점으로 알립니다.
점을 없애면 저장이 왜 막히는지 알 방법이 사라집니다
저장 검사는 보고 있지 않은 카테고리까지 봅니다. 예를 들어 자격증 등록을 켜 놓고 유형을 안 고르면, 기본정보 화면에 있어도 저장이 막힙니다. 그때 화면에는 아무 문제가 없어 보입니다 — 점이 유일한 단서예요.
닫기 가드
X · 취소 · ESC 가 전부 같은 길(attemptClosePanel())로 가고, 손대지 않은 폼은 바로 닫히고 입력했으면 저장확인이 뜹니다. 두 패널이 공유하는 규칙이라 흐름도는 패널 폼 패턴 → 닫기 가드에 있습니다.
ESC 층
패널 위 레이어를 위에서부터 하나씩만 닫고, 맨 앞에서 권한 팝업에 양보합니다. 순서와 500ms 유예는 패널 폼 패턴 → ESC 층에 있습니다.
카테고리별 필수값
addCatValid(id) 가 넷으로 갈라 검사하고, 하나라도 false 면 저장이 막힙니다.
기본정보 — 성·이름은 필수, 사번은 직접 입력 또는 자동 생성 중 하나.
| 항목 | 규칙 |
|---|---|
| 성 · 이름 | 필수 |
| 사번 | 입력하거나 자동 생성을 켜야 함 |
| 이메일 | 선택이지만, 입력했으면 형식 검사 |
| 생년월일 | 선택. 조직 시간대 기준 미래 날짜는 차단 |
| 주민번호 | 선택. 13자리를 다 채웠을 때만 체크섬 검사 |
| 주소 | 선택. 단 주소를 골랐으면 상세주소까지 필수 |
주민번호 앞자리는 생년월일에서 자동으로 옵니다
두 패널이 같은 유틸(utils/rrn.ts)을 씁니다 — 파생·자릿수·체크섬 규칙은 패널 폼 패턴 → 주민등록번호에 있습니다.
자격증·면허 — 등록을 켜 두고 유형을 하나도 안 고르면 폼 전체가 막힙니다. 「기타」도 정상 선택지이고, 유형이 선택된 행만 셉니다.
저장 payload
빈 값을 빈 문자열로 보내지 마세요
안 채운 선택 항목은 "" 가 아니라 키 자체를 뺍니다. 테스트가 이 동작을 고정하고 있습니다.
| 항목 | 언제 넣나 |
|---|---|
| 전화 · 생년월일 | 입력했을 때만 |
| 주민번호 | 13자리가 유효할 때만. 부분 입력·체크섬 오류면 뺌 |
| 주소 | street · postalCode · detail 구조로. 아무것도 안 고르면 통째로 뺌 |
| 근태 방식 | 조직 기본값과 다를 때만. 그대로면 뺌 |
주소 검색
Daum 우편번호 서비스를 lazy 로드하는 공유 흐름입니다 — 규칙은 패널 폼 패턴 → 주소 검색에 있습니다.
마운트
<div x-data="addEmployeePanel({
onComplete: (createdId) => {
loadEmployees();
$store.toast.success('직원을 추가했습니다');
},
})">
<!-- include "../../packages/components/html/add-employee-panel.html" -->
</div>onComplete 는 만들어진 직원 id 를 받습니다(못 받으면 undefined). 예전 시그니처(() => …)와 호환되므로 안 써도 됩니다.
여는 법
showAddEmployee 를 켭니다. 앱에서는 보통 버튼이 그 값을 직접 만집니다.
<button @click="$data.showAddEmployee = true">직원 추가</button>함께 필요한 것
hereby스토어 — 문구(t()),sdk, 조직 시간대(생년월일 미래 검사에 씁니다).- 참조 데이터 — 팀 · 역할 · 근무 형태 · 근무 그룹 · 자격 유형 · 호봉. 컴포넌트가 직접 불러옵니다(
fetchAllPages). - 네트워크 — 주소 검색이 외부 CDN 스크립트를 lazy 로드합니다.
API
addEmployeePanel(opts) Alpine.data factory.
| Prop | 타입 | 설명 |
|---|---|---|
onComplete | (createdEmployeeId?: string) => void | 생성 성공 후 호출. id 를 못 받으면 undefined |
주요 상태
| 멤버 | 설명 |
|---|---|
showAddEmployee | 패널 열림 |
addEmployeeCat · addEmployeeCats | 지금 카테고리 · 카테고리 목록(basic hr cert contract) |
addEmployeeForm | 폼 값 전체 |
addFormErrors · rrnError | 중복 검사 오류(email · phone · employeeNumber) · 주민번호 오류 |
showCloseConfirm · closeBaseline | 저장확인 · 열 때 찍은 폼 스냅샷 |
notice | 공용 알림 팝업 { show, tone, message } — alert() 대체 |
previewOpen · addrSearchOpen · shiftRefOpen | 패널 위 레이어들 |
topLayerClosedAt | 레이어를 막 닫은 시각 — ESC 연타 유예(500ms) 키 |
dataReady | 참조 데이터 로드 완료 |
주요 메서드
| 멤버 | 설명 |
|---|---|
addCatValid(id) | 카테고리별 필수값 검사 → 빨간 점 표시의 근거 |
attemptSaveEmployee() | 네 카테고리 전부 통과 + 중복 없음일 때만 저장 |
saveNewEmployee() | 실제 생성 호출. 빈 선택 항목은 payload 에서 뺌 |
attemptClosePanel() | 손대지 않았거나 성공 화면이면 바로 닫고, 아니면 저장확인 |
escapeClose() | ESC — 권한 팝업 양보 → 레이어 하나 → 패널 |
syncRrnFromBirth() · onResidentNumberInput() | 주민번호 앞자리 파생 · 뒷자리 입력 정리와 체크섬 |
showNotice(msg, tone) · closeNotice() | 공용 알림 팝업 |
사용처
| 앱 | onComplete 에서 하는 일 |
|---|---|
| admin | 목록·미배정·통계 갱신, 팀 구성 새로고침, 방금 추가 표시, 토스트 |
| hr-system | employee-saved 커스텀 이벤트 발사 |
단일 출처
| 파트 | 파일 |
|---|---|
| Alpine 컴포넌트 | packages/components/src/components/AddEmployeePanel.ts |
| HTML partial | packages/components/html/add-employee-panel.html |
| 동작 명세(테스트) | AddEmployeePanel.test.ts — 이 페이지 규칙 대부분이 여기 고정돼 있습니다 |
| ESC 양보 | utils/esc-guard.ts |
검토 필요 사항
- 저장이 막힌 이유를 저장 버튼 근처에서 알 수 없습니다. 빨간 점은 좌측 메뉴에만 있어서, 긴 폼을 스크롤하다 저장을 누르면 왜 안 되는지 바로 안 보입니다. 버튼 옆에 「미완성: 자격증·면허」 같은 한 줄을 둘지 검토가 필요합니다. 두 패널이 함께 안고 있는 것(알림 팝업 자체 구현 · 주소 검색 로더 세 벌 · 외부 CDN 의존 · 수정 패널과 규칙이 갈리는 것)은 패널 폼 패턴 → 검토 필요 사항에 모아 뒀습니다.
변경 이력
| 날짜 | 내용 |
|---|---|
| 2026-09-09 | 본문의 「위저드」 표기를 **「패널」**로 맞췄습니다(컴포넌트는 73fc32b0 에서 이미 개명). ⚠️ 단계형이 아닙니다의 「넘기는 위저드가 아니라」는 일반명사라 그대로 둡니다 — 여기서 바꾸면 뜻이 정반대가 됩니다 |
| 2026-09-09 | 첫 줄이 이 컴포넌트를 「패널 모달입니다」로 규정하고 있어 「폼」으로 바로잡고, 담기는 그릇은 패널 모달 링크로 넘겼습니다. 모달은 이 컴포넌트의 정체가 아니라 뜨는 방식이라, 사이드바에서도 「컴포넌트 — 폼」에 있습니다 |
| 2026-09-09 | 규칙 블록의 ESC 트랩이 패널 폼 패턴 → ESC 층의 공유 규칙을 다시 쓰고 있어 직원 수정처럼 가리키기만 하도록 줄였습니다. 함께 적혀 있던 「레이어가 넷」은 다섯이 맞습니다 — escapeClose() 가 「근무 참고」까지 처리하는데 그 하나가 빠져 있었습니다 |
| 2026-09-08 | AddEmployeePanel 페이지 신규 작성 — 좌측 카테고리 메뉴 Playground(카테고리 × 완료 여부 4축) + 모아보기 4상태. 닫기 가드·ESC 층 다이어그램, 카테고리별 필수값·저장 payload 규칙 포함. 폼 마크업(999줄)은 재현하지 않았습니다 |