다크모드
일괄 업로드 다이얼로그 — FE 메인테이너 참고
펀치리스트 Critical C-6 (최소 정직 배선). 정본 plan:
docs/superpowers/plans/2026-07-10-upload-dialog-honest-wire.md.
1. 배경
전 앱 ~75개 SheetCreateUpload*Art.vue는 파일 입력 + "서식 다운로드" 폼은 있었으나 footer(취소/업로드하기)가 통째 주석 처리 + <script setup> 빔인 복붙 패밀리였다 — 힌트 텍스트는 "[업로드하기] 클릭"인데 그 버튼이 없어 제출 불가능한 dead-end. 오너 결정 "최소 정직 배선": 실 CSV/xlsx 파싱·행 생성은 구현하지 않되(follow-up), 파일 선택 검증 + 제출 이벤트 발행 + 닫기까지는 정직하게 동작하도록 배선한다.
2. SheetCreateUploadArt (공유 컴포넌트)
src/components/_shared/SheetCreateUploadArt.vue
Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
id | String (required) | — | dialog id. 부모의 command="show-modal" commandfor 트리거 및 내부 close 버튼의 :commandfor="id"가 이 값을 공유한다. |
title | String | '' | sheet-header 제목 |
templateLabel | String | '서식 다운로드' | 서식 다운로드 버튼 라벨 |
description | String | '파일을 선택하고 [업로드하기] 버튼을 클릭하세요.' | 파일 입력 아래 안내 텍스트 |
Emit
| 이벤트 | 페이로드 | 시점 |
|---|---|---|
upload | File (선택된 원본 파일 객체, 파싱 없음) | [업로드하기] 클릭 시(파일 선택된 경우만 — 버튼이 disabled라 미선택 시 클릭 자체 불가) |
내부 상태 / 동작
selectedFileref —<input type="file" @change="onFileChange">로 갱신.canUploadcomputed —!!selectedFile. 업로드하기 버튼의:disabled="!canUpload"게이트.submit():canUpload확인 →emit('upload', selectedFile)→closeDialog()(dialog.close(), jsdom 미구현 typeof 가드) →selectedFile/file input 리셋.cancel():selectedFile리셋 + file input 리셋 (close는command="close"네이티브 버튼 동작에 위임).defineExpose({ selectedFile, canUpload, onFileChange, cancel, submit })— 테스트 훅.
DS 사용
기존 마크업(header/close/file input/서식 다운로드 버튼) 보존, 신규 DS 클래스 0. sheet-footer는 button-neutral-subtle(취소) + button-primary-bold(업로드하기, disabled 게이트) — CLAUDE.md 버튼 스타일 매트릭스 준수.
3. 얇은 wrapper 패턴 (Task 2, 5곳 적용)
각 도메인 wrapper는 기존 id·title을 그대로 prop으로 전달하며 공유 컴포넌트를 렌더하는 얇은 래퍼로 교체됐다:
vue
<script setup>
import SheetCreateUploadArt from "@/components/_shared/SheetCreateUploadArt.vue";
</script>
<template>
<SheetCreateUploadArt id="기존-id-그대로" title="정정된 제목" />
</template>| Wrapper 파일 | id (보존) | 트리거 상태 |
|---|---|---|
tax-invoice/tax-invoice/unit/general/overlays/SheetCreateUploadUnitArt.vue | sheet-create-upload-unit-art | ✅ 살아있음 — tax-invoice/issue/single/general/blocks/DataToolBarOrg.vue가 command="show-modal" commandfor="sheet-create-upload-unit-art"로 연다 |
administration/billing/workspace/property/overlays/SheetCreateUploadPropertyArt.vue (루트) | sheet-create-upload-property-art | ✅ 살아있음 — administration/billing/workspace/property/blocks/DataToolBarOrg.vue가 연다 |
administration/billing/workspace/property/console/general/overlays/SheetCreateUploadPropertyArt.vue | sheet-create-upload-property-art | ❌ orphan — 이 콘솔의 DataToolBarOrg에 여는 트리거 없음 |
administration/billing/workspace/property/console/account/overlays/SheetCreateUploadSiteArt.vue | sheet-create-upload-site-art | ❌ orphan — 동일 |
administration/billing/workspace/property/console/plan/overlays/SheetCreateUploadBuildingArt.vue | sheet-create-upload-building-art | ❌ orphan — 이 모듈(billing/…/console/plan)의 DataToolBarOrg.vue는 sheet-create-building-art(생성 시트)만 열며, 업로드 시트를 여는 트리거는 없다 |
정정된 제목: 5개 wrapper 모두 원본이 복붙 버그로 "유닛 업로드"였던 것을, 각 엔티티에 맞게 정정했다(유닛=그대로 정확·프로퍼티/현장/동=정정). 자세한 근거는 각 wrapper 파일 상단 주석 참고.
⚠️ id 중복 주의: 이 앱 전역에서 dialog id 문자열이 여러 파일에 재사용되는 사례가 이미 존재한다(예:
sheet-create-upload-building-art는administration/organization/workspace/property/console/building/overlays/SheetCreateUploadBuildingArt.vue에도 존재하며 그쪽은 자체DataToolBarOrg.vue에서 살아있는 트리거를 갖는다 — 이번에 건드린 billing 모듈의 동명 id와는 별개 파일·별개 라우트). 이번 슬라이스는 이 pre-existing 중복을 만들지도 해소하지도 않았다 — id는 각 wrapper 내에서만 보존.
4. Orphan 다이얼로그 (3곳) — follow-up 필요
console/general(프로퍼티)·console/account(현장)·console/plan(동) 3개 wrapper는 공유 컴포넌트로 정직하게 배선됐지만, 여는 버튼이 없어 현재 화면에서 도달 불가능하다. 이는 이번 스왑이 만든 문제가 아니라 스왑 이전부터 있던 gap이다(원본도 각 모듈 DataToolBarOrg에 트리거가 없었다).
follow-up: 각 콘솔의 blocks/DataToolBarOrg.vue에 <button command="show-modal" commandfor="{해당 id}">업로드</button>를 추가하면 즉시 도달 가능해진다(공유 컴포넌트·wrapper 쪽 변경 불필요).
5. ~75개 패밀리 — 기계적 스왑 follow-up
SheetCreateUpload*Art.vue 명명 패턴을 가진 파일이 앱 전역에 ~75개 존재하며(이번에 적용한 5개 제외), 전부 동일한 dead-end 패턴(footer 주석·script 빔)을 공유한다. 도메인: HR(human-capital/* — 이번 작업 미접촉, 건드리지 말 것)·facility·crm·contract·master-data·billing/hospitality 등.
예시 목록(전량 아님, 대표):
human-capital/compensation/payroll/general/overlays/SheetCreateUploadEmployeeArt.vue외 human-capital 4개facility/asset/*,facility/maintenance/*,facility/status/*,facility/task/*각 하위 다수contract/integration/*,contract/resource/*각 하위 다수crm/resource/note/*3개master-data/resource/*다수(counterparty·associate·business·cash-receipt·direct-debit·member·unit·vehicle·virtual-bank-account 등)accounting/accounting-mgmt/financial-accounting/basic-setting/business-partner/general/overlays/SheetCreateUploadBusinessPartnerArt.vuebilling/hospitality/master/room/general/overlays/SheetCreateUploadRoomArt.vue
follow-up 작업 방식: 각 파일을 이번 5개와 동일한 얇은 wrapper 패턴(<SheetCreateUploadArt :id=".." :title=".." />)으로 기계적 치환 — 로직 변경 없음, id/title만 원본에서 이관. 각 도메인이 판매 스코프에 들어올 때 그 도메인 담당 슬라이스에서 처리 권장(한 번에 75개를 몰아서 하지 않는다 — 회귀 범위 관리).
6. 테스트 위치
| 파일 | 대상 |
|---|---|
src/components/_shared/__tests__/SheetCreateUploadArt.spec.js | 공유 컴포넌트 — 파일 미선택 disabled·선택 후 활성·upload emit+close·cancel 리셋·id prop 렌더 |
src/components/tax-invoice/tax-invoice/unit/general/overlays/__tests__/SheetCreateUploadUnitArt.spec.js | wrapper — id/commandfor 보존 |
src/components/administration/billing/workspace/property/overlays/__tests__/SheetCreateUploadPropertyArt.spec.js | wrapper(루트) — id/commandfor 보존 |
src/components/administration/billing/workspace/property/console/general/overlays/__tests__/SheetCreateUploadPropertyArt.spec.js | wrapper(콘솔) — id 보존(orphan) |
src/components/administration/billing/workspace/property/console/account/overlays/__tests__/SheetCreateUploadSiteArt.spec.js | wrapper(콘솔) — id 보존(orphan) |
src/components/administration/billing/workspace/property/console/plan/overlays/__tests__/SheetCreateUploadBuildingArt.spec.js | wrapper(콘솔) — id 보존(orphan) |
7. 확장 포인트
- 실 파싱 연결:
@upload="handleFile"리스너를 각 wrapper(또는 부모 MainOrg)에 추가하고 BE 핸드오프(§3 계약 스케치) 구현. - orphan 트리거 추가: §4 참고, 컴포넌트 변경 없이 DataToolBarOrg 버튼만 추가.
- 나머지 ~70개 스왑: §5 참고, 도메인별 follow-up 슬라이스로 분할 권장.