1(function () { 2 'use strict'; 3 4 document.addEventListener('DOMContentLoaded', function () { 5 const tableBody = document.getElementById('vcBody'); 6 if (!tableBody) return; // upsell gösteriliyor, tablo yok 7 8 let coupons = Array.isArray(window.__vcCoupons) ? window.__vcCoupons : []; 9 const debugSuffix = window.__vcDebugSuffix || ''; 10 const tokenInput = document.querySelector('input[name="__RequestVerificationToken"]'); 11 const token = tokenInput ? tokenInput.value : ''; 12 13 const emptyState = document.getElementById('vcEmpty'); 14 const tableWrap = document.querySelector('.vc-table-wrap'); 15 16 const couponModalEl = document.getElementById('vcCouponModal'); 17 const couponModal = couponModalEl && window.bootstrap ? new window.bootstrap.Modal(couponModalEl) : null; 18 const deleteModalEl = document.getElementById('vcDeleteModal'); 19 const deleteModal = deleteModalEl && window.bootstrap ? new window.bootstrap.Modal(deleteModalEl) : null; 20 21 const modalTitle = document.getElementById('vcCouponModalLabel'); 22 const formError = document.getElementById('vcFormError'); 23 const couponIdInput = document.getElementById('vcCouponId'); 24 const codeInput = document.getElementById('vcCode'); 25 const discountTypeSelect = document.getElementById('vcDiscountType'); 26 const valueInput = document.getElementById('vcValue'); 27 const valueLabel = document.getElementById('vcValueLabel'); 28 const valueHint = document.getElementById('vcValueHint'); 29 const minimumOrderAmountInput = document.getElementById('vcMinimumOrderAmount'); 30 const startsAtInput = document.getElementById('vcStartsAt'); 31 const endsAtInput = document.getElementById('vcEndsAt'); 32 const totalUsageLimitInput = document.getElementById('vcTotalUsageLimit'); 33 const perUserUsageLimitInput = document.getElementById('vcPerUserUsageLimit'); 34 const saveBtn = document.getElementById('vcSaveBtn'); 35 36 let pendingDeleteId = null; 37 38 // ===== Yardımcılar ===== 39 40 // ---- i18n (Issue #1625 B, vendor dilimi) --------------------------------- 41 // Kok eleman Tools/VendorCoupons.cshtml'deki .vp-shell; data-i18n-* degerlerini 42 // VendorCoupons.resx / .en.resx doldurur. Attribute yoksa bugunku TR metin. 43 const i18nRoot = document.querySelector('.vp-shell'); 44 const t = (name, fallback) => { 45 if (!i18nRoot) return fallback; 46 const value = i18nRoot.getAttribute('data-i18n-' + name); 47 return (value === null || value === '') ? fallback : value; 48 }; 49 const fmt = (template, ...args) => String(template == null ? '' : template) 50 .replace(/\{(\d+)\}/g, (match, index) => (args[Number(index)] === undefined ? match : args[Number(index)])); 51 52 const buildHandlerUrl = (handler) => { 53 const base = '?handler=' + encodeURIComponent(handler); 54 return debugSuffix ? base + (debugSuffix.startsWith('?') ? '&' + debugSuffix.slice(1) : '&' + debugSuffix) : base; 55 }; 56 57 const formatMoney = (value) => (Number(value) || 0).toFixed(2) + ' TL'; 58 59 const formatDate = (iso) => { 60 if (!iso) return 'â'; 61 try { 62 return window.BinderimFormat.formatDateTime(new Date(iso), { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); 63 } catch { 64 return 'â'; 65 } 66 }; 67 68 const discountLabel = (coupon) => { 69 if (coupon.discountType === 'Percent') return fmt(t('discount-percent-format', '%{0} indirim'), coupon.value); 70 if (coupon.discountType === 'Fixed') return fmt(t('discount-fixed-format', '{0} indirim'), formatMoney(coupon.value)); 71 if (coupon.discountType === 'FreeShipping') { 72 return coupon.value > 0 73 ? fmt(t('discount-free-shipping-capped-format', 'Ãcretsiz kargo (en fazla {0})'), formatMoney(coupon.value)) 74 : t('discount-free-shipping-full', 'Tam ücretsiz kargo'); 75 } 76 return coupon.discountType; 77 }; 78 79 const statusBadge = (coupon) => { 80 const now = new Date(); 81 if (coupon.isActive === false) { 82 return '<span class="vc-badge vc-badge--off">' + escapeHtml(t('status-inactive', 'Pasif')) + '</span>'; 83 } 84 if (coupon.endsAtUtc && new Date(coupon.endsAtUtc) < now) { 85 return '<span class="vc-badge vc-badge--expired">' + escapeHtml(t('status-expired', 'Süresi Doldu')) + '</span>'; 86 } 87 if (coupon.startsAtUtc && new Date(coupon.startsAtUtc) > now) { 88 return '<span class="vc-badge vc-badge--pending">' + escapeHtml(t('status-not-started', 'Henüz BaÅlamadı')) + '</span>'; 89 } 90 if (coupon.totalUsageLimit && coupon.usageCount >= coupon.totalUsageLimit) { 91 return '<span class="vc-badge vc-badge--expired">' + escapeHtml(t('status-limit-reached', 'Limit Doldu')) + '</span>'; 92 } 93 return '<span class="vc-badge vc-badge--on">' + escapeHtml(t('status-active', 'Aktif')) + '</span>'; 94 }; 95 96 const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (ch) => ({ 97 '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' 98 }[ch])); 99 100 // ===== Render ===== 101 102 const render = () => { 103 if (!coupons.length) { 104 tableBody.innerHTML = ''; 105 if (tableWrap) tableWrap.hidden = true; 106 if (emptyState) emptyState.hidden = false;
107 return; 108 } 109 110 if (tableWrap) tableWrap.hidden = false; 111 if (emptyState) emptyState.hidden = true; 112 113 tableBody.innerHTML = coupons.map((c) => { 114 const usageText = c.totalUsageLimit ? c.usageCount + ' / ' + c.totalUsageLimit : c.usageCount + ' / â'; 115 // ð´ Review m3: iki deger de asagida innerHTML'e gomuluyor; ceviri degerleri 116 // (resx) aci parantez/tirnak tasirsa satiri kirar. escapeHtml ZORUNLU. 117 const perUserText = c.perUserUsageLimit ? ' ' + escapeHtml(fmt(t('per-user-limit-format', '(kiÅi baÅı {0})'), c.perUserUsageLimit)) : ''; 118 const validityText = escapeHtml((c.startsAtUtc ? formatDate(c.startsAtUtc) : t('validity-now', 'Hemen')) + ' â ' + (c.endsAtUtc ? formatDate(c.endsAtUtc) : t('validity-unlimited', 'Süresiz'))); 119 return '<tr data-id="' + c.id + '">' + 120 '<td class="vc-col-code"><strong>' + escapeHtml(c.code) + '</strong></td>' + 121 '<td>' + escapeHtml(discountLabel(c)) + '</td>' + 122 '<td>' + (c.minimumOrderAmount > 0 ? formatMoney(c.minimumOrderAmount) : 'â') + '</td>' + 123 '<td class="vc-col-validity">' + validityText + '</td>' + 124 '<td>' + usageText + perUserText + '</td>' + 125 '<td>' + formatMoney(c.totalDiscountGranted) + '</td>' + 126 '<td>' + statusBadge(c) + '</td>' + 127 '<td class="vc-col-actions">' + 128 '<button type="button" class="vc-icon-btn vc-toggle-btn" data-id="' + c.id + '" data-active="' + c.isActive + '" title="' + escapeHtml(c.isActive ? t('toggle-off-title', 'Devre dıÅı bırak') : t('toggle-on-title', 'EtkinleÅtir')) + '">' + 129 '<i class="bi ' + (c.isActive ? 'bi-toggle-on' : 'bi-toggle-off') + '"></i>' + 130 '</button>' + 131 '<button type="button" class="vc-icon-btn vc-edit-btn" data-id="' + c.id + '" title="' + escapeHtml(t('edit-title', 'Düzenle')) + '"><i class="bi bi-pencil"></i></button>' + 132 '<button type="button" class="vc-icon-btn vc-delete-btn" data-id="' + c.id + '" title="' + escapeHtml(t('delete-title', 'Sil')) + '"><i class="bi bi-trash"></i></button>' + 133 '</td>' + 134 '</tr>'; 135 }).join(''); 136 }; 137 138 // ===== Form durumu ===== 139 140 const updateValueFieldForType = () => { 141 const type = discountTypeSelect.value; 142 if (type === 'Percent') { 143 valueLabel.textContent = t('value-label-percent', 'Yüzde (0-100)'); 144 valueHint.style.display = 'none'; 145 valueInput.max = '100'; 146 } else if (type === 'Fixed') { 147 valueLabel.textContent = t('value-label-fixed', 'Sabit Tutar (âº)'); 148 valueHint.style.display = 'none'; 149 valueInput.removeAttribute('max'); 150 } else { 151 valueLabel.textContent = t('value-label-shipping', 'Kargo İndirim Tutarı (âº, 0 = tam ücretsiz)'); 152 valueHint.style.display = 'block'; 153 valueInput.removeAttribute('max'); 154 } 155 }; 156 157 discountTypeSelect.addEventListener('change', updateValueFieldForType); 158 159 const toLocalInputValue = (iso) => { 160 if (!iso) return ''; 161 const d = new Date(iso); 162 const pad = (n) => String(n).padStart(2, '0'); 163 return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + 'T' + pad(d.getHours()) + ':' + pad(d.getMinutes()); 164 }; 165 166 const openCreateModal = () => { 167 modalTitle.innerHTML = '<i class="bi bi-ticket-perforated"></i> Yeni Kupon'; 168 couponIdInput.value = ''; 169 codeInput.value = ''; 170 discountTypeSelect.value = 'Percent'; 171 valueInput.value = ''; 172 minimumOrderAmountInput.value = '0'; 173 startsAtInput.value = ''; 174 endsAtInput.value = ''; 175 totalUsageLimitInput.value = ''; 176 perUserUsageLimitInput.value = ''; 177 formError.classList.add('d-none'); 178 formError.textContent = '';
179 updateValueFieldForType(); 180 if (couponModal) couponModal.show(); 181 }; 182 183 const openEditModal = (coupon) => { 184 modalTitle.innerHTML = '<i class="bi bi-pencil"></i> ' + escapeHtml(t('modal-edit-title', 'Kuponu Düzenle')); 185 couponIdInput.value = coupon.id; 186 codeInput.value = coupon.code; 187 discountTypeSelect.value = coupon.discountType; 188 valueInput.value = coupon.value; 189 minimumOrderAmountInput.value = coupon.minimumOrderAmount; 190 startsAtInput.value = toLocalInputValue(coupon.startsAtUtc); 191 endsAtInput.value = toLocalInputValue(coupon.endsAtUtc); 192 totalUsageLimitInput.value = coupon.totalUsageLimit || ''; 193 perUserUsageLimitInput.value = coupon.perUserUsageLimit || ''; 194 formError.classList.add('d-none'); 195 formError.textContent = ''; 196 updateValueFieldForType(); 197 if (couponModal) couponModal.show(); 198 }; 199 200 const newCouponBtn = document.getElementById('vcNewCouponBtn'); 201 const emptyNewCouponBtn = document.getElementById('vcEmptyNewCouponBtn'); 202 if (newCouponBtn) newCouponBtn.addEventListener('click', openCreateModal); 203 if (emptyNewCouponBtn) emptyNewCouponBtn.addEventListener('click', openCreateModal); 204 205 // ===== AJAX ===== 206 207 const postJson = async (handler, body) => { 208 const response = await fetch(buildHandlerUrl(handler), { 209 method: 'POST', 210 headers: { 211 'Content-Type': 'application/json', 212 'RequestVerificationToken': token 213 }, 214 body: JSON.stringify(body) 215 }); 216 let data = null; 217 try { data = await response.json(); } catch { /* boÅ yanıt olabilir */ } 218 return { ok: response.ok, status: response.status, data }; 219 }; 220 221 const refreshList = async () => { 222 try { 223 const response = await fetch(buildHandlerUrl('List'), { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); 224 if (!response.ok) return; 225 const data = await response.json(); 226 coupons = Array.isArray(data.coupons) ? data.coupons : []; 227 render(); 228 } catch (err) { 229 console.warn('Kupon listesi yenilenemedi:', err); 230 } 231 }; 232 233 saveBtn.addEventListener('click', async () => { 234 const id = couponIdInput.value ? parseInt(couponIdInput.value, 10) : null; 235 const payload = { 236 id: id, 237 code: codeInput.value.trim(), 238 discountType: discountTypeSelect.value, 239 value: parseFloat(valueInput.value) || 0, 240 minimumOrderAmount: parseFloat(minimumOrderAmountInput.value) || 0, 241 startsAtUtc: startsAtInput.value ? new Date(startsAtInput.value).toISOString() : null, 242 endsAtUtc: endsAtInput.value ? new Date(endsAtInput.value).toISOString() : null, 243 totalUsageLimit: totalUsageLimitInput.value ? parseInt(totalUsageLimitInput.value, 10) : null, 244 perUserUsageLimit: perUserUsageLimitInput.value ? parseInt(perUserUsageLimitInput.value, 10) : null 245 }; 246 247 if (!payload.code) { 248 formError.textContent = 'Kupon kodu zorunludur.'; 249 formError.classList.remove('d-none'); 250 return; 251 } 252 253 saveBtn.disabled = true; 254 const result = id 255 ? await postJson('Update', payload) 256 : await postJson('Create', payload); 257 saveBtn.disabled = false; 258 259 if (!result.ok) { 260 formError.textContent = (result.data && result.data.error) || t('action-failed', 'İÅlem baÅarısız oldu.'); 261 formError.classList.remove('d-none'); 262 return; 263 } 264 265 if (couponModal) couponModal.hide(); 266 await refreshList(); 267 if (window.toastr) toastr.success(id ? t('coupon-updated', 'Kupon güncellendi.') : t('coupon-created', 'Kupon oluÅturuldu.')); 268 }); 269 270 tableBody.addEventListener('click', async (event) => { 271 const editBtn = event.target.closest('.vc-edit-btn'); 272 const toggleBtn = event.target.closest('.vc-toggle-btn'); 273 const deleteBtn = event.target.closest('.vc-delete-btn'); 274 275 if (editBtn) { 276 const id = parseInt(editBtn.dataset.id, 10); 277 const coupon = coupons.find((c) => c.id === id); 278 if (coupon) openEditModal(coupon); 279 return; 280 } 281 282 if (toggleBtn) { 283 const id = parseInt(toggleBtn.dataset.id, 10); 284 const isActive = toggleBtn.dataset.active === 'true'; 285 toggleBtn.disabled = true; 286 const result = await postJson('Toggle', { id: id, isActive: !isActive }); 287 toggleBtn.disabled = false;
288 if (!result.ok) { 289 if (window.toastr) toastr.error((result.data && result.data.error) || t('action-failed', 'İÅlem baÅarısız oldu.')); 290 return; 291 } 292 await refreshList(); 293 return; 294 } 295 296 if (deleteBtn) { 297 pendingDeleteId = parseInt(deleteBtn.dataset.id, 10); 298 if (deleteModal) deleteModal.show(); 299 } 300 }); 301 302 const confirmDeleteBtn = document.getElementById('vcConfirmDeleteBtn'); 303 if (confirmDeleteBtn) { 304 confirmDeleteBtn.addEventListener('click', async () => { 305 if (!pendingDeleteId) return; 306 confirmDeleteBtn.disabled = true; 307 const result = await postJson('Delete', { id: pendingDeleteId }); 308 confirmDeleteBtn.disabled = false; 309 pendingDeleteId = null; 310 311 if (!result.ok) { 312 if (window.toastr) toastr.error((result.data && result.data.error) || t('delete-failed', 'Silme baÅarısız oldu.')); 313 return; 314 } 315 316 if (deleteModal) deleteModal.hide(); 317 await refreshList(); 318 if (window.toastr) toastr.success('Kupon silindi.'); 319 }); 320 } 321 322 updateValueFieldForType(); 323 render(); 324 }); 325})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.