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 = '";
// 품목 표 (모달 오른쪽 열) — 넓은 창이라 한 줄에 다 들어간다.
- 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`)이
있으면 닫을 때 확인창.