nodebb-plugin-niki-loyalty 1.5.0 → 1.5.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/library.js CHANGED
@@ -6,8 +6,12 @@ const routeHelpers = require.main.require('./src/controllers/helpers');
6
6
  const nconf = require.main.require('nconf');
7
7
  const socketHelpers = require.main.require('./src/socket.io/index');
8
8
  const SocketPlugins = require.main.require('./src/socket.io/plugins');
9
+ const groups = require.main.require('./src/groups');
9
10
  const Plugin = {};
10
11
 
12
+ // Ödül kullanabilecek gruplar
13
+ const WALLET_GROUPS = ['Premium', 'Lite', 'VIP'];
14
+
11
15
  // =========================
12
16
  // ⚙️ AYARLAR & KURALLAR (GAME LOGIC)
13
17
  // =========================
@@ -293,33 +297,6 @@ Plugin.init = async function (params) {
293
297
  const router = params.router;
294
298
  const middleware = params.middleware;
295
299
 
296
- // 🔄 1 SEFERLİK MİGRASYON: 180+ puanlı herkesten 50 puan düş
297
- try {
298
- const migrationKey = 'niki:migration:deduct50_from_180plus_v1';
299
- const alreadyDone = await db.get(migrationKey);
300
- if (!alreadyDone) {
301
- console.log('[Niki-Loyalty] Migration başlıyor: 180+ puanlı kullanıcılardan 50 puan düşülecek...');
302
- const allUids = await db.getSortedSetRange('users:joindate', 0, -1);
303
- if (allUids && allUids.length > 0) {
304
- const usersData = await user.getUsersFields(allUids, ['uid', 'niki_points', 'username']);
305
- let affected = 0;
306
- for (const u of usersData) {
307
- const pts = parseFloat(u.niki_points || 0);
308
- if (pts >= 180) {
309
- await user.decrementUserFieldBy(u.uid, 'niki_points', 50);
310
- await addUserLog(u.uid, 'admin_adjust', 50, 'Sistem düzenlemesi: -50 puan (dengeleme)');
311
- affected++;
312
- console.log(`[Niki-Loyalty] Migration: ${u.username} (UID:${u.uid}) ${pts} → ${pts - 50} puan`);
313
- }
314
- }
315
- console.log(`[Niki-Loyalty] Migration tamamlandı. Etkilenen kullanıcı: ${affected}`);
316
- }
317
- await db.set(migrationKey, Date.now().toString());
318
- }
319
- } catch (migErr) {
320
- console.error('[Niki-Loyalty] Migration hatası:', migErr);
321
- }
322
-
323
300
  // 1) HEARTBEAT (Artık "Okuma" Puanı veriyor)
324
301
  // Client-side script her 30-60 saniyede bir bu adrese istek atmalıdır.
325
302
  router.post('/api/niki-loyalty/heartbeat', middleware.ensureLoggedIn, async (req, res) => {
@@ -368,13 +345,18 @@ Plugin.init = async function (params) {
368
345
  const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
369
346
 
370
347
  // Veritabanından verileri çek
371
- const [userData, dailyData, actionCounts, historyRaw] = await Promise.all([
348
+ const [userData, dailyData, actionCounts, historyRaw, memberChecks] = await Promise.all([
372
349
  user.getUserFields(uid, ['niki_points']),
373
350
  db.getObject(`niki:daily:${uid}:${today}`),
374
- db.getObject(`niki:daily:${uid}:${today}:counts`), // <--- YENİ: Sayaçları çekiyoruz
351
+ db.getObject(`niki:daily:${uid}:${today}:counts`),
375
352
  db.getListRange(`niki:activity:${uid}`, 0, -1),
353
+ Promise.all(WALLET_GROUPS.map(g => groups.isMember(uid, g))),
376
354
  ]);
377
355
 
356
+ // Kullanıcı WALLET_GROUPS'dan herhangi birinde mi?
357
+ const canRedeem = memberChecks.some(Boolean);
358
+ const userGroup = canRedeem ? WALLET_GROUPS[memberChecks.indexOf(true)] : null;
359
+
378
360
  const dailyScore = parseFloat(dailyData?.score || 0);
379
361
  let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
380
362
  if (dailyPercent > 100) dailyPercent = 100;
@@ -386,9 +368,13 @@ Plugin.init = async function (params) {
386
368
  dailyScore,
387
369
  dailyCap: SETTINGS.dailyCap,
388
370
  dailyPercent,
389
- counts: actionCounts || {}, // <--- YENİ: Frontend'e gönderiyoruz
371
+ counts: actionCounts || {},
372
+ actions: ACTIONS,
390
373
  history,
391
374
  rewards: REWARDS,
375
+ canRedeem,
376
+ userGroup,
377
+ walletGroups: WALLET_GROUPS,
392
378
  });
393
379
  } catch (err) {
394
380
  return res.status(500).json({ points: 0, history: [] });
@@ -508,43 +494,63 @@ Plugin.init = async function (params) {
508
494
  router.post('/api/niki-loyalty/generate-qr', middleware.ensureLoggedIn, async (req, res) => {
509
495
  try {
510
496
  const uid = req.uid;
497
+
498
+ // Grup kontrolü
499
+ const memberChecks = await Promise.all(WALLET_GROUPS.map(g => groups.isMember(uid, g)));
500
+ if (!memberChecks.some(Boolean)) {
501
+ return res.json({ success: false, message: 'Ödül kullanmak için Premium, Lite veya VIP grubuna katılmalısın.' });
502
+ }
503
+
504
+ const rewardIndex = parseInt(req.body.rewardIndex, 10);
511
505
  const points = parseFloat((await user.getUserField(uid, 'niki_points')) || 0);
512
- const minCost = REWARDS[REWARDS.length - 1].cost; // En ucuz ödül
513
506
 
514
- if (!TEST_MODE_UNLIMITED && points < minCost) {
515
- return res.json({ success: false, message: `Yetersiz Puan. En az ${minCost} gerekli.` });
507
+ // Seçilen ödülü bul
508
+ if (isNaN(rewardIndex) || rewardIndex < 0 || rewardIndex >= REWARDS.length) {
509
+ return res.json({ success: false, message: 'Geçersiz ödül seçimi.' });
510
+ }
511
+ const selectedReward = REWARDS[rewardIndex];
512
+
513
+ if (!TEST_MODE_UNLIMITED && points < selectedReward.cost) {
514
+ return res.json({ success: false, message: `Yetersiz Puan. ${selectedReward.name} için ${selectedReward.cost} puan gerekli.` });
516
515
  }
517
516
  const token = crypto.randomBytes(16).toString('hex');
518
- await db.set(`niki:qr:${token}`, uid);
517
+ // Token'a kullanıcı ve seçilen ödül bilgisini kaydet
518
+ await db.setObject(`niki:qr:${token}`, { uid: String(uid), rewardIndex: String(rewardIndex) });
519
519
  await db.expire(`niki:qr:${token}`, 120); // 2 dakika geçerli
520
- return res.json({ success: true, token });
520
+ return res.json({ success: true, token, rewardName: selectedReward.name, rewardCost: selectedReward.cost });
521
521
  } catch (e) { return res.status(500).json({ success: false }); }
522
522
  });
523
523
 
524
524
  // 5) QR TARATMA (Kasa İşlemi)
525
525
  router.post('/api/niki-loyalty/scan-qr', middleware.ensureLoggedIn, async (req, res) => {
526
- // ... (Mevcut kodunun aynısı)
527
526
  try {
528
527
  const token = req.body.token;
529
528
  const isAdmin = await user.isAdministrator(req.uid);
530
529
  const isMod = await user.isGlobalModerator(req.uid);
531
530
  if (!isAdmin && !isMod) return res.status(403).json({ success: false, message: 'Yetkisiz' });
532
531
 
533
- const custUid = await db.get(`niki:qr:${token}`);
534
- if (!custUid) return res.json({ success: false, message: 'Geçersiz Kod' });
532
+ const qrData = await db.getObject(`niki:qr:${token}`);
533
+ if (!qrData || !qrData.uid) return res.json({ success: false, message: 'Geçersiz Kod' });
535
534
 
535
+ const custUid = qrData.uid;
536
+ const rewardIndex = parseInt(qrData.rewardIndex, 10);
536
537
  const pts = parseFloat(await user.getUserField(custUid, 'niki_points') || 0);
537
538
 
538
539
  let selectedReward = null;
539
540
  if (!TEST_MODE_UNLIMITED) {
540
- for (const r of REWARDS) {
541
- if (pts >= r.cost) { selectedReward = r; break; }
541
+ // Token'daki ödül index'ini kullan
542
+ if (!isNaN(rewardIndex) && rewardIndex >= 0 && rewardIndex < REWARDS.length) {
543
+ selectedReward = REWARDS[rewardIndex];
544
+ } else {
545
+ // Fallback: en yüksek ödülü seç
546
+ for (const r of REWARDS) {
547
+ if (pts >= r.cost) { selectedReward = r; break; }
548
+ }
542
549
  }
543
550
  if (!selectedReward) return res.json({ success: false, message: 'Puan Yetersiz' });
544
551
  } else { selectedReward = REWARDS[0]; }
545
552
 
546
553
  if (!TEST_MODE_UNLIMITED) {
547
- // Negatif bakiye kontrolü - önce kontrol, sonra düş
548
554
  if (pts < selectedReward.cost) {
549
555
  await db.delete(`niki:qr:${token}`);
550
556
  return res.json({ success: false, message: 'Puan yetersiz, işlem iptal edildi.' });
@@ -650,15 +656,21 @@ Plugin.socketScanQR = async function (socket, data) {
650
656
  const token = data.token;
651
657
  if (!token) throw new Error('Geçersiz Token');
652
658
 
653
- const custUid = await db.get(`niki:qr:${token}`);
654
- if (!custUid) throw new Error('QR Kod Geçersiz veya Süresi Dolmuş');
659
+ const qrData = await db.getObject(`niki:qr:${token}`);
660
+ if (!qrData || !qrData.uid) throw new Error('QR Kod Geçersiz veya Süresi Dolmuş');
655
661
 
662
+ const custUid = qrData.uid;
663
+ const rewardIndex = parseInt(qrData.rewardIndex, 10);
656
664
  const pts = parseFloat((await user.getUserField(custUid, 'niki_points')) || 0);
657
665
 
658
666
  let selectedReward = null;
659
667
  if (!TEST_MODE_UNLIMITED) {
660
- for (const r of REWARDS) {
661
- if (pts >= r.cost) { selectedReward = r; break; }
668
+ if (!isNaN(rewardIndex) && rewardIndex >= 0 && rewardIndex < REWARDS.length) {
669
+ selectedReward = REWARDS[rewardIndex];
670
+ } else {
671
+ for (const r of REWARDS) {
672
+ if (pts >= r.cost) { selectedReward = r; break; }
673
+ }
662
674
  }
663
675
  if (!selectedReward) throw new Error('Puan Yetersiz');
664
676
  } else { selectedReward = REWARDS[0]; }
package/niki-admin.txt CHANGED
@@ -1,6 +1,3 @@
1
- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
2
- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/locale/tr.min.js"></script>
3
-
4
1
  <div class="niki-dashboard">
5
2
  <!-- Yükleniyor -->
6
3
  <div id="niki-loader" class="niki-loader">
@@ -968,22 +965,71 @@
968
965
 
969
966
  <script>
970
967
  (function () {
971
- let g_users = [];
972
- let g_targetUid = 0;
973
- moment.locale('tr');
968
+ // jQuery hazır olana kadar bekle
969
+ function waitAndInit() {
970
+ console.log('[Niki-Admin] waitAndInit çağrıldı, jQuery var mı:', typeof $ !== 'undefined', 'pathname:', window.location.pathname);
971
+ if (typeof $ === 'undefined' || typeof jQuery === 'undefined') {
972
+ console.log('[Niki-Admin] jQuery henüz yok, bekleniyor...');
973
+ setTimeout(waitAndInit, 100);
974
+ return;
975
+ }
976
+ // Sadece admin sayfasında çalış
977
+ if (window.location.pathname.indexOf('niki-admin') === -1) {
978
+ console.log('[Niki-Admin] Admin sayfasında değiliz, iptal.');
979
+ return;
980
+ }
981
+ if (!$('.niki-dashboard').length) {
982
+ console.log('[Niki-Admin] .niki-dashboard DOM bulunamadı, iptal.');
983
+ return;
984
+ }
985
+ console.log('[Niki-Admin] Tüm kontroller OK, başlatılıyor...');
986
+ initAdminModule();
987
+ }
988
+
989
+ function initAdminModule() {
990
+ console.log('[Niki-Admin] initAdminModule başladı');
991
+ // Moment.js'i dinamik yükle (CDN script tag'ları AJAX navigasyonda çalışmıyor)
992
+ function loadMoment(callback) {
993
+ if (typeof moment !== 'undefined') {
994
+ moment.locale('tr');
995
+ return callback();
996
+ }
997
+ var s = document.createElement('script');
998
+ s.src = 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment-with-locales.min.js';
999
+ s.onload = function () {
1000
+ moment.locale('tr');
1001
+ callback();
1002
+ };
1003
+ document.head.appendChild(s);
1004
+ }
1005
+
1006
+ var g_users = [];
1007
+ var g_targetUid = 0;
974
1008
 
975
- // JSON parse helper - string ise parse et, değilse aynen döndür
976
1009
  function safeParseMaybeJson(item) {
977
1010
  if (!item) return null;
978
1011
  if (typeof item === 'object') return item;
979
- try {
980
- return JSON.parse(item);
981
- } catch (e) {
982
- return null;
983
- }
1012
+ try { return JSON.parse(item); } catch (e) { return null; }
1013
+ }
1014
+
1015
+ function escapeHtml(text) {
1016
+ if (!text) return '';
1017
+ var div = document.createElement('div');
1018
+ div.textContent = text;
1019
+ return div.innerHTML;
1020
+ }
1021
+
1022
+ // Zaman formatlama - moment varsa kullan, yoksa basit format
1023
+ function formatTime(ts) {
1024
+ if (typeof moment !== 'undefined') return moment(ts).format('DD MMM YYYY, HH:mm');
1025
+ return new Date(ts).toLocaleString('tr-TR');
1026
+ }
1027
+
1028
+ function formatFromNow(ts) {
1029
+ if (typeof moment !== 'undefined') return moment(ts).fromNow();
1030
+ return new Date(ts).toLocaleDateString('tr-TR');
984
1031
  }
985
1032
 
986
- // Global functions
987
1033
  window.closeManageModal = function () { $('#modal-manage').fadeOut(200); };
988
1034
  window.closeDetailModal = function () { $('#modal-detail').fadeOut(200); };
989
1035
 
@@ -1001,12 +1047,10 @@
1001
1047
  $('#detail-body').html('<div class="niki-loader" style="padding:40px;"><div class="spinner"></div></div>');
1002
1048
 
1003
1049
  socket.emit('plugins.niki.getUserDetail', { uid: uid }, function (err, data) {
1004
- console.log('[Niki-Admin] getUserDetail response:', err, data);
1005
1050
  if (err) {
1006
1051
  $('#detail-body').html('<div class="detail-empty"><i class="fa fa-exclamation-circle"></i>Hata: ' + (err.message || 'Bilinmeyen hata') + '</div>');
1007
1052
  return;
1008
1053
  }
1009
- // Data string olarak gelebilir, parse et
1010
1054
  var parsedData = safeParseMaybeJson(data);
1011
1055
  if (!parsedData) {
1012
1056
  $('#detail-body').html('<div class="detail-empty"><i class="fa fa-exclamation-circle"></i>Veri parse edilemedi</div>');
@@ -1017,27 +1061,20 @@
1017
1061
  };
1018
1062
 
1019
1063
  function renderDetailModal(data) {
1020
- console.log('[Niki-Admin] renderDetailModal data:', data);
1021
-
1022
- // user ve stats da string olarak gelebilir
1023
1064
  var u = safeParseMaybeJson(data.user) || data.user || {};
1024
1065
  var stats = safeParseMaybeJson(data.stats) || data.stats || {};
1025
1066
  var rp = (typeof config !== 'undefined' && config.relative_path) ? config.relative_path : '';
1026
1067
 
1027
- console.log('[Niki-Admin] Parsed user:', u);
1028
- console.log('[Niki-Admin] Parsed stats:', stats);
1029
-
1030
- let avatarHtml = '';
1068
+ var avatarHtml = '';
1031
1069
  if (u.picture) {
1032
1070
  avatarHtml = '<img src="' + u.picture + '" class="detail-avatar">';
1033
1071
  } else {
1034
- const letter = u.username ? u.username[0].toUpperCase() : '?';
1072
+ var letter = u.username ? u.username[0].toUpperCase() : '?';
1035
1073
  avatarHtml = '<div class="detail-letter-avatar" style="background:' + (u.iconBg || '#555') + '">' + letter + '</div>';
1036
1074
  }
1037
1075
 
1038
- const dailyCap = stats.dailyCap || 35;
1039
- const dailyPercent = Math.min(100, ((stats.todayScore || 0) / dailyCap) * 100);
1040
-
1076
+ var dailyCap = stats.dailyCap || 35;
1077
+ var dailyPercent = Math.min(100, ((stats.todayScore || 0) / dailyCap) * 100);
1041
1078
  var kasaLength = (data.kasaHistory && Array.isArray(data.kasaHistory)) ? data.kasaHistory.length : 0;
1042
1079
  var todayScoreVal = stats.todayScore || 0;
1043
1080
  var totalEarnedVal = Math.floor(stats.totalEarned || 0).toLocaleString('tr-TR');
@@ -1045,7 +1082,7 @@
1045
1082
  var pointsVal = Math.floor(u.points || 0).toLocaleString('tr-TR');
1046
1083
  var usernameVal = escapeHtml(u.username || 'Bilinmeyen');
1047
1084
  var emailVal = escapeHtml(u.email) || 'E-posta yok';
1048
- var lastOnlineVal = u.lastonline ? moment(u.lastonline).fromNow() : 'Bilinmiyor';
1085
+ var lastOnlineVal = u.lastonline ? formatFromNow(u.lastonline) : 'Bilinmiyor';
1049
1086
  var dailyPercentFixed = dailyPercent.toFixed(0);
1050
1087
 
1051
1088
  var html = '<div class="detail-header">' +
@@ -1062,35 +1099,16 @@
1062
1099
  '</div>' +
1063
1100
 
1064
1101
  '<div class="detail-stats">' +
1065
- '<div class="detail-stat">' +
1066
- '<div class="detail-stat-val green">+' + totalEarnedVal + '</div>' +
1067
- '<div class="detail-stat-lbl">Kazanılan</div>' +
1068
- '</div>' +
1069
- '<div class="detail-stat">' +
1070
- '<div class="detail-stat-val red">-' + totalSpentVal + '</div>' +
1071
- '<div class="detail-stat-lbl">Harcanan</div>' +
1072
- '</div>' +
1073
- '<div class="detail-stat">' +
1074
- '<div class="detail-stat-val">' + todayScoreVal + '</div>' +
1075
- '<div class="detail-stat-lbl">Bugün</div>' +
1076
- '</div>' +
1077
- '<div class="detail-stat">' +
1078
- '<div class="detail-stat-val">' + kasaLength + '</div>' +
1079
- '<div class="detail-stat-lbl">Ödül Kullanımı</div>' +
1080
- '</div>' +
1102
+ '<div class="detail-stat"><div class="detail-stat-val green">+' + totalEarnedVal + '</div><div class="detail-stat-lbl">Kazanılan</div></div>' +
1103
+ '<div class="detail-stat"><div class="detail-stat-val red">-' + totalSpentVal + '</div><div class="detail-stat-lbl">Harcanan</div></div>' +
1104
+ '<div class="detail-stat"><div class="detail-stat-val">' + todayScoreVal + '</div><div class="detail-stat-lbl">Bugün</div></div>' +
1105
+ '<div class="detail-stat"><div class="detail-stat-val">' + kasaLength + '</div><div class="detail-stat-lbl">Ödül Kullanımı</div></div>' +
1081
1106
  '</div>' +
1082
1107
 
1083
1108
  '<div class="detail-section">' +
1084
1109
  '<div class="detail-section-title"><i class="fa fa-line-chart"></i> Günlük İlerleme</div>' +
1085
- '<div class="daily-progress">' +
1086
- '<div class="daily-progress-bar">' +
1087
- '<div class="daily-progress-fill" style="width: ' + dailyPercent + '%"></div>' +
1088
- '</div>' +
1089
- '<div class="daily-progress-label">' +
1090
- '<span>' + todayScoreVal + ' / ' + dailyCap + ' puan</span>' +
1091
- '<span>' + dailyPercentFixed + '%</span>' +
1092
- '</div>' +
1093
- '</div>' +
1110
+ '<div class="daily-progress"><div class="daily-progress-bar"><div class="daily-progress-fill" style="width: ' + dailyPercent + '%"></div></div>' +
1111
+ '<div class="daily-progress-label"><span>' + todayScoreVal + ' / ' + dailyCap + ' puan</span><span>' + dailyPercentFixed + '%</span></div></div>' +
1094
1112
  '</div>' +
1095
1113
 
1096
1114
  '<div class="detail-section">' +
@@ -1104,8 +1122,6 @@
1104
1122
  '</div>';
1105
1123
 
1106
1124
  $('#detail-body').html(html);
1107
-
1108
- // İlk sekmeyi göster
1109
1125
  window.currentDetailData = data;
1110
1126
  switchHistoryTab('earn');
1111
1127
  }
@@ -1115,11 +1131,11 @@
1115
1131
  if (el) $(el).addClass('active');
1116
1132
  else $('.detail-tab').first().addClass('active');
1117
1133
 
1118
- const data = window.currentDetailData;
1134
+ var data = window.currentDetailData;
1119
1135
  if (!data) return;
1120
1136
 
1121
- const container = $('#history-container');
1122
- let items = [];
1137
+ var container = $('#history-container');
1138
+ var items = [];
1123
1139
 
1124
1140
  if (type === 'earn') {
1125
1141
  items = (data.earnHistory || []).map(safeParseMaybeJson).filter(Boolean);
@@ -1127,12 +1143,7 @@
1127
1143
  items = (data.spendHistory || []).map(safeParseMaybeJson).filter(Boolean);
1128
1144
  } else if (type === 'kasa') {
1129
1145
  items = (data.kasaHistory || []).map(safeParseMaybeJson).filter(Boolean).map(function (k) {
1130
- return {
1131
- type: 'spend',
1132
- ts: k.ts,
1133
- amt: k.amt,
1134
- txt: k.reward || 'Ödül Kullanımı'
1135
- };
1146
+ return { type: 'spend', ts: k.ts, amt: k.amt, txt: k.reward || 'Ödül Kullanımı' };
1136
1147
  });
1137
1148
  }
1138
1149
 
@@ -1148,37 +1159,30 @@
1148
1159
  var icon = isEarn ? 'fa-arrow-up' : 'fa-arrow-down';
1149
1160
  var valClass = isEarn ? 'earn' : 'spend';
1150
1161
  var sign = isEarn ? '+' : '-';
1151
- var descText = escapeHtml(item.txt || 'İşlem');
1152
- var timeText = moment(item.ts).format('DD MMM YYYY, HH:mm');
1153
- var amtVal = item.amt || 0;
1154
1162
 
1155
1163
  html += '<div class="detail-history-item">' +
1156
1164
  '<div class="detail-history-icon ' + iconClass + '"><i class="fa ' + icon + '"></i></div>' +
1157
1165
  '<div class="detail-history-text">' +
1158
- '<div class="detail-history-desc">' + descText + '</div>' +
1159
- '<div class="detail-history-time">' + timeText + '</div>' +
1166
+ '<div class="detail-history-desc">' + escapeHtml(item.txt || 'İşlem') + '</div>' +
1167
+ '<div class="detail-history-time">' + formatTime(item.ts) + '</div>' +
1160
1168
  '</div>' +
1161
- '<div class="detail-history-val ' + valClass + '">' + sign + amtVal + '</div>' +
1169
+ '<div class="detail-history-val ' + valClass + '">' + sign + (item.amt || 0) + '</div>' +
1162
1170
  '</div>';
1163
1171
  });
1164
-
1165
1172
  container.html(html);
1166
1173
  };
1167
1174
 
1168
1175
  window.submitManagePoints = function () {
1169
- const amt = parseFloat($('#manage-amount').val());
1170
- const reason = $('#manage-reason').val().trim();
1171
- const action = $('input[name="manage_action"]:checked').val();
1172
- const $err = $('#manage-error');
1176
+ var amt = parseFloat($('#manage-amount').val());
1177
+ var reason = $('#manage-reason').val().trim();
1178
+ var action = $('input[name="manage_action"]:checked').val();
1179
+ var $err = $('#manage-error');
1173
1180
 
1174
1181
  if (!amt || amt <= 0) { $err.text('Geçerli bir miktar giriniz.').show(); return; }
1175
1182
  if (!reason) { $err.text('Sebep girmek zorunludur.').show(); return; }
1176
1183
 
1177
1184
  socket.emit('plugins.niki.managePoints', {
1178
- targetUid: g_targetUid,
1179
- amount: amt,
1180
- action: action,
1181
- reason: reason
1185
+ targetUid: g_targetUid, amount: amt, action: action, reason: reason
1182
1186
  }, function (err, result) {
1183
1187
  if (err) {
1184
1188
  $err.text(err.message || 'Hata oluştu.').show();
@@ -1190,29 +1194,16 @@
1190
1194
  });
1191
1195
  };
1192
1196
 
1193
- var _initRetries = 0;
1194
1197
  function initNikiAdmin() {
1195
- if (typeof socket === 'undefined' || typeof app === 'undefined' || typeof app.user === 'undefined') {
1196
- _initRetries++;
1197
- if (_initRetries > 25) {
1198
- console.error('[Niki-Admin] Socket/app 5 saniye içinde hazır olmadı, yükleme iptal.');
1199
- $('#niki-loader').hide();
1200
- $('.niki-dashboard').append('<div style="text-align:center; padding:40px; color:#ef5350;">Bağlantı kurulamadı. Sayfayı yenileyin.</div>');
1201
- return;
1202
- }
1203
- setTimeout(initNikiAdmin, 200);
1204
- return;
1205
- }
1206
- _initRetries = 0;
1207
-
1208
- const $loader = $('#niki-loader');
1209
- const $content = $('#niki-content');
1198
+ console.log('[Niki-Admin] initNikiAdmin başladı, socket var mı:', typeof socket !== 'undefined', 'app var mı:', typeof app !== 'undefined');
1199
+ var $loader = $('#niki-loader');
1200
+ var $content = $('#niki-content');
1210
1201
  $loader.show(); $content.hide();
1211
1202
 
1212
- // Önce istatistikleri al
1213
- console.log('[Niki-Admin] getStats emit ediliyor...');
1203
+ // İstatistikleri ve kullanıcıları paralel çek
1204
+ console.log('[Niki-Admin] getStats çağrılıyor...');
1214
1205
  socket.emit('plugins.niki.getStats', {}, function (err, stats) {
1215
- console.log('[Niki-Admin] getStats sonuç:', err, stats);
1206
+ console.log('[Niki-Admin] getStats sonuç:', err ? 'HATA: ' + err.message : 'OK', stats);
1216
1207
  if (!err && stats) {
1217
1208
  $('#val-users').text(stats.usersWithPoints || 0);
1218
1209
  $('#val-points').text(Number(stats.totalPoints || 0).toLocaleString('tr-TR'));
@@ -1223,87 +1214,73 @@
1223
1214
  }
1224
1215
  });
1225
1216
 
1226
- console.log('[Niki-Admin] getUsers emit ediliyor...');
1217
+ console.log('[Niki-Admin] getUsers çağrılıyor...');
1227
1218
  socket.emit('plugins.niki.getUsers', {}, function (err, users) {
1228
- console.log('[Niki-Admin] getUsers sonuç:', err, users);
1219
+ console.log('[Niki-Admin] getUsers sonuç:', err ? 'HATA: ' + err.message : 'OK, kullanıcı sayısı: ' + (users ? users.length : 0));
1229
1220
  $loader.hide();
1230
1221
  if (err) {
1231
- console.error('[Niki-Admin] getUsers HATA:', err);
1232
1222
  $('.niki-dashboard').html('<div style="color:#d32f2f; text-align:center; padding:40px;">HATA: ' + (err.message || 'Yetkisiz') + '</div>');
1233
1223
  return;
1234
1224
  }
1235
1225
  if (!users) users = [];
1236
- console.log('[Niki-Admin] ✅ ' + users.length + ' kullanıcı yüklendi');
1237
1226
  g_users = users;
1238
1227
  $content.fadeIn(300);
1239
1228
  renderList(users);
1240
1229
 
1241
1230
  $('#nk-search-input').off('keyup').on('keyup', function () {
1242
- const val = $(this).val().toLowerCase();
1243
- const filtered = users.filter(function (u) {
1231
+ var val = $(this).val().toLowerCase();
1232
+ renderList(users.filter(function (u) {
1244
1233
  return u.username.toLowerCase().indexOf(val) > -1;
1245
- });
1246
- renderList(filtered);
1234
+ }));
1247
1235
  });
1248
1236
  });
1249
1237
  }
1250
1238
 
1251
1239
  function renderList(list) {
1252
- const $container = $('#nk-list-body');
1240
+ var $container = $('#nk-list-body');
1253
1241
  $container.empty();
1254
1242
  if (list.length === 0) {
1255
- $container.html('<div style="text-align:center; padding:30px; color:#444;">Sonuç bulunamadı.</div>'); return;
1243
+ $container.html('<div style="text-align:center; padding:30px; color:#444;">Sonuç bulunamadı.</div>');
1244
+ return;
1256
1245
  }
1257
- const rp = config.relative_path || '';
1246
+ var rp = (typeof config !== 'undefined' && config.relative_path) ? config.relative_path : '';
1258
1247
 
1259
1248
  $.each(list, function (i, u) {
1260
- const row = $('<div class="nk-row"></div>');
1249
+ var row = $('<div class="nk-row"></div>');
1261
1250
  row.append('<div class="nk-idx">' + (i + 1) + '</div>');
1262
1251
 
1263
- const info = $('<div class="nk-user-info"></div>');
1264
- if (u.picture) { info.append('<img src="' + u.picture + '" class="nk-avatar">'); }
1265
- else {
1266
- const letter = (u.username && u.username[0]) ? u.username[0].toUpperCase() : '?';
1267
- const bg = u.iconBg || '#555';
1268
- info.append('<div class="nk-letter-avatar" style="background:' + bg + '">' + letter + '</div>');
1252
+ var info = $('<div class="nk-user-info"></div>');
1253
+ if (u.picture) {
1254
+ info.append('<img src="' + u.picture + '" class="nk-avatar">');
1255
+ } else {
1256
+ var letter = (u.username && u.username[0]) ? u.username[0].toUpperCase() : '?';
1257
+ info.append('<div class="nk-letter-avatar" style="background:' + (u.iconBg || '#555') + '">' + letter + '</div>');
1269
1258
  }
1270
- const link = $('<a class="nk-username" target="_blank"></a>').attr('href', rp + '/user/' + u.userslug).text(u.username);
1271
- info.append(link);
1259
+ info.append($('<a class="nk-username" target="_blank"></a>').attr('href', rp + '/user/' + u.userslug).text(u.username));
1272
1260
  row.append(info);
1273
1261
 
1274
- const pText = Math.floor(u.points || 0).toLocaleString('tr-TR');
1275
- row.append('<div class="nk-points">' + pText + '</div>');
1276
-
1277
- // Butonlar
1278
- const btnCol = $('<div class="nk-actions"></div>');
1262
+ row.append('<div class="nk-points">' + Math.floor(u.points || 0).toLocaleString('tr-TR') + '</div>');
1279
1263
 
1280
- // Detay Butonu
1281
- const btnDetail = $('<button class="nk-btn-action info" title="Detay"><i class="fa fa-eye"></i></button>');
1282
- btnDetail.click(function () { openDetailModal(u.uid); });
1283
- btnCol.append(btnDetail);
1284
-
1285
- // Düzenle Butonu
1286
- const btnManage = $('<button class="nk-btn-action" title="Puan Düzenle"><i class="fa fa-cog"></i></button>');
1287
- btnManage.click(function () { openManageModal(u.uid, u.username); });
1288
- btnCol.append(btnManage);
1264
+ var btnCol = $('<div class="nk-actions"></div>');
1265
+ $('<button class="nk-btn-action info" title="Detay"><i class="fa fa-eye"></i></button>')
1266
+ .click(function () { openDetailModal(u.uid); }).appendTo(btnCol);
1267
+ $('<button class="nk-btn-action" title="Puan Düzenle"><i class="fa fa-cog"></i></button>')
1268
+ .click(function () { openManageModal(u.uid, u.username); }).appendTo(btnCol);
1289
1269
 
1290
1270
  row.append(btnCol);
1291
1271
  $container.append(row);
1292
1272
  });
1293
1273
  }
1294
1274
 
1295
- function escapeHtml(text) {
1296
- if (!text) return '';
1297
- const div = document.createElement('div');
1298
- div.textContent = text;
1299
- return div.innerHTML;
1300
- }
1301
-
1302
- // Bu script widget HTML'i içinde olduğundan, çalıştığında DOM zaten hazır.
1303
- // action:ajaxify.end widget'lardan ÖNCE tetiklendiği için kullanılmamalı.
1304
- // Ayrıca her widget yüklemesinde yeni listener eklenmesi sorun yaratır.
1305
- if ($('.niki-dashboard').length) {
1275
+ // Hemen başlat - widget yüklendiğinde socket/app zaten hazır
1276
+ console.log('[Niki-Admin] Moment yükleniyor...');
1277
+ loadMoment(function () {
1278
+ console.log('[Niki-Admin] Moment hazır, initNikiAdmin çağrılıyor...');
1306
1279
  initNikiAdmin();
1307
- }
1280
+ });
1281
+ } // end initAdminModule
1282
+
1283
+ // Başlat
1284
+ waitAndInit();
1308
1285
  })();
1309
1286
  </script>