-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadmin.html
More file actions
378 lines (358 loc) · 19.7 KB
/
Copy pathadmin.html
File metadata and controls
378 lines (358 loc) · 19.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#003399" />
<title>EU연합 · 관리자 대시보드</title>
<meta name="description" content="EU연합 통합시스템 관리자 대시보드 — 문원 관리, 공성 점수 통계, 성주 현황">
<link rel="manifest" href="manifest.webmanifest">
<link rel="icon" type="image/svg+xml" href="icon.svg">
<link rel="apple-touch-icon" href="icon.svg">
<link rel="stylesheet" href="styles.css?v=41" />
<link rel="stylesheet" href="admin.css?v=41" />
<link rel="preconnect" href="https://script.google.com" crossorigin>
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
</head>
<body class="admin-body">
<header class="site-header admin-header">
<div class="eu-flag-bar" aria-label="EU 연합 깃발">
<svg class="eu-flag-svg" viewBox="0 0 90 60" aria-hidden="true">
<rect width="90" height="60" fill="#003399"/>
<g fill="#FFCC00">
<g transform="translate(45,30) rotate(0) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(30) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(60) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(90) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(120) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(150) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(180) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(210) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(240) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(270) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(300) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
<g transform="translate(45,30) rotate(330) translate(0,-15)"><polygon points="0,-3.5 0.82,-1.08 3.33,-1.08 1.31,0.41 2.06,2.83 0,1.35 -2.06,2.83 -1.31,0.41 -3.33,-1.08 -0.82,-1.08"/></g>
</g>
</svg>
<span class="eu-text">EU 연합</span>
</div>
<div class="brand">
<span class="brand-icon" aria-hidden="true">⚙️</span>
<h1>관리자 대시보드</h1>
</div>
<div class="admin-top-actions" id="topActions" hidden>
<a href="index.html" class="ghost small-btn">← 사용자 페이지</a>
<span class="scope-tag" id="scopeTag"></span>
<select id="guildFilter" class="ghost" aria-label="문파 필터" hidden>
<option value="all">전체 문파</option>
</select>
<button type="button" class="ghost small-btn" id="logoutBtn">로그아웃</button>
</div>
</header>
<dialog id="loginDialog" class="dialog">
<form method="dialog" id="loginForm">
<h3>🔐 관리자 로그인</h3>
<input type="text" id="userInput" placeholder="아이디" autocomplete="username" required style="margin-bottom:8px;" />
<input type="password" id="pwInput" placeholder="비밀번호" required autocomplete="current-password" />
<p class="hint error" id="loginError" hidden>로그인 실패</p>
<menu>
<a href="index.html" class="ghost" style="text-decoration:none; display:inline-flex; align-items:center;">취소</a>
<button value="login" class="primary" id="loginBtn">로그인</button>
</menu>
</form>
</dialog>
<main class="admin-main" id="adminMain" hidden>
<!-- 통계 카드 -->
<section class="card">
<h2>📊 이번 주 신청 현황</h2>
<div class="kpi-grid">
<div class="kpi">
<div class="kpi-num" id="kpiSubmitted">-</div>
<div class="kpi-lbl">신청 / 전체</div>
</div>
<div class="kpi">
<div class="kpi-num" id="kpiPercentage">-</div>
<div class="kpi-lbl">신청률</div>
</div>
<div class="kpi">
<div class="kpi-num" id="kpiElite">-</div>
<div class="kpi-lbl">정예 참전</div>
</div>
<div class="kpi">
<div class="kpi-num" id="kpiPeriod">-</div>
<div class="kpi-lbl">기간 (KST)</div>
</div>
</div>
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
<div class="kpi-row" id="eliteBreakdown"></div>
</section>
<!-- 신청자 / 미신청자 토글 -->
<section class="card">
<div class="list-header">
<h2>📋 이번 주 신청자 / 미신청자</h2>
<div class="filter-bar">
<select id="memberFilter">
<option value="all">전체</option>
<option value="submitted">신청자만</option>
<option value="notSubmitted">미신청자만</option>
</select>
</div>
</div>
<div class="table-wrap">
<table class="data-table" id="memberStatsTable">
<thead>
<tr><th>닉네임</th><th>상태</th><th class="num">점수</th><th>정예</th><th>성</th><th>시간</th></tr>
</thead>
<tbody id="memberStatsTbody">
<tr><td colspan="6" class="empty">로딩 중…</td></tr>
</tbody>
</table>
</div>
</section>
<!-- 지난주 vs 이번주 비교 -->
<section class="card">
<div class="list-header">
<h2>📈 지난주 vs 이번주 점수 비교</h2>
<button type="button" class="ghost small-btn" id="reloadComparisonBtn">새로고침</button>
</div>
<div class="table-wrap">
<table class="data-table" id="comparisonTable">
<thead>
<tr><th>닉네임</th><th class="num">지난주</th><th class="num">이번주</th><th class="num">증감</th></tr>
</thead>
<tbody id="comparisonTbody">
<tr><td colspan="4" class="empty">로딩 중…</td></tr>
</tbody>
</table>
</div>
</section>
<!-- 1달 성장 -->
<section class="card">
<div class="list-header">
<h2>🚀 최근 28일 성장 통계</h2>
<button type="button" class="ghost small-btn" id="reloadMonthlyBtn">새로고침</button>
</div>
<div class="table-wrap">
<table class="data-table" id="monthlyTable">
<thead>
<tr><th>닉네임</th><th class="num">시작</th><th class="num">최고</th><th class="num">성장</th><th class="num">신청수</th></tr>
</thead>
<tbody id="monthlyTbody">
<tr><td colspan="5" class="empty">로딩 중…</td></tr>
</tbody>
</table>
</div>
</section>
<!-- 비문원 등록 시도 -->
<section class="card" id="nonMemberSection" hidden>
<h2>⚠️ 비문원이 신청한 내역</h2>
<div class="table-wrap">
<table class="data-table">
<thead><tr><th>닉네임</th><th class="num">점수</th><th>성</th><th>시간</th></tr></thead>
<tbody id="nonMemberTbody"></tbody>
</table>
</div>
</section>
<!-- 문원 관리 -->
<section class="card">
<div class="list-header">
<h2>👥 문원 명단 관리</h2>
<span class="hint" id="memberCountLabel">-</span>
</div>
<div class="member-mgmt">
<details class="member-bulk">
<summary>📋 일괄 등록 / 교체 (펼치기)</summary>
<p class="hint">한 줄에 한 명씩 입력. 형식: <code>닉네임, 문파, 비고/직책</code>. 저장 시 기존 명단이 <strong>완전 교체</strong> 됩니다.</p>
<div class="ocr-section">
<div class="ocr-header">
<span class="ocr-title">📸 스크린샷에서 OCR 추출 (최대 15장)</span>
<span class="ocr-hint">바람의나라 클래식 문파원 화면 스샷에서 자동 추출. <code>문파장</code> (1명) / <code>부문파장</code> (2명) 자동 분류. <code>:</code> 왼쪽 텍스트만 사용.</span>
</div>
<div class="ocr-controls">
<label class="ghost small-btn ocr-pick-label" for="memberOcrFiles">
📷 사진 선택 / 추가
</label>
<input type="file" id="memberOcrFiles" accept="image/*" multiple hidden />
<select id="ocrGuildPick" class="ghost" aria-label="OCR 결과에 적용할 문파">
<option value="">문파 선택 (선택)</option>
</select>
<button type="button" class="primary small-btn" id="runOcrBtn" disabled>🔍 OCR 실행</button>
<button type="button" class="ghost small-btn" id="clearOcrBtn" disabled>✕ 사진 비우기</button>
</div>
<div class="ocr-previews" id="ocrPreviews"></div>
<div class="ocr-progress" id="ocrProgress" hidden>
<div class="ocr-progress-bar"><div class="ocr-progress-fill" id="ocrProgressFill"></div></div>
<div class="ocr-progress-text" id="ocrProgressText">준비 중…</div>
</div>
<p class="hint" id="ocrResultMsg"></p>
</div>
<textarea id="bulkArea" rows="10" placeholder="힐킵, 주스터콜, 문파장 현토성, 주스터콜, 부문파장 안다웅, 주스터콜, 부문파장 도탈출, 주스터콜 ..."></textarea>
<div class="actions">
<button type="button" class="ghost" id="loadCurrentBtn">현재 명단 불러오기</button>
<button type="button" class="primary" id="saveBulkBtn">저장 (전체 교체)</button>
</div>
</details>
<div class="member-add">
<input type="text" id="newMemberInput" placeholder="닉네임 추가" maxlength="20" />
<button type="button" class="primary" id="addMemberBtn">추가</button>
</div>
<div class="member-list" id="memberList"></div>
</div>
</section>
<!-- 성주 현황 관리 (전체 관리자 전용) -->
<section class="card scope-all-only" id="castleSection">
<h2>👑 성주 현황 관리</h2>
<p class="hint">현재 4성을 점령한 문파를 지정합니다. 비우고 저장 = 미점령.</p>
<div class="castle-lord-form" id="castleLordForm">
<div class="cl-row" data-castle="주작성">
<div class="cl-label">🔴 주작성</div>
<input type="text" data-field="guild" placeholder="성주 문파 (예: 연가, 매화…)" maxlength="20" list="guildOptions" />
<button type="button" class="primary small-btn" data-action="save">저장</button>
</div>
<div class="cl-row" data-castle="현무성">
<div class="cl-label">🔵 현무성</div>
<input type="text" data-field="guild" placeholder="성주 문파" maxlength="20" list="guildOptions" />
<button type="button" class="primary small-btn" data-action="save">저장</button>
</div>
<div class="cl-row" data-castle="청룡성">
<div class="cl-label">🟢 청룡성</div>
<input type="text" data-field="guild" placeholder="성주 문파" maxlength="20" list="guildOptions" />
<button type="button" class="primary small-btn" data-action="save">저장</button>
</div>
<div class="cl-row" data-castle="백호성">
<div class="cl-label">⚪ 백호성</div>
<input type="text" data-field="guild" placeholder="성주 문파" maxlength="20" list="guildOptions" />
<button type="button" class="primary small-btn" data-action="save">저장</button>
</div>
</div>
<datalist id="guildOptions"></datalist>
</section>
<!-- 관리자 계정 관리 (전체 관리자 전용) -->
<section class="card scope-all-only" id="accountsSection">
<h2>🗝️ 관리자 계정 관리</h2>
<p class="hint">문파별 관리자에게 발급할 계정. ID 는 문파명 추천.</p>
<div class="account-add">
<input type="text" id="acctUser" placeholder="아이디" maxlength="32" />
<input type="password" id="acctPw" placeholder="비밀번호" maxlength="64" />
<select id="acctScope">
<option value="">문파 선택…</option>
<option value="all">전체 (총관리자)</option>
</select>
<button type="button" class="primary small-btn" id="acctSaveBtn">저장/수정</button>
</div>
<p class="hint" id="acctMsg"></p>
<div class="account-list" id="accountList"></div>
</section>
<!-- 문파 모집 정보 -->
<section class="card">
<h2>📋 문파 모집/연락처 관리</h2>
<p class="hint">랜딩 페이지 문파 카드의 ℹ️ 클릭 시 표시됩니다. 본인 권한 범위의 문파만 편집 가능.</p>
<div class="guild-info-edit">
<div class="gi-row">
<label class="gi-label">대상 문파</label>
<select id="giGuildPick" class="gi-input"></select>
</div>
<div class="gi-row">
<label class="gi-label">모집 상태</label>
<div class="gi-toggle">
<label><input type="radio" name="giRecruiting" value="1"> ✨ 모집중</label>
<label><input type="radio" name="giRecruiting" value="0" checked> 마감</label>
</div>
</div>
<div class="gi-row">
<label class="gi-label" for="giDescription">문파 소개</label>
<textarea id="giDescription" class="gi-input" rows="3" maxlength="1000" placeholder="예) EU연합 본진 문파. 공성 활동 중심..."></textarea>
</div>
<div class="gi-row">
<label class="gi-label" for="giRequirements">가입 조건</label>
<textarea id="giRequirements" class="gi-input" rows="3" maxlength="500" placeholder="예) 4차 검황 이상, 공성 의무 참여, 디스코드 가입..."></textarea>
</div>
<div class="gi-row">
<label class="gi-label" for="giContact">연락처</label>
<input type="text" id="giContact" class="gi-input" maxlength="200" placeholder="예) 디스코드 vindobona / 카카오톡 ID xxx" />
</div>
<div class="gi-row">
<label class="gi-label" for="giDiscordInvite">디스코드 초대 링크</label>
<input type="url" id="giDiscordInvite" class="gi-input" maxlength="200" placeholder="https://discord.gg/xxxxxxx" pattern="https://(discord\.gg|discord\.com/invite)/.*" />
</div>
<div class="actions" style="display:flex; gap:8px; margin-top:8px;">
<button type="button" class="ghost" id="giLoadBtn">기존 불러오기</button>
<button type="button" class="primary" id="giSaveBtn">저장</button>
</div>
<p class="hint" id="giMsg"></p>
</div>
</section>
<!-- 공성 즉석 매칭 -->
<section class="card" id="siegeHelpSection">
<h2>📢 공성 즉석 보충 요청</h2>
<p class="hint">디스코드 채널에 즉시 알림 게시. <strong>DISCORD_WEBHOOK_URL 설정된 경우만 작동</strong>.</p>
<div class="siege-help-form">
<div class="gi-row">
<label class="gi-label">문파</label>
<select id="shGuildPick" class="gi-input"></select>
</div>
<div class="gi-row">
<label class="gi-label">필요 인원/직업</label>
<div class="sh-quick">
<button type="button" data-role="전사">🗡️ 전사</button>
<button type="button" data-role="도적">🥷 도적</button>
<button type="button" data-role="주술사">🔥 주술사</button>
<button type="button" data-role="도사">🌿 도사</button>
<button type="button" data-role="딜러">⚔️ 딜러</button>
<button type="button" data-role="체력형">🛡️ 체력형</button>
</div>
<input type="text" id="shRole" class="gi-input" maxlength="32" placeholder="또는 직접 입력 (예: 주술사 광역딜)" />
</div>
<div class="gi-row">
<label class="gi-label" for="shCount">인원</label>
<input type="number" id="shCount" class="gi-input" min="1" max="20" value="1" />
</div>
<div class="gi-row">
<label class="gi-label" for="shNote">비고 (선택)</label>
<input type="text" id="shNote" class="gi-input" maxlength="200" placeholder="예) 오늘 22:00까지, 디코 음성방 들어와주세요" />
</div>
<div class="gi-row">
<label class="gi-label gi-checkbox">
<input type="checkbox" id="shUrgent"> 🚨 긴급 (@everyone 멘션)
</label>
</div>
<div class="actions" style="display:flex; gap:8px; margin-top:8px;">
<button type="button" class="primary" id="shSendBtn">📢 보내기</button>
</div>
<p class="hint" id="shMsg"></p>
</div>
</section>
<!-- 연합 지침 편집 -->
<section class="card">
<h2>📜 연합 지침사항 편집</h2>
<p class="hint">랜딩 페이지에서 "연합 지침사항 열람" 으로 누구나 볼 수 있습니다. 빈 줄로 단락 구분 가능.</p>
<textarea id="guidelinesArea" rows="10" placeholder="예) 1. 공성 점수 인증은 매주 월~목 23:30 까지... 2. 정예 참전 여부는 반드시 선택... ..."></textarea>
<div class="actions" style="display:flex; gap:8px; margin-top:8px;">
<button type="button" class="ghost" id="reloadGuidelinesBtn">기존 불러오기</button>
<button type="button" class="primary" id="saveGuidelinesBtn">저장</button>
</div>
<p class="hint" id="guidelinesMsg"></p>
</section>
<!-- 보안 설정 -->
<section class="card">
<h2>🔐 비밀번호 변경</h2>
<p class="hint">관리자 비밀번호를 변경합니다. 변경 후엔 새 비밀번호로 다시 로그인해야 합니다.</p>
<div class="pw-change">
<input type="password" id="newPwInput" placeholder="새 비밀번호 (3자 이상)" minlength="3" maxlength="64" />
<input type="password" id="newPwConfirm" placeholder="새 비밀번호 확인" minlength="3" maxlength="64" />
<button type="button" class="primary" id="changePwBtn">변경</button>
</div>
<p class="hint" id="pwChangeMsg"></p>
</section>
</main>
<footer class="site-footer">
<div class="footer-line footer-powered">
<span class="eu-stars-mini" aria-hidden="true">★ ★ ★</span>
Powered by <strong>주술사연합회</strong>
<span class="eu-stars-mini" aria-hidden="true">★ ★ ★</span>
</div>
</footer>
<script src="shared.js?v=41" defer></script>
<script src="admin.js?v=41" defer></script>
</body>
</html>