diff --git a/app/modules/cafe24/templates/cafe24/products.html b/app/modules/cafe24/templates/cafe24/products.html index 4ce6cc3..00e5216 100644 --- a/app/modules/cafe24/templates/cafe24/products.html +++ b/app/modules/cafe24/templates/cafe24/products.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + {% endblock %} {% block content %} @@ -1007,7 +1007,7 @@ } // 옵션 정의 편집 (모달 왼쪽 열) - var html = '
' + + var html = '
' + '

옵션 정의

' + '
'; opt.options.forEach(function (o, gi) { @@ -1041,7 +1041,7 @@ "
"; // 품목 표 (모달 오른쪽 열) — 넓은 창이라 한 줄에 다 들어간다. - html += '

품목 (' + variants.length + ')

'; + html += '

품목 (' + variants.length + ')

'; if (!variants.length) { html += '

품목이 아직 조회되지 않았습니다. 잠시 뒤 창을 닫았다 다시 여세요.

'; } else { @@ -1054,8 +1054,8 @@ var amount = String(parseInt(v.additional_amount || "0", 10) || 0); html += '' + '' + (v.image ? '' : '') + "" + - '' + escHtml(label) + "" + - '' + escHtml(v.variant_code) + "" + + '' + escHtml(label) + "" + + '" + '' + '' + '" + @@ -1147,6 +1147,34 @@ if (!saveBtn) return; var wrap = body.querySelector(".cf24-variants-wrap"); trackDirty(wrap); + // 품목코드 클릭 = 클립보드 복사 (HTTPS 가 아니면 execCommand 로 대체) + wrap.querySelectorAll(".cf24-v-copy").forEach(function (btn) { + btn.addEventListener("click", function () { + var text = btn.dataset.copyText || btn.textContent; + var done = function () { + var old = btn.textContent; + btn.textContent = "복사됨"; + btn.classList.add("is-copied"); + setTimeout(function () { btn.textContent = old; btn.classList.remove("is-copied"); }, 1200); + }; + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(text).then(done, function () { fallbackCopy(text, done); }); + } else { + fallbackCopy(text, done); + } + }); + }); + function fallbackCopy(text, done) { + var ta = document.createElement("textarea"); + ta.value = text; + ta.setAttribute("readonly", ""); + ta.style.position = "fixed"; + ta.style.opacity = "0"; + document.body.appendChild(ta); + ta.select(); + try { if (document.execCommand("copy")) done(); } catch (e) { /* 복사 불가 — 조용히 무시 */ } + document.body.removeChild(ta); + } wrap.querySelectorAll(".cf24-v-toggle").forEach(function (btn) { btn.addEventListener("click", function () { var on = btn.dataset.on !== "1"; diff --git a/app/static/cafe24.css b/app/static/cafe24.css index f1d2c75..d29853a 100644 --- a/app/static/cafe24.css +++ b/app/static/cafe24.css @@ -1130,8 +1130,10 @@ body.cf24-modal-open { .cf24-modal-box { display: flex; flex-direction: column; - width: min(1180px, 96vw); - max-height: 90vh; + width: min(1280px, 96vw); + /* max-height 가 아니라 height 고정 — 두 열이 창 바닥까지 늘어나고 각 열 안에서 + 스크롤한다(품목이 많을 때 표만 스크롤, 저장 버튼은 항상 보임). */ + height: 90vh; background: var(--color-canvas-white, #fff); border-radius: var(--r-xl, 14px); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); @@ -1176,27 +1178,52 @@ body.cf24-modal-open { } .cf24-modal-body { - padding: 16px 18px 20px; - overflow: auto; + padding: 16px 18px 18px; flex: 1 1 auto; min-height: 0; + display: flex; + flex-direction: column; } +/* 옵션 정의(왼쪽, 좁게) | 품목 표(오른쪽, 남는 폭 전부). 두 열 모두 창 바닥까지. */ .cf24-modal-grid { + flex: 1 1 auto; + min-height: 0; display: grid; - grid-template-columns: 400px minmax(0, 1fr); + grid-template-columns: 340px minmax(0, 1fr); + grid-template-rows: minmax(0, 1fr); gap: 24px; - align-items: start; + align-items: stretch; } @media (max-width: 960px) { .cf24-modal-grid { grid-template-columns: minmax(0, 1fr); + grid-template-rows: auto; } } .cf24-modal-col { min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; +} + +/* 왼쪽 열은 통째로 스크롤, 오른쪽 열은 표만 스크롤(제목·저장 버튼 고정) */ +.cf24-modal-col-options { + overflow: auto; +} + +.cf24-modal-col-variants > .cf24-variants-wrap { + flex: 1 1 auto; + min-height: 0; + overflow: auto; + border-bottom: 1px solid var(--color-subtle-ash, #e5e5e5); +} + +.cf24-modal-col-variants > .cf24-side-actions { + flex: 0 0 auto; } /* 옵션이 없는 상품의 생성 폼 — 넓은 모달에서 끝까지 늘어나지 않게 */ @@ -1229,18 +1256,37 @@ body.cf24-modal-open { background: var(--color-canvas-white, #fff); } -.cf24-variants-wrap { - max-height: 62vh; - overflow: auto; +/* 열 폭 — 옵션 이름이 한 줄에 들어가도록 나머지를 좁게 잡는다 + (1280px 모달: 340 옵션 + 24 간격 → 표 약 880px, 이름 칸 약 420px). */ +.cf24-v-imgcol { width: 40px; } +.cf24-v-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 0; } +.cf24-v-syscode { width: 104px; } +.cf24-v-code { width: 132px; } +.cf24-v-amount { width: 90px; } +.cf24-v-amount input { text-align: right; } +.cf24-v-flag { width: 56px; text-align: center; } + +/* 품목코드 — 클릭하면 클립보드로. 눌리는 것임을 커서·밑줄로 알린다. */ +.cf24-v-copy { + font-family: var(--font-geist-mono, ui-monospace, monospace); + font-size: 11px; + color: var(--color-midtone-gray, #737373); + background: transparent; + border: 0; + padding: 2px 4px; + border-radius: var(--r-sm, 4px); + cursor: copy; + white-space: nowrap; } -.cf24-v-imgcol { width: 40px; } -.cf24-v-syscode { width: 110px; } -.cf24-v-syscode code { font-size: 11px; color: var(--color-midtone-gray, #737373); } -.cf24-v-code { width: 150px; } -.cf24-v-amount { width: 96px; } -.cf24-v-amount input { text-align: right; } -.cf24-v-flag { width: 58px; text-align: center; } +.cf24-v-copy:hover { + background: var(--color-ghost-gray, #f2f2f2); + color: var(--color-rich-black, #0a0a0a); +} + +.cf24-v-copy.is-copied { + color: var(--color-success-green, #10c22b); +} .cf24-v-img { width: 32px; @@ -1258,7 +1304,6 @@ body.cf24-modal-open { .cf24-v-name { font-weight: 500; - word-break: break-word; } .cf24-variants input[type="text"] { @@ -1292,11 +1337,6 @@ body.cf24-modal-open { box-shadow: 0 0 0 2px #f0b429; } -.cf24-variants-wrap { - max-height: 320px; - overflow: auto; -} - .cf24-opt-danger { display: flex; justify-content: flex-end; diff --git a/docs/CAFE24_MODULE.md b/docs/CAFE24_MODULE.md index 3425779..9b60f3d 100644 --- a/docs/CAFE24_MODULE.md +++ b/docs/CAFE24_MODULE.md @@ -149,7 +149,7 @@ Admin API(OAuth)로는 스킨 파일을 읽거나 쓸 방법이 없다(`config.p 상품 클릭 시 `/products/{no}/pane` 이 편집기 조각과 패널 조각을 **한 응답**(`_panes.html`, `[data-pane=editor]`/`[data-pane=side]`)으로 돌려주고 JS 가 각각 끼운다 — 카페24 상품 조회는 1회. 옵션/품목은 패널의 「팝업에서 관리」 버튼으로 **모달**(`#cf24-options-modal`, - 최대 1180px — 왼쪽 옵션 정의 / 오른쪽 품목 표)을 열 때 JSON 으로 따로 불러온다(호출 2회 + 최대 1280px·높이 90vh — 왼쪽 옵션 정의(340px) / 오른쪽 품목 표, 두 열 모두 창 바닥까지 각자 스크롤. 품목코드 클릭 = 클립보드 복사)을 열 때 JSON 으로 따로 불러온다(호출 2회 절약). 380px 패널은 품목 표를 담기에 좁아(실측) 별도 창으로 뺐다. 모달은 페이지에 하나만 두고(패널 조각 교체와 무관) 다른 상품을 고르면 닫힌다. 저장 안 한 변경(`.is-dirty`)이 있으면 닫을 때 확인창.