feat(cupang): 등록된 제품 수정 팝업

- 동작 열에 [수정] 버튼 → 제품명/제품코드/쿠팡상품코드 수정 팝업
- POST /cupang/api/products/{id}/edit (JSON), 제품코드 중복은 400
- 제품코드 변경 시 cupang_box_rules 의 product_code/스냅샷도 함께 이동
  (같은 코드 규칙이 이미 있으면 이동하지 않음). 출고 라인은 과거 기록이라 유지

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 12:00:33 +09:00
parent 9ddced268f
commit 7174edd702
3 changed files with 178 additions and 0 deletions
@@ -81,6 +81,8 @@
</td>
<td>
<div class="cpg-row-actions">
<button type="button" class="erp-btn erp-btn-outline cpg-prod-edit"
data-edit-id="{{ p.id }}">수정</button>
<form method="post" action="/cupang/products/{{ p.id }}/delete" class="cpg-inline-form"
onsubmit="return confirm('완전 삭제합니다. 계속할까요?');">
<button type="submit" class="erp-btn erp-btn-danger">삭제</button>
@@ -137,6 +139,90 @@
})();
</script>
<!-- 등록된 제품 수정 팝업 -->
<div class="cpg-modal" id="cpg-edit-dlg" hidden>
<div class="cpg-modal-back" data-edit-close></div>
<div class="cpg-modal-box" role="dialog" aria-modal="true" aria-labelledby="cpg-edit-title">
<h3 id="cpg-edit-title">제품 수정</h3>
<label class="erp-field"><span>제품명 *</span>
<input class="erp-input" type="text" id="cpg-edit-name" /></label>
<label class="erp-field"><span>제품코드 *</span>
<input class="erp-input" type="text" id="cpg-edit-code" /></label>
<label class="erp-field"><span>쿠팡상품코드</span>
<input class="erp-input" type="text" id="cpg-edit-cpcode" placeholder="비우면 지워집니다" /></label>
<p class="erp-muted" id="cpg-edit-msg" style="margin:0;font-size:12px;"></p>
<div class="cpg-dlg-actions erp-page-actions">
<button type="button" class="erp-btn erp-btn-outline" data-edit-close>취소</button>
<button type="button" class="erp-btn erp-btn-primary" id="cpg-edit-save">저장</button>
</div>
</div>
</div>
<script>
// 등록된 제품 수정 — [수정] 클릭 → 팝업에서 제품명/제품코드/쿠팡상품코드 변경.
(function () {
var tbody = document.getElementById("cpg-prod-tbody");
var dlg = document.getElementById("cpg-edit-dlg");
if (!tbody || !dlg) return;
var elName = document.getElementById("cpg-edit-name");
var elCode = document.getElementById("cpg-edit-code");
var elCp = document.getElementById("cpg-edit-cpcode");
var elMsg = document.getElementById("cpg-edit-msg");
var saveBtn = document.getElementById("cpg-edit-save");
var editingId = null;
function close() { dlg.hidden = true; editingId = null; saveBtn.disabled = false; }
tbody.addEventListener("click", function (e) {
var btn = e.target.closest(".cpg-prod-edit");
if (!btn) return;
var row = btn.closest("tr");
editingId = btn.getAttribute("data-edit-id");
elName.value = row.getAttribute("data-name") || "";
elCode.value = row.getAttribute("data-code") || "";
elCp.value = row.getAttribute("data-cpcode") || "";
elMsg.textContent = "";
dlg.hidden = false;
elName.focus();
});
Array.prototype.forEach.call(dlg.querySelectorAll("[data-edit-close]"), function (el) {
el.addEventListener("click", close);
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !dlg.hidden) close();
});
saveBtn.addEventListener("click", function () {
if (!editingId) return;
var body = {
product_name: (elName.value || "").trim(),
product_code: (elCode.value || "").trim(),
coupang_item_code: (elCp.value || "").trim()
};
if (!body.product_name || !body.product_code) {
elMsg.textContent = "제품명과 제품코드는 필수입니다."; return;
}
saveBtn.disabled = true;
elMsg.textContent = "저장 중…";
fetch("/cupang/api/products/" + editingId + "/edit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
}).then(function (r) {
return r.json().then(function (d) {
if (!r.ok) throw new Error((d && d.detail) || "저장 실패");
return d;
});
}).then(function () { location.reload(); })
.catch(function (err) {
saveBtn.disabled = false;
elMsg.textContent = err.message || "저장 실패";
});
});
})();
</script>
<script>
// 상태 배지 클릭 → 활성/비활성 토글 (페이지 새로고침 없음).
(function () {