Update intranet tools and voucher comparison
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}사용자 관리{% endblock %}
|
||||
|
||||
{% block head_extra %}
|
||||
<style>
|
||||
.admin-user-form {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.admin-user-main-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(110px, 0.9fr) minmax(120px, 1fr) minmax(140px, 1fr) minmax(130px, 0.9fr) auto;
|
||||
gap: 10px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.admin-user-form .field {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-user-form input:not([type="checkbox"]),
|
||||
.admin-user-form select {
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
color: var(--ink);
|
||||
box-sizing: border-box;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.admin-user-actions {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: flex-end;
|
||||
min-width: 76px;
|
||||
}
|
||||
|
||||
.admin-user-actions button {
|
||||
width: 76px;
|
||||
min-height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.admin-user-permission-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.permission-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 7px 0 0;
|
||||
}
|
||||
|
||||
.permission-grid label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
min-height: 28px;
|
||||
padding: 4px 9px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
color: var(--ink);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.permission-grid input[type="checkbox"],
|
||||
.admin-active-toggle input[type="checkbox"] {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
accent-color: #111827;
|
||||
}
|
||||
|
||||
.permission-chip-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.permission-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
padding: 2px 7px;
|
||||
border: 1px solid var(--line);
|
||||
background: #f7f9fb;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.admin-active-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-height: 30px;
|
||||
padding-top: 19px;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.admin-user-main-row {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.admin-user-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.admin-user-main-row,
|
||||
.admin-user-permission-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>사용자 관리</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="admin-user-form" method="post" action="/admin/users">
|
||||
<div class="admin-user-main-row">
|
||||
<div class="field">
|
||||
<label for="username">아이디</label>
|
||||
<input id="username" name="username" required autocomplete="off">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="displayName">이름</label>
|
||||
<input id="displayName" name="display_name">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="password">비밀번호</label>
|
||||
<input id="password" name="password" type="password" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="role">역할</label>
|
||||
<select id="role" name="role">
|
||||
<option value="viewer">사용자별 권한</option>
|
||||
<option value="admin">관리자</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="admin-user-actions">
|
||||
<button type="submit">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-user-permission-row">
|
||||
<div>
|
||||
<label>페이지 권한</label>
|
||||
<div class="permission-grid">
|
||||
{% for permission in permission_options %}
|
||||
<label>
|
||||
<input type="checkbox" name="permissions" value="{{ permission.key }}" {% if permission.key != 'admin' %}checked{% endif %}>
|
||||
{{ permission.label }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<label class="admin-active-toggle">
|
||||
<input type="checkbox" name="is_active" value="1" checked>
|
||||
활성
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>아이디</th>
|
||||
<th>이름</th>
|
||||
<th>역할</th>
|
||||
<th>페이지 권한</th>
|
||||
<th>상태</th>
|
||||
<th>관리자</th>
|
||||
<th>수정일</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr
|
||||
data-username="{{ user.username }}"
|
||||
data-display-name="{{ user.display_name }}"
|
||||
data-role="{{ 'admin' if user.is_admin else 'viewer' }}"
|
||||
data-active="{{ '1' if user.is_active else '0' }}"
|
||||
data-permissions="{{ ','.join(user.direct_permissions) }}"
|
||||
>
|
||||
<td><button type="button" class="button-secondary user-edit-button">{{ user.username }}</button></td>
|
||||
<td>{{ user.display_name }}</td>
|
||||
<td>{{ ', '.join(user.roles) }}</td>
|
||||
<td>
|
||||
<div class="permission-chip-list">
|
||||
{% for permission in permission_options %}
|
||||
{% if permission.key in user.direct_permissions %}
|
||||
<span class="permission-chip">{{ permission.label }}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if user.is_admin %}
|
||||
<span class="permission-chip">전체</span>
|
||||
{% elif not user.direct_permissions %}
|
||||
<span class="muted">역할 기본값</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ '활성' if user.is_active else '비활성' }}</td>
|
||||
<td>{{ '예' if user.is_admin else '-' }}</td>
|
||||
<td>{{ user.updated_at }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="empty">사용자가 없습니다.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
(() => {
|
||||
const form = document.querySelector("form[action='/admin/users']");
|
||||
if (!form) return;
|
||||
const username = form.querySelector("[name='username']");
|
||||
const displayName = form.querySelector("[name='display_name']");
|
||||
const password = form.querySelector("[name='password']");
|
||||
const role = form.querySelector("[name='role']");
|
||||
const active = form.querySelector("[name='is_active']");
|
||||
const permissionInputs = [...form.querySelectorAll("[name='permissions']")];
|
||||
|
||||
document.querySelectorAll(".user-edit-button").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const row = button.closest("tr");
|
||||
const permissions = new Set(String(row?.dataset.permissions || "").split(",").filter(Boolean));
|
||||
username.value = row?.dataset.username || "";
|
||||
displayName.value = row?.dataset.displayName || "";
|
||||
password.value = "";
|
||||
role.value = row?.dataset.role || "viewer";
|
||||
active.checked = row?.dataset.active === "1";
|
||||
permissionInputs.forEach((input) => {
|
||||
input.checked = permissions.has(input.value) || (role.value !== "admin" && !permissions.size && input.value !== "admin");
|
||||
});
|
||||
username.focus();
|
||||
});
|
||||
});
|
||||
|
||||
role.addEventListener("change", () => {
|
||||
const isAdmin = role.value === "admin";
|
||||
permissionInputs.forEach((input) => {
|
||||
input.disabled = isAdmin;
|
||||
if (isAdmin) input.checked = false;
|
||||
});
|
||||
});
|
||||
|
||||
form.addEventListener("submit", () => {
|
||||
const isAdmin = role.value === "admin";
|
||||
permissionInputs.forEach((input) => {
|
||||
input.disabled = isAdmin;
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user