다크모드
FE 메인테이너 — 멤버 상세 시트 + 마스터데이터 목록 IA
계약 연결 탭 시각 계약
- 계약 연결 시트의 탭 목록은 SSOT 클래스
tabs-list tabs-list-primary-moderate tabs-list-radius-full을 사용한다. - 각 실제 목적지만
tabs-trigger tabs-trigger-md로 렌더링한다. - 탭 목록 안에
arrow_menu_close같은 아이콘 전용 가짜 탭을 삽입하지 않는다. 접기·이전 이동이 필요하면 탭과 분리된 공식 컨트롤을 사용한다. - 탭 CSS는
leysys-design카피본을 그대로 유지하며 제품 레포에서 크기·패딩·radius를 덮어쓰지 않는다.
독자: FE 메인테이너/AI. "이 페이지를 이어 개발하려면 무엇을 알아야 하나". 대상: 멤버(general) 목록 + 통합 상세 시트(R/C/U) + 연결 컬렉션 오케스트레이션. 정본 패턴:
CLAUDE.md§0(Sheet 편집 패턴, 오너 이원화) · §1(코드 체계) · 리스트 IA.
1. 라우트
| path | view | 상태 |
|---|---|---|
/master-data/resource/member/general | src/views/master-data/resource/member/general/IndexView.vue | ✅구현 |
/master-data/resource/member/group | .../group/IndexView.vue | (별도) |
/master-data/resource/member/label | .../label/IndexView.vue | (별도) |
정의: src/router/index.js (general = L921 부근).
2. 컴포넌트 트리
IndexView.vue (general)
├── HeaderOrg.vue
└── MainOrg.vue
├── SearchToolBarOrg.vue (검색)
├── CriteriaToolBarOrg.vue (필터)
├── DataToolBarOrg.vue (생성/삭제/업로드/다운로드 — 생성 진입점)
├── DynamicTableOrg.vue (목록: 이름 클릭 = 상세 진입)
├── PaginationToolBarOrg.vue
│
└── (overlays — MainOrg가 형제로 마운트)
├── SheetReadMemberArt.vue ★ 통합 R/C/U 상세 시트 (read|edit|create)
├── SheetCreateUploadMemberArt.vue (CSV 등 일괄 업로드)
│
└── 연결 컬렉션 시트(자체 R/C/U) — SheetReadMemberArt 내 탭에서 호출:
├── 차량 Sheet{Read,Create,Update}VehicleArt
├── 일원 Sheet{Read,Create,Update}AssociateArt
├── 사업자 Sheet{Read,Create,Update}BusinessArt
├── 현금영수증 Sheet{Read,Create,Update}CashReceiptArt
├── 가상계좌 Sheet{Read,Create,Update}VirtualBankAccountArt
├── 자동이체 Sheet{Read,Create,Update}DirectDebitArt
└── 노트 Sheet{Read,Create,Update}NoteArt
└── 공통 다이얼로그: Dialog{Create,Read,Update,Delete,Confirm,Cancel,List,Attach}Art- 마운트 전략:
MainOrg.vue가<dialog popover>형 시트/다이얼로그를 전부 형제로 한 번씩 마운트해두고, 목록·툴바·시트 내부 버튼이command="show-modal" commandfor="..."(네이티브 popover/dialog command)로 연다. Vue v-if 토글이 아니라 DOM id 기반 오케스트레이션이다. - 연결 컬렉션 시트들은 다른 도메인 디렉터리(
master-data/resource/{vehicle,associate,business,...})에서 import해 재사용한다. 멤버 전용이 아님.
3. 소비 컴포저블 — useMemberForm.js
src/composables/useMemberForm.js. 모듈 스코프 reactive 싱글톤(프로토타입 — useReceipts·useForwarding 선례). 여러 컴포넌트(DynamicTableOrg, DataToolBarOrg, SheetReadMemberArt)가 같은 인스턴스를 공유한다.
노출 상태/메서드
| 이름 | 종류 | 역할 |
|---|---|---|
record | reactive | 표시 원본(조회). 저장 시 여기에 커밋 |
draft | reactive | 편집·생성 작업본 |
mode | ref 'read'|'edit'|'create' | 시트 통합 모드 |
valid | computed | 필수 충족: type && regNum && name && representative |
dirty | computed | draft ≠ 편집 진입 스냅샷(baseline) |
openRead(rec?) | fn | rec 있으면 record 주입 + mode='read' (목록에서 호출) |
startEdit() | fn | draft←record + 스냅샷 + mode='edit' |
startCreate() | fn | 빈 폼 + 기본값(개인/남자) + 관리코드 자동부여 + mode='create' |
cancel() | fn | mode='read' (작업분 버림) |
save() | fn | record←draft + mode='read' |
syncDraft() | fn | 카드별 편집용 — mode 불변, draft←record + 스냅샷 |
commitDraft() | fn | 카드별 편집용 — record←draft (mode 불변) |
autoRegNum() | fn | [자동입력] 등록번호 생성(아래 §5) |
⚠ 프로토타입 한계: 단일 데모 레코드(
DEMO()). 목록의openRead()는 인자 없이 호출되어 항상 같은 데모 레코드를 연다(행별 주입 미구현 ❌예정). 실제 구현 시 디렉터리/스토어에서 선택 행을openRead(rec)로 주입.
4. 편집 모델 — 이원화 (사용자 결정, CLAUDE.md §0)
SheetReadMemberArt.vue 속성 영역은 두 편집 경로를 병존시킨다(중복이 아니라 의도된 이원화).
4-1. 카드별 편집 (card-footer)
- 기본·컨택·주소 각 카드가 자체
[편집]→[취소/저장]로 그 카드만 인라인 폼 전환. - 상태:
reactive cardEdit { basic, contact, address }. editCard(key)→syncDraft()+cardEdit[key]=true.cancelCard(key)→cardEdit[key]=false(버림).saveCard(key)→commitDraft()+false.- 카드 폼 표시 여부 =
{section}Editing = editing(통합) || cardEdit.{section}.
4-2. 시트 통합 편집 (sheet-footer)
[편집]→startEdit()→mode='edit': 속성 전체가 한 번에 폼 전환.editing = computed(mode !== 'read').[취소]=cancel(),[저장/생성]=save()—:disabled="!valid || !dirty"(통째 편집 게이트 §0-7).
4-3. 이원 충돌 방지
- 통합 편집 중(
mode!=='read')엔 카드 footer를 숨긴다(v-if="mode === 'read'"). 커밋 경로를 sheet-footer 하나로 모아 이중 저장 방지. create모드: 카드 footer 없음(전부 통합), 연결 컬렉션 탭은v-show="mode !== 'create'"로 숨김(아직 멤버 미존재).
5. 필드 잠금 / 자동화 규칙 (CLAUDE.md §1 코드 체계)
| 필드 | create | edit | 규칙 |
|---|---|---|---|
관리코드 mgmtCode | 편집 가능 필드(자동부여 디폴트 mgmt-####, 직접 수정 가능) | 비노출 — zone-top에 subtitle-sm로만 표시 | 최초 등록만 결정. 이후 불변 |
등록번호 regNum | 입력 가능 + [자동입력] 버튼(autoRegNum) | :disabled(잠금) | 준식별 코드. 최초 등록만 변경 |
생년월일 birthDate | 입력 가능 | :disabled(잠금) | 불변 |
이름 name | 입력 가능 | 입력 가능 | 중복 허용. 개인이면 대표자=이름 watch 동기화 |
대표자 representative | 법인만 노출 입력 | 법인만 노출 | 개인은 숨김(watch가 name으로 자동 세팅) |
타입 type | Select(개인/법인) | Select | 필수 |
등록번호 자동입력 포맷(autoRegNum)
- 개인: 생년월일 8자리 있으면
생년월일6 + 영숫자7, 6자리면앞6 + 영숫자7, 없으면영숫자6 + 영숫자7. - 법인:
영숫자6 + 영숫자7. - 영숫자(
alnum)는 첫 글자 소문자 영문 강제(숫자만 패턴 회피).
대표자 동기화 watch
watch([draft.type, draft.name]) → if 개인: draft.representative = draft.name. 개인 멤버는 대표자 입력 필드를 노출하지 않는 근거.
6. 리스트 IA — DynamicTableOrg.vue
- 제목(이름) 클릭 = 상세 시트 진입. 전용 "상세" 버튼 폐기(
CLAUDE.md리스트 IA). 이름 셀이<button @click="openRead()" command="show-modal" commandfor="sheet-read-member-art">. - 클릭 affordance:
hover:underline+cursor-pointer+table-hover(행 호버 하이라이트) +sticky-thead+ 이름 컬럼sticky-col. - 컬럼 노출(§1 UI 노출 순서): 타입(badge) → 이름 → 연락처(dropdown) → 차량/일원/사업자/현금영수증/가상계좌/자동이체/노트(요약 badge). 등록번호·대표자·생년월일·성별 컬럼은 주석으로 보존(노출 안 함). 관리코드는 목록 비노출(정책 준수).
- 데이터: 로컬
membersref(데모 3건). i18n 라벨은 KO 값 →legalTypeLabel()등으로 로케일 파생(값은 KO 유지).
7. 엔티티 속성 vs 연결 컬렉션 (CLAUDE.md §0-6)
- 엔티티 속성(기본·컨택·주소): 한 몸 — read↔edit 토글 + 통째 저장(또는 카드별).
useMemberForm의record/draft가 바인딩. - 연결 컬렉션(차량·일원·증빙〔사업자+현금영수증〕·페이〔가상계좌+자동이체〕·노트): 독립 생명주기 1:N. 탭 + disclosure + 자체 Data Tool Bar(생성/연결/해제/삭제) + 자체 상세 시트.
useMemberForm과 무관, read↔edit 토글과 분리. - 시트 폭
sheet-width-3xl(48rem) — 세로 폼 집중 폭(§0-5).
8. 확장 / 전파 포인트
- 행별 레코드 주입(❌예정):
openRead(member)인자 전달 +record행별 매핑. 현재 데모 싱글톤. - 실제 디렉터리/스토어:
DEMO()reactive 싱글톤 → API 백킹 스토어로 교체.record/draft계약은 유지. - 유효성 확장:
valid에 등록번호 포맷·연락처 정규식 등 추가 시useMemberFormcomputed에 집중(컴포넌트 ad-hoc 금지). - 연결 컬렉션 추가: 새 1:N은 같은 패턴(탭 + Data Tool Bar + 외부 도메인 시트 import). 속성 폼에 섞지 말 것.
- 다른 마스터데이터 리소스(vehicle/business 등) 목록 IA: 본 패턴(이름 클릭 진입 + table-hover + 상세버튼 폐기)을 동일 적용 — 본 문서가 레퍼런스.
- leysys-design SSOT: card/field/sheet/table 클래스는 전부 SSOT 카피본. DS 결함은 본 레포 즉석 패치 금지(§SSOT 강제 룰).