|
|
<!DOCTYPE html> |
|
|
<html lang="ko"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>๊ด๋ฆฌ์ ํ์ด์ง - SOY NV AI</title> |
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> |
|
|
<style> |
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|
|
background: #f8f9fa; |
|
|
color: #202124; |
|
|
} |
|
|
|
|
|
.header { |
|
|
background: white; |
|
|
border-bottom: 1px solid #dadce0; |
|
|
padding: 16px 24px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: space-between; |
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.header-title { |
|
|
font-size: 20px; |
|
|
font-weight: 500; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 12px; |
|
|
} |
|
|
|
|
|
.header-actions { |
|
|
display: flex; |
|
|
gap: 12px; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.menu-toggle { |
|
|
display: none; |
|
|
background: none; |
|
|
border: none; |
|
|
font-size: 24px; |
|
|
cursor: pointer; |
|
|
padding: 8px; |
|
|
color: #202124; |
|
|
} |
|
|
|
|
|
.mobile-menu { |
|
|
display: none; |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background: rgba(0, 0, 0, 0.5); |
|
|
z-index: 1000; |
|
|
} |
|
|
|
|
|
.mobile-menu.active { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.mobile-menu-content { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
right: -100%; |
|
|
width: 280px; |
|
|
max-width: 80%; |
|
|
height: 100%; |
|
|
background: white; |
|
|
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1); |
|
|
transition: right 0.3s ease; |
|
|
overflow-y: auto; |
|
|
z-index: 1001; |
|
|
} |
|
|
|
|
|
.mobile-menu.active .mobile-menu-content { |
|
|
right: 0; |
|
|
} |
|
|
|
|
|
.mobile-menu-header { |
|
|
padding: 16px 20px; |
|
|
border-bottom: 1px solid #dadce0; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
background: white; |
|
|
position: sticky; |
|
|
top: 0; |
|
|
z-index: 10; |
|
|
} |
|
|
|
|
|
.mobile-menu-title { |
|
|
font-size: 18px; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.mobile-menu-close { |
|
|
background: none; |
|
|
border: none; |
|
|
font-size: 24px; |
|
|
cursor: pointer; |
|
|
color: #202124; |
|
|
padding: 0; |
|
|
width: 32px; |
|
|
height: 32px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
} |
|
|
|
|
|
.mobile-menu-items { |
|
|
padding: 8px 0; |
|
|
} |
|
|
|
|
|
.mobile-menu-item { |
|
|
display: block; |
|
|
padding: 12px 20px; |
|
|
color: #202124; |
|
|
text-decoration: none; |
|
|
border-bottom: 1px solid #f1f3f4; |
|
|
transition: background 0.2s; |
|
|
} |
|
|
|
|
|
.mobile-menu-item:hover { |
|
|
background: #f8f9fa; |
|
|
} |
|
|
|
|
|
.mobile-menu-user { |
|
|
padding: 16px 20px; |
|
|
border-bottom: 1px solid #dadce0; |
|
|
color: #5f6368; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.header { |
|
|
padding: 12px 16px; |
|
|
} |
|
|
|
|
|
.header-title { |
|
|
font-size: 18px; |
|
|
} |
|
|
|
|
|
.header-title span:first-child { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.menu-toggle { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.header-actions { |
|
|
display: none; |
|
|
} |
|
|
} |
|
|
|
|
|
.btn { |
|
|
padding: 8px 16px; |
|
|
border: none; |
|
|
border-radius: 6px; |
|
|
font-size: 14px; |
|
|
font-weight: 500; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s; |
|
|
text-decoration: none; |
|
|
display: inline-block; |
|
|
} |
|
|
|
|
|
.btn-primary { |
|
|
background: #1a73e8; |
|
|
color: white; |
|
|
} |
|
|
|
|
|
.btn-primary:hover { |
|
|
background: #1557b0; |
|
|
} |
|
|
|
|
|
.btn-secondary { |
|
|
background: #f1f3f4; |
|
|
color: #202124; |
|
|
} |
|
|
|
|
|
.btn-secondary:hover { |
|
|
background: #e8eaed; |
|
|
} |
|
|
|
|
|
.btn-danger { |
|
|
background: #ea4335; |
|
|
color: white; |
|
|
} |
|
|
|
|
|
.btn-danger:hover { |
|
|
background: #c5221f; |
|
|
} |
|
|
|
|
|
.container { |
|
|
max-width: 1200px; |
|
|
margin: 0 auto; |
|
|
padding: 24px; |
|
|
} |
|
|
|
|
|
.page-header { |
|
|
margin-bottom: 24px; |
|
|
} |
|
|
|
|
|
.page-header h1 { |
|
|
font-size: 28px; |
|
|
font-weight: 600; |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
|
|
|
.page-header p { |
|
|
color: #5f6368; |
|
|
} |
|
|
|
|
|
.card { |
|
|
background: white; |
|
|
border-radius: 8px; |
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
|
|
padding: 24px; |
|
|
margin-bottom: 24px; |
|
|
} |
|
|
|
|
|
.card-header { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.card-title { |
|
|
font-size: 18px; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
table { |
|
|
width: 100%; |
|
|
border-collapse: collapse; |
|
|
} |
|
|
|
|
|
thead { |
|
|
background: #f8f9fa; |
|
|
} |
|
|
|
|
|
th, td { |
|
|
padding: 12px; |
|
|
text-align: left; |
|
|
border-bottom: 1px solid #e8eaed; |
|
|
} |
|
|
|
|
|
th { |
|
|
font-weight: 500; |
|
|
font-size: 14px; |
|
|
color: #5f6368; |
|
|
} |
|
|
|
|
|
td { |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
.badge { |
|
|
display: inline-block; |
|
|
padding: 4px 8px; |
|
|
border-radius: 4px; |
|
|
font-size: 12px; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.badge-admin { |
|
|
background: #e8f0fe; |
|
|
color: #1967d2; |
|
|
} |
|
|
|
|
|
.badge-user { |
|
|
background: #e8f5e9; |
|
|
color: #137333; |
|
|
} |
|
|
|
|
|
.badge-inactive { |
|
|
background: #fce8e6; |
|
|
color: #c5221f; |
|
|
} |
|
|
|
|
|
.modal { |
|
|
display: none; |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background: rgba(0, 0, 0, 0.5); |
|
|
z-index: 1000; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
} |
|
|
|
|
|
.modal.active { |
|
|
display: flex; |
|
|
} |
|
|
|
|
|
.modal-content { |
|
|
background: white; |
|
|
border-radius: 8px; |
|
|
padding: 24px; |
|
|
width: 90%; |
|
|
max-width: 500px; |
|
|
max-height: 90vh; |
|
|
overflow-y: auto; |
|
|
} |
|
|
|
|
|
.modal-header { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.modal-title { |
|
|
font-size: 20px; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.modal-close { |
|
|
background: none; |
|
|
border: none; |
|
|
font-size: 24px; |
|
|
cursor: pointer; |
|
|
color: #5f6368; |
|
|
} |
|
|
|
|
|
.form-group { |
|
|
margin-bottom: 16px; |
|
|
} |
|
|
|
|
|
.form-group label { |
|
|
display: block; |
|
|
font-size: 14px; |
|
|
font-weight: 500; |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
|
|
|
.form-group input, |
|
|
.form-group select { |
|
|
width: 100%; |
|
|
padding: 10px 12px; |
|
|
border: 1px solid #dadce0; |
|
|
border-radius: 6px; |
|
|
font-size: 14px; |
|
|
font-family: inherit; |
|
|
} |
|
|
|
|
|
.form-group input:focus, |
|
|
.form-group select:focus { |
|
|
outline: none; |
|
|
border-color: #1a73e8; |
|
|
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1); |
|
|
} |
|
|
|
|
|
.form-group-checkbox { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
} |
|
|
|
|
|
.form-group-checkbox input { |
|
|
width: auto; |
|
|
} |
|
|
|
|
|
.modal-actions { |
|
|
display: flex; |
|
|
gap: 8px; |
|
|
justify-content: flex-end; |
|
|
margin-top: 24px; |
|
|
} |
|
|
|
|
|
.alert { |
|
|
padding: 12px 16px; |
|
|
border-radius: 6px; |
|
|
margin-bottom: 16px; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
.alert.error { |
|
|
background: #fce8e6; |
|
|
color: #c5221f; |
|
|
} |
|
|
|
|
|
.alert.success { |
|
|
background: #e8f5e9; |
|
|
color: #137333; |
|
|
} |
|
|
|
|
|
|
|
|
.file-upload-section { |
|
|
margin-top: 24px; |
|
|
} |
|
|
|
|
|
.file-upload-input-wrapper { |
|
|
position: relative; |
|
|
margin-bottom: 12px; |
|
|
border: 2px dashed #dadce0; |
|
|
border-radius: 8px; |
|
|
padding: 20px; |
|
|
text-align: center; |
|
|
background: #f8f9fa; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.file-upload-input-wrapper:hover { |
|
|
border-color: #1a73e8; |
|
|
background: #e8f0fe; |
|
|
} |
|
|
|
|
|
.file-upload-input-wrapper.dragover { |
|
|
border-color: #1a73e8; |
|
|
background: #e8f0fe; |
|
|
} |
|
|
|
|
|
.file-upload-input-wrapper input[type="file"] { |
|
|
position: absolute; |
|
|
opacity: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.file-upload-label { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
color: #5f6368; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
.file-upload-label svg { |
|
|
width: 32px; |
|
|
height: 32px; |
|
|
} |
|
|
|
|
|
.file-upload-status { |
|
|
font-size: 12px; |
|
|
margin-top: 8px; |
|
|
min-height: 16px; |
|
|
} |
|
|
|
|
|
.file-upload-status.success { |
|
|
color: #137333; |
|
|
} |
|
|
|
|
|
.file-upload-status.error { |
|
|
color: #c5221f; |
|
|
} |
|
|
|
|
|
.file-upload-status.progress { |
|
|
color: #1a73e8; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.file-upload-progress { |
|
|
margin-top: 12px; |
|
|
padding: 12px; |
|
|
background: #f8f9fa; |
|
|
border-radius: 6px; |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.file-upload-progress.active { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.progress-item { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: space-between; |
|
|
padding: 8px 0; |
|
|
border-bottom: 1px solid #e8eaed; |
|
|
} |
|
|
|
|
|
.progress-item:last-child { |
|
|
border-bottom: none; |
|
|
} |
|
|
|
|
|
.progress-item-name { |
|
|
flex: 1; |
|
|
font-size: 13px; |
|
|
color: #202124; |
|
|
overflow: hidden; |
|
|
text-overflow: ellipsis; |
|
|
white-space: nowrap; |
|
|
margin-right: 12px; |
|
|
} |
|
|
|
|
|
.progress-item-status { |
|
|
font-size: 12px; |
|
|
font-weight: 500; |
|
|
min-width: 80px; |
|
|
text-align: right; |
|
|
} |
|
|
|
|
|
.progress-item-status.uploading { |
|
|
color: #1a73e8; |
|
|
} |
|
|
|
|
|
.progress-item-status.success { |
|
|
color: #137333; |
|
|
} |
|
|
|
|
|
.progress-item-status.error { |
|
|
color: #c5221f; |
|
|
} |
|
|
|
|
|
.file-upload-input-wrapper.disabled { |
|
|
opacity: 0.6; |
|
|
pointer-events: none; |
|
|
cursor: not-allowed; |
|
|
} |
|
|
|
|
|
.spinner { |
|
|
display: inline-block; |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
border: 2px solid #e8eaed; |
|
|
border-top-color: #1a73e8; |
|
|
border-radius: 50%; |
|
|
animation: spin 0.8s linear infinite; |
|
|
margin-right: 6px; |
|
|
vertical-align: middle; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
to { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
.files-table { |
|
|
margin-top: 16px; |
|
|
} |
|
|
|
|
|
.file-size { |
|
|
color: #5f6368; |
|
|
font-size: 12px; |
|
|
} |
|
|
|
|
|
.file-actions { |
|
|
display: flex; |
|
|
gap: 4px; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="header"> |
|
|
<div class="header-title"> |
|
|
<span>๐ค</span> |
|
|
<span>SOY NV AI ๊ด๋ฆฌ์ ํ์ด์ง</span> |
|
|
</div> |
|
|
<button class="menu-toggle" onclick="toggleMobileMenu()" aria-label="๋ฉ๋ด ์ด๊ธฐ">โฐ</button> |
|
|
<div class="header-actions"> |
|
|
<span style="margin-right: 12px; color: #5f6368;">{{ current_user.nickname or current_user.username }}</span> |
|
|
<a href="{{ url_for('main.admin_webnovels') }}" class="btn btn-secondary">์น์์ค ๊ด๋ฆฌ</a> |
|
|
<a href="{{ url_for('main.admin_files') }}" class="btn btn-secondary">ํ์ผ ๋ชฉ๋ก</a> |
|
|
<a href="{{ url_for('main.admin_messages') }}" class="btn btn-secondary">๋ฉ์์ง ํ์ธ</a> |
|
|
<a href="{{ url_for('main.admin_prompts') }}" class="btn btn-secondary">ํ๋กฌํํธ ๊ด๋ฆฌ</a> |
|
|
<a href="{{ url_for('main.admin_settings') }}" class="btn btn-secondary">AI ์ค์ </a> |
|
|
<a href="{{ url_for('main.index') }}" class="btn btn-secondary">๋ฉ์ธ์ผ๋ก</a> |
|
|
<a href="{{ url_for('main.logout') }}" class="btn btn-secondary">๋ก๊ทธ์์</a> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mobile-menu" id="mobileMenu" onclick="closeMobileMenuOnBackdrop(event)"> |
|
|
<div class="mobile-menu-content" onclick="event.stopPropagation()"> |
|
|
<div class="mobile-menu-header"> |
|
|
<div class="mobile-menu-title">๋ฉ๋ด</div> |
|
|
<button class="mobile-menu-close" onclick="toggleMobileMenu()" aria-label="๋ฉ๋ด ๋ซ๊ธฐ">×</button> |
|
|
</div> |
|
|
<div class="mobile-menu-user">{{ current_user.nickname or current_user.username }}</div> |
|
|
<div class="mobile-menu-items"> |
|
|
<a href="{{ url_for('main.admin_webnovels') }}" class="mobile-menu-item" onclick="closeMobileMenu()">์น์์ค ๊ด๋ฆฌ</a> |
|
|
<a href="{{ url_for('main.admin_files') }}" class="mobile-menu-item" onclick="closeMobileMenu()">ํ์ผ ๋ชฉ๋ก</a> |
|
|
<a href="{{ url_for('main.admin_messages') }}" class="mobile-menu-item" onclick="closeMobileMenu()">๋ฉ์์ง ํ์ธ</a> |
|
|
<a href="{{ url_for('main.admin_prompts') }}" class="mobile-menu-item" onclick="closeMobileMenu()">ํ๋กฌํํธ ๊ด๋ฆฌ</a> |
|
|
<a href="{{ url_for('main.admin_settings') }}" class="mobile-menu-item" onclick="closeMobileMenu()">AI ์ค์ </a> |
|
|
<a href="{{ url_for('main.index') }}" class="mobile-menu-item" onclick="closeMobileMenu()">๋ฉ์ธ์ผ๋ก</a> |
|
|
<a href="{{ url_for('main.logout') }}" class="mobile-menu-item" onclick="closeMobileMenu()">๋ก๊ทธ์์</a> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="container"> |
|
|
<div class="page-header"> |
|
|
<h1>์ฌ์ฉ์ ๊ด๋ฆฌ</h1> |
|
|
<p>์์คํ
์ฌ์ฉ์๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.</p> |
|
|
</div> |
|
|
|
|
|
<div id="alertContainer"></div> |
|
|
|
|
|
<div class="card"> |
|
|
<div class="card-header"> |
|
|
<div class="card-title">์ฌ์ฉ์ ๋ชฉ๋ก</div> |
|
|
<button class="btn btn-primary" onclick="openCreateModal()">์ ์ฌ์ฉ์ ์ถ๊ฐ</button> |
|
|
</div> |
|
|
|
|
|
<table> |
|
|
<thead> |
|
|
<tr> |
|
|
<th>ID</th> |
|
|
<th>์ฌ์ฉ์๋ช
</th> |
|
|
<th>๋๋ค์</th> |
|
|
<th>๊ถํ</th> |
|
|
<th>์ํ</th> |
|
|
<th>์์ฑ์ผ</th> |
|
|
<th>๋ง์ง๋ง ๋ก๊ทธ์ธ</th> |
|
|
<th>์์
</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody id="usersTableBody"> |
|
|
{% for user in users %} |
|
|
<tr data-user-id="{{ user.id }}"> |
|
|
<td>{{ user.id }}</td> |
|
|
<td>{{ user.username }}</td> |
|
|
<td>{{ user.nickname or '-' }}</td> |
|
|
<td> |
|
|
{% if user.is_admin %} |
|
|
<span class="badge badge-admin">๊ด๋ฆฌ์</span> |
|
|
{% else %} |
|
|
<span class="badge badge-user">์ผ๋ฐ ์ฌ์ฉ์</span> |
|
|
{% endif %} |
|
|
</td> |
|
|
<td> |
|
|
{% if user.is_active %} |
|
|
<span class="badge badge-user">ํ์ฑ</span> |
|
|
{% else %} |
|
|
<span class="badge badge-inactive">๋นํ์ฑ</span> |
|
|
{% endif %} |
|
|
</td> |
|
|
<td>{{ user.created_at.strftime('%Y-%m-%d %H:%M') if user.created_at else '-' }}</td> |
|
|
<td>{{ user.last_login.strftime('%Y-%m-%d %H:%M') if user.last_login else '-' }}</td> |
|
|
<td> |
|
|
<button class="btn btn-secondary" onclick="openEditModal({{ user.id }}, '{{ user.username }}', '{{ user.nickname or '' }}', {{ user.is_admin|lower }}, {{ user.is_active|lower }})" style="padding: 4px 8px; font-size: 12px;">์์ </button> |
|
|
{% if user.id != current_user.id %} |
|
|
<button class="btn btn-danger" onclick="deleteUser({{ user.id }})" style="padding: 4px 8px; font-size: 12px;">์ญ์ </button> |
|
|
{% endif %} |
|
|
</td> |
|
|
</tr> |
|
|
{% endfor %} |
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="userModal" class="modal"> |
|
|
<div class="modal-content"> |
|
|
<div class="modal-header"> |
|
|
<div class="modal-title" id="modalTitle">์ ์ฌ์ฉ์ ์ถ๊ฐ</div> |
|
|
<button class="modal-close" onclick="closeModal()">×</button> |
|
|
</div> |
|
|
<form id="userForm" onsubmit="saveUser(event)"> |
|
|
<input type="hidden" id="userId" name="user_id"> |
|
|
<div class="form-group"> |
|
|
<label for="username">์ฌ์ฉ์๋ช
</label> |
|
|
<input type="text" id="username" name="username" required> |
|
|
</div> |
|
|
<div class="form-group"> |
|
|
<label for="nickname">๋๋ค์</label> |
|
|
<input type="text" id="nickname" name="nickname" placeholder="์ ํ์ฌํญ"> |
|
|
</div> |
|
|
<div class="form-group"> |
|
|
<label for="password">๋น๋ฐ๋ฒํธ</label> |
|
|
<input type="password" id="password" name="password" id="passwordInput"> |
|
|
<small style="color: #5f6368; font-size: 12px;">์์ ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณ๊ฒฝํ์ง ์์ผ๋ ค๋ฉด ๋น์๋์ธ์.</small> |
|
|
</div> |
|
|
<div class="form-group-checkbox"> |
|
|
<input type="checkbox" id="isAdmin" name="is_admin"> |
|
|
<label for="isAdmin">๊ด๋ฆฌ์ ๊ถํ</label> |
|
|
</div> |
|
|
<div class="form-group-checkbox"> |
|
|
<input type="checkbox" id="isActive" name="is_active" checked> |
|
|
<label for="isActive">ํ์ฑ ์ํ</label> |
|
|
</div> |
|
|
<div class="modal-actions"> |
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal()">์ทจ์</button> |
|
|
<button type="submit" class="btn btn-primary">์ ์ฅ</button> |
|
|
</div> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
let currentEditUserId = null; |
|
|
|
|
|
function toggleMobileMenu() { |
|
|
const menu = document.getElementById('mobileMenu'); |
|
|
menu.classList.toggle('active'); |
|
|
document.body.style.overflow = menu.classList.contains('active') ? 'hidden' : ''; |
|
|
} |
|
|
|
|
|
function closeMobileMenu() { |
|
|
const menu = document.getElementById('mobileMenu'); |
|
|
menu.classList.remove('active'); |
|
|
document.body.style.overflow = ''; |
|
|
} |
|
|
|
|
|
function closeMobileMenuOnBackdrop(event) { |
|
|
if (event.target.id === 'mobileMenu') { |
|
|
closeMobileMenu(); |
|
|
} |
|
|
} |
|
|
|
|
|
function showAlert(message, type = 'success') { |
|
|
const container = document.getElementById('alertContainer'); |
|
|
container.innerHTML = `<div class="alert ${type}">${message}</div>`; |
|
|
setTimeout(() => { |
|
|
container.innerHTML = ''; |
|
|
}, 5000); |
|
|
} |
|
|
|
|
|
function openCreateModal() { |
|
|
currentEditUserId = null; |
|
|
document.getElementById('modalTitle').textContent = '์ ์ฌ์ฉ์ ์ถ๊ฐ'; |
|
|
document.getElementById('userForm').reset(); |
|
|
document.getElementById('userId').value = ''; |
|
|
document.getElementById('password').required = true; |
|
|
document.getElementById('isActive').checked = true; |
|
|
document.getElementById('userModal').classList.add('active'); |
|
|
} |
|
|
|
|
|
function openEditModal(userId, username, nickname, isAdmin, isActive) { |
|
|
currentEditUserId = userId; |
|
|
document.getElementById('modalTitle').textContent = '์ฌ์ฉ์ ์์ '; |
|
|
document.getElementById('userId').value = userId; |
|
|
document.getElementById('username').value = username; |
|
|
document.getElementById('nickname').value = nickname || ''; |
|
|
document.getElementById('password').value = ''; |
|
|
document.getElementById('password').required = false; |
|
|
document.getElementById('isAdmin').checked = isAdmin; |
|
|
document.getElementById('isActive').checked = isActive; |
|
|
document.getElementById('userModal').classList.add('active'); |
|
|
} |
|
|
|
|
|
function closeModal() { |
|
|
document.getElementById('userModal').classList.remove('active'); |
|
|
currentEditUserId = null; |
|
|
} |
|
|
|
|
|
async function saveUser(event) { |
|
|
event.preventDefault(); |
|
|
|
|
|
const formData = { |
|
|
username: document.getElementById('username').value.trim(), |
|
|
nickname: document.getElementById('nickname').value.trim(), |
|
|
password: document.getElementById('password').value, |
|
|
is_admin: document.getElementById('isAdmin').checked, |
|
|
is_active: document.getElementById('isActive').checked |
|
|
}; |
|
|
|
|
|
const userId = document.getElementById('userId').value; |
|
|
const url = userId ? `/api/admin/users/${userId}` : '/api/admin/users'; |
|
|
const method = userId ? 'PUT' : 'POST'; |
|
|
|
|
|
if (!userId && !formData.password) { |
|
|
showAlert('๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
const response = await fetch(url, { |
|
|
method: method, |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
}, |
|
|
body: JSON.stringify(formData) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (response.ok) { |
|
|
showAlert(data.message, 'success'); |
|
|
closeModal(); |
|
|
setTimeout(() => { |
|
|
location.reload(); |
|
|
}, 1000); |
|
|
} else { |
|
|
showAlert(data.error || '์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.', 'error'); |
|
|
} |
|
|
} catch (error) { |
|
|
showAlert(`์ค๋ฅ: ${error.message}`, 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
async function deleteUser(userId) { |
|
|
if (!confirm('์ ๋ง ์ด ์ฌ์ฉ์๋ฅผ ์ญ์ ํ์๊ฒ ์ต๋๊น?')) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
const response = await fetch(`/api/admin/users/${userId}`, { |
|
|
method: 'DELETE' |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (response.ok) { |
|
|
showAlert(data.message, 'success'); |
|
|
setTimeout(() => { |
|
|
location.reload(); |
|
|
}, 1000); |
|
|
} else { |
|
|
showAlert(data.error || '์ญ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.', 'error'); |
|
|
} |
|
|
} catch (error) { |
|
|
showAlert(`์ค๋ฅ: ${error.message}`, 'error'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('userModal').addEventListener('click', function(e) { |
|
|
if (e.target === this) { |
|
|
closeModal(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|
|
|
|