djs-builder 0.7.0 → 0.7.1-8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2137 -272
- package/function/dash.js +705 -210
- package/function/function.js +255 -8
- package/function/level.js +337 -10
- package/function/log.js +407 -332
- package/handler/helper.js +46 -25
- package/handler/starter.js +47 -13
- package/package.json +23 -9
- package/views/dashboard.ejs +52 -18
- package/views/giveaways.ejs +1 -0
- package/views/guild.ejs +4 -0
- package/views/levels.ejs +672 -32
- package/views/logs.ejs +624 -0
package/views/levels.ejs
CHANGED
|
@@ -138,8 +138,35 @@
|
|
|
138
138
|
.action-btn { padding: 8px 12px; background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-secondary); cursor: pointer; font-size: 14px; transition: all 0.3s; }
|
|
139
139
|
.action-btn:hover { background: var(--accent); border-color: var(--accent); color: white; }
|
|
140
140
|
.action-btn.danger:hover { background: var(--danger); border-color: var(--danger); }
|
|
141
|
+
.action-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
142
|
+
.action-btn:disabled:hover { background: rgba(255,255,255,0.03); border-color: var(--border); color: var(--text-secondary); }
|
|
141
143
|
|
|
142
|
-
.
|
|
144
|
+
.reward-card { background: rgba(255,255,255,0.02); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; display: flex; align-items: center; justify-content: space-between; transition: all 0.3s; position: relative; overflow: hidden; }
|
|
145
|
+
.reward-card:hover { border-color: var(--accent); background: rgba(88, 101, 242, 0.05); transform: translateY(-2px); }
|
|
146
|
+
.reward-card::before { content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background: var(--role-color, var(--accent)); opacity: 0.5; }
|
|
147
|
+
.reward-icon-wrapper { width: 56px; height: 56px; border-radius: 14px; background: color-mix(in srgb, var(--role-color, var(--accent)) 15%, transparent); display: flex; align-items: center; justify-content: center; border: 1px solid color-mix(in srgb, var(--role-color, var(--accent)) 30%, transparent); }
|
|
148
|
+
.reward-icon-wrapper i { font-size: 28px; color: var(--role-color, var(--accent)); }
|
|
149
|
+
.reward-content { flex: 1; margin-right: 20px; }
|
|
150
|
+
.reward-level-tag { font-size: 11px; font-weight: 800; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px; }
|
|
151
|
+
.reward-role-name { font-size: 18px; font-weight: 700; color: var(--text-primary); display: flex; align-items: center; gap: 8px; }
|
|
152
|
+
.role-indicator { width: 12px; height: 12px; border-radius: 50%; background: var(--role-color, var(--accent)); box-shadow: 0 0 10px var(--role-color, var(--accent)); }
|
|
153
|
+
.empty-rewards-state { text-align: center; padding: 60px 40px; background: rgba(255,255,255,0.01); border: 2px dashed var(--border); border-radius: var(--radius); grid-column: 1 / -1; }
|
|
154
|
+
.empty-rewards-icon { font-size: 48px; color: var(--text-muted); opacity: 0.3; margin-bottom: 16px; }
|
|
155
|
+
.empty-rewards-text { font-size: 18px; font-weight: 700; margin-bottom: 8px; }
|
|
156
|
+
.empty-rewards-hint { color: var(--text-secondary); font-size: 14px; max-width: 400px; margin: 0 auto; }
|
|
157
|
+
|
|
158
|
+
.readonly-notice { display: flex; align-items: center; gap: 14px; padding: 16px 24px; background: rgba(88, 101, 242, 0.1); border: 2px solid var(--accent); border-radius: var(--radius); margin-bottom: 24px; border-style: dashed; }
|
|
159
|
+
|
|
160
|
+
.role-select { width: 100%; padding: 14px 18px; background: linear-gradient(145deg, #1e2433, #171c28); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 14px; cursor: pointer; transition: all 0.3s; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: left 14px center; padding-left: 40px; font-weight: 500; }
|
|
161
|
+
.role-select:hover { border-color: var(--accent); background: linear-gradient(145deg, #252b3d, #1e2433); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
|
|
162
|
+
.role-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
|
163
|
+
.role-select:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
164
|
+
.role-select option { background: #1a1f2e; color: var(--text-primary); padding: 14px; font-size: 14px; }
|
|
165
|
+
.role-select option:checked { background: var(--accent); color: white; }
|
|
166
|
+
.readonly-notice i { font-size: 24px; color: var(--accent); }
|
|
167
|
+
.readonly-notice-text { flex: 1; }
|
|
168
|
+
.readonly-notice-title { font-weight: 700; font-size: 15px; margin-bottom: 2px; }
|
|
169
|
+
.readonly-notice-desc { font-size: 13px; color: var(--text-secondary); }
|
|
143
170
|
.empty-state i { font-size: 56px; color: var(--text-muted); margin-bottom: 20px; opacity: 0.3; }
|
|
144
171
|
.empty-state h3 { font-size: 20px; margin-bottom: 10px; }
|
|
145
172
|
.empty-state p { color: var(--text-secondary); }
|
|
@@ -202,6 +229,142 @@
|
|
|
202
229
|
.modal-content { width: 95%; margin: 12px; padding: 20px; }
|
|
203
230
|
}
|
|
204
231
|
@media (min-width: 993px) { .mobile-menu-btn { display: none; } }
|
|
232
|
+
|
|
233
|
+
/* Settings Styles */
|
|
234
|
+
.status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: 50px; font-size: 12px; font-weight: 600; }
|
|
235
|
+
.status-badge.success { background: rgba(16, 185, 129, 0.15); color: var(--success); border: 1px solid rgba(16, 185, 129, 0.3); }
|
|
236
|
+
|
|
237
|
+
.settings-section { background: linear-gradient(145deg, var(--bg-card), rgba(18,18,26,0.6)); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 20px; overflow: hidden; }
|
|
238
|
+
.section-header { padding: 20px 24px; display: flex; align-items: center; justify-content: space-between; cursor: pointer; transition: background 0.3s; }
|
|
239
|
+
.section-header:hover { background: rgba(255,255,255,0.02); }
|
|
240
|
+
.section-title { font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 10px; }
|
|
241
|
+
.section-title i { color: var(--accent); font-size: 20px; }
|
|
242
|
+
.toggle-icon { font-size: 20px; color: var(--text-muted); transition: transform 0.3s; }
|
|
243
|
+
.section-content { padding: 0 24px 24px; }
|
|
244
|
+
.section-content.collapsed { display: none; }
|
|
245
|
+
|
|
246
|
+
.setting-card { background: rgba(255,255,255,0.02); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 18px; display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
|
247
|
+
.setting-card.vertical { flex-direction: column; align-items: stretch; gap: 14px; }
|
|
248
|
+
.setting-card.mini { flex-direction: column; align-items: flex-start; gap: 10px; padding: 14px; }
|
|
249
|
+
.setting-info { flex: 1; }
|
|
250
|
+
.setting-label { font-weight: 600; font-size: 14px; margin-bottom: 4px; }
|
|
251
|
+
.setting-desc { font-size: 12px; color: var(--text-muted); }
|
|
252
|
+
|
|
253
|
+
.setting-select { padding: 14px 18px; background: linear-gradient(145deg, #1e2433, #171c28); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 14px; width: 100%; cursor: pointer; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: left 14px center; padding-left: 40px; transition: all 0.3s; font-weight: 500; }
|
|
254
|
+
.setting-select:hover { border-color: var(--accent); background: linear-gradient(145deg, #252b3d, #1e2433); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
|
|
255
|
+
.setting-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
|
256
|
+
.setting-select option { background: #1a1f2e; color: var(--text-primary); padding: 14px 18px; font-size: 14px; }
|
|
257
|
+
.setting-select option:checked { background: var(--accent); color: white; }
|
|
258
|
+
.setting-input { padding: 10px 14px; background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 14px; width: 100%; }
|
|
259
|
+
.setting-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
|
260
|
+
.setting-input.small { width: 80px; }
|
|
261
|
+
|
|
262
|
+
.input-with-btn { display: flex; gap: 10px; }
|
|
263
|
+
.input-with-btn input { flex: 1; }
|
|
264
|
+
|
|
265
|
+
.inline-settings { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
266
|
+
.inline-settings span { color: var(--text-muted); font-size: 13px; }
|
|
267
|
+
|
|
268
|
+
.settings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-top: 16px; }
|
|
269
|
+
|
|
270
|
+
.toggle-switch { position: relative; display: inline-block; width: 52px; height: 28px; flex-shrink: 0; }
|
|
271
|
+
.toggle-switch input { opacity: 0; width: 0; height: 0; }
|
|
272
|
+
.toggle-slider { position: absolute; cursor: pointer; inset: 0; background: rgba(255,255,255,0.1); transition: 0.3s; border-radius: 28px; }
|
|
273
|
+
.toggle-slider:before { position: absolute; content: ""; height: 22px; width: 22px; right: 3px; bottom: 3px; background: white; transition: 0.3s; border-radius: 50%; }
|
|
274
|
+
.toggle-switch input:checked + .toggle-slider { background: var(--accent); }
|
|
275
|
+
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(-24px); }
|
|
276
|
+
|
|
277
|
+
.add-reward-row { display: flex; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; align-items: center; padding: 18px; background: linear-gradient(135deg, rgba(88,101,242,0.05), rgba(155,89,182,0.03)); border-radius: var(--radius); border: 2px dashed rgba(88,101,242,0.3); }
|
|
278
|
+
.rewards-list, .blacklist-list { display: grid; gap: 12px; }
|
|
279
|
+
.rewards-list { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
|
|
280
|
+
.reward-item { display: flex; align-items: center; gap: 14px; padding: 16px 18px; background: linear-gradient(145deg, rgba(88,101,242,0.1), rgba(155,89,182,0.06)); border: 1px solid rgba(88,101,242,0.2); border-radius: 14px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; }
|
|
281
|
+
.reward-item::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: linear-gradient(180deg, var(--accent), #9b59b6); opacity: 0; transition: opacity 0.3s; }
|
|
282
|
+
.reward-item:hover { border-color: var(--accent); transform: translateY(-3px); box-shadow: 0 12px 28px rgba(88,101,242,0.2); }
|
|
283
|
+
.reward-item:hover::before { opacity: 1; }
|
|
284
|
+
.blacklist-list { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
|
|
285
|
+
.blacklist-item { display: flex; align-items: center; gap: 12px; padding: 14px 18px; background: rgba(239,68,68,0.05); border: 1px solid rgba(239,68,68,0.2); border-radius: var(--radius); transition: all 0.3s; }
|
|
286
|
+
.blacklist-item:hover { border-color: var(--danger); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(239,68,68,0.1); }
|
|
287
|
+
.reward-level { display: flex; align-items: center; justify-content: center; min-width: 52px; height: 52px; background: linear-gradient(145deg, var(--accent), #9b59b6); border-radius: 14px; font-weight: 900; font-size: 18px; color: white; flex-shrink: 0; box-shadow: 0 4px 12px rgba(88,101,242,0.3); }
|
|
288
|
+
.reward-level i { display: none; }
|
|
289
|
+
.reward-info { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
290
|
+
.reward-info .level-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.8px; font-weight: 600; }
|
|
291
|
+
.reward-info .role-name { font-weight: 700; font-size: 15px; display: flex; align-items: center; gap: 10px; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
292
|
+
.reward-arrow { display: none; }
|
|
293
|
+
.reward-role { font-weight: 600; flex: 1; display: flex; align-items: center; gap: 8px; }
|
|
294
|
+
.reward-role .role-dot, .role-name .role-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 10px currentColor; }
|
|
295
|
+
.channel-name { flex: 1; color: var(--text-secondary); font-weight: 500; display: flex; align-items: center; gap: 8px; }
|
|
296
|
+
.channel-name i { color: var(--danger); }
|
|
297
|
+
|
|
298
|
+
.btn-icon { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.05); border: 1px solid var(--border); border-radius: 10px; color: var(--text-muted); cursor: pointer; transition: all 0.3s; }
|
|
299
|
+
.btn-icon:hover { background: var(--accent); border-color: var(--accent); color: white; transform: scale(1.1); }
|
|
300
|
+
.btn-icon.danger:hover { background: var(--danger); border-color: var(--danger); }
|
|
301
|
+
|
|
302
|
+
.empty-mini { text-align: center; padding: 24px; color: var(--text-muted); font-size: 13px; }
|
|
303
|
+
|
|
304
|
+
.empty-rewards-state { text-align: center; padding: 40px 20px; background: linear-gradient(145deg, rgba(88,101,242,0.05), rgba(155,89,182,0.03)); border: 2px dashed rgba(88,101,242,0.2); border-radius: 16px; }
|
|
305
|
+
.empty-rewards-icon { width: 72px; height: 72px; margin: 0 auto 16px; display: flex; align-items: center; justify-content: center; background: linear-gradient(145deg, rgba(88,101,242,0.15), rgba(155,89,182,0.1)); border-radius: 50%; }
|
|
306
|
+
.empty-rewards-icon i { font-size: 32px; background: linear-gradient(135deg, var(--accent), #9b59b6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
|
|
307
|
+
.empty-rewards-text { font-size: 16px; font-weight: 700; color: var(--text-primary); margin-bottom: 8px; }
|
|
308
|
+
.empty-rewards-hint { font-size: 13px; color: var(--text-muted); max-width: 280px; margin: 0 auto; line-height: 1.5; }
|
|
309
|
+
|
|
310
|
+
.notice-card { display: flex; align-items: flex-start; gap: 16px; padding: 20px 24px; background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3); border-radius: var(--radius); margin-bottom: 24px; }
|
|
311
|
+
.notice-card i { font-size: 24px; color: var(--warning); flex-shrink: 0; }
|
|
312
|
+
.notice-card strong { display: block; margin-bottom: 4px; color: var(--warning); }
|
|
313
|
+
.notice-card p { font-size: 13px; color: var(--text-secondary); margin: 0; }
|
|
314
|
+
.notice-card code { background: rgba(0,0,0,0.3); padding: 2px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; }
|
|
315
|
+
|
|
316
|
+
.btn-warning { background: linear-gradient(135deg, var(--warning), #d97706); color: white; }
|
|
317
|
+
|
|
318
|
+
/* Placeholders Help Box */
|
|
319
|
+
.placeholders-help { background: linear-gradient(135deg, rgba(88,101,242,0.1), rgba(155,89,182,0.05)); border: 1px solid rgba(88,101,242,0.2); border-radius: var(--radius-sm); padding: 16px; margin-top: 12px; }
|
|
320
|
+
.placeholders-title { font-size: 13px; font-weight: 600; color: var(--accent); margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
|
|
321
|
+
.placeholders-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; }
|
|
322
|
+
.placeholder-item { display: flex; align-items: center; gap: 8px; font-size: 12px; }
|
|
323
|
+
.placeholder-code { background: rgba(0,0,0,0.3); padding: 3px 8px; border-radius: 4px; font-family: monospace; color: var(--accent); }
|
|
324
|
+
.placeholder-desc { color: var(--text-muted); }
|
|
325
|
+
|
|
326
|
+
/* XP Settings Modal Styles */
|
|
327
|
+
.xp-settings-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
|
|
328
|
+
.xp-setting-card { background: linear-gradient(145deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01)); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 16px; text-align: center; }
|
|
329
|
+
.xp-setting-card:hover { border-color: var(--accent); }
|
|
330
|
+
.xp-setting-icon { font-size: 28px; margin-bottom: 8px; }
|
|
331
|
+
.xp-setting-card:nth-child(1) .xp-setting-icon { color: #10b981; }
|
|
332
|
+
.xp-setting-card:nth-child(2) .xp-setting-icon { color: #f59e0b; }
|
|
333
|
+
.xp-setting-card:nth-child(3) .xp-setting-icon { color: #8b5cf6; }
|
|
334
|
+
.xp-setting-card:nth-child(4) .xp-setting-icon { color: #ec4899; }
|
|
335
|
+
.xp-setting-label { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }
|
|
336
|
+
.xp-setting-card input { text-align: center; font-size: 18px; font-weight: 700; background: transparent; border: none; color: var(--text-primary); width: 100%; }
|
|
337
|
+
.xp-setting-card input:focus { outline: none; }
|
|
338
|
+
|
|
339
|
+
/* Cooldown & Bonus Cards */
|
|
340
|
+
.bonus-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-top: 20px; }
|
|
341
|
+
.bonus-card { background: linear-gradient(145deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01)); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 20px; }
|
|
342
|
+
.bonus-card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
|
|
343
|
+
.bonus-card-title { font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
|
344
|
+
.bonus-card-title i { color: var(--accent); }
|
|
345
|
+
.bonus-card-body { display: flex; align-items: center; gap: 12px; }
|
|
346
|
+
.bonus-card-body span { color: var(--text-muted); font-size: 13px; }
|
|
347
|
+
|
|
348
|
+
/* Role Selector Improved */
|
|
349
|
+
.role-select-wrapper { position: relative; flex: 1; }
|
|
350
|
+
.role-select { width: 100%; padding: 14px 18px; background: linear-gradient(145deg, #1e2433, #171c28); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 14px; cursor: pointer; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: left 14px center; padding-left: 40px; font-weight: 500; transition: all 0.3s; }
|
|
351
|
+
.role-select:hover { border-color: var(--accent); background: linear-gradient(145deg, #252b3d, #1e2433); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
|
|
352
|
+
.role-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
|
353
|
+
.role-select option { background: #1a1f2e; padding: 14px; font-size: 14px; }
|
|
354
|
+
|
|
355
|
+
/* Level Input Wrapper */
|
|
356
|
+
.level-input-wrapper { position: relative; display: inline-flex; align-items: center; }
|
|
357
|
+
.level-input-wrapper i { position: absolute; right: 12px; color: var(--accent); font-size: 16px; pointer-events: none; }
|
|
358
|
+
.level-input-wrapper input { padding-right: 36px !important; }
|
|
359
|
+
|
|
360
|
+
/* Tabs System */
|
|
361
|
+
.tabs-nav { display: flex; gap: 8px; margin-bottom: 28px; background: rgba(255,255,255,0.02); padding: 6px; border-radius: var(--radius); border: 1px solid var(--border); }
|
|
362
|
+
.tab-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 10px; padding: 14px 24px; border-radius: var(--radius-sm); font-weight: 600; font-size: 14px; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border: none; background: transparent; color: var(--text-secondary); }
|
|
363
|
+
.tab-btn:hover { background: rgba(255,255,255,0.03); color: var(--text-primary); }
|
|
364
|
+
.tab-btn.active { background: linear-gradient(135deg, var(--accent), var(--accent-light)); color: white; box-shadow: 0 4px 15px var(--accent-glow); }
|
|
365
|
+
.tab-btn i { font-size: 18px; }
|
|
366
|
+
.tab-content { display: none; animation: fadeIn 0.3s ease; }
|
|
367
|
+
.tab-content.active { display: block; }
|
|
205
368
|
</style>
|
|
206
369
|
</head>
|
|
207
370
|
<body>
|
|
@@ -227,6 +390,7 @@
|
|
|
227
390
|
<div class="nav-section"><div class="nav-section-title">الميزات</div>
|
|
228
391
|
<a href="/dashboard/<%= guild.id %>/levels" class="nav-link active"><i class="ri-bar-chart-grouped-line"></i><span>نظام المستويات</span></a>
|
|
229
392
|
<a href="/dashboard/<%= guild.id %>/giveaways" class="nav-link"><i class="ri-gift-2-line"></i><span>الهدايا</span></a>
|
|
393
|
+
<a href="/dashboard/<%= guild.id %>/logs" class="nav-link"><i class="ri-shield-check-line"></i><span>سجلات المراقبة</span></a>
|
|
230
394
|
</div>
|
|
231
395
|
</nav>
|
|
232
396
|
<div class="sidebar-footer"><a href="/dashboard" class="back-btn"><i class="ri-arrow-right-line"></i><span>العودة للوحة التحكم</span></a></div>
|
|
@@ -235,49 +399,325 @@
|
|
|
235
399
|
<main class="main">
|
|
236
400
|
<div class="page-header animate-in">
|
|
237
401
|
<div><h1><i class="ri-bar-chart-grouped-line"></i> نظام المستويات</h1><p>إدارة مستويات ونقاط الأعضاء</p></div>
|
|
402
|
+
<div style="display: flex; gap: 10px;">
|
|
403
|
+
<% if (!canModify) { %>
|
|
404
|
+
<span class="status-badge" style="background: rgba(245, 158, 11, 0.1); color: var(--warning); border: 1px solid rgba(245, 158, 11, 0.3);"><i class="ri-lock-line"></i> وضع القراءة فقط</span>
|
|
405
|
+
<% } %>
|
|
406
|
+
<% if (databaseEnabled) { %>
|
|
407
|
+
<span class="status-badge success"><i class="ri-database-2-line"></i> نظام المستويات مفعّل</span>
|
|
408
|
+
<% } else if (mongoConnected) { %>
|
|
409
|
+
<span class="status-badge" style="background: rgba(245, 158, 11, 0.1); color: var(--warning); border: 1px solid rgba(245, 158, 11, 0.3);"><i class="ri-error-warning-line"></i> غير مُفعّل</span>
|
|
410
|
+
<% } else { %>
|
|
411
|
+
<span class="status-badge" style="background: rgba(239, 68, 68, 0.1); color: var(--danger); border: 1px solid rgba(239, 68, 68, 0.3);"><i class="ri-database-2-line"></i> قاعدة البيانات غير متصلة</span>
|
|
412
|
+
<% } %>
|
|
413
|
+
</div>
|
|
238
414
|
</div>
|
|
239
415
|
|
|
240
|
-
|
|
241
|
-
<div class="
|
|
242
|
-
|
|
243
|
-
<div class="
|
|
244
|
-
<
|
|
245
|
-
<
|
|
246
|
-
<div class="search-results" id="searchResults"></div>
|
|
416
|
+
<% if (!canModify) { %>
|
|
417
|
+
<div class="readonly-notice animate-in">
|
|
418
|
+
<i class="ri-information-line"></i>
|
|
419
|
+
<div class="readonly-notice-text">
|
|
420
|
+
<div class="readonly-notice-title">وضع العرض فقط مفعل</div>
|
|
421
|
+
<div class="readonly-notice-desc">تم تعطيل التعديل من لوحة التحكم في إعدادات البوت الرئيسية. يمكنك عرض البيانات ولكن لا يمكنك تعديلها.</div>
|
|
247
422
|
</div>
|
|
248
|
-
<button class="btn btn-primary" onclick="openAddXPModal()"><i class="ri-add-line"></i> إضافة XP لعضو</button>
|
|
249
423
|
</div>
|
|
424
|
+
<% } %>
|
|
425
|
+
|
|
426
|
+
<% if (!databaseEnabled && mongoConnected) { %>
|
|
427
|
+
<div class="readonly-notice animate-in" style="border-color: var(--warning); background: rgba(245, 158, 11, 0.1);">
|
|
428
|
+
<i class="ri-error-warning-line" style="color: var(--warning);"></i>
|
|
429
|
+
<div class="readonly-notice-text">
|
|
430
|
+
<div class="readonly-notice-title">نظام المستويات غير مُفعّل</div>
|
|
431
|
+
<div class="readonly-notice-desc">لتفعيل نظام المستويات، استخدم <code style="background: rgba(0,0,0,0.3); padding: 2px 6px; border-radius: 4px;">setLevel(client, [{ id: "GUILD_ID", Dashboard: true }])</code> في ملف البوت الرئيسي.</div>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
<% } %>
|
|
435
|
+
|
|
436
|
+
<!-- Tabs Navigation -->
|
|
437
|
+
<div class="tabs-nav animate-in" style="animation-delay: 0.02s">
|
|
438
|
+
<button class="tab-btn active" onclick="switchTab('leaderboard')" id="tab-leaderboard">
|
|
439
|
+
<i class="ri-trophy-line"></i> الترتيب والمتصدرون
|
|
440
|
+
</button>
|
|
441
|
+
<button class="tab-btn" onclick="switchTab('settings')" id="tab-settings">
|
|
442
|
+
<i class="ri-settings-4-line"></i> إعدادات النظام
|
|
443
|
+
</button>
|
|
250
444
|
</div>
|
|
251
445
|
|
|
252
|
-
|
|
446
|
+
<!-- Leaderboard Tab Content -->
|
|
447
|
+
<div class="tab-content active" id="content-leaderboard">
|
|
448
|
+
<div class="search-section animate-in" style="animation-delay: 0.05s">
|
|
449
|
+
<div class="search-title"><i class="ri-search-line"></i> البحث عن عضو وإعطاء XP</div>
|
|
450
|
+
<div class="search-row">
|
|
451
|
+
<div class="search-input">
|
|
452
|
+
<i class="ri-search-line"></i>
|
|
453
|
+
<input type="text" id="memberSearch" placeholder="ابحث عن عضو بالاسم أو المعرف..." autocomplete="off">
|
|
454
|
+
<div class="search-results" id="searchResults"></div>
|
|
455
|
+
</div>
|
|
456
|
+
<button class="btn btn-primary" onclick="openAddXPModal()" <%= !canModify ? 'disabled' : '' %>><i class="ri-add-line"></i> إضافة XP لعضو</button>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<% const totalXP = leaderboard.reduce((sum, u) => sum + (u.totalXP || 0), 0); const highestLevel = leaderboard.length > 0 ? (leaderboard[0].level || 0) : 0; %>
|
|
461
|
+
|
|
462
|
+
<div class="stats-row animate-in" style="animation-delay: 0.1s">
|
|
463
|
+
<div class="mini-stat"><i class="ri-group-line"></i><div><div class="mini-stat-value"><%= leaderboard.length %></div><div class="mini-stat-label">مستخدم مسجل</div></div></div>
|
|
464
|
+
<div class="mini-stat"><i class="ri-bar-chart-grouped-line"></i><div><div class="mini-stat-value"><%= highestLevel %></div><div class="mini-stat-label">أعلى مستوى</div></div></div>
|
|
465
|
+
<div class="mini-stat"><i class="ri-star-line"></i><div><div class="mini-stat-value"><%= totalXP.toLocaleString() %></div><div class="mini-stat-label">إجمالي XP</div></div></div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<% if (leaderboard.length > 0) { %>
|
|
469
|
+
<%
|
|
470
|
+
// XP calculation function based on leveling config
|
|
471
|
+
function calculateXpNeeded(level, config) {
|
|
472
|
+
const leveling = config && config.leveling ? config.leveling : { type: 'line', amount: 100 };
|
|
473
|
+
const type = leveling.type || 'line';
|
|
474
|
+
const amount = leveling.amount || 100;
|
|
475
|
+
|
|
476
|
+
switch (type) {
|
|
477
|
+
case 'exponential':
|
|
478
|
+
return Math.floor(amount * Math.pow(2, level));
|
|
479
|
+
case 'balanced':
|
|
480
|
+
const a = leveling.a || 5;
|
|
481
|
+
const b = leveling.b || 50;
|
|
482
|
+
const c = leveling.c || 100;
|
|
483
|
+
return Math.floor(a * Math.pow(level, 2) + b * level + c);
|
|
484
|
+
case 'line':
|
|
485
|
+
default:
|
|
486
|
+
return (level + 1) * amount;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
%>
|
|
490
|
+
<div class="leaderboard animate-in" style="animation-delay: 0.15s">
|
|
491
|
+
<div class="leaderboard-header"><h2><i class="ri-trophy-line"></i> المتصدرون</h2></div>
|
|
492
|
+
<table class="leaderboard-table">
|
|
493
|
+
<thead><tr><th>الترتيب</th><th>المستخدم</th><th>المستوى</th><th>نقاط XP</th><th>إجراءات</th></tr></thead>
|
|
494
|
+
<tbody id="leaderboardBody">
|
|
495
|
+
<% leaderboard.forEach((user, index) => { if (!user) return; const userXP = user.totalXP || 0; const userLevel = user.level || 0; const xpNeeded = calculateXpNeeded(userLevel, levelConfigDb); const progress = xpNeeded > 0 ? Math.min((userXP / xpNeeded) * 100, 100) : 0; const rankClass = index === 0 ? 'gold' : index === 1 ? 'silver' : index === 2 ? 'bronze' : ''; %>
|
|
496
|
+
<tr data-user-id="<%= user.userId %>">
|
|
497
|
+
<td><span class="rank <%= rankClass %>">#<%= index + 1 %></span></td>
|
|
498
|
+
<td><div class="user-cell"><img src="<%= user.avatar %>" data-user-id="<%= user.userId %>" alt="" style="border-radius: 12px; border: 2px solid <%= index === 0 ? '#FFD700' : index === 1 ? '#C0C0C0' : index === 2 ? '#CD7F32' : 'var(--border)' %>; padding: 2px; width: 48px; height: 48px;"><div><div class="user-name user-name-<%= user.userId %>"><%= user.username %></div><div class="user-id"><%= user.userId %></div></div></div></td>
|
|
499
|
+
<td><span class="level-badge" style="background: <%= index === 0 ? 'linear-gradient(135deg, #FFD700, #FDB931)' : index === 1 ? 'linear-gradient(135deg, #C0C0C0, #8E8E8E)' : index === 2 ? 'linear-gradient(135deg, #CD7F32, #A0522D)' : '' %>"><%= userLevel %></span></td>
|
|
500
|
+
<td><div class="xp-bar"><div class="xp-text"><%= userXP.toLocaleString() %> / <%= xpNeeded.toLocaleString() %> XP</div><div class="xp-progress"><div class="xp-fill" style="width: <%= progress %>%; background: <%= index === 0 ? 'linear-gradient(90deg, #FFD700, #FDB931)' : '' %>"></div></div></div></td>
|
|
501
|
+
<td><div class="actions-cell"><button class="action-btn" onclick="openEditModal('<%= user.userId %>', <%= userXP %>, <%= userLevel %>)" title="تعديل" <%= !canModify ? 'disabled' : '' %>><i class="ri-edit-line"></i></button><button class="action-btn danger" onclick="resetUser('<%= user.userId %>')" title="إعادة تعيين" <%= !canModify ? 'disabled' : '' %>><i class="ri-refresh-line"></i></button></div></td>
|
|
502
|
+
</tr>
|
|
503
|
+
<% }); %>
|
|
504
|
+
</tbody>
|
|
505
|
+
</table>
|
|
506
|
+
</div>
|
|
507
|
+
<% } else { %>
|
|
508
|
+
<div class="empty-state animate-in"><i class="ri-bar-chart-grouped-line"></i><h3>لا توجد بيانات</h3><p>لم يكتسب أي عضو نقاط XP بعد</p></div>
|
|
509
|
+
<% } %>
|
|
510
|
+
</div>
|
|
253
511
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
512
|
+
<!-- Settings Tab Content -->
|
|
513
|
+
<div class="tab-content" id="content-settings">
|
|
514
|
+
<% if (databaseEnabled) { %>
|
|
515
|
+
<!-- Level Settings Section -->
|
|
516
|
+
<div class="settings-section animate-in" style="animation-delay: 0.03s">
|
|
517
|
+
<div class="section-header" onclick="toggleSection('levelSettings')">
|
|
518
|
+
<div class="section-title"><i class="ri-settings-4-line"></i> إعدادات XP المتقدمة</div>
|
|
519
|
+
<i class="ri-arrow-down-s-line toggle-icon" id="levelSettingsIcon"></i>
|
|
520
|
+
</div>
|
|
521
|
+
<div class="section-content collapsed" id="levelSettings">
|
|
522
|
+
<!-- Enable/Disable Toggle -->
|
|
523
|
+
<div class="setting-card">
|
|
524
|
+
<div class="setting-info">
|
|
525
|
+
<div class="setting-label">تفعيل النظام</div>
|
|
526
|
+
<div class="setting-desc">تفعيل أو تعطيل نظام المستويات في هذا السيرفر</div>
|
|
527
|
+
</div>
|
|
528
|
+
<label class="toggle-switch">
|
|
529
|
+
<input type="checkbox" id="systemEnabled" <%= (levelConfigDb && !levelConfigDb.disabled) ? 'checked' : '' %> onchange="toggleSystem()" <%= !canModify ? 'disabled' : '' %>>
|
|
530
|
+
<span class="toggle-slider"></span>
|
|
531
|
+
</label>
|
|
532
|
+
</div>
|
|
533
|
+
|
|
534
|
+
<!-- XP Settings Grid -->
|
|
535
|
+
<div class="xp-settings-grid" style="margin-top: 16px;">
|
|
536
|
+
<div class="xp-setting-card">
|
|
537
|
+
<div class="xp-setting-icon"><i class="ri-arrow-down-line"></i></div>
|
|
538
|
+
<div class="xp-setting-label">أقل XP لكل رسالة</div>
|
|
539
|
+
<input type="number" id="minXP" value="<%= levelConfigDb && levelConfigDb.minXP ? levelConfigDb.minXP : 15 %>" min="1" <%= !canModify ? 'disabled' : '' %>>
|
|
540
|
+
</div>
|
|
541
|
+
<div class="xp-setting-card">
|
|
542
|
+
<div class="xp-setting-icon"><i class="ri-arrow-up-line"></i></div>
|
|
543
|
+
<div class="xp-setting-label">أكثر XP لكل رسالة</div>
|
|
544
|
+
<input type="number" id="maxXP" value="<%= levelConfigDb && levelConfigDb.maxXP ? levelConfigDb.maxXP : 25 %>" min="1" <%= !canModify ? 'disabled' : '' %>>
|
|
545
|
+
</div>
|
|
546
|
+
<div class="xp-setting-card">
|
|
547
|
+
<div class="xp-setting-icon"><i class="ri-percent-line"></i></div>
|
|
548
|
+
<div class="xp-setting-label">مضاعف XP</div>
|
|
549
|
+
<input type="number" id="xpMultiplier" value="<%= levelConfigDb && levelConfigDb.xpMultiplier ? levelConfigDb.xpMultiplier : 1 %>" min="0.1" step="0.1" <%= !canModify ? 'disabled' : '' %>>
|
|
550
|
+
</div>
|
|
551
|
+
<div class="xp-setting-card">
|
|
552
|
+
<div class="xp-setting-icon"><i class="ri-trophy-line"></i></div>
|
|
553
|
+
<div class="xp-setting-label">أقصى مستوى</div>
|
|
554
|
+
<input type="number" id="maxLevel" value="<%= levelConfigDb && levelConfigDb.maxLevel ? levelConfigDb.maxLevel : 100 %>" min="1" <%= !canModify ? 'disabled' : '' %>>
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
|
|
558
|
+
<!-- Cooldown -->
|
|
559
|
+
<div class="setting-card" style="margin-top: 16px;">
|
|
560
|
+
<div class="setting-info">
|
|
561
|
+
<div class="setting-label"><i class="ri-timer-line" style="color: var(--accent); margin-left: 6px;"></i> Cooldown</div>
|
|
562
|
+
<div class="setting-desc">المدة بالثواني بين الرسائل (0 = معطل)</div>
|
|
563
|
+
</div>
|
|
564
|
+
<input type="number" id="cooldown" class="setting-input" style="width: 100px;" value="<%= levelConfigDb && levelConfigDb.cooldown ? levelConfigDb.cooldown : 0 %>" min="0" <%= !canModify ? 'disabled' : '' %>>
|
|
565
|
+
</div>
|
|
566
|
+
|
|
567
|
+
<!-- Leveling Type -->
|
|
568
|
+
<div class="setting-card vertical" style="margin-top: 16px;">
|
|
569
|
+
<div class="setting-info">
|
|
570
|
+
<div class="setting-label"><i class="ri-bar-chart-line" style="color: var(--accent); margin-left: 6px;"></i> نوع حساب المستوى</div>
|
|
571
|
+
<div class="setting-desc">اختر طريقة حساب XP المطلوب للترقية</div>
|
|
572
|
+
</div>
|
|
573
|
+
<select id="levelingType" class="setting-select" onchange="toggleLevelingOptions()" <%= !canModify ? 'disabled' : '' %>>
|
|
574
|
+
<option value="line" <%= levelConfigDb && levelConfigDb.leveling && levelConfigDb.leveling.type === 'line' ? 'selected' : (!levelConfigDb || !levelConfigDb.leveling ? 'selected' : '') %>>خطي (سهل)</option>
|
|
575
|
+
<option value="exponential" <%= levelConfigDb && levelConfigDb.leveling && levelConfigDb.leveling.type === 'exponential' ? 'selected' : '' %>>أسي (صعب)</option>
|
|
576
|
+
<option value="balanced" <%= levelConfigDb && levelConfigDb.leveling && levelConfigDb.leveling.type === 'balanced' ? 'selected' : '' %>>متوازن (متوسط)</option>
|
|
577
|
+
</select>
|
|
578
|
+
</div>
|
|
579
|
+
|
|
580
|
+
<!-- Leveling Amount -->
|
|
581
|
+
<div id="levelingAmountGroup" class="setting-card" style="margin-top: 16px;">
|
|
582
|
+
<div class="setting-info">
|
|
583
|
+
<div class="setting-label">قيمة XP الأساسية (amount)</div>
|
|
584
|
+
<div class="setting-desc">للخطي: XP = (level + 1) × amount | للأسي: XP = amount × 2^level</div>
|
|
585
|
+
</div>
|
|
586
|
+
<input type="number" id="levelingAmount" class="setting-input" style="width: 100px;" value="<%= levelConfigDb && levelConfigDb.leveling && levelConfigDb.leveling.amount ? levelConfigDb.leveling.amount : 100 %>" min="1" <%= !canModify ? 'disabled' : '' %>>
|
|
587
|
+
</div>
|
|
588
|
+
|
|
589
|
+
<!-- Balanced Options -->
|
|
590
|
+
<div id="balancedOptions" style="display: none; margin-top: 16px; padding: 16px; background: rgba(255,255,255,0.02); border-radius: 12px; border: 1px solid var(--border);">
|
|
591
|
+
<div class="setting-desc" style="margin-bottom: 12px;"><i class="ri-function-line"></i> المعادلة: XP = a×level² + b×level + c</div>
|
|
592
|
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;">
|
|
593
|
+
<div>
|
|
594
|
+
<label style="color: var(--muted); font-size: 12px;">معامل a (level²)</label>
|
|
595
|
+
<input type="number" id="levelingA" class="setting-input" value="<%= levelConfigDb && levelConfigDb.leveling && levelConfigDb.leveling.a ? levelConfigDb.leveling.a : 5 %>" min="1" <%= !canModify ? 'disabled' : '' %>>
|
|
596
|
+
</div>
|
|
597
|
+
<div>
|
|
598
|
+
<label style="color: var(--muted); font-size: 12px;">معامل b (level)</label>
|
|
599
|
+
<input type="number" id="levelingB" class="setting-input" value="<%= levelConfigDb && levelConfigDb.leveling && levelConfigDb.leveling.b ? levelConfigDb.leveling.b : 50 %>" min="1" <%= !canModify ? 'disabled' : '' %>>
|
|
600
|
+
</div>
|
|
601
|
+
<div>
|
|
602
|
+
<label style="color: var(--muted); font-size: 12px;">ثابت c</label>
|
|
603
|
+
<input type="number" id="levelingC" class="setting-input" value="<%= levelConfigDb && levelConfigDb.leveling && levelConfigDb.leveling.c ? levelConfigDb.leveling.c : 100 %>" min="0" <%= !canModify ? 'disabled' : '' %>>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<!-- Save Button -->
|
|
609
|
+
<div style="margin-top: 20px; text-align: left;">
|
|
610
|
+
<button class="btn btn-primary" onclick="saveAllXPSettings()" <%= !canModify ? 'disabled' : '' %>><i class="ri-save-line"></i> حفظ الإعدادات</button>
|
|
611
|
+
</div>
|
|
612
|
+
</div>
|
|
258
613
|
</div>
|
|
259
614
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
<
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
615
|
+
<!-- Role Rewards Section -->
|
|
616
|
+
<div class="settings-section animate-in" style="animation-delay: 0.06s">
|
|
617
|
+
<div class="section-header" onclick="toggleSection('roleRewards')">
|
|
618
|
+
<div class="section-title"><i class="ri-vip-crown-line"></i> مكافآت الرتب</div>
|
|
619
|
+
<i class="ri-arrow-down-s-line toggle-icon" id="roleRewardsIcon"></i>
|
|
620
|
+
</div>
|
|
621
|
+
<div class="section-content collapsed" id="roleRewards">
|
|
622
|
+
<div class="setting-desc" style="margin-bottom: 16px;">أضف رتب تُعطى تلقائياً عند الوصول لمستوى معين</div>
|
|
623
|
+
|
|
624
|
+
<div class="add-reward-row" style="border-style: solid; background: rgba(255,255,255,0.02); border-color: var(--border);">
|
|
625
|
+
<div class="level-input-wrapper" style="flex: 1;">
|
|
626
|
+
<i class="ri-medal-fill"></i>
|
|
627
|
+
<input type="number" id="rewardLevel" class="setting-input" placeholder="رقم المستوى" min="1" style="padding-right: 40px;" <%= !canModify ? 'disabled' : '' %>>
|
|
628
|
+
</div>
|
|
629
|
+
<div class="role-select-wrapper" style="flex: 2;">
|
|
630
|
+
<select id="rewardRole" class="role-select" <%= !canModify ? 'disabled' : '' %>>
|
|
631
|
+
<option value="">🎭 اختر الرتبة التي سيحصل عليها العضو...</option>
|
|
632
|
+
<% roles.forEach(role => { %>
|
|
633
|
+
<option value="<%= role.id %>" data-color="<%= role.color %>">@<%= role.name %></option>
|
|
634
|
+
<% }); %>
|
|
635
|
+
</select>
|
|
636
|
+
</div>
|
|
637
|
+
<button class="btn btn-primary" onclick="addRoleReward()" <%= !canModify ? 'disabled' : '' %> style="padding: 12px 30px;">
|
|
638
|
+
<i class="ri-add-circle-line"></i> إضافة للمكافآت
|
|
639
|
+
</button>
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
<div class="rewards-list" id="rewardsList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 20px;">
|
|
643
|
+
<% if (levelConfigDb && levelConfigDb.roleReward && Object.keys(levelConfigDb.roleReward).length > 0) { %>
|
|
644
|
+
<% Object.entries(levelConfigDb.roleReward).sort((a,b) => parseInt(a[0]) - parseInt(b[0])).forEach(([lvl, roleId]) => {
|
|
645
|
+
const role = roles.find(r => r.id === roleId);
|
|
646
|
+
const roleColor = role && role.color && role.color !== '#000000' ? role.color : '#5865F2';
|
|
647
|
+
%>
|
|
648
|
+
<div class="reward-card" style="--role-color: <%= roleColor %>;">
|
|
649
|
+
<div class="reward-icon-wrapper">
|
|
650
|
+
<i class="ri-vip-medal-fill"></i>
|
|
651
|
+
</div>
|
|
652
|
+
<div class="reward-content">
|
|
653
|
+
<div class="reward-level-tag">المستوى <%= lvl %></div>
|
|
654
|
+
<div class="reward-role-name">
|
|
655
|
+
<span class="role-indicator"></span>
|
|
656
|
+
<%= role ? role.name : 'رتبة محذوفة' %>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
<button class="btn-icon danger" onclick="removeRoleReward(<%= lvl %>, '<%= roleId %>')" title="حذف" <%= !canModify ? 'disabled' : '' %> style="width: 42px; height: 42px; border-radius: 12px; background: rgba(239, 68, 68, 0.1); color: var(--danger); border: 1px solid rgba(239, 68, 68, 0.2);">
|
|
660
|
+
<i class="ri-delete-bin-line"></i>
|
|
661
|
+
</button>
|
|
662
|
+
</div>
|
|
274
663
|
<% }); %>
|
|
275
|
-
|
|
276
|
-
|
|
664
|
+
<% } else { %>
|
|
665
|
+
<div class="empty-rewards-state">
|
|
666
|
+
<div class="empty-rewards-icon"><i class="ri-medal-fill"></i></div>
|
|
667
|
+
<div class="empty-rewards-text">قائمة المكافآت فارغة</div>
|
|
668
|
+
<div class="empty-rewards-hint">لا توجد رتب مكافآت مسجلة حالياً. ابدأ بإضافة بعض المكافآت لتحفيز أعضاء السيرفر على التفاعل!</div>
|
|
669
|
+
</div>
|
|
670
|
+
<% } %>
|
|
671
|
+
</div>
|
|
277
672
|
</div>
|
|
673
|
+
</div>
|
|
674
|
+
|
|
675
|
+
<!-- Blacklisted Channels Section -->
|
|
676
|
+
<div class="settings-section animate-in" style="animation-delay: 0.09s">
|
|
677
|
+
<div class="section-header" onclick="toggleSection('blacklist')">
|
|
678
|
+
<div class="section-title"><i class="ri-forbid-line"></i> القنوات المحظورة</div>
|
|
679
|
+
<i class="ri-arrow-down-s-line toggle-icon" id="blacklistIcon"></i>
|
|
680
|
+
</div>
|
|
681
|
+
<div class="section-content collapsed" id="blacklist">
|
|
682
|
+
<div class="setting-desc" style="margin-bottom: 16px;">القنوات التي لا يُكتسب فيها XP</div>
|
|
683
|
+
|
|
684
|
+
<div class="add-reward-row">
|
|
685
|
+
<select id="blacklistChannel" class="setting-select" style="flex: 1;" <%= !canModify ? 'disabled' : '' %>>
|
|
686
|
+
<option value="">🔍 اختر قناة...</option>
|
|
687
|
+
<% channels.forEach(ch => { %>
|
|
688
|
+
<option value="<%= ch.id %>"># <%= ch.name %></option>
|
|
689
|
+
<% }); %>
|
|
690
|
+
</select>
|
|
691
|
+
<button class="btn btn-warning" onclick="addBlacklistChannel()" <%= !canModify ? 'disabled' : '' %>><i class="ri-forbid-line"></i> إضافة للقائمة</button>
|
|
692
|
+
</div>
|
|
693
|
+
|
|
694
|
+
<div class="blacklist-list" id="blacklistList">
|
|
695
|
+
<% if (levelConfigDb && levelConfigDb.blacklistedChannels && levelConfigDb.blacklistedChannels.length > 0) { %>
|
|
696
|
+
<% levelConfigDb.blacklistedChannels.forEach(chId => {
|
|
697
|
+
const ch = channels.find(c => c.id === chId);
|
|
698
|
+
%>
|
|
699
|
+
<div class="blacklist-item" data-channel="<%= chId %>">
|
|
700
|
+
<span class="channel-name"><i class="ri-hashtag"></i> <%= ch ? ch.name : 'قناة محذوفة' %></span>
|
|
701
|
+
<button class="btn-icon danger" onclick="removeBlacklistChannel('<%= chId %>')" title="إزالة" <%= !canModify ? 'disabled' : '' %>><i class="ri-delete-bin-line"></i></button>
|
|
702
|
+
</div>
|
|
703
|
+
<% }); %>
|
|
704
|
+
<% } else { %>
|
|
705
|
+
<div class="empty-mini"><i class="ri-checkbox-circle-line" style="font-size: 32px; opacity: 0.3; margin-bottom: 8px; display: block; color: var(--success);"></i>جميع القنوات مفعّلة<br><small style="color: var(--text-muted);">لم يتم حظر أي قناة</small></div>
|
|
706
|
+
<% } %>
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
</div>
|
|
278
710
|
<% } else { %>
|
|
279
|
-
|
|
711
|
+
<!-- Database Mode Not Enabled Notice -->
|
|
712
|
+
<div class="notice-card animate-in" style="animation-delay: 0.03s">
|
|
713
|
+
<i class="ri-information-line"></i>
|
|
714
|
+
<div>
|
|
715
|
+
<strong>وضع قاعدة البيانات غير مفعّل</strong>
|
|
716
|
+
<p>الإعدادات تُدار تلقائياً. تأكد من اتصال قاعدة البيانات.</p>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
280
719
|
<% } %>
|
|
720
|
+
</div>
|
|
281
721
|
</main>
|
|
282
722
|
|
|
283
723
|
<div class="modal" id="editModal">
|
|
@@ -303,11 +743,211 @@
|
|
|
303
743
|
</div>
|
|
304
744
|
</div>
|
|
305
745
|
|
|
746
|
+
<!-- Confirm Modal -->
|
|
747
|
+
<div class="modal" id="confirmModal">
|
|
748
|
+
<div class="modal-content" style="max-width: 400px;">
|
|
749
|
+
<div class="modal-header">
|
|
750
|
+
<h3 id="confirmTitle"><i class="ri-question-line" style="color: var(--warning);"></i> تأكيد</h3>
|
|
751
|
+
<button class="modal-close" onclick="closeConfirmModal()">×</button>
|
|
752
|
+
</div>
|
|
753
|
+
<div class="modal-body">
|
|
754
|
+
<p id="confirmMessage" style="font-size: 15px; text-align: center; padding: 10px 0;"></p>
|
|
755
|
+
</div>
|
|
756
|
+
<div class="modal-footer" style="justify-content: center;">
|
|
757
|
+
<button class="btn btn-secondary" onclick="closeConfirmModal()">إلغاء</button>
|
|
758
|
+
<button class="btn btn-primary" id="confirmBtn" style="background: var(--danger);"><i class="ri-check-line"></i> تأكيد</button>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
</div>
|
|
762
|
+
|
|
306
763
|
<div class="toast" id="toast"></div>
|
|
307
764
|
|
|
308
765
|
<script>
|
|
309
766
|
const guildId = '<%= guild.id %>';
|
|
767
|
+
const databaseEnabled = <%= databaseEnabled ? 'true' : 'false' %>;
|
|
768
|
+
|
|
310
769
|
function showToast(message, type = 'success') { const toast = document.getElementById('toast'); toast.textContent = message; toast.className = 'toast ' + type + ' show'; setTimeout(() => toast.classList.remove('show'), 3000); }
|
|
770
|
+
|
|
771
|
+
// Confirm Modal Functions
|
|
772
|
+
let confirmCallback = null;
|
|
773
|
+
function showConfirm(message, callback, title = 'تأكيد') {
|
|
774
|
+
document.getElementById('confirmMessage').textContent = message;
|
|
775
|
+
document.getElementById('confirmTitle').innerHTML = '<i class="ri-question-line" style="color: var(--warning);"></i> ' + title;
|
|
776
|
+
confirmCallback = callback;
|
|
777
|
+
document.getElementById('confirmModal').classList.add('show');
|
|
778
|
+
}
|
|
779
|
+
function closeConfirmModal() {
|
|
780
|
+
document.getElementById('confirmModal').classList.remove('show');
|
|
781
|
+
confirmCallback = null;
|
|
782
|
+
}
|
|
783
|
+
document.getElementById('confirmBtn').addEventListener('click', function() {
|
|
784
|
+
if (confirmCallback) confirmCallback();
|
|
785
|
+
closeConfirmModal();
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
function toggleSection(id) {
|
|
789
|
+
const content = document.getElementById(id);
|
|
790
|
+
const icon = document.getElementById(id + 'Icon');
|
|
791
|
+
content.classList.toggle('collapsed');
|
|
792
|
+
icon.style.transform = content.classList.contains('collapsed') ? '' : 'rotate(180deg)';
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Tab Switching Function
|
|
796
|
+
function switchTab(tabName) {
|
|
797
|
+
// Remove active from all tabs and contents
|
|
798
|
+
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
799
|
+
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
|
800
|
+
|
|
801
|
+
// Add active to selected tab and content
|
|
802
|
+
document.getElementById('tab-' + tabName).classList.add('active');
|
|
803
|
+
document.getElementById('content-' + tabName).classList.add('active');
|
|
804
|
+
|
|
805
|
+
// Update URL hash without reload
|
|
806
|
+
history.replaceState(null, null, '#' + tabName);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Restore tab from URL hash on page load
|
|
810
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
811
|
+
const hash = window.location.hash.replace('#', '');
|
|
812
|
+
if (hash && (hash === 'settings' || hash === 'leaderboard')) {
|
|
813
|
+
switchTab(hash);
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
function toggleLevelingOptions() {
|
|
818
|
+
const type = document.getElementById('levelingType').value;
|
|
819
|
+
const balancedOptions = document.getElementById('balancedOptions');
|
|
820
|
+
const amountGroup = document.getElementById('levelingAmountGroup');
|
|
821
|
+
if (type === 'balanced') {
|
|
822
|
+
balancedOptions.style.display = 'block';
|
|
823
|
+
amountGroup.style.display = 'none';
|
|
824
|
+
} else {
|
|
825
|
+
balancedOptions.style.display = 'none';
|
|
826
|
+
amountGroup.style.display = 'block';
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
// Run on load
|
|
830
|
+
toggleLevelingOptions();
|
|
831
|
+
|
|
832
|
+
async function saveAllXPSettings() {
|
|
833
|
+
const minXP = parseInt(document.getElementById('minXP').value) || 15;
|
|
834
|
+
const maxXP = parseInt(document.getElementById('maxXP').value) || 25;
|
|
835
|
+
const xpMultiplier = parseFloat(document.getElementById('xpMultiplier').value) || 1;
|
|
836
|
+
const maxLevel = parseInt(document.getElementById('maxLevel').value) || 100;
|
|
837
|
+
const cooldown = parseInt(document.getElementById('cooldown').value) || 0;
|
|
838
|
+
|
|
839
|
+
// Leveling settings
|
|
840
|
+
const leveling = {
|
|
841
|
+
type: document.getElementById('levelingType').value,
|
|
842
|
+
amount: parseInt(document.getElementById('levelingAmount').value) || 100,
|
|
843
|
+
a: parseInt(document.getElementById('levelingA').value) || 5,
|
|
844
|
+
b: parseInt(document.getElementById('levelingB').value) || 50,
|
|
845
|
+
c: parseInt(document.getElementById('levelingC').value) || 100,
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
try {
|
|
849
|
+
const res = await fetch(`/api/${guildId}/level/config/xp`, {
|
|
850
|
+
method: 'POST',
|
|
851
|
+
headers: { 'Content-Type': 'application/json' },
|
|
852
|
+
body: JSON.stringify({
|
|
853
|
+
minXP, maxXP, xpMultiplier, maxLevel, cooldown, leveling
|
|
854
|
+
})
|
|
855
|
+
});
|
|
856
|
+
const data = await res.json();
|
|
857
|
+
if (data.success) {
|
|
858
|
+
showToast('تم حفظ جميع الإعدادات بنجاح ✓');
|
|
859
|
+
} else showToast(data.error || 'حدث خطأ', 'error');
|
|
860
|
+
} catch (e) { showToast('حدث خطأ في الاتصال', 'error'); }
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Level Config API Functions
|
|
864
|
+
async function toggleSystem() {
|
|
865
|
+
const enabled = document.getElementById('systemEnabled').checked;
|
|
866
|
+
try {
|
|
867
|
+
const res = await fetch(`/api/${guildId}/level/config/toggle`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: !enabled }) });
|
|
868
|
+
const data = await res.json();
|
|
869
|
+
if (data.success) showToast(enabled ? 'تم تفعيل النظام' : 'تم تعطيل النظام');
|
|
870
|
+
else showToast(data.error || 'حدث خطأ', 'error');
|
|
871
|
+
} catch (e) { showToast('حدث خطأ في الاتصال', 'error'); }
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
async function updateXPSettings() {
|
|
875
|
+
const minXP = parseInt(document.getElementById('minXP').value) || 15;
|
|
876
|
+
const maxXP = parseInt(document.getElementById('maxXP').value) || 25;
|
|
877
|
+
const xpMultiplier = parseFloat(document.getElementById('xpMultiplier').value) || 1;
|
|
878
|
+
const maxLevel = parseInt(document.getElementById('maxLevel').value) || 100;
|
|
879
|
+
try {
|
|
880
|
+
const res = await fetch(`/api/${guildId}/level/config/xp`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ minXP, maxXP, xpMultiplier, maxLevel }) });
|
|
881
|
+
const data = await res.json();
|
|
882
|
+
if (data.success) showToast('تم تحديث إعدادات XP');
|
|
883
|
+
else showToast(data.error || 'حدث خطأ', 'error');
|
|
884
|
+
} catch (e) { showToast('حدث خطأ في الاتصال', 'error'); }
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
async function updateCooldown() {
|
|
888
|
+
const cooldown = parseInt(document.getElementById('cooldown').value) || 0;
|
|
889
|
+
try {
|
|
890
|
+
const res = await fetch(`/api/${guildId}/level/config/xp`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cooldown }) });
|
|
891
|
+
const data = await res.json();
|
|
892
|
+
if (data.success) showToast('تم تحديث إعدادات Cooldown');
|
|
893
|
+
else showToast(data.error || 'حدث خطأ', 'error');
|
|
894
|
+
} catch (e) { showToast('حدث خطأ في الاتصال', 'error'); }
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
async function addRoleReward() {
|
|
898
|
+
const level = parseInt(document.getElementById('rewardLevel').value);
|
|
899
|
+
const roleId = document.getElementById('rewardRole').value;
|
|
900
|
+
if (!level || level < 1) return showToast('أدخل مستوى صحيح', 'error');
|
|
901
|
+
if (!roleId) return showToast('اختر رتبة', 'error');
|
|
902
|
+
try {
|
|
903
|
+
const res = await fetch(`/api/${guildId}/level/config/role`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ level, roleId }) });
|
|
904
|
+
const data = await res.json();
|
|
905
|
+
if (data.success) {
|
|
906
|
+
showToast('تمت إضافة مكافأة الرتبة');
|
|
907
|
+
document.getElementById('rewardLevel').value = '';
|
|
908
|
+
document.getElementById('rewardRole').value = '';
|
|
909
|
+
setTimeout(() => { window.location.href = window.location.pathname + '#settings'; window.location.reload(); }, 800);
|
|
910
|
+
}
|
|
911
|
+
else showToast(data.error || 'حدث خطأ', 'error');
|
|
912
|
+
} catch (e) { showToast('حدث خطأ في الاتصال', 'error'); }
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function removeRoleReward(level, roleId) {
|
|
916
|
+
showConfirm('هل تريد حذف هذه المكافأة؟', async () => {
|
|
917
|
+
try {
|
|
918
|
+
const res = await fetch(`/api/${guildId}/level/config/role`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ level, roleId: null }) });
|
|
919
|
+
const data = await res.json();
|
|
920
|
+
if (data.success) { showToast('تم حذف المكافأة'); document.querySelector(`[data-level="${level}"][data-role="${roleId}"]`).remove(); }
|
|
921
|
+
else showToast(data.error || 'حدث خطأ', 'error');
|
|
922
|
+
} catch (e) { showToast('حدث خطأ في الاتصال', 'error'); }
|
|
923
|
+
}, 'حذف المكافأة');
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async function addBlacklistChannel() {
|
|
927
|
+
const channelId = document.getElementById('blacklistChannel').value;
|
|
928
|
+
if (!channelId) return showToast('اختر قناة', 'error');
|
|
929
|
+
try {
|
|
930
|
+
const res = await fetch(`/api/${guildId}/level/config/blacklist`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channelId, action: 'add' }) });
|
|
931
|
+
const data = await res.json();
|
|
932
|
+
if (data.success) {
|
|
933
|
+
showToast('تمت إضافة القناة للقائمة السوداء');
|
|
934
|
+
setTimeout(() => { window.location.href = window.location.pathname + '#settings'; window.location.reload(); }, 800);
|
|
935
|
+
}
|
|
936
|
+
else showToast(data.error || 'حدث خطأ', 'error');
|
|
937
|
+
} catch (e) { showToast('حدث خطأ في الاتصال', 'error'); }
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function removeBlacklistChannel(channelId) {
|
|
941
|
+
showConfirm('هل تريد إزالة هذه القناة من القائمة السوداء؟', async () => {
|
|
942
|
+
try {
|
|
943
|
+
const res = await fetch(`/api/${guildId}/level/config/blacklist`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channelId, action: 'remove' }) });
|
|
944
|
+
const data = await res.json();
|
|
945
|
+
if (data.success) { showToast('تمت إزالة القناة'); document.querySelector(`[data-channel="${channelId}"]`).remove(); }
|
|
946
|
+
else showToast(data.error || 'حدث خطأ', 'error');
|
|
947
|
+
} catch (e) { showToast('حدث خطأ في الاتصال', 'error'); }
|
|
948
|
+
}, 'إزالة القناة');
|
|
949
|
+
}
|
|
950
|
+
|
|
311
951
|
async function loadUserData() { const userIds = [...document.querySelectorAll('[data-user-id]')].map(el => el.dataset.userId); const uniqueIds = [...new Set(userIds)]; for (const userId of uniqueIds) { try { const res = await fetch(`/api/${guildId}/member/${userId}`); const data = await res.json(); if (data.user) { const avatarUrl = data.user.avatar ? `https://cdn.discordapp.com/avatars/${userId}/${data.user.avatar}.png?size=64` : `https://cdn.discordapp.com/embed/avatars/${parseInt(userId) % 5}.png`; document.querySelectorAll(`.user-avatar-${userId}`).forEach(img => img.src = avatarUrl); document.querySelectorAll(`.user-name-${userId}`).forEach(el => el.textContent = data.user.username || data.user.global_name || userId); } } catch (e) { console.error('Error loading user:', userId, e); } } }
|
|
312
952
|
let searchTimeout; const searchInput = document.getElementById('memberSearch'); const searchResults = document.getElementById('searchResults');
|
|
313
953
|
searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.trim(); if (query.length < 2) { searchResults.classList.remove('show'); return; } searchTimeout = setTimeout(async () => { try { const res = await fetch(`/api/${guildId}/members/search?q=${encodeURIComponent(query)}`); const data = await res.json(); if (data.members && data.members.length > 0) { searchResults.innerHTML = data.members.map(m => `<div class="search-result-item" onclick="selectMember('${m.id}', '${m.username}', '${m.avatar || ''}')"><img src="${m.avatar ? `https://cdn.discordapp.com/avatars/${m.id}/${m.avatar}.png?size=64` : `https://cdn.discordapp.com/embed/avatars/${parseInt(m.id) % 5}.png`}" alt=""><div><div class="name">${m.displayName || m.username}</div><div class="tag">${m.username}</div></div></div>`).join(''); searchResults.classList.add('show'); } else { searchResults.innerHTML = '<div style="padding: 16px; text-align: center; color: var(--text-muted);">لا توجد نتائج</div>'; searchResults.classList.add('show'); } } catch (e) { searchResults.classList.remove('show'); } }, 300); });
|
|
@@ -319,7 +959,7 @@
|
|
|
319
959
|
function openAddXPModal() { document.getElementById('addXPModal').classList.add('show'); }
|
|
320
960
|
function closeAddXPModal() { document.getElementById('addXPModal').classList.remove('show'); document.getElementById('newUserId').value = ''; searchInput.value = ''; }
|
|
321
961
|
async function addXPToUser() { const userId = document.getElementById('newUserId').value.trim(); const xp = parseInt(document.getElementById('newUserXP').value) || 100; const level = parseInt(document.getElementById('newUserLevel').value) || 0; if (!userId) return showToast('أدخل معرف العضو', 'error'); try { const res = await fetch(`/api/${guildId}/level/add`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, xp, level }) }); const data = await res.json(); if (data.success) { showToast('تمت الإضافة بنجاح'); closeAddXPModal(); setTimeout(() => location.reload(), 1000); } else showToast(data.error || 'حدث خطأ', 'error'); } catch (e) { showToast('حدث خطأ في الاتصال', 'error'); } }
|
|
322
|
-
|
|
962
|
+
function resetUser(userId) { showConfirm('هل أنت متأكد من إعادة تعيين بيانات هذا المستخدم؟', async () => { try { const res = await fetch(`/api/${guildId}/level/reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId }) }); const data = await res.json(); if (data.success) { showToast('تمت إعادة التعيين'); setTimeout(() => location.reload(), 1000); } else showToast(data.error || 'حدث خطأ', 'error'); } catch (e) { showToast('حدث خطأ', 'error'); } }, 'إعادة تعيين'); }
|
|
323
963
|
loadUserData();
|
|
324
964
|
</script>
|
|
325
965
|
</body>
|