nodebb-plugin-niki-loyalty 1.3.1 → 1.3.3

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
@@ -319,37 +319,112 @@ Plugin.init = async function (params) {
319
319
  }
320
320
  });
321
321
 
322
- // 3) KASA HISTORY - BASİT VERSİYON
322
+ // 3) KASA HISTORY - GELİŞMİŞ VERSİYON (Filtre + İstatistik)
323
323
  router.get('/api/niki-loyalty/kasa-history', middleware.ensureLoggedIn, async (req, res) => {
324
324
  try {
325
325
  const isAdmin = await user.isAdministrator(req.uid);
326
326
  const isMod = await user.isGlobalModerator(req.uid);
327
- if (!isAdmin && !isMod) return res.status(403).json([]);
327
+ if (!isAdmin && !isMod) return res.status(403).json({ error: 'Yetkisiz' });
328
+
329
+ // Query parametreleri
330
+ const { startDate, endDate, search, rewardType, exportAll } = req.query;
328
331
 
329
332
  const raw = await db.getListRange('niki:kasa:history', 0, -1);
330
- const rows = (raw || []).map(safeParseMaybeJson).filter(Boolean).reverse();
333
+ let rows = (raw || []).map(safeParseMaybeJson).filter(Boolean).reverse();
334
+
335
+ // Personel bilgilerini de al
336
+ const staffUids = [...new Set(rows.map(r => parseInt(r.staff, 10)).filter(n => Number.isFinite(n) && n > 0))];
337
+ const custUids = rows.map(r => parseInt(r.cuid, 10)).filter(n => Number.isFinite(n) && n > 0);
338
+ const allUids = [...new Set([...staffUids, ...custUids])];
331
339
 
332
- const uids = rows.map(r => parseInt(r.cuid, 10)).filter(n => Number.isFinite(n) && n > 0);
333
- const users = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'icon:bgColor']);
340
+ const usersData = await user.getUsersFields(allUids, ['uid', 'username', 'userslug', 'picture', 'icon:bgColor']);
334
341
  const userMap = {};
335
- (users || []).forEach(u => userMap[u.uid] = u);
342
+ (usersData || []).forEach(u => userMap[u.uid] = u);
336
343
 
337
344
  const rp = nconf.get('relative_path') || '';
338
- const enriched = rows.map(r => {
339
- const u = userMap[r.cuid] || {};
345
+
346
+ // Zenginleştir
347
+ let enriched = rows.map(r => {
348
+ const custUser = userMap[r.cuid] || {};
349
+ const staffUser = userMap[r.staff] || {};
340
350
  return {
341
351
  ...r,
342
- cust: u.username || r.cust || 'Bilinmeyen',
343
- picture: u.picture || '',
344
- iconBg: u['icon:bgColor'] || '#4b5563',
345
- profileUrl: u.userslug ? `${rp}/user/${u.userslug}` : '',
346
- reward: r.reward || 'İşlem'
352
+ cust: custUser.username || r.cust || 'Bilinmeyen',
353
+ picture: custUser.picture || '',
354
+ iconBg: custUser['icon:bgColor'] || '#4b5563',
355
+ profileUrl: custUser.userslug ? `${rp}/user/${custUser.userslug}` : '',
356
+ reward: r.reward || 'İşlem',
357
+ staffName: staffUser.username || 'Personel',
358
+ staffPicture: staffUser.picture || '',
359
+ date: new Date(r.ts).toISOString().slice(0, 10) // YYYY-MM-DD
347
360
  };
348
361
  });
349
- return res.json(enriched);
362
+
363
+ // FİLTRELEME
364
+ // 1. Tarih aralığı
365
+ if (startDate) {
366
+ const start = new Date(startDate).getTime();
367
+ enriched = enriched.filter(r => r.ts >= start);
368
+ }
369
+ if (endDate) {
370
+ const end = new Date(endDate).getTime() + 86400000; // gün sonu
371
+ enriched = enriched.filter(r => r.ts < end);
372
+ }
373
+
374
+ // 2. Arama (kullanıcı adı)
375
+ if (search && search.trim()) {
376
+ const q = search.toLowerCase().trim();
377
+ enriched = enriched.filter(r =>
378
+ (r.cust && r.cust.toLowerCase().includes(q)) ||
379
+ (r.staffName && r.staffName.toLowerCase().includes(q))
380
+ );
381
+ }
382
+
383
+ // 3. Ödül tipi
384
+ if (rewardType && rewardType !== 'all') {
385
+ enriched = enriched.filter(r => r.reward === rewardType);
386
+ }
387
+
388
+ // İSTATİSTİKLER
389
+ const stats = {
390
+ totalTransactions: enriched.length,
391
+ totalPoints: enriched.reduce((sum, r) => sum + (parseFloat(r.amt) || 0), 0),
392
+ byReward: {},
393
+ byStaff: {},
394
+ byDate: {}
395
+ };
396
+
397
+ enriched.forEach(r => {
398
+ // Ödül bazında
399
+ stats.byReward[r.reward] = (stats.byReward[r.reward] || 0) + 1;
400
+ // Personel bazında
401
+ stats.byStaff[r.staffName] = (stats.byStaff[r.staffName] || 0) + 1;
402
+ // Gün bazında (son 7 gün için chart)
403
+ stats.byDate[r.date] = (stats.byDate[r.date] || 0) + 1;
404
+ });
405
+
406
+ // Benzersiz ödül tipleri (filter dropdown için)
407
+ const rewardTypes = [...new Set(rows.map(r => r.reward || 'İşlem'))];
408
+
409
+ // Export all için sayfalama yok
410
+ if (exportAll === 'true') {
411
+ return res.json({
412
+ data: enriched,
413
+ stats,
414
+ rewardTypes
415
+ });
416
+ }
417
+
418
+ // Normal görünüm (son 100 işlem)
419
+ return res.json({
420
+ data: enriched.slice(0, 100),
421
+ stats,
422
+ rewardTypes,
423
+ hasMore: enriched.length > 100
424
+ });
350
425
  } catch (e) {
351
426
  console.error('[Niki-Loyalty] Kasa history error:', e);
352
- return res.status(500).json([]);
427
+ return res.status(500).json({ error: 'Sunucu hatası' });
353
428
  }
354
429
  });
355
430
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-niki-loyalty",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "Niki The Cat Coffee Loyalty System - Earn points while studying on IEU Forum.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {
@@ -487,105 +487,5 @@ $(document).ready(function () {
487
487
  });
488
488
  });
489
489
  }
490
- // -------------------------------------------------------------
491
- // 📢 DUYURU SİSTEMİ (Marquee + Günlük Limit + Kapat Butonu)
492
- // -------------------------------------------------------------
493
- try {
494
- const TARGET_CATEGORY_ID = 1; // Duyuruların çekileceği kategori ID
495
- const MAX_DAILY_SHOW = 5; // Günde en fazla kaç kere gösterilecek
496
-
497
- function checkAnnouncements() {
498
- // Widget elementlerini bul veya oluştur
499
- let $widget = $('.niki-marquee-widget');
500
- if ($widget.length === 0) {
501
- // Henüz yoksa oluştur (Başlangıçta gizli)
502
- const widgetHtml = `
503
- <div class="niki-marquee-widget" style="display:none;">
504
- <div class="nm-content">
505
- <div class="nm-icon">📢</div>
506
- <div class="nm-text-container">
507
- <span class="nm-text" id="niki-marquee-text">Duyuru Yükleniyor...</span>
508
- </div>
509
- <button class="nm-close" id="niki-marquee-close" title="Kapat">✖</button>
510
- </div>
511
- </div>
512
- <style>
513
- .niki-marquee-widget {
514
- position: fixed; bottom: 85px; right: 20px; z-index: 9999;
515
- background: #1a1a1a; border: 1px solid #C5A065; border-radius: 50px;
516
- box-shadow: 0 5px 20px rgba(0,0,0,0.4); padding: 5px 15px;
517
- width: 320px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
518
- animation: slideInUp 0.5s ease-out;
519
- }
520
- .nm-content { display: flex; align-items: center; justify-content: space-between; height: 36px; }
521
- .nm-icon { font-size: 18px; margin-right: 10px; animation: pulse 2s infinite; }
522
- .nm-text-container { flex: 1; overflow: hidden; white-space: nowrap; position: relative; mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); }
523
- .nm-text {
524
- display: inline-block; color: #fff; font-size: 13px; font-weight: 500;
525
- padding-left: 100%; animation: marquee 10s linear infinite;
526
- }
527
- .nm-close {
528
- background: none; border: none; color: #888; cursor: pointer; font-size: 14px;
529
- margin-left: 10px; padding: 0 5px; transition: 0.2s;
530
- }
531
- .nm-close:hover { color: #ff5252; transform: scale(1.1); }
532
- @keyframes marquee { 0% { transform: translate(0, 0); } 100% { transform: translate(-100%, 0); } }
533
- @keyframes slideInUp { from { transform: translateY(100px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
534
- @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.2); } 100% { transform: scale(1); } }
535
- </style>
536
- `;
537
- $('body').append(widgetHtml);
538
- $widget = $('.niki-marquee-widget');
539
-
540
- // Kapatma butonu olayı
541
- $('#niki-marquee-close').on('click', function () {
542
- $widget.fadeOut();
543
- sessionStorage.setItem('niki_session_closed', 'true'); // Bu oturumda bir daha açma
544
- });
545
- }
546
-
547
- // Eğer bu oturumda kullanıcı kapattıysa gösterme
548
- if (sessionStorage.getItem('niki_session_closed') === 'true') return;
549
-
550
- // Günlük limit kontrolü
551
- const today = new Date().toISOString().slice(0, 10);
552
- const storageKey = 'niki_announce_count_' + today;
553
- let count = parseInt(localStorage.getItem(storageKey) || '0');
554
-
555
- if (count >= MAX_DAILY_SHOW) return; // Limit dolmuş
556
-
557
- // API'den son konuyu çek
558
- $.getJSON('/api/category/' + TARGET_CATEGORY_ID, function (data) {
559
- if (data && data.topics && data.topics.length > 0) {
560
- const topic = data.topics[0]; // En son konu
561
- const lastTid = localStorage.getItem('niki_last_seen_tid');
562
-
563
- // Yeni konu varsa veya henüz limit dolmadıysa göster
564
- // (Kullanıcı "sürekli akmasını istiyorum" dediği için her sayfa yenilemede göstereceğiz, limite kadar)
565
- const text = topic.title;
566
- const url = config.relative_path + '/topic/' + topic.slug;
567
-
568
- $('#niki-marquee-text').html(`<a href="${url}" style="color:#fff;text-decoration:none;">${text}</a>`);
569
- $widget.fadeIn();
570
-
571
- // Sayacı artır (Sadece görünür olduğunda)
572
- if ($widget.is(':visible')) {
573
- localStorage.setItem(storageKey, count + 1);
574
- // Yeni konuyu gördü olarak işaretle
575
- localStorage.setItem('niki_last_seen_tid', topic.tid);
576
- }
577
- }
578
- });
579
- }
580
-
581
- // Başlat (Sayfa yüklendikten biraz sonra)
582
- setTimeout(checkAnnouncements, 2000);
583
-
584
- // Sayfa geçişlerinde de kontrol et
585
- $(window).on('action:ajaxify.end', function () {
586
- setTimeout(checkAnnouncements, 2000);
587
- });
588
-
589
- } catch (e) { console.warn("[Niki] Duyuru hatası:", e); }
590
-
591
490
  });
491
+