PageSourceSearch

https://binderim.com/js/vendor/tools/vendor-coupons.ndcy3ogd1a.js

js binderim.com collected 2026-09-24 14:41:53 UTC 16,465 bytes, 325 lines download raw bytes

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            '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
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.