diff --git a/app/templates/admin.html b/app/templates/admin.html index 0fdd9d2..ceadcfa 100644 --- a/app/templates/admin.html +++ b/app/templates/admin.html @@ -24,11 +24,30 @@ font-family: inherit; } - .erp-mod-th { text-align: center; font-size: 11px; padding: 6px 4px; } - .erp-mod-cell { text-align: center; padding: 4px; } - .erp-mod-group { display: inline-block; padding: 2px 6px; border-radius: 6px; + .erp-mod-group { display: inline-block; margin-left: 6px; padding: 2px 6px; border-radius: 6px; background: var(--color-ghost-gray); color: var(--color-midtone-gray); - font-size: 10px; font-weight: 500; } + font-size: 10px; font-weight: 500; vertical-align: middle; } + + /* 권한 칩 — 모듈 수가 늘어도 줄바꿈되어 가로로 안 넓어짐 */ + .erp-perm-cell { padding: 8px 6px; } + .erp-perm-chip { + display: inline-flex; align-items: center; gap: 4px; + margin: 3px; padding: 4px 11px; border-radius: 999px; + border: 1px solid var(--color-subtle-ash); background: #fff; + color: var(--color-midtone-gray); font-size: 12px; font-weight: 500; + font-family: inherit; cursor: pointer; user-select: none; + transition: background .1s, color .1s, border-color .1s; + } + .erp-perm-chip:hover { border-color: var(--color-midtone-gray); } + .erp-perm-chip.is-on { + background: var(--color-rich-black); color: #fff; border-color: var(--color-rich-black); + } + /* 승인 권한 칩 — 켜지면 파랑 강조로 구분 */ + .erp-perm-chip.is-approver.is-on { + background: #1d4ed8; border-color: #1d4ed8; + } + .erp-perm-chip[data-locked="true"] { opacity: .5; cursor: not-allowed; } + .erp-perm-chip[data-locked="true"]:hover { border-color: var(--color-subtle-ash); } @@ -89,15 +108,10 @@ - + - {% for key in module_keys %} - - {% endfor %} - + + @@ -130,18 +144,19 @@ - {% for key in module_keys %} - - {% endfor %} @@ -188,15 +203,16 @@ showToast._t = setTimeout(() => { toastEl.dataset.visible = "false"; }, 2400); } - function syncModuleSwitches(tr) { + function syncPermChips(tr) { const isSuper = tr.dataset.super === "true"; - const role = tr.querySelector("select[data-field='role']").value; - const adminRole = role === "admin"; - tr.querySelectorAll("input[data-field='module']").forEach((cb) => { + const adminRole = tr.querySelector("select[data-field='role']").value === "admin"; + tr.querySelectorAll(".erp-perm-chip").forEach((chip) => { if (isSuper || adminRole) { - cb.checked = true; cb.disabled = true; + chip.classList.add("is-on"); + chip.setAttribute("aria-pressed", "true"); + chip.setAttribute("data-locked", "true"); } else { - cb.disabled = false; + chip.removeAttribute("data-locked"); } }); } @@ -204,12 +220,22 @@ tbody.addEventListener("change", (e) => { const tr = e.target.closest("tr"); if (!tr) return; - if (e.target.dataset.field === "role") syncModuleSwitches(tr); + if (e.target.dataset.field === "role") syncPermChips(tr); markDirty(tr); }); tbody.addEventListener("click", async (e) => { - const btn = e.target.closest("button"); + // 권한 칩 토글 (잠금 상태 제외) + const chip = e.target.closest(".erp-perm-chip"); + if (chip) { + if (chip.getAttribute("data-locked") === "true") return; + const on = chip.classList.toggle("is-on"); + chip.setAttribute("aria-pressed", on ? "true" : "false"); + markDirty(chip.closest("tr")); + return; + } + + const btn = e.target.closest("button[data-action]"); if (!btn) return; const tr = btn.closest("tr"); const email = tr.dataset.email; @@ -217,8 +243,8 @@ if (btn.dataset.action === "save") { const role = tr.querySelector("select[data-field='role']").value; const modules = {}; - tr.querySelectorAll("input[data-field='module']").forEach((cb) => { - modules[cb.dataset.module] = cb.checked; + tr.querySelectorAll(".erp-perm-chip").forEach((c) => { + modules[c.dataset.module] = c.classList.contains("is-on"); }); btn.disabled = true; btn.textContent = "저장 중…"; try {
사용자사용자 역할 - {{ module_labels.get(key, key) }} - {% if key in approver_keys %}
승인{% endif %} -
최근 로그인접근 권한 클릭하여 토글최근 로그인 동작
- + + {% for key in module_keys %} + + {% endfor %} {{ (u.last_login or '미접속')[:16].replace('T', ' ') }}