/** * Target Conditions List Preview * * Handles showing item previews in admin list views when clicking on target count badges. * Uses the TargetConditions trait's AJAX endpoint to load preview data. * * Note: Uses native event capturing to intercept clicks before PrestaShop's * inline onclick handlers on parent td elements can navigate away. */ (function($) { 'use strict'; var TargetListPreview = { $popover: null, $backdrop: null, currentTrigger: null, currentConditions: null, ajaxUrl: '', trans: {}, currentEntityType: null, filterQuery: '', filterTimeout: null, loadedData: {}, itemsPerPage: 20, init: function(config) { this.ajaxUrl = config.ajaxUrl || ''; this.trans = config.trans || {}; this.createPopover(); this.bindEvents(); this.loadBadgeCounts(); }, loadBadgeCounts: function() { var self = this; var $triggers = $('.target-preview-trigger'); if ($triggers.length === 0) { return; } $triggers.each(function() { var $trigger = $(this); var conditions = $trigger.data('conditions'); if (!conditions) { return; } if (typeof conditions === 'string') { try { conditions = JSON.parse(conditions); } catch (e) { return; } } // Check for summary badge (more than 3 entity types) var $summaryBadge = $trigger.find('.badge[data-entity-type="summary"]'); if ($summaryBadge.length > 0) { self.fetchSummaryCount($trigger, conditions, $summaryBadge); return; } // Find entity types with groups for (var entityType in conditions) { if (conditions[entityType] && conditions[entityType].groups && conditions[entityType].groups.length > 0) { self.fetchEntityCount($trigger, entityType, conditions[entityType]); } } }); }, fetchSummaryCount: function($trigger, conditions, $badge) { var self = this; var entityTypes = ($badge.data('entity-types') || '').split(',').filter(function(t) { return t; }); var pendingCount = entityTypes.length; var totalSum = 0; var hasAll = $badge.data('has-all') === 1 || $badge.data('has-all') === '1'; if (pendingCount === 0) { return; } entityTypes.forEach(function(entityType) { var conditionsData = conditions[entityType]; if (!conditionsData || !conditionsData.groups || conditionsData.groups.length === 0) { pendingCount--; if (pendingCount === 0) { self.updateSummaryBadge($badge, totalSum, hasAll); } return; } $.ajax({ url: self.ajaxUrl, type: 'POST', dataType: 'json', data: { ajax: 1, action: 'previewTargetConditions', trait: 'TargetConditions', block_type: entityType, conditions: JSON.stringify(conditionsData), limit: 0 } }).done(function(response) { if (response.success && typeof response.total !== 'undefined') { totalSum += response.total; } }).always(function() { pendingCount--; if (pendingCount === 0) { self.updateSummaryBadge($badge, totalSum, hasAll); } }); }); }, updateSummaryBadge: function($badge, total, hasAll) { var displayText; if (hasAll) { displayText = (this.trans.multiple || 'Multiple') + ' (' + total + ')'; } else { displayText = total + ' ' + (this.trans.items || 'items'); } $badge.empty() .append($('', { class: 'icon-th-list' })) .append(' ' + displayText); $badge.attr('title', displayText); if (total === 0) { $badge.removeClass('badge-info badge-success badge-warning').addClass('badge-danger'); } }, fetchEntityCount: function($trigger, entityType, conditionsData) { var self = this; // Skip AJAX if no badge exists for this entity type var $badge = $trigger.find('.badge[data-entity-type="' + entityType + '"]'); if ($badge.length === 0) { return; } $.ajax({ url: this.ajaxUrl, type: 'POST', dataType: 'json', data: { ajax: 1, action: 'previewTargetConditions', trait: 'TargetConditions', block_type: entityType, conditions: JSON.stringify(conditionsData), limit: 0 } }).done(function(response) { if (response.success && typeof response.total !== 'undefined') { self.updateBadgeCount($trigger, entityType, response.total); } }); }, updateBadgeCount: function($trigger, entityType, total) { // Find badge for this specific entity type only - no fallback var $badge = $trigger.find('.badge[data-entity-type="' + entityType + '"]'); if ($badge.length === 0) { return; } var hasAll = $badge.data('has-all') === 1 || $badge.data('has-all') === '1'; var excludes = parseInt($badge.data('excludes') || 0, 10); // Entity icon mapping var iconMap = { 'products': 'icon-cube', 'categories': 'icon-folder-open', 'manufacturers': 'icon-building', 'suppliers': 'icon-truck', 'cms': 'icon-file-text', 'cms_categories': 'icon-folder' }; // Get entity icon (first that's not the spinner) var $existingIcon = $badge.find('i').not('.icon-spinner').first(); var iconClass = $existingIcon.length ? $existingIcon.attr('class') : (iconMap[entityType] || 'icon-list'); var $icon = $('', { class: iconClass }); // Build display text var displayText; var entityLabels = this.trans || {}; var singular = entityLabels[entityType + '_singular'] || entityType.replace(/_/g, ' ').replace(/s$/, ''); var plural = entityLabels[entityType + '_plural'] || entityType.replace(/_/g, ' '); if (hasAll) { // "All products (49)" or "All products" var allLabel = entityLabels[entityType + '_all'] || ('All ' + plural); displayText = allLabel + ' (' + total + ')'; } else { // "5 products" displayText = total + ' ' + (total === 1 ? singular : plural); } // Update badge content $badge.empty().append($icon).append(' ' + displayText); // Update tooltip var tooltip = displayText; if (excludes > 0) { tooltip += ' (with ' + excludes + ' exclusion' + (excludes === 1 ? '' : 's') + ')'; } $badge.attr('title', tooltip); // Update color based on count if (total === 0) { $badge.removeClass('badge-info badge-success badge-warning').addClass('badge-danger'); } }, createPopover: function() { this.$popover = $('
', { class: 'target-list-preview-popover', css: { display: 'none' } }).appendTo('body'); this.$backdrop = $('
', { class: 'target-list-preview-backdrop', css: { display: 'none' } }).appendTo('body'); this.initDragAndResize(); }, setPopoverContent: function(html) { this.$popover.html(html + '
'); }, initDragAndResize: function() { var self = this; var isDragging = false; var isResizing = false; var startX, startY, startLeft, startTop, startWidth, startHeight; // Drag by header this.$popover.on('mousedown', '.preview-header', function(e) { if ($(e.target).closest('.preview-close').length) return; isDragging = true; startX = e.clientX; startY = e.clientY; startLeft = parseInt(self.$popover.css('left'), 10) || 0; startTop = parseInt(self.$popover.css('top'), 10) || 0; self.$popover.addClass('dragging'); e.preventDefault(); }); // Resize by handle this.$popover.on('mousedown', '.popover-resize-handle', function(e) { isResizing = true; startX = e.clientX; startY = e.clientY; startWidth = self.$popover.outerWidth(); startHeight = self.$popover.outerHeight(); self.$popover.addClass('resizing'); e.preventDefault(); e.stopPropagation(); }); $(document).on('mousemove', function(e) { if (isDragging) { var dx = e.clientX - startX; var dy = e.clientY - startY; var newLeft = startLeft + dx; var newTop = startTop + dy; // Keep within viewport var maxLeft = $(window).width() - self.$popover.outerWidth() - 10; var maxTop = $(window).height() - 50; newLeft = Math.max(10, Math.min(newLeft, maxLeft)); newTop = Math.max(10, Math.min(newTop, maxTop)); self.$popover.css({ left: newLeft, top: newTop }); } if (isResizing) { var dx = e.clientX - startX; var dy = e.clientY - startY; var newWidth = Math.max(300, Math.min(startWidth + dx, $(window).width() - 40)); var newHeight = Math.max(200, Math.min(startHeight + dy, $(window).height() - 40)); self.$popover.css({ width: newWidth, height: newHeight }); } }); $(document).on('mouseup', function() { if (isDragging) { isDragging = false; self.$popover.removeClass('dragging'); } if (isResizing) { isResizing = false; self.$popover.removeClass('resizing'); } }); }, bindEvents: function() { var self = this; // Use native event listener with capture phase to intercept BEFORE // the inline onclick on parent td elements can fire document.addEventListener('click', function(e) { var trigger = e.target.closest('.target-preview-trigger'); if (trigger) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); // Also remove inline onclick from parent td to prevent navigation var td = trigger.closest('td'); if (td && td.onclick) { td._originalOnclick = td.onclick; td.onclick = null; // Restore after a tick setTimeout(function() { if (td._originalOnclick) { td.onclick = td._originalOnclick; delete td._originalOnclick; } }, 0); } self.showPreview($(trigger)); return false; } }, true); // true = capture phase // Click backdrop to close this.$backdrop.on('click', function() { self.hidePreview(); }); // ESC to close $(document).on('keydown', function(e) { if (e.key === 'Escape' && self.$popover.is(':visible')) { self.hidePreview(); } }); // Close button this.$popover.on('click', '.preview-close', function() { self.hidePreview(); }); // Tab switching this.$popover.on('click', '.preview-tab', function() { var $tab = $(this); var entityType = $tab.data('entity'); self.$popover.find('.preview-tab').removeClass('active'); $tab.addClass('active'); self.$popover.find('.preview-content').removeClass('active'); self.$popover.find('.preview-content[data-entity="' + entityType + '"]').addClass('active'); self.currentEntityType = entityType; self.filterQuery = ''; self.$popover.find('.preview-filter-input').val(''); }); // Filter input with debounce this.$popover.on('input', '.preview-filter-input', function() { var query = $(this).val().trim().toLowerCase(); clearTimeout(self.filterTimeout); self.filterTimeout = setTimeout(function() { self.filterQuery = query; self.applyFilter(); }, 150); }); // Load more button click this.$popover.on('click', '.btn-load-more', function() { var $content = $(this).closest('.preview-content'); var entityType = $content.data('entity'); var limit = parseInt($content.find('.load-more-select').val(), 10); self.loadMoreItems(entityType, limit); }); // Rule badge click - show entity names this.$popover.on('click', '.preview-rule', function(e) { e.stopPropagation(); var $rule = $(this); // If clicking on same rule with open popup, close it if (self.$currentRuleElement && self.$currentRuleElement.is($rule)) { $('.rule-detail-popup').remove(); self.$currentRuleElement = null; self.$currentRulePopup = null; return; } self.showRuleDetails($rule); }); // Close rule popup when clicking elsewhere $(document).on('click', function(e) { if (!$(e.target).closest('.preview-rule').length && !$(e.target).closest('.rule-detail-popup').length) { $('.rule-detail-popup').remove(); self.$currentRuleElement = null; self.$currentRulePopup = null; } }); }, showRuleDetails: function($rule) { var self = this; var entityType = $rule.data('entity-type'); var method = $rule.data('method'); var values = $rule.data('values'); // Methods that don't have entity IDs to resolve var nonEntityMethods = ['all', 'by_price_range', 'by_quantity_range', 'by_condition', 'by_visibility', 'by_active_status', 'by_stock_status', 'by_on_sale', 'by_is_virtual', 'by_is_pack', 'by_has_combinations', 'by_name_pattern', 'by_reference_pattern', 'by_date_added', 'by_date_updated', 'by_weight_range', 'by_depth', 'by_active', 'by_newsletter', 'by_optin']; if (nonEntityMethods.indexOf(method) !== -1 || !entityType || !values || values.length === 0) { // Show simple info popup with properly formatted values var valuesDisplay = this.formatValuesForDisplay(values); this.showRulePopup($rule, '
' + '' + this.humanizeMethod(method) + '' + (valuesDisplay ? '
' + valuesDisplay + '' : '') + '
'); return; } // Show loading state this.showRulePopup($rule, '
Loading...
'); // Fetch entity names $.ajax({ url: this.ajaxUrl, type: 'POST', dataType: 'json', data: { ajax: 1, action: 'getTargetEntitiesByIds', trait: 'TargetConditions', entity_type: entityType, ids: JSON.stringify(values) } }).done(function(response) { if (response.success && response.entities) { var icon = self.getEntityIcon(entityType); var html = '
'; response.entities.forEach(function(entity) { html += '
'; if (entity.image) { html += ''; } else { html += ''; } html += '' + self.escapeHtml(entity.name || entity.title || 'ID: ' + entity.id) + ''; html += '
'; }); html += '
'; self.updateRulePopup(html); } else { self.updateRulePopup('
Failed to load names
'); } }).fail(function() { self.updateRulePopup('
Failed to load names
'); }); }, formatValuesForDisplay: function(values) { if (!values || values.length === 0) { return ''; } var formatted = []; for (var i = 0; i < values.length && i < 10; i++) { var val = values[i]; if (typeof val === 'object' && val !== null) { // Handle object values (like {min: x, max: y} for ranges) if (val.min !== undefined || val.max !== undefined) { formatted.push((val.min || '0') + ' - ' + (val.max || '∞')); } else if (val.pattern !== undefined) { formatted.push('"' + val.pattern + '"'); } else if (val.from !== undefined || val.to !== undefined) { formatted.push((val.from || 'start') + ' → ' + (val.to || 'now')); } else { // Generic object - show key: value pairs var parts = []; for (var key in val) { if (val.hasOwnProperty(key)) { parts.push(key + ': ' + val[key]); } } formatted.push(parts.join(', ')); } } else { formatted.push(String(val)); } } var result = formatted.join(', '); if (values.length > 10) { result += ' (+' + (values.length - 10) + ' more)'; } return result; }, showRulePopup: function($rule, content) { // Remove any existing popup $('.rule-detail-popup').remove(); // Create popup and append to body for proper positioning var $popup = $('
', { class: 'rule-detail-popup' }).html(content); $('body').append($popup); // Position below the rule badge var offset = $rule.offset(); var ruleHeight = $rule.outerHeight(); var popupWidth = $popup.outerWidth(); var windowWidth = $(window).width(); var left = offset.left; var top = offset.top + ruleHeight + 4; // Keep popup within viewport if (left + popupWidth > windowWidth - 10) { left = windowWidth - popupWidth - 10; } if (left < 10) { left = 10; } $popup.css({ position: 'fixed', top: top - $(window).scrollTop(), left: left }); this.$currentRulePopup = $popup; this.$currentRuleElement = $rule; }, updateRulePopup: function(content) { if (this.$currentRulePopup) { this.$currentRulePopup.html(content); } }, showPreview: function($trigger) { var self = this; var conditions = $trigger.data('conditions'); if (!conditions) { return; } if (typeof conditions === 'string') { try { conditions = JSON.parse(conditions); } catch (e) { console.error('[TargetListPreview] Failed to parse conditions:', e); return; } } this.currentTrigger = $trigger; this.currentConditions = conditions; this.loadedData = {}; this.filterQuery = ''; // Show loading state this.setPopoverContent(this.renderLoading()); this.$popover.show(); this.$backdrop.show(); // Position popover this.positionPopover(); // Load preview data this.loadPreview(conditions); }, hidePreview: function() { this.$popover.hide(); this.$backdrop.hide(); this.currentTrigger = null; this.currentConditions = null; this.currentEntityType = null; // Clean up any open rule popups $('.rule-detail-popup').remove(); this.$currentRuleElement = null; this.$currentRulePopup = null; }, positionPopover: function() { if (!this.currentTrigger) return; var $trigger = this.currentTrigger; var offset = $trigger.offset(); var triggerWidth = $trigger.outerWidth(); var triggerHeight = $trigger.outerHeight(); var windowWidth = $(window).width(); var windowHeight = $(window).height(); var scrollTop = $(window).scrollTop(); var scrollLeft = $(window).scrollLeft(); var popoverWidth = this.$popover.outerWidth() || 380; var popoverHeight = this.$popover.outerHeight() || 500; // Convert document offset to viewport position (for position: fixed) var triggerViewportLeft = offset.left - scrollLeft; var triggerViewportTop = offset.top - scrollTop; // Center popover below trigger var left = triggerViewportLeft + (triggerWidth / 2) - (popoverWidth / 2); var top = triggerViewportTop + triggerHeight + 8; // Keep within viewport bounds if (left < 10) left = 10; if (left + popoverWidth > windowWidth - 10) { left = windowWidth - popoverWidth - 10; } // Calculate space available above and below var spaceBelow = windowHeight - (triggerViewportTop + triggerHeight + 8); var spaceAbove = triggerViewportTop - 8; // Only flip to above if: // 1. Not enough space below for popover AND // 2. More space above than below if (spaceBelow < popoverHeight && spaceAbove > spaceBelow) { top = triggerViewportTop - popoverHeight - 8; // Ensure not above viewport if (top < 10) top = 10; } else { // Show below, constrain to viewport if needed if (top + popoverHeight > windowHeight - 10) { top = windowHeight - popoverHeight - 10; } } this.$popover.css({ left: left, top: top }); }, loadPreview: function(conditions) { var self = this; var entityTypes = []; for (var type in conditions) { if (conditions[type] && conditions[type].groups && conditions[type].groups.length > 0) { entityTypes.push(type); } } if (entityTypes.length === 0) { this.setPopoverContent(this.renderEmpty()); return; } var promises = []; var results = {}; entityTypes.forEach(function(entityType) { var promise = $.ajax({ url: self.ajaxUrl, type: 'POST', dataType: 'json', data: { ajax: 1, action: 'previewTargetConditions', trait: 'TargetConditions', block_type: entityType, conditions: JSON.stringify(conditions[entityType]), limit: self.itemsPerPage } }).then(function(response) { results[entityType] = response; self.loadedData[entityType] = { items: response.items || [], total: response.total || 0, offset: response.items ? response.items.length : 0 }; }); promises.push(promise); }); $.when.apply($, promises).then(function() { self.currentEntityType = entityTypes[0]; self.setPopoverContent(self.renderPreview(entityTypes, results)); self.positionPopover(); }).fail(function() { self.setPopoverContent(self.renderError()); }); }, loadMoreItems: function(entityType, limit) { var self = this; var data = this.loadedData[entityType]; if (!data || data.offset >= data.total) return; var requestLimit = limit || this.itemsPerPage; var $content = this.$popover.find('.preview-content[data-entity="' + entityType + '"]'); var $loadMore = $content.find('.preview-footer'); $loadMore.html(' ' + (this.trans.loading || 'Loading...') + ''); $.ajax({ url: this.ajaxUrl, type: 'POST', dataType: 'json', data: { ajax: 1, action: 'previewTargetConditions', trait: 'TargetConditions', block_type: entityType, conditions: JSON.stringify(this.currentConditions[entityType]), limit: requestLimit, offset: data.offset } }).done(function(response) { if (response.success && response.items) { data.items = data.items.concat(response.items); data.offset += response.items.length; var $list = $content.find('.preview-items'); response.items.forEach(function(item) { $list.append(self.renderItem(item)); }); self.updateFooter(entityType); self.applyFilter(); } }).fail(function() { $loadMore.html(' ' + (self.trans.error || 'Failed to load') + ''); }); }, applyFilter: function() { var query = this.filterQuery; var $content = this.$popover.find('.preview-content.active'); var $items = $content.find('.preview-item'); if (!query) { $items.show(); return; } $items.each(function() { var $item = $(this); var name = ($item.find('.preview-item-name').text() || '').toLowerCase(); var ref = ($item.find('.preview-item-ref').text() || '').toLowerCase(); if (name.indexOf(query) !== -1 || ref.indexOf(query) !== -1) { $item.show(); } else { $item.hide(); } }); }, updateFooter: function(entityType) { var data = this.loadedData[entityType]; var $content = this.$popover.find('.preview-content[data-entity="' + entityType + '"]'); var $footer = $content.find('.preview-footer'); if (data.offset >= data.total) { $footer.remove(); } else { var remaining = data.total - data.offset; var html = '
'; html += 'Load'; html += ''; html += 'of ' + remaining + ' remaining'; html += ''; html += '
'; $footer.html(html); } }, renderLoading: function() { return '
' + ' ' + (this.trans.loading || 'Loading...') + '
'; }, renderEmpty: function() { return '
' + '' + (this.trans.items_preview || 'Items Preview') + '' + '' + '
' + '
' + ' ' + (this.trans.no_items || 'No items selected') + '
'; }, renderError: function() { return '
' + '' + (this.trans.items_preview || 'Items Preview') + '' + '' + '
' + '
' + ' ' + (this.trans.error || 'Failed to load preview') + '
'; }, renderPreview: function(entityTypes, results) { var self = this; var html = '
'; html += '' + (this.trans.items_preview || 'Items Preview') + ''; html += ''; html += '
'; // Tabs if multiple entity types if (entityTypes.length > 1) { html += '
'; entityTypes.forEach(function(type, index) { var result = results[type]; var count = result && result.total ? result.total : 0; var icon = self.getEntityIcon(type); var label = self.getEntityLabel(type); var activeClass = index === 0 ? ' active' : ''; html += ''; }); html += '
'; } // Filter input html += '
'; html += ''; html += ''; html += '
'; // Content for each entity type html += '
'; entityTypes.forEach(function(type, index) { var activeClass = index === 0 ? ' active' : ''; html += '
'; html += self.renderEntityPreview(type, results[type]); html += '
'; }); html += '
'; return html; }, renderEntityPreview: function(entityType, result) { if (!result || !result.success) { return '
' + (this.trans.error || 'Failed to load') + '
'; } var items = result.items || []; var total = result.total || 0; var self = this; var html = ''; // Render rules summary if conditions are available var conditions = this.currentConditions && this.currentConditions[entityType]; if (conditions && conditions.groups && conditions.groups.length > 0) { html += this.renderRulesSummary(entityType, conditions.groups); } if (items.length === 0) { return html + '
' + (this.trans.no_items || 'No items') + '
'; } html += '
'; items.forEach(function(item) { html += self.renderItem(item); }); html += '
'; if (total > items.length) { var remaining = total - items.length; html += ''; } return html; }, renderRulesSummary: function(entityType, groups) { var self = this; var html = '
'; groups.forEach(function(group, index) { var include = group.include || {}; var excludes = group.excludes || []; var method = include.method || 'unknown'; var values = include.values || []; html += '
'; // Group label if (groups.length > 1) { html += 'Group ' + (index + 1) + ''; } // Include rule - clickable to show details var entityTypeForMethod = self.getEntityTypeForMethod(entityType, method); html += '
'; html += ''; html += '' + self.formatRuleMethod(method, values.length) + ''; html += '
'; // Exclusion rules if (excludes.length > 0) { excludes.forEach(function(exclude) { var exMethod = exclude.method || 'unknown'; var exValues = exclude.values || []; var exEntityType = self.getEntityTypeForMethod(entityType, exMethod); html += '
'; html += ''; html += '' + self.formatRuleMethod(exMethod, exValues.length) + ''; html += '
'; }); } html += '
'; }); html += '
'; return html; }, getEntityTypeForMethod: function(baseEntityType, method) { // Map method to entity type for resolving names var methodToEntity = { 'specific': baseEntityType, 'by_category': 'categories', 'by_manufacturer': 'manufacturers', 'by_supplier': 'suppliers', 'by_attribute': 'attributes', 'by_feature': 'features', 'by_parent': 'categories', 'by_cms_category': 'cms_categories', 'by_group': 'customer_groups', 'by_profile': 'profiles', 'by_zone': 'zones', 'by_country': 'countries' }; return methodToEntity[method] || null; }, buildRuleTooltip: function(type, method, values) { var tooltip = type + ': ' + this.humanizeMethod(method); if (values && values.length > 0) { tooltip += ' (' + values.length + ' selected)'; } return tooltip; }, escapeAttr: function(str) { return String(str) .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); }, formatRuleMethod: function(method, valueCount) { var countStr = valueCount !== undefined ? ' (' + valueCount + ')' : ''; var methodLabels = { // Basic methods 'all': 'All', 'specific': 'Specific' + countStr, // Product selection methods 'by_category': 'Category' + countStr, 'by_manufacturer': 'Manufacturer' + countStr, 'by_supplier': 'Supplier' + countStr, 'by_attribute': 'Attribute' + countStr, 'by_feature': 'Feature' + countStr, 'by_price_range': 'Price range', 'by_quantity_range': 'Quantity range', 'by_condition': 'Condition' + countStr, 'by_visibility': 'Visibility' + countStr, 'by_active_status': 'Active status', 'by_stock_status': 'Stock status', 'by_on_sale': 'On sale', 'by_is_virtual': 'Virtual products', 'by_is_pack': 'Pack products', 'by_has_combinations': 'Has combinations', 'by_name_pattern': 'Name pattern', 'by_reference_pattern': 'Reference pattern', 'by_date_added': 'Date added', 'by_date_updated': 'Date updated', 'by_weight_range': 'Weight range', // Category methods 'by_parent': 'Parent category' + countStr, 'by_depth': 'Depth level', 'by_active': 'Active status', // CMS methods 'by_cms_category': 'CMS category' + countStr, // Customer methods 'by_group': 'Customer group' + countStr, 'by_newsletter': 'Newsletter', 'by_optin': 'Opt-in', // Employee methods 'by_profile': 'Profile' + countStr, // Generic 'by_zone': 'Zone' + countStr, 'by_country': 'Country' + countStr }; return methodLabels[method] || this.humanizeMethod(method) + countStr; }, humanizeMethod: function(method) { return method .replace(/^by_/, '') .replace(/_/g, ' ') .replace(/\b\w/g, function(l) { return l.toUpperCase(); }); }, renderItem: function(item) { var html = '
'; if (item.image) { html += ''; } else { html += ''; } html += '
'; html += '' + this.escapeHtml(item.name || item.title || 'Item #' + item.id) + ''; if (item.reference) { html += '' + this.escapeHtml(item.reference) + ''; } if (item.price_formatted) { html += '' + item.price_formatted + ''; } html += '
'; html += '
'; return html; }, escapeHtml: function(text) { var div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, getEntityIcon: function(entityType) { var icons = { 'products': 'icon-cube', 'categories': 'icon-folder-open', 'manufacturers': 'icon-building', 'suppliers': 'icon-truck', 'cms': 'icon-file-text', 'cms_categories': 'icon-folder', 'carriers': 'icon-truck', 'zones': 'icon-globe', 'countries': 'icon-flag', 'currencies': 'icon-money', 'languages': 'icon-language', 'customer_groups': 'icon-users', 'shops': 'icon-shopping-cart', 'shop_groups': 'icon-th', 'customers': 'icon-user', 'employees': 'icon-user-secret', 'profiles': 'icon-id-card', 'order_states': 'icon-check-circle', 'taxes': 'icon-percent' }; return icons[entityType] || 'icon-list'; }, getEntityLabel: function(entityType) { var labels = { 'products': this.trans.products || 'Products', 'categories': this.trans.categories || 'Categories', 'manufacturers': this.trans.manufacturers || 'Manufacturers', 'suppliers': this.trans.suppliers || 'Suppliers', 'cms': this.trans.cms_pages || 'CMS Pages', 'cms_categories': this.trans.cms_categories || 'CMS Categories', 'carriers': this.trans.carriers || 'Carriers', 'zones': this.trans.zones || 'Zones', 'countries': this.trans.countries || 'Countries', 'currencies': this.trans.currencies || 'Currencies', 'languages': this.trans.languages || 'Languages', 'customer_groups': this.trans.customer_groups || 'Customer Groups', 'shops': this.trans.shops || 'Shops', 'shop_groups': this.trans.shop_groups || 'Shop Groups', 'customers': this.trans.customers || 'Customers', 'employees': this.trans.employees || 'Employees', 'profiles': this.trans.profiles || 'Profiles', 'order_states': this.trans.order_states || 'Order States', 'taxes': this.trans.taxes || 'Taxes' }; return labels[entityType] || entityType; } }; // Initialize when config is available $(document).ready(function() { if (typeof targetListPreviewConfig !== 'undefined') { TargetListPreview.init(targetListPreviewConfig); } }); window.TargetListPreview = TargetListPreview; })(jQuery);