nodebb-plugin-niki-loyalty 1.2.9 → 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.
- package/library.js +182 -26
- package/package.json +1 -1
- package/static/widgets/niki-admin.tpl +686 -0
- package/static/widgets/niki-kasa-debug.html +1020 -0
- package/static/widgets/niki-kasa-isolated.html +419 -0
- package/static/widgets/niki-kasa-simple.html +623 -0
- package/static/widgets/niki-kasa-widget.html +506 -0
- package/templates/niki-kasa.tpl +207 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
|
|
2
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
|
|
3
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/locale/tr.min.js"></script>
|
|
4
|
+
|
|
5
|
+
<style>
|
|
6
|
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
|
|
7
|
+
body.niki-kasa-body { background-color: #000; margin: 0; padding: 0; overflow: hidden; }
|
|
8
|
+
#content { padding: 0 !important; margin: 0 !important; width: 100% !important; background: #000; }
|
|
9
|
+
|
|
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; }
|
|
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; }
|
|
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); }
|
|
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
|
+
|
|
16
|
+
.camera-ui { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #000; display: none; z-index: 200; }
|
|
17
|
+
#reader { width: 100% !important; height: 100% !important; border: none !important; }
|
|
18
|
+
#reader video { object-fit: cover !important; width: 100% !important; height: 100% !important; }
|
|
19
|
+
.session-bar-bg { position: absolute; top:0; left:0; width:100%; height:6px; background:rgba(255,255,255,0.2); z-index: 220; }
|
|
20
|
+
.session-bar-fill { height:100%; background:#C5A065; width:100%; transition: width 1s linear; }
|
|
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; }
|
|
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; }
|
|
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; }
|
|
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; }
|
|
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; }
|
|
28
|
+
|
|
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; }
|
|
30
|
+
.res-modal.active { transform: translateY(0); }
|
|
31
|
+
.icon-big { font-size: 60px; margin-bottom: 15px; }
|
|
32
|
+
.res-title { color: #fff; font-size: 24px; font-weight: 700; margin-bottom: 10px; }
|
|
33
|
+
.res-msg { color: #aaa; font-size: 15px; margin-bottom: 30px; }
|
|
34
|
+
.btn-new { background: #fff; color: #000; width: 100%; padding: 18px; border-radius: 16px; font-size: 16px; font-weight: 700; border: none; cursor: pointer; }
|
|
35
|
+
|
|
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; }
|
|
42
|
+
.hist-info { flex: 1; }
|
|
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; }
|
|
47
|
+
</style>
|
|
48
|
+
|
|
49
|
+
<div id="start-view" class="start-view">
|
|
50
|
+
<div class="logo-box"><i class="fa fa-qrcode" style="font-size:45px; color:#C5A065;"></i></div>
|
|
51
|
+
<h2 style="margin:0 0 5px; font-size:24px;">NIKI POS</h2>
|
|
52
|
+
<p style="color:#888; font-size:13px; margin:0 0 40px;">Yetkili Personel Terminali</p>
|
|
53
|
+
<div class="btn-group">
|
|
54
|
+
<button class="btn-start" onclick="initSession()"><i class="fa fa-camera"></i> OTURUMU BAŞLAT</button>
|
|
55
|
+
<button class="btn-hist" onclick="openHistory()"><i class="fa fa-history"></i> İŞLEM GEÇMİŞİ</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div id="camera-ui" class="camera-ui">
|
|
60
|
+
<div class="session-bar-bg"><div class="session-bar-fill" id="timer-bar"></div></div>
|
|
61
|
+
<div class="top-ctrl">
|
|
62
|
+
<div class="badge-live">● CANLI</div>
|
|
63
|
+
<button class="btn-x" onclick="stopSession()"><i class="fa fa-times"></i></button>
|
|
64
|
+
</div>
|
|
65
|
+
<div id="reader"></div>
|
|
66
|
+
<div class="scan-guide"><div class="laser"></div></div>
|
|
67
|
+
<div class="scan-hint">QR Kodu karenin içine tutun</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div id="res-view" class="res-modal">
|
|
71
|
+
<div id="res-icon" class="icon-big"></div>
|
|
72
|
+
<div id="res-title" class="res-title">...</div>
|
|
73
|
+
<div id="res-msg" class="res-msg">...</div>
|
|
74
|
+
<button class="btn-new" onclick="resumeScan()">YENİ İŞLEM</button>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div id="hist-view" class="hist-modal">
|
|
78
|
+
<div class="hist-head">
|
|
79
|
+
<span class="hist-head-title"><i class="fa fa-history"></i> İŞLEM GEÇMİŞİ</span>
|
|
80
|
+
<button onclick="closeHistory()" style="background:none; border:none; color:#fff; font-size:20px; cursor:pointer;"><i class="fa fa-times"></i></button>
|
|
81
|
+
</div>
|
|
82
|
+
<ul id="hist-list" class="hist-body" style="list-style:none; margin:0; padding:10px;"></ul>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<script>
|
|
86
|
+
var html5QrCode;
|
|
87
|
+
var isProcessing = false;
|
|
88
|
+
var sessionTimer;
|
|
89
|
+
moment.locale('tr');
|
|
90
|
+
|
|
91
|
+
async function initSession() {
|
|
92
|
+
$('#start-view').fadeOut(200);
|
|
93
|
+
$('#camera-ui').fadeIn(300);
|
|
94
|
+
startTimer(600);
|
|
95
|
+
html5QrCode = new Html5Qrcode("reader");
|
|
96
|
+
try {
|
|
97
|
+
var devices = await Html5Qrcode.getCameras();
|
|
98
|
+
if (devices && devices.length) {
|
|
99
|
+
var cameraId = devices[devices.length - 1].id;
|
|
100
|
+
for (var i = 0; i < devices.length; i++) {
|
|
101
|
+
var label = devices[i].label.toLowerCase();
|
|
102
|
+
if (label.includes("back") || label.includes("arka") || label.includes("environment")) {
|
|
103
|
+
cameraId = devices[i].id;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await html5QrCode.start(cameraId, { fps: 15, aspectRatio: window.innerWidth / window.innerHeight }, onScanSuccess);
|
|
108
|
+
} else {
|
|
109
|
+
alert("Kamera bulunamadı!");
|
|
110
|
+
stopSession();
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
alert("Kamera Başlatılamadı: " + err);
|
|
114
|
+
stopSession();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function startTimer(duration) {
|
|
119
|
+
var timeLeft = duration;
|
|
120
|
+
$('#timer-bar').css('width', '100%');
|
|
121
|
+
clearInterval(sessionTimer);
|
|
122
|
+
sessionTimer = setInterval(function() {
|
|
123
|
+
timeLeft--;
|
|
124
|
+
var pct = (timeLeft / duration) * 100;
|
|
125
|
+
$('#timer-bar').css('width', pct + '%');
|
|
126
|
+
if(timeLeft <= 0) { alert("Oturum doldu."); stopSession(); }
|
|
127
|
+
}, 1000);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function onScanSuccess(decodedText) {
|
|
131
|
+
if (isProcessing) return;
|
|
132
|
+
isProcessing = true;
|
|
133
|
+
if(navigator.vibrate) navigator.vibrate(200);
|
|
134
|
+
html5QrCode.pause();
|
|
135
|
+
|
|
136
|
+
$.post('/api/niki-loyalty/scan-qr', { token: decodedText, _csrf: config.csrf_token }, function(res) {
|
|
137
|
+
if(res.success) {
|
|
138
|
+
$('#res-icon').html('<i class="fa fa-check-circle" style="color:#2E7D32"></i>');
|
|
139
|
+
$('#res-title').text('ÖDEME ALINDI');
|
|
140
|
+
$('#res-msg').html('<b>'+res.customer.username+'</b><br>'+res.cost+' Puan Tahsil Edildi.');
|
|
141
|
+
} else {
|
|
142
|
+
$('#res-icon').html('<i class="fa fa-times-circle" style="color:#C62828"></i>');
|
|
143
|
+
$('#res-title').text('HATA');
|
|
144
|
+
$('#res-msg').text(res.message);
|
|
145
|
+
}
|
|
146
|
+
$('#res-view').addClass('active');
|
|
147
|
+
}).fail(function() { alert("Sunucu Hatası!"); resumeScan(); });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function resumeScan() {
|
|
151
|
+
$('#res-view').removeClass('active');
|
|
152
|
+
setTimeout(function() { isProcessing = false; html5QrCode.resume(); }, 300);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function stopSession() {
|
|
156
|
+
clearInterval(sessionTimer);
|
|
157
|
+
if(html5QrCode) html5QrCode.stop().then(function() { html5QrCode.clear(); }).catch(function(){});
|
|
158
|
+
$('#camera-ui').hide();
|
|
159
|
+
$('#res-view').removeClass('active');
|
|
160
|
+
$('#start-view').fadeIn(200);
|
|
161
|
+
isProcessing = false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function openHistory() {
|
|
165
|
+
$('#hist-view').fadeIn().css('display','flex');
|
|
166
|
+
var list = $('#hist-list');
|
|
167
|
+
list.html('<li style="text-align:center; padding:40px; color:#888;">Yükleniyor...</li>');
|
|
168
|
+
|
|
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 || []);
|
|
172
|
+
|
|
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>');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
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);
|
|
189
|
+
});
|
|
190
|
+
}).fail(function() {
|
|
191
|
+
list.html('<li class="hist-empty">Bağlantı hatası</li>');
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function closeHistory() {
|
|
196
|
+
$('#hist-view').fadeOut();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
$(document).ready(function() {
|
|
200
|
+
if (!app.user.isAdmin && !app.user.isGlobalMod) {
|
|
201
|
+
$('.start-view').remove();
|
|
202
|
+
$('body').html('<div style="text-align:center; padding:50px; color:#fff;"><h3>⛔ Yetkisiz Giriş</h3></div>');
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
$(window).on('action:ajaxify.start', function() { stopSession(); closeHistory(); });
|
|
207
|
+
</script>
|