PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.44
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.44
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
king-addons / includes / extensions / Fomo_Notifications / assets / admin.js

admin.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.44, at includes/extensions/Fomo_Notifications/assets/admin.js

1,164 lines 40.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Fomo Notifications - Admin JavaScript
3 *
4 * @package King_Addons
5 */
6
7 (function($) {
8 'use strict';
9
10 /**
11 * Main Fomo Notifications Admin Class
12 */
13 const KngFomoAdmin = {
14 /**
15 * Current wizard data
16 */
17 wizardData: {
18 step: 1,
19 notification_id: 0,
20 type: '',
21 source: '',
22 source_config: {},
23 design: {
24 template: 'default',
25 position: 'bottom-left',
26 animation: 'slide',
27 bg_color: '#ffffff',
28 text_color: '#1d1d1f',
29 accent_color: '#0071e3',
30 border_radius: 16,
31 shadow: true
32 },
33 content: {
34 title: '',
35 message: '',
36 image_type: 'product',
37 custom_image: '',
38 show_time: true,
39 time_format: 'relative',
40 cta_text: '',
41 cta_url: ''
42 },
43 display: {
44 delay: 3,
45 duration: 5,
46 interval: 10,
47 max_per_session: 5,
48 devices: ['desktop', 'tablet', 'mobile'],
49 pages: 'all',
50 page_rules: [],
51 audience: 'all',
52 exclude_logged_in: false
53 },
54 customize: {
55 z_index: 99999,
56 close_button: true,
57 click_action: 'link',
58 sound: false,
59 analytics: true
60 }
61 },
62
63 /**
64 * Initialize
65 */
66 init: function() {
67 this.bindEvents();
68 this.initWizard();
69 this.initCharts();
70 this.initToggles();
71 },
72
73 /**
74 * Bind events
75 */
76 bindEvents: function() {
77 const self = this;
78
79 // Navigation
80 $(document).on('click', '.kng-fomo-nav-item', function(e) {
81 const href = $(this).attr('href');
82 if (href && href.indexOf('#') !== 0) {
83 return;
84 }
85 e.preventDefault();
86 const tab = $(this).data('tab');
87 self.switchTab(tab);
88 });
89
90 // Wizard steps
91 $(document).on('click', '.kng-fomo-wizard-step', function() {
92 const step = parseInt($(this).data('step'));
93 if (step <= self.wizardData.step || self.canGoToStep(step)) {
94 self.goToStep(step);
95 }
96 });
97
98 // Wizard navigation buttons
99 $(document).on('click', '.kng-fomo-wizard-prev', function() {
100 self.prevStep();
101 });
102
103 $(document).on('click', '.kng-fomo-wizard-next', function() {
104 self.nextStep();
105 });
106
107 // Notification type selection
108 $(document).on('change', 'input[name="notification_type"]', function() {
109 self.wizardData.type = $(this).val();
110 self.updateSourceOptions();
111 });
112
113 // Source selection
114 $(document).on('change', 'input[name="notification_source"]', function() {
115 self.wizardData.source = $(this).val();
116 self.updateSourceConfig();
117 });
118
119 // Design template selection
120 $(document).on('click', '.kng-fomo-template-card', function() {
121 $('.kng-fomo-template-card').removeClass('is-selected');
122 $(this).addClass('is-selected');
123 self.wizardData.design.template = $(this).data('template');
124 self.updatePreview();
125 });
126
127 // Position selection - buttons
128 $(document).on('click', '.kng-fomo-position-btn', function() {
129 const position = $(this).data('position');
130 $('.kng-fomo-position-btn').removeClass('is-active');
131 $(this).addClass('is-active');
132 $('.kng-fomo-position-dot').removeClass('is-active');
133 $('.kng-fomo-position-dot[data-pos="' + position + '"]').addClass('is-active');
134 self.wizardData.design.position = position;
135 self.updatePreview();
136 });
137
138 // Position selection - dots (visual preview)
139 $(document).on('click', '.kng-fomo-position-dot', function() {
140 const position = $(this).data('pos');
141 $('.kng-fomo-position-dot').removeClass('is-active');
142 $(this).addClass('is-active');
143 $('.kng-fomo-position-btn').removeClass('is-active');
144 $('.kng-fomo-position-btn[data-position="' + position + '"]').addClass('is-active');
145 self.wizardData.design.position = position;
146 self.updatePreview();
147 });
148
149 // Form inputs
150 $(document).on('change input', '.kng-fomo-wizard [data-field]', function() {
151 const field = $(this).data('field');
152 const section = $(this).data('section');
153 const value = $(this).is(':checkbox') ? $(this).is(':checked') : $(this).val();
154
155 if (section && self.wizardData[section]) {
156 self.wizardData[section][field] = value;
157 } else {
158 self.wizardData[field] = value;
159 }
160
161 self.updatePreview();
162 });
163
164 // Color pickers
165 $(document).on('input', '.kng-fomo-color-picker', function() {
166 const field = $(this).data('field');
167 const value = $(this).val();
168 $(this).siblings('.kng-fomo-color-input').val(value);
169 self.wizardData.design[field] = value;
170 self.updatePreview();
171 });
172
173 $(document).on('input', '.kng-fomo-color-input', function() {
174 const field = $(this).data('field');
175 const value = $(this).val();
176 $(this).siblings('.kng-fomo-color-picker').val(value);
177 self.wizardData.design[field] = value;
178 self.updatePreview();
179 });
180
181 // Save notification
182 $(document).on('click', '.kng-fomo-save-notification', function() {
183 self.saveNotification();
184 });
185
186 // Toggle notification status
187 $(document).on('change', '.kng-fomo-toggle input', function() {
188 const notificationId = $(this).closest('tr').data('id') || $(this).data('id');
189 const status = $(this).is(':checked') ? 'enabled' : 'disabled';
190 self.toggleNotification(notificationId, status);
191 });
192
193 // Delete notification
194 $(document).on('click', '.kng-fomo-delete', function(e) {
195 e.preventDefault();
196 const notificationId = $(this).closest('tr').data('id') || $(this).data('id');
197 self.deleteNotification(notificationId);
198 });
199
200 // Duplicate notification
201 $(document).on('click', '.kng-fomo-duplicate', function(e) {
202 e.preventDefault();
203 const notificationId = $(this).closest('tr').data('id') || $(this).data('id');
204 self.duplicateNotification(notificationId);
205 });
206
207 // Edit notification
208 $(document).on('click', '.kng-fomo-edit', function(e) {
209 e.preventDefault();
210 const notificationId = $(this).closest('tr').data('id') || $(this).data('id');
211 self.loadNotification(notificationId);
212 });
213
214 // Import/Export
215 $(document).on('click', '.kng-fomo-export', function() {
216 self.exportNotifications();
217 });
218
219 $(document).on('click', '.kng-fomo-import', function() {
220 self.importNotifications();
221 });
222
223 // Settings save
224 $(document).on('click', '.kng-fomo-save-settings', function() {
225 self.saveSettings();
226 });
227
228 // Modal
229 $(document).on('click', '.kng-fomo-modal-close, .kng-fomo-modal-overlay', function(e) {
230 if (e.target === this) {
231 self.closeModal();
232 }
233 });
234
235 // Analytics date range
236 $(document).on('change', '.kng-fomo-date-range', function() {
237 self.loadAnalytics($(this).val());
238 });
239
240 // Tab switching
241 $(document).on('click', '.kng-fomo-tab', function() {
242 const tabId = $(this).data('tab');
243 self.switchContentTab(tabId);
244 });
245
246 // Device checkboxes
247 $(document).on('change', '.kng-fomo-device-check', function() {
248 const device = $(this).val();
249 const checked = $(this).is(':checked');
250
251 if (checked) {
252 if (!self.wizardData.display.devices.includes(device)) {
253 self.wizardData.display.devices.push(device);
254 }
255 } else {
256 self.wizardData.display.devices = self.wizardData.display.devices.filter(d => d !== device);
257 }
258 });
259
260 // Page rules
261 $(document).on('click', '.kng-fomo-add-rule', function() {
262 self.addPageRule();
263 });
264
265 $(document).on('click', '.kng-fomo-remove-rule', function() {
266 $(this).closest('.kng-fomo-page-rule').remove();
267 self.collectPageRules();
268 });
269
270 $(document).on('change', '.kng-fomo-page-rule select, .kng-fomo-page-rule input', function() {
271 self.collectPageRules();
272 });
273 },
274
275 /**
276 * Initialize wizard
277 */
278 initWizard: function() {
279 if (!$('.kng-fomo-wizard').length) {
280 return;
281 }
282
283 // Check if editing existing notification
284 const editId = this.getUrlParam('edit');
285 if (editId) {
286 this.loadNotification(editId);
287 } else {
288 this.goToStep(1);
289 }
290 },
291
292 /**
293 * Go to step
294 */
295 goToStep: function(step) {
296 this.wizardData.step = step;
297
298 // Update steps UI
299 $('.kng-fomo-wizard-step').each(function() {
300 const stepNum = parseInt($(this).data('step'));
301 $(this).removeClass('is-active is-completed');
302
303 if (stepNum === step) {
304 $(this).addClass('is-active');
305 } else if (stepNum < step) {
306 $(this).addClass('is-completed');
307 }
308 });
309
310 // Show/hide content panels
311 $('.kng-fomo-wizard-panel').removeClass('is-active');
312 $('.kng-fomo-wizard-panel[data-step="' + step + '"]').addClass('is-active');
313
314 // Update buttons
315 if (step === 1) {
316 $('.kng-fomo-wizard-prev').hide();
317 } else {
318 $('.kng-fomo-wizard-prev').show();
319 }
320
321 if (step === 5) {
322 $('.kng-fomo-wizard-next').hide();
323 $('.kng-fomo-save-notification').show();
324 } else {
325 $('.kng-fomo-wizard-next').show();
326 $('.kng-fomo-save-notification').hide();
327 }
328
329 // Update preview
330 this.updatePreview();
331
332 // Scroll to top
333 const wizardEl = $('.kng-fomo-wizard').get(0);
334 if (wizardEl) {
335 wizardEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
336 }
337 },
338
339 /**
340 * Previous step
341 */
342 prevStep: function() {
343 if (this.wizardData.step > 1) {
344 this.goToStep(this.wizardData.step - 1);
345 }
346 },
347
348 /**
349 * Next step
350 */
351 nextStep: function() {
352 if (this.validateStep(this.wizardData.step)) {
353 if (this.wizardData.step < 5) {
354 this.goToStep(this.wizardData.step + 1);
355 }
356 }
357 },
358
359 /**
360 * Validate step
361 */
362 validateStep: function(step) {
363 let isValid = true;
364 let message = '';
365
366 switch (step) {
367 case 1:
368 if (!this.wizardData.type) {
369 message = kngFomoAdmin.i18n.select_type || 'Please select a notification type.';
370 isValid = false;
371 }
372 break;
373 case 2:
374 // Template has default value, so this is always valid
375 break;
376 case 3:
377 // Content is optional for some notification types
378 break;
379 }
380
381 if (!isValid && message) {
382 this.showToast(message, 'error');
383 }
384
385 return isValid;
386 },
387
388 /**
389 * Can go to step
390 */
391 canGoToStep: function(step) {
392 for (let i = 1; i < step; i++) {
393 if (!this.validateStep(i)) {
394 return false;
395 }
396 }
397 return true;
398 },
399
400 /**
401 * Update source options based on type
402 */
403 updateSourceOptions: function() {
404 const type = this.wizardData.type;
405 const $sources = $('.kng-fomo-source-options');
406
407 if (!$sources.length) {
408 return;
409 }
410
411 // Show relevant sources
412 $sources.find('.kng-fomo-radio-card').hide();
413 $sources.find('.kng-fomo-radio-card[data-type="' + type + '"], .kng-fomo-radio-card[data-type="all"]').show();
414 },
415
416 /**
417 * Update source configuration
418 */
419 updateSourceConfig: function() {
420 const source = this.wizardData.source;
421 const $config = $('.kng-fomo-source-config');
422
423 if (!$config.length) {
424 return;
425 }
426
427 // Hide all config panels
428 $config.find('.kng-fomo-source-config-panel').hide();
429
430 // Show relevant config
431 $config.find('.kng-fomo-source-config-panel[data-source="' + source + '"]').fadeIn(200);
432 },
433
434 /**
435 * Update live preview
436 */
437 updatePreview: function() {
438 const $preview = $('.kng-fomo-preview-notification');
439
440 if (!$preview.length) {
441 return;
442 }
443
444 const data = this.wizardData;
445
446 // Update position class
447 $preview.removeClass('pos-top-left pos-top-right pos-bottom-left pos-bottom-right pos-top-center pos-bottom-center');
448 $preview.addClass('pos-' + data.design.position);
449
450 // Update styles
451 $preview.css({
452 '--bg-color': data.design.bg_color,
453 '--text-color': data.design.text_color,
454 '--accent-color': data.design.accent_color,
455 '--border-radius': data.design.border_radius + 'px'
456 });
457
458 // Update content
459 if (data.content.title) {
460 $preview.find('.kng-fomo-preview-title').text(data.content.title);
461 }
462 if (data.content.message) {
463 $preview.find('.kng-fomo-preview-message').text(data.content.message);
464 }
465
466 // Toggle shadow
467 if (data.design.shadow) {
468 $preview.addClass('has-shadow');
469 } else {
470 $preview.removeClass('has-shadow');
471 }
472
473 // Toggle close button
474 if (data.customize.close_button) {
475 $preview.find('.kng-fomo-preview-close').show();
476 } else {
477 $preview.find('.kng-fomo-preview-close').hide();
478 }
479 },
480
481 /**
482 * Save notification
483 */
484 saveNotification: function() {
485 const self = this;
486 const $btn = $('.kng-fomo-save-notification');
487
488 $btn.prop('disabled', true).addClass('is-loading');
489
490 $.ajax({
491 url: kngFomoAdmin.ajaxUrl,
492 type: 'POST',
493 data: {
494 action: 'kng_fomo_save_notification',
495 nonce: kngFomoAdmin.nonce,
496 id: this.wizardData.notification_id,
497 title: this.wizardData.content.title || 'Untitled Notification',
498 status: 'disabled',
499 type: this.wizardData.type,
500 source: this.wizardData.source,
501 source_config: JSON.stringify(this.wizardData.source_config),
502 design: JSON.stringify(this.wizardData.design),
503 content: JSON.stringify(this.wizardData.content),
504 display: JSON.stringify(this.wizardData.display),
505 customize: JSON.stringify(this.wizardData.customize)
506 },
507 success: function(response) {
508 if (response.success) {
509 self.showToast(kngFomoAdmin.i18n.saved, 'success');
510
511 // Redirect to list
512 setTimeout(function() {
513 window.location.href = kngFomoAdmin.listUrl;
514 }, 1000);
515 } else {
516 self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error');
517 }
518 },
519 error: function() {
520 self.showToast(kngFomoAdmin.i18n.error, 'error');
521 },
522 complete: function() {
523 $btn.prop('disabled', false).removeClass('is-loading');
524 }
525 });
526 },
527
528 /**
529 * Load notification for editing
530 */
531 loadNotification: function(id) {
532 const self = this;
533
534 $.ajax({
535 url: kngFomoAdmin.ajaxUrl,
536 type: 'POST',
537 data: {
538 action: 'kng_fomo_get_notification',
539 nonce: kngFomoAdmin.nonce,
540 notification_id: id
541 },
542 success: function(response) {
543 if (response.success) {
544 self.wizardData = $.extend(true, self.wizardData, response.data);
545 self.populateWizardFields();
546 self.goToStep(1);
547 } else {
548 self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error');
549 }
550 },
551 error: function() {
552 self.showToast(kngFomoAdmin.i18n.error, 'error');
553 }
554 });
555 },
556
557 /**
558 * Populate wizard fields from loaded data
559 */
560 populateWizardFields: function() {
561 const data = this.wizardData;
562
563 // Type
564 if (data.type) {
565 $('input[name="notification_type"][value="' + data.type + '"]').prop('checked', true);
566 this.updateSourceOptions();
567 }
568
569 // Source
570 if (data.source) {
571 $('input[name="notification_source"][value="' + data.source + '"]').prop('checked', true);
572 this.updateSourceConfig();
573 }
574
575 // Design - Template
576 if (data.design.template) {
577 $('.kng-fomo-template-card').removeClass('is-selected');
578 $('.kng-fomo-template-card[data-template="' + data.design.template + '"]').addClass('is-selected');
579 }
580
581 // Design - Position
582 if (data.design.position) {
583 $('.kng-fomo-position-btn').removeClass('is-active');
584 $('.kng-fomo-position-btn[data-position="' + data.design.position + '"]').addClass('is-active');
585 $('.kng-fomo-position-dot').removeClass('is-active');
586 $('.kng-fomo-position-dot[data-pos="' + data.design.position + '"]').addClass('is-active');
587 }
588
589 // Colors
590 $('[data-field="bg_color"]').val(data.design.bg_color);
591 $('[data-field="text_color"]').val(data.design.text_color);
592 $('[data-field="accent_color"]').val(data.design.accent_color);
593 $('[data-field="border_radius"]').val(data.design.border_radius);
594 $('[data-field="shadow"]').prop('checked', data.design.shadow);
595
596 // Content
597 $('[data-field="title"]').val(data.content.title);
598 $('[data-field="message"]').val(data.content.message);
599 $('[data-field="cta_text"]').val(data.content.cta_text);
600 $('[data-field="cta_url"]').val(data.content.cta_url);
601
602 // Display
603 $('[data-field="delay"]').val(data.display.delay);
604 $('[data-field="duration"]').val(data.display.duration);
605 $('[data-field="interval"]').val(data.display.interval);
606 $('[data-field="max_per_session"]').val(data.display.max_per_session);
607
608 // Devices
609 data.display.devices.forEach(function(device) {
610 $('.kng-fomo-device-check[value="' + device + '"]').prop('checked', true);
611 });
612
613 // Customize
614 $('[data-field="z_index"]').val(data.customize.z_index);
615 $('[data-field="close_button"]').prop('checked', data.customize.close_button);
616 $('[data-field="sound"]').prop('checked', data.customize.sound);
617 $('[data-field="analytics"]').prop('checked', data.customize.analytics);
618
619 this.updatePreview();
620 },
621
622 /**
623 * Toggle notification status
624 */
625 toggleNotification: function(id, status) {
626 const self = this;
627
628 $.ajax({
629 url: kngFomoAdmin.ajaxUrl,
630 type: 'POST',
631 data: {
632 action: 'kng_fomo_toggle_status',
633 nonce: kngFomoAdmin.nonce,
634 id: id,
635 status: status
636 },
637 success: function(response) {
638 if (response.success) {
639 self.showToast(kngFomoAdmin.i18n.saved, 'success');
640 } else {
641 self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error');
642 }
643 },
644 error: function() {
645 self.showToast(kngFomoAdmin.i18n.error, 'error');
646 }
647 });
648 },
649
650 /**
651 * Delete notification
652 */
653 deleteNotification: function(id) {
654 const self = this;
655
656 if (!confirm(kngFomoAdmin.i18n.confirmDelete)) {
657 return;
658 }
659
660 $.ajax({
661 url: kngFomoAdmin.ajaxUrl,
662 type: 'POST',
663 data: {
664 action: 'kng_fomo_delete_notification',
665 nonce: kngFomoAdmin.nonce,
666 id: id
667 },
668 success: function(response) {
669 if (response.success) {
670 self.showToast(kngFomoAdmin.i18n.deleted, 'success');
671 $('tr[data-id="' + id + '"]').fadeOut(300, function() {
672 $(this).remove();
673 self.checkEmptyState();
674 });
675 } else {
676 self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error');
677 }
678 },
679 error: function() {
680 self.showToast(kngFomoAdmin.i18n.error, 'error');
681 }
682 });
683 },
684
685 /**
686 * Duplicate notification
687 */
688 duplicateNotification: function(id) {
689 const self = this;
690
691 $.ajax({
692 url: kngFomoAdmin.ajaxUrl,
693 type: 'POST',
694 data: {
695 action: 'kng_fomo_duplicate_notification',
696 nonce: kngFomoAdmin.nonce,
697 id: id
698 },
699 success: function(response) {
700 if (response.success) {
701 self.showToast(kngFomoAdmin.i18n.duplicated, 'success');
702 setTimeout(function() {
703 window.location.reload();
704 }, 500);
705 } else {
706 self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error');
707 }
708 },
709 error: function() {
710 self.showToast(kngFomoAdmin.i18n.error, 'error');
711 }
712 });
713 },
714
715 /**
716 * Check empty state
717 */
718 checkEmptyState: function() {
719 if ($('.kng-fomo-table tbody tr').length === 0) {
720 $('.kng-fomo-table-wrap').hide();
721 $('.kng-fomo-empty').show();
722 }
723 },
724
725 /**
726 * Add page rule
727 */
728 addPageRule: function() {
729 const $container = $('.kng-fomo-page-rules');
730 const template = `
731 <div class="kng-fomo-page-rule">
732 <select class="kng-fomo-input kng-fomo-input--sm kng-fomo-rule-type">
733 <option value="include">Include</option>
734 <option value="exclude">Exclude</option>
735 </select>
736 <select class="kng-fomo-input kng-fomo-input--sm kng-fomo-rule-condition">
737 <option value="page">Page</option>
738 <option value="post">Post</option>
739 <option value="url_contains">URL Contains</option>
740 <option value="url_is">URL Is</option>
741 </select>
742 <input type="text" class="kng-fomo-input kng-fomo-input--sm kng-fomo-rule-value" placeholder="Value">
743 <button type="button" class="kng-fomo-btn kng-fomo-btn--sm kng-fomo-btn--danger kng-fomo-remove-rule">
744 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
745 </button>
746 </div>
747 `;
748 $container.append(template);
749 },
750
751 /**
752 * Collect page rules
753 */
754 collectPageRules: function() {
755 const rules = [];
756
757 $('.kng-fomo-page-rule').each(function() {
758 const rule = {
759 type: $(this).find('.kng-fomo-rule-type').val(),
760 condition: $(this).find('.kng-fomo-rule-condition').val(),
761 value: $(this).find('.kng-fomo-rule-value').val()
762 };
763
764 if (rule.value) {
765 rules.push(rule);
766 }
767 });
768
769 this.wizardData.display.page_rules = rules;
770 },
771
772 /**
773 * Initialize charts
774 */
775 initCharts: function() {
776 if (!$('#kng-fomo-chart').length) {
777 return;
778 }
779
780 this.loadAnalytics('7days');
781 },
782
783 /**
784 * Load analytics data
785 */
786 loadAnalytics: function(range) {
787 const self = this;
788
789 $.ajax({
790 url: kngFomoAdmin.ajaxUrl,
791 type: 'POST',
792 data: {
793 action: 'kng_fomo_get_analytics',
794 nonce: kngFomoAdmin.nonce,
795 range: range
796 },
797 success: function(response) {
798 if (response.success) {
799 self.renderChart(response.data);
800 self.updateKPIs(response.data);
801 }
802 }
803 });
804 },
805
806 /**
807 * Render chart
808 */
809 renderChart: function(data) {
810 const ctx = document.getElementById('kng-fomo-chart');
811
812 if (!ctx) {
813 return;
814 }
815
816 // Destroy existing chart
817 if (this.chart) {
818 this.chart.destroy();
819 }
820
821 this.chart = new Chart(ctx, {
822 type: 'line',
823 data: {
824 labels: data.labels,
825 datasets: [
826 {
827 label: 'Views',
828 data: data.views,
829 borderColor: '#0071e3',
830 backgroundColor: 'rgba(0, 113, 227, 0.1)',
831 tension: 0.4,
832 fill: true
833 },
834 {
835 label: 'Clicks',
836 data: data.clicks,
837 borderColor: '#34c759',
838 backgroundColor: 'rgba(52, 199, 89, 0.1)',
839 tension: 0.4,
840 fill: true
841 }
842 ]
843 },
844 options: {
845 responsive: true,
846 maintainAspectRatio: false,
847 plugins: {
848 legend: {
849 display: true,
850 position: 'top',
851 labels: {
852 usePointStyle: true,
853 padding: 20,
854 font: {
855 family: "-apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif",
856 size: 13
857 }
858 }
859 }
860 },
861 scales: {
862 x: {
863 grid: {
864 display: false
865 },
866 ticks: {
867 font: {
868 family: "-apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif",
869 size: 12
870 }
871 }
872 },
873 y: {
874 beginAtZero: true,
875 grid: {
876 color: 'rgba(0, 0, 0, 0.04)'
877 },
878 ticks: {
879 font: {
880 family: "-apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif",
881 size: 12
882 }
883 }
884 }
885 },
886 interaction: {
887 intersect: false,
888 mode: 'index'
889 }
890 }
891 });
892 },
893
894 /**
895 * Update KPI cards
896 */
897 updateKPIs: function(data) {
898 $('.kng-fomo-kpi-value[data-kpi="views"]').text(this.formatNumber(data.totals.views));
899 $('.kng-fomo-kpi-value[data-kpi="clicks"]').text(this.formatNumber(data.totals.clicks));
900 $('.kng-fomo-kpi-value[data-kpi="ctr"]').text(data.totals.ctr + '%');
901 },
902
903 /**
904 * Format number
905 */
906 formatNumber: function(num) {
907 if (num >= 1000000) {
908 return (num / 1000000).toFixed(1) + 'M';
909 }
910 if (num >= 1000) {
911 return (num / 1000).toFixed(1) + 'K';
912 }
913 return num.toLocaleString();
914 },
915
916 /**
917 * Initialize toggles
918 */
919 initToggles: function() {
920 // Module toggles
921 $(document).on('change', '.kng-fomo-module-toggle', function() {
922 const module = $(this).data('module');
923 const enabled = $(this).is(':checked');
924
925 // Visual feedback
926 $(this).closest('.kng-fomo-module').toggleClass('is-enabled', enabled);
927 });
928 },
929
930 /**
931 * Save settings
932 */
933 saveSettings: function() {
934 const self = this;
935 const $btn = $('.kng-fomo-save-settings');
936 const settings = {};
937
938 // Collect all settings
939 $('.kng-fomo-settings-form [name]').each(function() {
940 const name = $(this).attr('name');
941 const value = $(this).is(':checkbox') ? $(this).is(':checked') : $(this).val();
942 settings[name] = value;
943 });
944
945 // Collect modules
946 const modules = {};
947 $('.kng-fomo-module-toggle').each(function() {
948 modules[$(this).data('module')] = $(this).is(':checked');
949 });
950 settings.modules = modules;
951
952 $btn.prop('disabled', true).addClass('is-loading');
953
954 $.ajax({
955 url: kngFomoAdmin.ajaxUrl,
956 type: 'POST',
957 data: {
958 action: 'kng_fomo_save_settings',
959 nonce: kngFomoAdmin.nonce,
960 settings: JSON.stringify(settings)
961 },
962 success: function(response) {
963 if (response.success) {
964 self.showToast(kngFomoAdmin.i18n.settings_saved, 'success');
965 } else {
966 self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error');
967 }
968 },
969 error: function() {
970 self.showToast(kngFomoAdmin.i18n.error, 'error');
971 },
972 complete: function() {
973 $btn.prop('disabled', false).removeClass('is-loading');
974 }
975 });
976 },
977
978 /**
979 * Export notifications
980 */
981 exportNotifications: function() {
982 const self = this;
983
984 $.ajax({
985 url: kngFomoAdmin.ajaxUrl,
986 type: 'POST',
987 data: {
988 action: 'kng_fomo_export',
989 nonce: kngFomoAdmin.nonce
990 },
991 success: function(response) {
992 if (response.success) {
993 // Download JSON file
994 const blob = new Blob([JSON.stringify(response.data, null, 2)], { type: 'application/json' });
995 const url = URL.createObjectURL(blob);
996 const a = document.createElement('a');
997 a.href = url;
998 a.download = 'fomo-notifications-export.json';
999 a.click();
1000 URL.revokeObjectURL(url);
1001
1002 self.showToast(kngFomoAdmin.i18n.exported, 'success');
1003 } else {
1004 self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error');
1005 }
1006 },
1007 error: function() {
1008 self.showToast(kngFomoAdmin.i18n.error, 'error');
1009 }
1010 });
1011 },
1012
1013 /**
1014 * Import notifications
1015 */
1016 importNotifications: function() {
1017 const self = this;
1018 const $input = $('<input type="file" accept=".json">');
1019
1020 $input.on('change', function(e) {
1021 const file = e.target.files[0];
1022 if (!file) {
1023 return;
1024 }
1025
1026 const reader = new FileReader();
1027 reader.onload = function(e) {
1028 try {
1029 const data = JSON.parse(e.target.result);
1030 self.processImport(data);
1031 } catch (error) {
1032 self.showToast(kngFomoAdmin.i18n.invalid_file, 'error');
1033 }
1034 };
1035 reader.readAsText(file);
1036 });
1037
1038 $input.click();
1039 },
1040
1041 /**
1042 * Process import
1043 */
1044 processImport: function(data) {
1045 const self = this;
1046
1047 $.ajax({
1048 url: kngFomoAdmin.ajaxUrl,
1049 type: 'POST',
1050 data: {
1051 action: 'kng_fomo_import',
1052 nonce: kngFomoAdmin.nonce,
1053 import_data: JSON.stringify(data)
1054 },
1055 success: function(response) {
1056 if (response.success) {
1057 self.showToast(kngFomoAdmin.i18n.imported, 'success');
1058 setTimeout(function() {
1059 window.location.reload();
1060 }, 500);
1061 } else {
1062 self.showToast(response.data.message || kngFomoAdmin.i18n.error, 'error');
1063 }
1064 },
1065 error: function() {
1066 self.showToast(kngFomoAdmin.i18n.error, 'error');
1067 }
1068 });
1069 },
1070
1071 /**
1072 * Switch tab
1073 */
1074 switchTab: function(tab) {
1075 $('.kng-fomo-nav-item').removeClass('is-active');
1076 $('.kng-fomo-nav-item[data-tab="' + tab + '"]').addClass('is-active');
1077
1078 // Update URL
1079 const url = new URL(window.location);
1080 url.searchParams.set('tab', tab);
1081 window.history.pushState({}, '', url);
1082 },
1083
1084 /**
1085 * Switch content tab
1086 */
1087 switchContentTab: function(tabId) {
1088 $('.kng-fomo-tab').removeClass('is-active');
1089 $('.kng-fomo-tab[data-tab="' + tabId + '"]').addClass('is-active');
1090
1091 $('.kng-fomo-tab-content').removeClass('is-active');
1092 $('#' + tabId).addClass('is-active');
1093 },
1094
1095 /**
1096 * Open modal
1097 */
1098 openModal: function(content) {
1099 const $overlay = $('.kng-fomo-modal-overlay');
1100
1101 if (!$overlay.length) {
1102 $('body').append('<div class="kng-fomo-modal-overlay"><div class="kng-fomo-modal"></div></div>');
1103 }
1104
1105 $('.kng-fomo-modal').html(content);
1106 $('.kng-fomo-modal-overlay').addClass('is-visible');
1107 $('body').css('overflow', 'hidden');
1108 },
1109
1110 /**
1111 * Close modal
1112 */
1113 closeModal: function() {
1114 $('.kng-fomo-modal-overlay').removeClass('is-visible');
1115 $('body').css('overflow', '');
1116 },
1117
1118 /**
1119 * Show toast notification
1120 */
1121 showToast: function(message, type) {
1122 type = type || 'success';
1123
1124 // Remove existing toasts
1125 $('.kng-fomo-toast').remove();
1126
1127 const icon = type === 'success'
1128 ? '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>'
1129 : '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>';
1130
1131 const $toast = $(`
1132 <div class="kng-fomo-toast kng-fomo-toast--${type}">
1133 <span class="kng-fomo-toast-icon">${icon}</span>
1134 <span class="kng-fomo-toast-message">${message}</span>
1135 </div>
1136 `);
1137
1138 $('body').append($toast);
1139
1140 // Auto remove
1141 setTimeout(function() {
1142 $toast.addClass('is-hiding');
1143 setTimeout(function() {
1144 $toast.remove();
1145 }, 200);
1146 }, 3000);
1147 },
1148
1149 /**
1150 * Get URL parameter
1151 */
1152 getUrlParam: function(param) {
1153 const urlParams = new URLSearchParams(window.location.search);
1154 return urlParams.get(param);
1155 }
1156 };
1157
1158 // Initialize on document ready
1159 $(document).ready(function() {
1160 KngFomoAdmin.init();
1161 });
1162
1163 })(jQuery);
1164