feat(cupang): 센터 알림을 가운데 팝업으로, 그룹 헤더/목록 구분 강화

브라우저 기본 alert/confirm 은 상단에 붙고 URL 이 그대로 보여 어수선했다.
기존 .cpg-modal 스타일을 재사용해 화면 가운데 팝업으로 바꾸고, 삭제 확인도
같은 팝업(빨간 확인 버튼)으로 통일했다. ESC·배경 클릭으로 닫힘.

목록은 헤더와 항목이 같은 평면에 있어 구분이 약했다. 그룹을 테두리 있는
패널로 만들고 헤더에 회색 바 + 카운트 배지 + 접기 캐럿을 뒀다. 센터명은
고정폭 글꼴로 정렬감을 주고, 비활성은 취소선으로 표시.
This commit is contained in:
2026-08-31 12:12:18 +09:00
parent 21ee27ac40
commit 74e33efa55
8 changed files with 188 additions and 64 deletions
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831c" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831d" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831c" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831d" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
+100 -15
View File
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831c" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831d" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
@@ -27,14 +27,16 @@
<span class="erp-muted">지역별로 묶어 표시. 이름 변경은 지원하지 않음 — 삭제 후 새로 등록.</span> <span class="erp-muted">지역별로 묶어 표시. 이름 변경은 지원하지 않음 — 삭제 후 새로 등록.</span>
</div> </div>
<!-- 목록: 지역 그룹 카드, 위→아래로 읽고 열로 흐름 --> <!-- 목록: 지역 그룹 패널, 위→아래로 읽고 열로 흐름 -->
<div class="cpg-center-cols" id="cpgCenterGrid"> <div class="cpg-center-cols" id="cpgCenterGrid">
{% for g in center_groups %} {% for g in center_groups %}
<div class="cpg-center-group" data-group="{{ g.name|lower }}"> <section class="cpg-center-group" data-group="{{ g.name|lower }}">
<div class="cpg-center-gtitle"> <button type="button" class="cpg-center-gtitle" aria-expanded="true">
<span class="cpg-center-caret" aria-hidden="true"></span>
<span class="cpg-center-gname">{{ g.name }}</span> <span class="cpg-center-gname">{{ g.name }}</span>
<span class="cpg-center-gcount">{{ g.count }}</span> <span class="cpg-center-gcount">{{ g.count }}</span>
</div> </button>
<div class="cpg-center-glist">
{% for c in g["items"] %} {% for c in g["items"] %}
<div class="cpg-center-row {% if not c.active %}is-inactive{% endif %}" data-name="{{ c.name|lower }}"> <div class="cpg-center-row {% if not c.active %}is-inactive{% endif %}" data-name="{{ c.name|lower }}">
<span class="cpg-center-dot" title="{% if c.active %}활성{% else %}비활성{% endif %}"></span> <span class="cpg-center-dot" title="{% if c.active %}활성{% else %}비활성{% endif %}"></span>
@@ -45,14 +47,13 @@
<button type="submit" class="cpg-icon-btn" <button type="submit" class="cpg-icon-btn"
title="{% if c.active %}비활성으로 전환{% else %}활성으로 전환{% endif %}">{% if c.active %}비활성{% else %}활성{% endif %}</button> title="{% if c.active %}비활성으로 전환{% else %}활성으로 전환{% endif %}">{% if c.active %}비활성{% else %}활성{% endif %}</button>
</form> </form>
<form method="post" action="/cupang/centers/{{ c.id }}/delete" class="cpg-inline-form" <button type="button" class="cpg-icon-btn is-danger" title="삭제"
onsubmit="return confirm('{{ c.name }} 센터를 삭제할까요?');"> data-del-url="/cupang/centers/{{ c.id }}/delete" data-del-name="{{ c.name }}">삭제</button>
<button type="submit" class="cpg-icon-btn is-danger" title="삭제">삭제</button>
</form>
</span> </span>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</section>
{% endfor %} {% endfor %}
</div> </div>
@@ -61,20 +62,103 @@
</div> </div>
</section> </section>
<!-- 화면 가운데 알림/확인 팝업 -->
<div class="cpg-modal" id="cpgCenterDlg" hidden>
<div class="cpg-modal-back" data-dlg-close></div>
<div class="cpg-modal-box cpg-center-dlg" role="dialog" aria-modal="true" aria-labelledby="cpgCenterDlgTitle">
<h3 id="cpgCenterDlgTitle">알림</h3>
<p class="cpg-center-dlgmsg" id="cpgCenterDlgMsg"></p>
<div class="cpg-dlg-actions cpg-center-dlgact">
<button type="button" class="erp-btn erp-btn-outline" id="cpgCenterDlgCancel" data-dlg-close hidden>취소</button>
<button type="button" class="erp-btn erp-btn-primary" id="cpgCenterDlgOk">확인</button>
</div>
</div>
</div>
<form method="post" id="cpgCenterDelForm" hidden></form>
<script> <script>
(function () { (function () {
// 추가 결과 알림 (중복 등록 방지) // ── 가운데 팝업 (alert/confirm 대체) ──
var dlg = document.getElementById('cpgCenterDlg');
var dlgTitle = document.getElementById('cpgCenterDlgTitle');
var dlgMsg = document.getElementById('cpgCenterDlgMsg');
var dlgOk = document.getElementById('cpgCenterDlgOk');
var dlgCancel = document.getElementById('cpgCenterDlgCancel');
var onConfirm = null;
var lastFocus = null;
function closeDlg() {
dlg.hidden = true;
onConfirm = null;
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
function openDlg(opts) {
lastFocus = document.activeElement;
dlgTitle.textContent = opts.title || '알림';
dlgMsg.textContent = opts.message || '';
onConfirm = opts.onConfirm || null;
dlgCancel.hidden = !opts.onConfirm;
dlgOk.textContent = opts.okText || '확인';
dlgOk.classList.toggle('erp-btn-danger', !!opts.danger);
dlgOk.classList.toggle('erp-btn-primary', !opts.danger);
dlg.hidden = false;
dlgOk.focus();
}
dlgOk.addEventListener('click', function () {
var fn = onConfirm;
closeDlg();
if (fn) fn();
});
Array.prototype.forEach.call(dlg.querySelectorAll('[data-dlg-close]'), function (el) {
el.addEventListener('click', closeDlg);
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && !dlg.hidden) closeDlg();
});
// ── 추가 결과 알림 (중복 등록 차단) ──
var flash = {{ flash|tojson }}; var flash = {{ flash|tojson }};
var fname = {{ flash_name|tojson }}; var fname = {{ flash_name|tojson }};
if (flash === 'dup') { if (flash === 'dup') {
alert('"' + fname + '" 센터는 이미 등록되어 있습니다.'); openDlg({ title: '이미 등록된 센터', message: '"' + fname + '" 센터는 이미 목록에 있습니다.' });
} else if (flash === 'dup_inactive') { } else if (flash === 'dup_inactive') {
alert('"' + fname + '" 센터는 이미 등록되어 있습니다. (현재 비활성 상태 — 목록에서 활성으로 전환하세요.)'); openDlg({
} title: '이미 등록된 센터',
if (flash) { message: '"' + fname + '" 센터는 이미 목록에 있습니다. 지금은 비활성 상태이니 목록에서 [활성] 을 눌러 사용하세요.'
history.replaceState(null, '', '/cupang/centers'); });
} }
if (flash) history.replaceState(null, '', '/cupang/centers');
// ── 삭제 확인 ──
var delForm = document.getElementById('cpgCenterDelForm');
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-del-url]');
if (!btn) return;
openDlg({
title: '센터 삭제',
message: '"' + btn.dataset.delName + '" 센터를 삭제할까요?',
okText: '삭제',
danger: true,
onConfirm: function () {
delForm.action = btn.dataset.delUrl;
delForm.submit();
}
});
});
// ── 그룹 접기 ──
document.addEventListener('click', function (e) {
var head = e.target.closest('.cpg-center-gtitle');
if (!head) return;
var group = head.closest('.cpg-center-group');
var collapsed = group.classList.toggle('is-collapsed');
head.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
});
// ── 검색 ──
var box = document.getElementById('cpgCenterSearch'); var box = document.getElementById('cpgCenterSearch');
var grid = document.getElementById('cpgCenterGrid'); var grid = document.getElementById('cpgCenterGrid');
var count = document.getElementById('cpgCenterCount'); var count = document.getElementById('cpgCenterCount');
@@ -92,6 +176,7 @@
if (hit) shown += 1; if (hit) shown += 1;
}); });
g.style.display = shown ? '' : 'none'; g.style.display = shown ? '' : 'none';
if (q && shown) g.classList.remove('is-collapsed');
n += shown; n += shown;
}); });
if (count) count.textContent = '(' + n + ')'; if (count) count.textContent = '(' + n + ')';
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831c" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831d" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831c" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831d" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831c" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831d" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
@@ -1,6 +1,6 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831c" />{% endblock %} {% block head_extra %}<link rel="stylesheet" href="/static/cupang.css?v=20260831d" />{% endblock %}
{% block content %} {% block content %}
<section class="cpg"> <section class="cpg">
+69 -30
View File
@@ -169,7 +169,7 @@
.cpg-inline-form { display: inline-flex; gap: 6px; align-items: center; margin: 0; } .cpg-inline-form { display: inline-flex; gap: 6px; align-items: center; margin: 0; }
.cpg-row-actions { display: flex; gap: 6px; flex-wrap: wrap; } .cpg-row-actions { display: flex; gap: 6px; flex-wrap: wrap; }
/* ── 입고센터 관리 (상단: 추가·검색 / 아래: 지역 그룹 목록) ── */ /* ── 입고센터 관리 (상단: 추가·검색 / 아래: 지역 그룹 패널) ── */
.cpg-center-card { padding: 16px; } .cpg-center-card { padding: 16px; }
.cpg-center-bar { .cpg-center-bar {
@@ -184,46 +184,80 @@
/* 멀티컬럼: 한 열을 위→아래로 읽고, 다 차면 다음 열로 */ /* 멀티컬럼: 한 열을 위→아래로 읽고, 다 차면 다음 열로 */
.cpg-center-cols { .cpg-center-cols {
columns: 4 220px; columns: 4 230px;
column-gap: 20px; column-gap: 16px;
}
.cpg-center-group {
break-inside: avoid; page-break-inside: avoid;
margin: 0 0 16px;
}
.cpg-center-gtitle {
display: flex; align-items: center; gap: 6px;
padding: 0 4px 4px;
border-bottom: 2px solid var(--color-rich-black);
margin-bottom: 4px;
}
.cpg-center-gname { font-size: 13px; font-weight: 700; letter-spacing: .02em; }
.cpg-center-gcount {
font-size: 11px; font-weight: 600; line-height: 1;
padding: 2px 6px; border-radius: 999px;
background: var(--color-subtle-ash); color: var(--color-midtone-gray);
} }
/* 그룹 = 헤더 바 + 목록 패널 */
.cpg-center-group {
break-inside: avoid; page-break-inside: avoid;
margin: 0 0 12px;
border: 1px solid var(--color-subtle-ash);
border-radius: 8px;
overflow: hidden;
background: var(--color-canvas-white);
}
.cpg-center-gtitle {
display: flex; align-items: center; gap: 6px; width: 100%;
padding: 7px 10px;
border: 0; border-bottom: 1px solid var(--color-subtle-ash);
background: var(--color-ghost-gray);
cursor: pointer; text-align: left;
font-family: inherit;
}
.cpg-center-gtitle:hover { background: var(--color-subtle-ash); }
.cpg-center-gname {
flex: 1 1 auto; min-width: 0;
font-size: 12px; font-weight: 700; letter-spacing: .04em;
color: var(--color-rich-black);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cpg-center-gcount {
flex: 0 0 auto;
font-family: var(--font-geist-mono);
font-size: 11px; font-weight: 600; line-height: 1;
padding: 3px 6px; border-radius: 4px;
background: var(--color-canvas-white);
border: 1px solid var(--color-subtle-ash);
color: var(--color-midtone-gray);
}
/* 접기 캐럿 */
.cpg-center-caret {
flex: 0 0 auto; width: 0; height: 0;
border-left: 4px solid var(--color-midtone-gray);
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
transform: rotate(90deg);
transition: transform .12s ease;
}
.cpg-center-group.is-collapsed .cpg-center-caret { transform: rotate(0deg); }
.cpg-center-group.is-collapsed .cpg-center-glist { display: none; }
.cpg-center-group.is-collapsed .cpg-center-gtitle { border-bottom: 0; }
.cpg-center-glist { padding: 2px 0; }
.cpg-center-row { .cpg-center-row {
display: flex; align-items: center; gap: 7px; display: flex; align-items: center; gap: 8px;
padding: 5px 4px; padding: 5px 10px;
border-bottom: 1px solid var(--color-subtle-ash);
min-width: 0; min-width: 0;
} }
.cpg-center-row:last-child { border-bottom: none; } .cpg-center-row + .cpg-center-row { border-top: 1px solid var(--color-ghost-gray); }
.cpg-center-row:hover { background: var(--color-ghost-gray); }
.cpg-center-dot { .cpg-center-dot {
flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; width: 6px; height: 6px; border-radius: 50%;
background: var(--color-success-green, #10c22b); background: var(--color-success-green);
} }
.cpg-center-row.is-inactive { opacity: .5; } .cpg-center-row.is-inactive .cpg-center-name { color: var(--color-midtone-gray); text-decoration: line-through; }
.cpg-center-row.is-inactive .cpg-center-dot { background: var(--color-subtle-ash); } .cpg-center-row.is-inactive .cpg-center-dot { background: var(--color-subtle-ash); }
.cpg-center-name { .cpg-center-name {
flex: 1 1 auto; min-width: 0; flex: 1 1 auto; min-width: 0;
font-size: 14px; font-weight: 500; font-family: var(--font-geist-mono);
font-size: 13px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
} }
/* 행 액션: 평소엔 흐리게, hover/focus 시 또렷하게 */ /* 행 액션: hover/focus 시에만 노출 */
.cpg-center-acts { .cpg-center-acts {
display: inline-flex; gap: 2px; flex: 0 0 auto; display: inline-flex; gap: 2px; flex: 0 0 auto;
opacity: 0; transition: opacity .12s ease; opacity: 0; transition: opacity .12s ease;
@@ -235,14 +269,19 @@
} }
.cpg-icon-btn { .cpg-icon-btn {
border: 0; background: none; cursor: pointer; border: 0; background: none; cursor: pointer;
font-size: 11px; line-height: 1; padding: 3px 5px; border-radius: 6px; font-family: inherit;
font-size: 11px; line-height: 1; padding: 3px 5px; border-radius: 4px;
color: var(--color-midtone-gray); color: var(--color-midtone-gray);
} }
.cpg-icon-btn:hover { background: var(--color-subtle-ash); color: var(--color-rich-black); } .cpg-icon-btn:hover { background: var(--color-subtle-ash); color: var(--color-rich-black); }
.cpg-icon-btn.is-danger:hover { background: var(--color-ghost-gray); color: var(--color-callout-red); } .cpg-icon-btn.is-danger:hover { background: var(--color-subtle-ash); color: var(--color-callout-red); }
.cpg-center-empty { margin: 8px 0 0; } .cpg-center-empty { margin: 8px 0 0; }
/* 센터 관리 팝업 */
.cpg-center-dlgmsg { margin: 0; font-size: 14px; line-height: 1.5; word-break: keep-all; }
.cpg-center-dlgact { display: flex; gap: 6px; justify-content: flex-end; margin-top: 4px; }
.cpg-btn-sm { padding: 3px 8px; font-size: 12px; flex: 0 0 auto; } .cpg-btn-sm { padding: 3px 8px; font-size: 12px; flex: 0 0 auto; }
/* ── 라인 테이블 ── */ /* ── 라인 테이블 ── */