feat(cafe24): 옵션값+품목 통합 목록, 드래그 순서, 옵션값 불러오기

- 옵션 1개 상품은 한 행에 썸네일·옵션값 이름·품목코드(복사)·자체코드·
  추가금액·진열·판매를 두고 「저장」 한 번으로 옵션 PUT → 품목 PUT.
- 옵션 없는 상품: 행 추가/삭제 후 저장 → 옵션 생성 → 카페24가 부여한
  품목코드를 재시도 조회(products.wait_for_variants)로 받아 자체코드·
  추가금액·썸네일까지 이어서 반영.
- 순서 드래그는 품목 display_order 로 저장(옵션값 재배열 PUT 은 위치 짝맞춤
  때문에 품목코드↔이름이 뒤바뀔 수 있어 사용하지 않음).
- 「옵션값 불러오기」: 탭/공백 구분 텍스트(이름·판매가·추가금액·자체코드)
  붙여넣기 → 행 자동 채움. 기존 옵션은 이름으로 짝지어 코드/금액만.
- 옵션 2개 이상(조합) 상품은 기존 2열 화면 유지.
- 모달 JS 를 app/static/cafe24-options.js 로 분리. 유닛테스트 90 통과,
  통합 하네스 통과, 헤드리스 크롬 렌더 확인.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 23:25:52 +09:00
parent be0d44ded6
commit 8125a59366
8 changed files with 1002 additions and 360 deletions
+109
View File
@@ -1288,6 +1288,115 @@ body.cf24-modal-open {
color: var(--color-success-green, #10c22b);
}
/* ── 통합 목록(옵션값 1 : 품목 1) — 모달 전체 폭 사용, 표만 스크롤 ── */
.cf24-u-wrap {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
gap: var(--sp-10, 10px);
}
.cf24-u-head {
display: flex;
align-items: flex-end;
gap: var(--sp-10, 10px);
flex: 0 0 auto;
}
.cf24-u-optname { flex: 0 1 320px; }
.cf24-u-head-actions {
margin-left: auto;
display: flex;
gap: var(--sp-8, 8px);
align-items: flex-end;
}
.cf24-u-tablewrap {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
border-top: 1px solid var(--color-subtle-ash, #e5e5e5);
border-bottom: 1px solid var(--color-subtle-ash, #e5e5e5);
}
.cf24-u-note { flex: 0 0 auto; margin: 0; }
.cf24-u-actions { flex: 0 0 auto; }
.cf24-u-table .cf24-u-drag { width: 26px; text-align: center; }
.cf24-u-table .cf24-u-thumb { width: 118px; }
.cf24-u-table .cf24-u-del { width: 30px; text-align: center; }
.cf24-u-table .cf24-u-name input[type="text"] { font-weight: 500; }
.cf24-u-thumb .cf24-opt-thumb-slot { display: inline-block; vertical-align: middle; margin-right: 6px; }
.cf24-u-thumb .cf24-opt-thumb,
.cf24-u-thumb .cf24-opt-thumb-empty { width: 40px; height: 40px; }
.cf24-u-pick { display: inline-flex; vertical-align: middle; }
.cf24-u-pick .erp-btn { padding: 3px 8px; font-size: 12px; }
.cf24-drag-handle {
cursor: grab;
color: var(--color-midtone-gray, #737373);
font-size: 14px;
user-select: none;
padding: 4px 2px;
letter-spacing: -1px;
}
.cf24-drag-handle:active { cursor: grabbing; }
.cf24-drag-handle.is-dirty { color: #f0b429; }
tr.cf24-u-row.is-dragging {
opacity: 0.45;
background: #fffbea;
}
.cf24-u-remove {
border: 0;
background: transparent;
color: var(--color-midtone-gray, #737373);
cursor: pointer;
font-size: 13px;
padding: 4px;
border-radius: var(--r-sm, 4px);
}
.cf24-u-remove:hover {
background: #fdefec;
color: var(--color-callout-red, #c22b10);
}
.cf24-btn-danger {
color: var(--color-callout-red, #c22b10);
border-color: var(--color-callout-red, #c22b10);
}
/* 옵션값 불러오기 대화상자 — 옵션 모달 위에 한 겹 더 */
.cf24-import { z-index: 1100; }
.cf24-import-box {
width: min(720px, 94vw);
height: auto;
max-height: 80vh;
}
.cf24-import-text {
width: 100%;
box-sizing: border-box;
min-height: 220px;
padding: var(--sp-10, 10px);
border: 1px solid var(--color-subtle-ash, #e5e5e5);
border-radius: var(--r-md, 6px);
font-family: var(--font-geist-mono, ui-monospace, monospace);
font-size: 12px;
line-height: 1.6;
white-space: pre;
resize: vertical;
}
.cf24-import-preview { margin: 6px 0 10px; min-height: 16px; }
.cf24-v-img {
width: 32px;
height: 32px;