nodebb-plugin-niki-loyalty 1.3.0 → 1.3.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.
@@ -7,175 +7,43 @@
7
7
  body.niki-kasa-body { background-color: #000; margin: 0; padding: 0; overflow: hidden; }
8
8
  #content { padding: 0 !important; margin: 0 !important; width: 100% !important; background: #000; }
9
9
 
10
- /* GİRİŞ */
11
- .start-view { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #111; z-index: 100; color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: 'Poppins', sans-serif; }
10
+ .start-view { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(1200px 700px at 50% 10%, rgba(197,160,101,0.18), transparent 60%), linear-gradient(180deg, #0b0b0b, #111 55%, #0b0b0b); z-index: 100; color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: 'Poppins', sans-serif; }
12
11
  .logo-box { width: 90px; height: 90px; border-radius: 50%; border: 3px solid #C5A065; display: flex; align-items: center; justify-content: center; margin-bottom: 30px; box-shadow: 0 0 30px rgba(197, 160, 101, 0.2); background: #000; }
13
12
  .btn-start { background: #C5A065; color: #000; padding: 18px 40px; border-radius: 14px; font-weight: 700; border: none; cursor: pointer; display: flex; align-items: center; gap: 10px; font-size: 16px; margin-bottom: 15px; width: 260px; justify-content: center; box-shadow: 0 5px 15px rgba(197, 160, 101, 0.3); }
14
13
  .btn-hist { background: #222; color: #fff; padding: 18px 40px; border-radius: 14px; font-weight: 600; border: 1px solid #333; cursor: pointer; display: flex; align-items: center; gap: 10px; font-size: 14px; width: 260px; justify-content: center; }
14
+ .btn-group { display: grid; gap: 12px; }
15
15
 
16
- /* KAMERA */
17
16
  .camera-ui { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #000; display: none; z-index: 200; }
18
17
  #reader { width: 100% !important; height: 100% !important; border: none !important; }
19
18
  #reader video { object-fit: cover !important; width: 100% !important; height: 100% !important; }
20
-
21
19
  .session-bar-bg { position: absolute; top:0; left:0; width:100%; height:6px; background:rgba(255,255,255,0.2); z-index: 220; }
22
20
  .session-bar-fill { height:100%; background:#C5A065; width:100%; transition: width 1s linear; }
23
-
24
21
  .top-ctrl { position: absolute; top: 20px; left: 0; width: 100%; padding: 0 20px; display: flex; justify-content: space-between; z-index: 230; box-sizing: border-box; }
25
- .badge-live { background: rgba(0,0,0,0.6); color: #C5A065; padding: 6px 14px; border-radius: 20px; font-size: 12px; font-weight: 700; border: 1px solid rgba(197, 160, 101, 0.4); backdrop-filter: blur(4px); }
26
- .btn-x { background: rgba(255,255,255,0.2); width: 40px; height: 40px; border-radius: 50%; border: none; color: #fff; cursor: pointer; font-size: 18px; backdrop-filter: blur(4px); display:flex; align-items:center; justify-content:center; }
27
-
28
- /* VİZÖR */
22
+ .badge-live { background: rgba(0,0,0,0.6); color: #C5A065; padding: 6px 14px; border-radius: 20px; font-size: 12px; font-weight: 700; }
23
+ .btn-x { background: rgba(255,255,255,0.2); width: 40px; height: 40px; border-radius: 50%; border: none; color: #fff; cursor: pointer; font-size: 18px; display:flex; align-items:center; justify-content:center; }
29
24
  .scan-guide { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 70vw; height: 70vw; max-width: 300px; max-height: 300px; box-shadow: 0 0 0 4000px rgba(0,0,0,0.85); border: 2px solid rgba(255,255,255,0.3); border-radius: 24px; pointer-events: none; z-index: 210; }
30
- .scan-guide::before { content:''; position:absolute; top:-2px; left:-2px; width:40px; height:40px; border-top:5px solid #C5A065; border-left:5px solid #C5A065; border-radius:20px 0 0 0; }
31
- .scan-guide::after { content:''; position:absolute; bottom:-2px; right:-2px; width:40px; height:40px; border-bottom:5px solid #C5A065; border-right:5px solid #C5A065; border-radius:0 0 20px 0; }
32
25
  .laser { width: 100%; height: 2px; background: #C5A065; position: absolute; top: 50%; box-shadow: 0 0 15px #C5A065; animation: scanAnim 2s infinite ease-in-out; }
33
26
  @keyframes scanAnim { 0% { top: 10%; opacity: 0; } 50% { opacity: 1; } 100% { top: 90%; opacity: 0; } }
27
+ .scan-hint { position: absolute; bottom: 60px; width: 100%; text-align: center; color: #fff; font-size: 13px; opacity: 0.8; z-index: 220; }
34
28
 
35
- .scan-hint { position: absolute; bottom: 60px; width: 100%; text-align: center; color: #fff; font-size: 13px; opacity: 0.8; z-index: 220; letter-spacing: 0.5px; }
36
-
37
- /* SONUÇ */
38
- .res-modal { position: fixed; bottom: 0; left: 0; width: 100%; background: #1a1a1a; border-radius: 30px 30px 0 0; padding: 40px 20px; text-align: center; transform: translateY(110%); transition: transform 0.3s; z-index: 600; box-sizing: border-box; border-top: 1px solid #333; }
29
+ .res-modal { position: fixed; bottom: 0; left: 0; width: 100%; background: #1a1a1a; border-radius: 30px 30px 0 0; padding: 40px 20px; text-align: center; transform: translateY(110%); transition: transform 0.3s; z-index: 600; box-sizing: border-box; }
39
30
  .res-modal.active { transform: translateY(0); }
40
31
  .icon-big { font-size: 60px; margin-bottom: 15px; }
41
32
  .res-title { color: #fff; font-size: 24px; font-weight: 700; margin-bottom: 10px; }
42
33
  .res-msg { color: #aaa; font-size: 15px; margin-bottom: 30px; }
43
34
  .btn-new { background: #fff; color: #000; width: 100%; padding: 18px; border-radius: 16px; font-size: 16px; font-weight: 700; border: none; cursor: pointer; }
44
35
 
45
- /* GEÇMİŞ - GELİŞMİŞ */
46
- .hist-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #0a0a0a; z-index: 700; display: none; flex-direction: column; font-family: 'Poppins', sans-serif; }
47
- .hist-head { padding: 16px 20px; background: linear-gradient(180deg, #151515, #1a1a1a); color: #fff; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #222; }
48
- .hist-head-title { font-weight: 700; letter-spacing: 1px; font-size: 16px; }
49
-
50
- /* İSTATİSTİK KARTLARI */
51
- .hist-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding: 16px 20px; background: #0f0f0f; border-bottom: 1px solid #1a1a1a; }
52
- .stat-card { background: linear-gradient(145deg, #1a1a1a, #141414); border-radius: 14px; padding: 14px 12px; text-align: center; border: 1px solid #222; }
53
- .stat-value { font-size: 24px; font-weight: 700; color: #C5A065; margin-bottom: 4px; }
54
- .stat-label { font-size: 11px; color: #777; text-transform: uppercase; letter-spacing: 0.5px; }
55
-
56
- /* FİLTRELER */
57
- .hist-filters { padding: 14px 20px; background: #0d0d0d; border-bottom: 1px solid #1a1a1a; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
58
- .filter-group { display: flex; flex-direction: column; gap: 4px; }
59
- .filter-group label { font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 0.5px; }
60
- .filter-input { background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 10px; padding: 10px 14px; color: #fff; font-size: 13px; min-width: 130px; }
61
- .filter-input:focus { outline: none; border-color: #C5A065; }
62
- .filter-input::placeholder { color: #555; }
63
-
64
- .filter-select { background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 10px; padding: 10px 14px; color: #fff; font-size: 13px; min-width: 140px; cursor: pointer; }
65
- .filter-select:focus { outline: none; border-color: #C5A065; }
66
-
67
- .filter-btn { background: #C5A065; color: #000; border: none; border-radius: 10px; padding: 10px 16px; font-weight: 600; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 6px; }
68
- .filter-btn:hover { filter: brightness(1.1); }
69
- .filter-btn.secondary { background: #2a2a2a; color: #fff; }
70
- .filter-btn.secondary:hover { background: #333; }
71
-
72
- /* CSV BUTONLARI */
73
- .action-buttons { margin-left: auto; display: flex; gap: 8px; }
74
- .btn-csv { background: linear-gradient(145deg, #2E7D32, #1B5E20); color: #fff; border: none; border-radius: 10px; padding: 10px 16px; font-weight: 600; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 6px; }
75
- .btn-csv:hover { filter: brightness(1.1); }
76
-
77
- /* LİSTE */
78
- .hist-body { flex: 1; overflow-y: auto; padding: 10px 0; }
79
- .hist-row { display: flex; align-items: center; padding: 14px 20px; margin: 8px 16px; border-radius: 14px; background: linear-gradient(145deg, #161616, #121212); border: 1px solid #1f1f1f; transition: transform 0.15s, border-color 0.15s; }
80
- .hist-row:hover { transform: translateX(4px); border-color: #333; }
81
- .hist-u-img { width: 48px; height: 48px; border-radius: 50%; margin-right: 14px; border: 2px solid #2a2a2a; object-fit: cover; background: #1a1a1a; }
36
+ .hist-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #111; z-index: 700; display: none; flex-direction: column; font-family: 'Poppins', sans-serif; }
37
+ .hist-head { padding: 20px; background: #1a1a1a; color: #fff; display: flex; justify-content: space-between; align-items: center; }
38
+ .hist-head-title { font-weight: 700; font-size: 18px; }
39
+ .hist-body { flex: 1; overflow-y: auto; padding: 10px; }
40
+ .hist-row { display: flex; align-items: center; padding: 15px; margin-bottom: 10px; border-radius: 12px; background: #1a1a1a; }
41
+ .hist-u-img { width: 50px; height: 50px; border-radius: 50%; margin-right: 15px; object-fit: cover; }
82
42
  .hist-info { flex: 1; }
83
- .hist-u-name { display: block; font-weight: 700; font-size: 14px; color: #f0f0f0; margin-bottom: 3px; }
84
- .hist-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
85
- .hist-time { font-size: 12px; color: #666; }
86
- .hist-staff { font-size: 11px; color: #888; background: #1f1f1f; padding: 3px 8px; border-radius: 6px; }
87
- .hist-reward-badge { font-size: 11px; color: #C5A065; background: rgba(197, 160, 101, 0.15); padding: 3px 8px; border-radius: 6px; border: 1px solid rgba(197, 160, 101, 0.3); }
88
- .hist-val { background: rgba(198,40,40,0.15); border: 1px solid rgba(198,40,40,0.3); color: #ef5350; padding: 10px 14px; border-radius: 12px; font-size: 14px; font-weight: 700; min-width: 70px; text-align: center; }
89
-
90
- /* BOŞ DURUM */
91
- .hist-empty { text-align: center; padding: 60px 20px; color: #555; }
92
- .hist-empty i { font-size: 48px; margin-bottom: 16px; display: block; opacity: 0.5; }
93
-
94
- /* LOADING */
95
- .hist-loading { text-align: center; padding: 40px; color: #666; }
96
- .hist-loading .spinner { display: inline-block; width: 30px; height: 30px; border: 3px solid #333; border-top-color: #C5A065; border-radius: 50%; animation: spin 1s linear infinite; }
97
- @keyframes spin { to { transform: rotate(360deg); } }
98
-
99
- /* =========================
100
- UI/UX OVERRIDES
101
- ========================= */
102
- body.niki-kasa-body { -webkit-font-smoothing: antialiased; touch-action: manipulation; }
103
- #content { min-height: 100vh; }
104
-
105
- .start-view, .camera-ui, .hist-modal {
106
- padding-top: env(safe-area-inset-top);
107
- padding-bottom: env(safe-area-inset-bottom);
108
- }
109
-
110
- .start-view {
111
- background: radial-gradient(1200px 700px at 50% 10%, rgba(197,160,101,0.18), transparent 60%),
112
- linear-gradient(180deg, #0b0b0b, #111 55%, #0b0b0b);
113
- }
114
-
115
- .btn-group { display: grid; gap: 12px; }
116
-
117
- .btn-start, .btn-hist, .btn-new, .btn-x { -webkit-tap-highlight-color: transparent; }
118
- .btn-start:active, .btn-hist:active, .btn-new:active, .btn-x:active { transform: scale(0.97); }
119
-
120
- .btn-start { width: min(320px, 88vw); border-radius: 16px; transition: transform 120ms ease; }
121
- .btn-hist { width: min(320px, 88vw); border-radius: 16px; transition: transform 120ms ease; }
122
-
123
- .top-ctrl { top: calc(14px + env(safe-area-inset-top)); padding: 0 16px; }
124
- .btn-x { border: 1px solid rgba(255,255,255,0.15); }
125
-
126
- .scan-guide { width: min(76vw, 320px); height: min(76vw, 320px); box-shadow: 0 0 0 5000px rgba(0,0,0,0.78); }
127
- .scan-hint { bottom: calc(56px + env(safe-area-inset-bottom)); }
128
-
129
- .res-modal {
130
- background: linear-gradient(180deg, #1c1c1c, #121212);
131
- border-top: 1px solid rgba(255,255,255,0.1);
132
- box-shadow: 0 -20px 50px rgba(0,0,0,0.6);
133
- padding-bottom: calc(20px + env(safe-area-inset-bottom));
134
- }
135
-
136
- .res-modal::before {
137
- content: "";
138
- display: block;
139
- width: 44px;
140
- height: 5px;
141
- background: rgba(255,255,255,0.18);
142
- border-radius: 99px;
143
- margin: -10px auto 16px;
144
- }
145
-
146
- .res-close {
147
- position: absolute;
148
- top: 14px;
149
- right: 14px;
150
- width: 42px;
151
- height: 42px;
152
- border-radius: 50%;
153
- background: rgba(255,255,255,0.12);
154
- border: 1px solid rgba(255,255,255,0.18);
155
- color: #fff;
156
- display:flex;
157
- align-items:center;
158
- justify-content:center;
159
- cursor:pointer;
160
- backdrop-filter: blur(6px);
161
- }
162
- .res-close:active { transform: scale(0.95); }
163
-
164
- .hist-body { padding-bottom: calc(16px + env(safe-area-inset-bottom)); }
165
-
166
- @media (prefers-reduced-motion: reduce) {
167
- .laser { animation: none; }
168
- }
169
-
170
- /* Mobil Uyum */
171
- @media (max-width: 600px) {
172
- .hist-stats { grid-template-columns: repeat(2, 1fr); }
173
- .hist-filters { flex-direction: column; align-items: stretch; }
174
- .filter-group { width: 100%; }
175
- .filter-input, .filter-select { width: 100%; }
176
- .action-buttons { width: 100%; justify-content: stretch; margin-left: 0; margin-top: 8px; }
177
- .action-buttons .btn-csv { flex: 1; justify-content: center; }
178
- }
43
+ .hist-u-name { font-weight: 700; font-size: 15px; color: #fff; }
44
+ .hist-time { font-size: 12px; color: #888; display: block; margin-top: 3px; }
45
+ .hist-val { background: rgba(198,40,40,0.2); color: #ef5350; padding: 8px 14px; border-radius: 10px; font-weight: 700; }
46
+ .hist-empty { text-align: center; padding: 60px; color: #666; }
179
47
  </style>
180
48
 
181
49
  <div id="start-view" class="start-view">
@@ -200,106 +68,43 @@
200
68
  </div>
201
69
 
202
70
  <div id="res-view" class="res-modal">
203
- <button class="res-close" onclick="stopSession()" aria-label="Kapat">
204
- <i class="fa fa-times"></i>
205
- </button>
206
71
  <div id="res-icon" class="icon-big"></div>
207
72
  <div id="res-title" class="res-title">...</div>
208
73
  <div id="res-msg" class="res-msg">...</div>
209
74
  <button class="btn-new" onclick="resumeScan()">YENİ İŞLEM</button>
210
75
  </div>
211
76
 
212
- <!-- GELİŞMİŞ İŞLEM GEÇMİŞİ -->
213
77
  <div id="hist-view" class="hist-modal">
214
78
  <div class="hist-head">
215
79
  <span class="hist-head-title"><i class="fa fa-history"></i> İŞLEM GEÇMİŞİ</span>
216
80
  <button onclick="closeHistory()" style="background:none; border:none; color:#fff; font-size:20px; cursor:pointer;"><i class="fa fa-times"></i></button>
217
81
  </div>
218
-
219
- <!-- İSTATİSTİKLER -->
220
- <div class="hist-stats">
221
- <div class="stat-card">
222
- <div class="stat-value" id="stat-total">0</div>
223
- <div class="stat-label">Toplam İşlem</div>
224
- </div>
225
- <div class="stat-card">
226
- <div class="stat-value" id="stat-points">0</div>
227
- <div class="stat-label">Toplam Puan</div>
228
- </div>
229
- <div class="stat-card">
230
- <div class="stat-value" id="stat-today">0</div>
231
- <div class="stat-label">Bugün</div>
232
- </div>
233
- </div>
234
-
235
- <!-- FİLTRELER -->
236
- <div class="hist-filters">
237
- <div class="filter-group">
238
- <label>Başlangıç</label>
239
- <input type="date" class="filter-input" id="filter-start">
240
- </div>
241
- <div class="filter-group">
242
- <label>Bitiş</label>
243
- <input type="date" class="filter-input" id="filter-end">
244
- </div>
245
- <div class="filter-group">
246
- <label>Ara</label>
247
- <input type="text" class="filter-input" id="filter-search" placeholder="Kullanıcı adı...">
248
- </div>
249
- <div class="filter-group">
250
- <label>Ödül Tipi</label>
251
- <select class="filter-select" id="filter-reward">
252
- <option value="all">Tümü</option>
253
- </select>
254
- </div>
255
- <button class="filter-btn" onclick="applyFilters()"><i class="fa fa-filter"></i> Filtrele</button>
256
- <button class="filter-btn secondary" onclick="clearFilters()"><i class="fa fa-refresh"></i> Sıfırla</button>
257
-
258
- <div class="action-buttons">
259
- <button class="btn-csv" onclick="exportCSV()"><i class="fa fa-download"></i> CSV İndir</button>
260
- </div>
261
- </div>
262
-
263
- <!-- LİSTE -->
264
- <ul id="hist-list" class="hist-body"></ul>
82
+ <ul id="hist-list" class="hist-body" style="list-style:none; margin:0; padding:10px;"></ul>
265
83
  </div>
266
84
 
267
- <div id="denied" style="display:none; text-align:center; padding:100px; color:#fff;"><h3>Yetkisiz Giriş</h3></div>
268
-
269
85
  <script>
270
- let html5QrCode;
271
- let isProcessing = false;
272
- let sessionTimer;
273
- let currentHistoryData = [];
86
+ var html5QrCode;
87
+ var isProcessing = false;
88
+ var sessionTimer;
274
89
  moment.locale('tr');
275
90
 
276
- // --- ARKA KAMERA BULMA VE BAŞLATMA ---
277
91
  async function initSession() {
278
92
  $('#start-view').fadeOut(200);
279
93
  $('#camera-ui').fadeIn(300);
280
94
  startTimer(600);
281
-
282
95
  html5QrCode = new Html5Qrcode("reader");
283
-
284
96
  try {
285
- const devices = await Html5Qrcode.getCameras();
286
-
97
+ var devices = await Html5Qrcode.getCameras();
287
98
  if (devices && devices.length) {
288
- let cameraId = devices[devices.length - 1].id;
289
-
290
- for (let i = 0; i < devices.length; i++) {
291
- let label = devices[i].label.toLowerCase();
99
+ var cameraId = devices[devices.length - 1].id;
100
+ for (var i = 0; i < devices.length; i++) {
101
+ var label = devices[i].label.toLowerCase();
292
102
  if (label.includes("back") || label.includes("arka") || label.includes("environment")) {
293
103
  cameraId = devices[i].id;
294
104
  break;
295
105
  }
296
106
  }
297
-
298
- await html5QrCode.start(
299
- cameraId,
300
- { fps: 15, aspectRatio: window.innerWidth / window.innerHeight },
301
- onScanSuccess
302
- );
107
+ await html5QrCode.start(cameraId, { fps: 15, aspectRatio: window.innerWidth / window.innerHeight }, onScanSuccess);
303
108
  } else {
304
109
  alert("Kamera bulunamadı!");
305
110
  stopSession();
@@ -311,12 +116,12 @@
311
116
  }
312
117
 
313
118
  function startTimer(duration) {
314
- let timeLeft = duration;
119
+ var timeLeft = duration;
315
120
  $('#timer-bar').css('width', '100%');
316
121
  clearInterval(sessionTimer);
317
- sessionTimer = setInterval(() => {
122
+ sessionTimer = setInterval(function() {
318
123
  timeLeft--;
319
- let pct = (timeLeft / duration) * 100;
124
+ var pct = (timeLeft / duration) * 100;
320
125
  $('#timer-bar').css('width', pct + '%');
321
126
  if(timeLeft <= 0) { alert("Oturum doldu."); stopSession(); }
322
127
  }, 1000);
@@ -325,224 +130,70 @@
325
130
  function onScanSuccess(decodedText) {
326
131
  if (isProcessing) return;
327
132
  isProcessing = true;
328
-
329
- const audio = new Audio('https://freesound.org/data/previews/171/171671_2437358-lq.mp3');
330
- audio.play().catch(e=>{});
331
133
  if(navigator.vibrate) navigator.vibrate(200);
332
-
333
134
  html5QrCode.pause();
334
135
 
335
136
  $.post('/api/niki-loyalty/scan-qr', { token: decodedText, _csrf: config.csrf_token }, function(res) {
336
137
  if(res.success) {
337
138
  $('#res-icon').html('<i class="fa fa-check-circle" style="color:#2E7D32"></i>');
338
139
  $('#res-title').text('ÖDEME ALINDI');
339
- $('#res-msg').html(`
340
- <img src="${res.customer.picture || ''}" style="width:60px; height:60px; border-radius:50%; border:2px solid #fff; margin-bottom:10px;"><br>
341
- <b>${res.customer.username}</b><br>
342
- ${res.cost} Puan Tahsil Edildi.<br>
343
- <small style="color:#C5A065">${res.rewardName}</small>
344
- `);
140
+ $('#res-msg').html('<b>'+res.customer.username+'</b><br>'+res.cost+' Puan Tahsil Edildi.');
345
141
  } else {
346
- new Audio('https://freesound.org/data/previews/142/142608_1840739-lq.mp3').play().catch(e=>{});
347
- if(navigator.vibrate) navigator.vibrate([100,50,100]);
348
142
  $('#res-icon').html('<i class="fa fa-times-circle" style="color:#C62828"></i>');
349
143
  $('#res-title').text('HATA');
350
144
  $('#res-msg').text(res.message);
351
145
  }
352
146
  $('#res-view').addClass('active');
353
- }).fail(function() {
354
- alert("Sunucu Hatası!"); resumeScan();
355
- });
147
+ }).fail(function() { alert("Sunucu Hatası!"); resumeScan(); });
356
148
  }
357
149
 
358
150
  function resumeScan() {
359
151
  $('#res-view').removeClass('active');
360
- setTimeout(() => { isProcessing = false; html5QrCode.resume(); }, 300);
152
+ setTimeout(function() { isProcessing = false; html5QrCode.resume(); }, 300);
361
153
  }
362
154
 
363
155
  function stopSession() {
364
156
  clearInterval(sessionTimer);
365
- if(html5QrCode) html5QrCode.stop().then(() => html5QrCode.clear()).catch(()=>{});
157
+ if(html5QrCode) html5QrCode.stop().then(function() { html5QrCode.clear(); }).catch(function(){});
366
158
  $('#camera-ui').hide();
367
159
  $('#res-view').removeClass('active');
368
160
  $('#start-view').fadeIn(200);
369
161
  isProcessing = false;
370
162
  }
371
163
 
372
- // =============================
373
- // GELİŞMİŞ İŞLEM GEÇMİŞİ
374
- // =============================
375
-
376
164
  function openHistory() {
377
165
  $('#hist-view').fadeIn().css('display','flex');
378
- loadHistory();
379
- }
380
-
381
- function closeHistory() {
382
- $('#hist-view').fadeOut();
383
- }
384
-
385
- function loadHistory(filters = {}) {
386
- const list = $('#hist-list');
387
- list.html('<li class="hist-loading"><div class="spinner"></div><br>Yükleniyor...</li>');
166
+ var list = $('#hist-list');
167
+ list.html('<li style="text-align:center; padding:40px; color:#888;">Yükleniyor...</li>');
388
168
 
389
- const params = new URLSearchParams();
390
- if (filters.startDate) params.append('startDate', filters.startDate);
391
- if (filters.endDate) params.append('endDate', filters.endDate);
392
- if (filters.search) params.append('search', filters.search);
393
- if (filters.rewardType) params.append('rewardType', filters.rewardType);
394
-
395
- $.get('/api/niki-loyalty/kasa-history?' + params.toString(), function(response) {
396
- if (response.error) {
397
- list.html('<li class="hist-empty"><i class="fa fa-exclamation-circle"></i>Hata: ' + response.error + '</li>');
398
- return;
399
- }
169
+ $.get('/api/niki-loyalty/kasa-history', function(response) {
170
+ // Hem eski (array) hem yeni ({data:[]}) formatı destekle
171
+ var data = Array.isArray(response) ? response : (response.data || []);
400
172
 
401
- const data = response.data || [];
402
- const stats = response.stats || {};
403
- const rewardTypes = response.rewardTypes || [];
404
-
405
- currentHistoryData = data;
406
-
407
- // İstatistikleri güncelle
408
- $('#stat-total').text(stats.totalTransactions || 0);
409
- $('#stat-points').text((stats.totalPoints || 0).toLocaleString('tr-TR'));
410
-
411
- // Bugünkü işlem sayısı
412
- const today = new Date().toISOString().slice(0, 10);
413
- $('#stat-today').text(stats.byDate[today] || 0);
414
-
415
- // Ödül tipi dropdown'ını doldur
416
- const rewardSelect = $('#filter-reward');
417
- const currentVal = rewardSelect.val();
418
- rewardSelect.html('<option value="all">Tümü</option>');
419
- rewardTypes.forEach(type => {
420
- rewardSelect.append(`<option value="${type}">${type}</option>`);
421
- });
422
- if (currentVal) rewardSelect.val(currentVal);
423
-
424
- // Listeyi render et
425
- renderHistoryList(data);
426
-
427
- }).fail(function() {
428
- list.html('<li class="hist-empty"><i class="fa fa-wifi"></i>Bağlantı hatası</li>');
429
- });
430
- }
431
-
432
- function renderHistoryList(data) {
433
- const list = $('#hist-list');
434
- list.empty();
435
-
436
- if (!data || data.length === 0) {
437
- list.html('<li class="hist-empty"><i class="fa fa-inbox"></i>İşlem bulunamadı</li>');
438
- return;
439
- }
440
-
441
- data.forEach(item => {
442
- const timeAgo = moment(item.ts).fromNow();
443
- const exactTime = moment(item.ts).format('DD MMM YYYY, HH:mm');
444
-
445
- const html = `
446
- <li class="hist-row">
447
- <img src="${item.picture || 'https://via.placeholder.com/50'}" class="hist-u-img" onerror="this.src='https://via.placeholder.com/50'">
448
- <div class="hist-info">
449
- <span class="hist-u-name">${escapeHtml(item.cust)}</span>
450
- <div class="hist-meta">
451
- <span class="hist-time" title="${exactTime}">${timeAgo}</span>
452
- <span class="hist-staff"><i class="fa fa-user-circle"></i> ${escapeHtml(item.staffName)}</span>
453
- <span class="hist-reward-badge">${escapeHtml(item.reward)}</span>
454
- </div>
455
- </div>
456
- <div class="hist-val">-${item.amt || 0}</div>
457
- </li>
458
- `;
459
- list.append(html);
460
- });
461
- }
462
-
463
- function applyFilters() {
464
- const filters = {
465
- startDate: $('#filter-start').val(),
466
- endDate: $('#filter-end').val(),
467
- search: $('#filter-search').val(),
468
- rewardType: $('#filter-reward').val()
469
- };
470
- loadHistory(filters);
471
- }
472
-
473
- function clearFilters() {
474
- $('#filter-start').val('');
475
- $('#filter-end').val('');
476
- $('#filter-search').val('');
477
- $('#filter-reward').val('all');
478
- loadHistory();
479
- }
480
-
481
- function exportCSV() {
482
- // Mevcut filtrelerle tüm veriyi al
483
- const filters = {
484
- startDate: $('#filter-start').val(),
485
- endDate: $('#filter-end').val(),
486
- search: $('#filter-search').val(),
487
- rewardType: $('#filter-reward').val()
488
- };
489
-
490
- const params = new URLSearchParams();
491
- if (filters.startDate) params.append('startDate', filters.startDate);
492
- if (filters.endDate) params.append('endDate', filters.endDate);
493
- if (filters.search) params.append('search', filters.search);
494
- if (filters.rewardType) params.append('rewardType', filters.rewardType);
495
- params.append('exportAll', 'true');
496
-
497
- $.get('/api/niki-loyalty/kasa-history?' + params.toString(), function(response) {
498
- if (response.error) {
499
- alert('CSV oluşturulamadı: ' + response.error);
173
+ if(data.length === 0) {
174
+ list.html('<li class="hist-empty"><i class="fa fa-inbox" style="font-size:40px; display:block; margin-bottom:15px;"></i>İşlem bulunamadı</li>');
500
175
  return;
501
176
  }
502
177
 
503
- const data = response.data || [];
504
- if (data.length === 0) {
505
- alert('İndirilecek veri bulunamadı.');
506
- return;
507
- }
508
-
509
- // CSV oluştur
510
- const headers = ['Tarih', 'Saat', 'Müşteri', 'Personel', 'Ödül', 'Puan'];
511
- const rows = data.map(item => {
512
- const date = moment(item.ts);
513
- return [
514
- date.format('DD.MM.YYYY'),
515
- date.format('HH:mm:ss'),
516
- '"' + (item.cust || '').replace(/"/g, '""') + '"',
517
- '"' + (item.staffName || '').replace(/"/g, '""') + '"',
518
- '"' + (item.reward || '').replace(/"/g, '""') + '"',
519
- item.amt || 0
520
- ].join(';');
178
+ list.empty();
179
+ data.forEach(function(item) {
180
+ var html = '<li class="hist-row">' +
181
+ '<img src="'+(item.picture || 'https://via.placeholder.com/50')+'" class="hist-u-img" onerror="this.src=\'https://via.placeholder.com/50\'">' +
182
+ '<div class="hist-info">' +
183
+ '<span class="hist-u-name">'+(item.cust || 'Bilinmeyen')+'</span>' +
184
+ '<span class="hist-time">'+moment(item.ts).fromNow()+'</span>' +
185
+ '</div>' +
186
+ '<div class="hist-val">-'+(item.amt || 250)+'</div>' +
187
+ '</li>';
188
+ list.append(html);
521
189
  });
522
-
523
- const csv = '\uFEFF' + headers.join(';') + '\n' + rows.join('\n');
524
-
525
- // İndir
526
- const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
527
- const url = URL.createObjectURL(blob);
528
- const link = document.createElement('a');
529
- link.href = url;
530
- link.download = `niki-kasa-${moment().format('YYYY-MM-DD')}.csv`;
531
- document.body.appendChild(link);
532
- link.click();
533
- document.body.removeChild(link);
534
- URL.revokeObjectURL(url);
535
-
536
190
  }).fail(function() {
537
- alert('CSV indirme başarısız.');
191
+ list.html('<li class="hist-empty">Bağlantı hatası</li>');
538
192
  });
539
193
  }
540
-
541
- function escapeHtml(text) {
542
- if (!text) return '';
543
- const div = document.createElement('div');
544
- div.textContent = text;
545
- return div.innerHTML;
194
+
195
+ function closeHistory() {
196
+ $('#hist-view').fadeOut();
546
197
  }
547
198
 
548
199
  $(document).ready(function() {
@@ -550,12 +201,6 @@
550
201
  $('.start-view').remove();
551
202
  $('body').html('<div style="text-align:center; padding:50px; color:#fff;"><h3>⛔ Yetkisiz Giriş</h3></div>');
552
203
  }
553
-
554
- // Varsayılan tarih aralığını ayarla (son 30 gün)
555
- const today = new Date().toISOString().slice(0, 10);
556
- const monthAgo = new Date(Date.now() - 30*24*60*60*1000).toISOString().slice(0, 10);
557
- $('#filter-end').val(today);
558
- $('#filter-start').val(monthAgo);
559
204
  });
560
205
 
561
206
  $(window).on('action:ajaxify.start', function() { stopSession(); closeHistory(); });