Feature: embedded layout mode and schedule improvements
- Add layout-embedded class for nested entity selectors - Simplified styling for embedded widgets (less padding, borders) - Schedule toggle row with summary badges - Summary badges show datetime range, weekly schedule, holiday count - Flag fallback styling for countries without valid ISO codes - Section hint margin after embedded entity selector - Holiday countries group without modifiers section Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -68,13 +68,31 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '<span class="entity-chip" data-id="' + this.escapeAttr(id) + '">';
|
||||
// Check if this is a country entity (for flag and holiday preview)
|
||||
var blockType = $block.data('blockType') || '';
|
||||
var searchEntity = $picker.attr('data-search-entity') || blockType;
|
||||
var isCountry = (searchEntity === 'countries');
|
||||
|
||||
if (data && data.image) {
|
||||
var html = '<span class="entity-chip" data-id="' + this.escapeAttr(id) + '"';
|
||||
if (isCountry && data && data.iso_code) {
|
||||
html += ' data-iso="' + this.escapeAttr(data.iso_code) + '"';
|
||||
}
|
||||
html += '>';
|
||||
|
||||
// Country: show flag
|
||||
if (isCountry && data && data.iso_code) {
|
||||
html += '<span class="chip-flag"><img src="https://flagcdn.com/16x12/' + this.escapeAttr(data.iso_code.toLowerCase()) + '.png" alt="' + this.escapeAttr(data.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline-flex\';"><i class="icon-flag flag-fallback" style="display:none;"></i></span>';
|
||||
} else if (data && data.image) {
|
||||
html += '<span class="chip-icon"><img src="' + this.escapeAttr(data.image) + '" alt=""></span>';
|
||||
}
|
||||
|
||||
html += '<span class="chip-name">' + this.escapeHtml(name) + '</span>';
|
||||
|
||||
// Country: add holiday preview button
|
||||
if (isCountry) {
|
||||
html += '<button type="button" class="chip-preview-holidays" title="Preview holidays"><i class="material-icons">visibility</i></button>';
|
||||
}
|
||||
|
||||
html += '<button type="button" class="chip-remove" title="Remove"><i class="icon-times"></i></button>';
|
||||
html += '</span>';
|
||||
|
||||
@@ -343,11 +361,14 @@
|
||||
|
||||
loadExistingSelections: function() {
|
||||
var self = this;
|
||||
console.log('[EntitySelector] loadExistingSelections called for id:', this.config.id);
|
||||
|
||||
// Collect all entity IDs to load, grouped by entity type
|
||||
var entitiesToLoad = {}; // { entity_type: { ids: [], pickers: [] } }
|
||||
|
||||
console.log('[EntitySelector] Looking for .selection-group in wrapper:', this.$wrapper.length ? 'found' : 'NOT FOUND');
|
||||
this.$wrapper.find('.selection-group').each(function() {
|
||||
console.log('[EntitySelector] Found .selection-group, index:', $(this).data('groupIndex'));
|
||||
var $group = $(this);
|
||||
var $block = $group.closest('.target-block');
|
||||
var blockType = $block.data('blockType');
|
||||
@@ -394,9 +415,12 @@
|
||||
|
||||
// Skip AJAX if no entities to load
|
||||
if (!hasEntities) {
|
||||
console.log('[EntitySelector] No entities to load, skipping AJAX');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[EntitySelector] Making bulk AJAX request for entities:', JSON.stringify(bulkRequest));
|
||||
|
||||
// Single bulk AJAX call for all entity types
|
||||
$.ajax({
|
||||
url: self.config.ajaxUrl,
|
||||
@@ -409,9 +433,12 @@
|
||||
entities: JSON.stringify(bulkRequest)
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('[EntitySelector] AJAX response:', response);
|
||||
if (!response.success || !response.entities) {
|
||||
console.log('[EntitySelector] Response failed or no entities');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
// Process each entity type's results
|
||||
Object.keys(entitiesToLoad).forEach(function(entityType) {
|
||||
@@ -431,6 +458,9 @@
|
||||
var $dataInput = $picker.find('.include-values-data, .exclude-values-data');
|
||||
var validIds = [];
|
||||
|
||||
// Check if this is a country entity
|
||||
var isCountry = (entityType === 'countries');
|
||||
|
||||
// Replace loading chips with real data
|
||||
pickerData.ids.forEach(function(id) {
|
||||
var $loadingChip = $chips.find('.entity-chip-loading[data-id="' + id + '"]');
|
||||
@@ -439,11 +469,26 @@
|
||||
validIds.push(entity.id);
|
||||
|
||||
// Create real chip
|
||||
var html = '<span class="entity-chip" data-id="' + self.escapeAttr(entity.id) + '">';
|
||||
if (entity.image) {
|
||||
var html = '<span class="entity-chip" data-id="' + self.escapeAttr(entity.id) + '"';
|
||||
if (isCountry && entity.iso_code) {
|
||||
html += ' data-iso="' + self.escapeAttr(entity.iso_code) + '"';
|
||||
}
|
||||
html += '>';
|
||||
|
||||
// Country: show flag
|
||||
if (isCountry && entity.iso_code) {
|
||||
html += '<span class="chip-flag"><img src="https://flagcdn.com/16x12/' + self.escapeAttr(entity.iso_code.toLowerCase()) + '.png" alt="' + self.escapeAttr(entity.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline-flex\';"><i class="icon-flag flag-fallback" style="display:none;"></i></span>';
|
||||
} else if (entity.image) {
|
||||
html += '<span class="chip-icon"><img src="' + self.escapeAttr(entity.image) + '" alt=""></span>';
|
||||
}
|
||||
|
||||
html += '<span class="chip-name">' + self.escapeHtml(entity.name) + '</span>';
|
||||
|
||||
// Country: add holiday preview button
|
||||
if (isCountry) {
|
||||
html += '<button type="button" class="chip-preview-holidays" title="Preview holidays"><i class="material-icons">visibility</i></button>';
|
||||
}
|
||||
|
||||
html += '<button type="button" class="chip-remove" title="Remove"><i class="icon-times"></i></button>';
|
||||
html += '</span>';
|
||||
|
||||
@@ -466,6 +511,16 @@
|
||||
self.updateBlockStatus($picker.closest('.target-block'));
|
||||
});
|
||||
});
|
||||
|
||||
// Update condition counts after chips are loaded (for holiday counts, etc.)
|
||||
self.updateAllConditionCounts();
|
||||
|
||||
} catch (e) {
|
||||
console.error('[EntitySelector] Error processing AJAX response:', e);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('[EntitySelector] AJAX request failed:', status, error, xhr.responseText);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -475,23 +530,30 @@
|
||||
* Also shows loading placeholders for entity_search types
|
||||
*/
|
||||
collectPickerEntities: function($picker, blockType, entitiesToLoad) {
|
||||
console.log('[EntitySelector] collectPickerEntities called, blockType:', blockType, 'picker length:', $picker.length);
|
||||
if (!$picker.length) {
|
||||
console.log('[EntitySelector] Picker not found, returning');
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var $dataInput = $picker.find('.include-values-data, .exclude-values-data');
|
||||
console.log('[EntitySelector] Looking for values-data input, found:', $dataInput.length);
|
||||
if (!$dataInput.length) {
|
||||
console.log('[EntitySelector] No data input found, returning');
|
||||
return;
|
||||
}
|
||||
|
||||
var valueType = $picker.attr('data-value-type');
|
||||
var rawValue = $dataInput.val() || '[]';
|
||||
console.log('[EntitySelector] valueType:', valueType, 'rawValue:', rawValue);
|
||||
|
||||
var values = [];
|
||||
try {
|
||||
values = JSON.parse(rawValue);
|
||||
console.log('[EntitySelector] Parsed values:', values);
|
||||
} catch (e) {
|
||||
console.log('[EntitySelector] JSON parse error:', e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -548,11 +548,15 @@
|
||||
var self = this;
|
||||
var data = {};
|
||||
|
||||
console.log('[EntitySelector] serializeAllBlocks called');
|
||||
|
||||
this.$wrapper.find('.target-block').each(function() {
|
||||
var $block = $(this);
|
||||
var blockType = $block.data('blockType');
|
||||
var groups = self.getBlockGroups($block);
|
||||
|
||||
console.log('[EntitySelector] Block:', blockType, 'Groups:', groups.length);
|
||||
|
||||
// Groups now contain their own modifiers, no block-level modifiers
|
||||
if (groups.length > 0) {
|
||||
data[blockType] = { groups: groups };
|
||||
@@ -563,7 +567,13 @@
|
||||
|
||||
// Update hidden input first
|
||||
var $input = this.$wrapper.find('input[name="' + this.config.name + '"]');
|
||||
$input.val(JSON.stringify(data));
|
||||
var jsonData = JSON.stringify(data);
|
||||
|
||||
console.log('[EntitySelector] Hidden input name:', this.config.name);
|
||||
console.log('[EntitySelector] Hidden input found:', $input.length);
|
||||
console.log('[EntitySelector] Serialized data:', jsonData.substring(0, 500));
|
||||
|
||||
$input.val(jsonData);
|
||||
|
||||
// Then update tab badges (reads from hidden input)
|
||||
this.updateTabBadges();
|
||||
@@ -901,7 +911,9 @@
|
||||
* Extract condition data from a row for bulk counting
|
||||
*/
|
||||
getConditionData: function($row, blockType) {
|
||||
console.log('[getConditionData] Called with blockType:', blockType);
|
||||
var $countEl = $row.find('.method-selector-wrapper > .condition-match-count, > .exclude-header-row .condition-match-count').first();
|
||||
console.log('[getConditionData] $countEl found:', $countEl.length);
|
||||
if (!$countEl.length) return null;
|
||||
|
||||
var isExclude = $row.hasClass('exclude-row');
|
||||
@@ -910,6 +922,7 @@
|
||||
: $row.find('.include-method-select');
|
||||
|
||||
var method = $methodSelect.val();
|
||||
console.log('[getConditionData] method:', method);
|
||||
if (!method) {
|
||||
$countEl.hide();
|
||||
return null;
|
||||
@@ -918,18 +931,49 @@
|
||||
var $picker = isExclude
|
||||
? $row.find('.exclude-picker')
|
||||
: $row.find('.include-picker');
|
||||
console.log('[getConditionData] $picker found:', $picker.length, 'data-value-type attr:', $picker.attr('data-value-type'));
|
||||
|
||||
var valueType = $picker.data('valueType') || $picker.attr('data-value-type') || 'none';
|
||||
console.log('[getConditionData] valueType:', valueType);
|
||||
|
||||
// Special case: "All countries" method - needs separate handling for holidays
|
||||
if (valueType === 'none' && blockType === 'countries' && method === 'all') {
|
||||
console.log('[getConditionData] All countries detected - triggering updateConditionCount');
|
||||
// Trigger separate update for this special case (uses nested AJAX)
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self.updateConditionCount($row, blockType);
|
||||
}, 0);
|
||||
return null; // Skip bulk processing, handled separately
|
||||
}
|
||||
|
||||
// Special case: Specific countries with entity_search - needs holiday counting, not entity counting
|
||||
var searchEntity = $picker.attr('data-search-entity') || '';
|
||||
if (blockType === 'countries' && valueType === 'entity_search' && searchEntity === 'countries') {
|
||||
console.log('[getConditionData] Specific countries detected - triggering updateConditionCount for holiday counting');
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self.updateConditionCount($row, blockType);
|
||||
}, 0);
|
||||
return null; // Skip bulk processing, handled separately
|
||||
}
|
||||
|
||||
// Hide badge for other "all" type methods (valueType === 'none') since they don't filter
|
||||
if (valueType === 'none') {
|
||||
$countEl.hide();
|
||||
return null;
|
||||
}
|
||||
|
||||
var valueType = $picker.data('valueType') || 'none';
|
||||
var values = this.getPickerValues($picker, valueType);
|
||||
|
||||
// Don't count if no values (except for boolean/all methods)
|
||||
// Don't count if no values (except for boolean methods)
|
||||
var hasNoValues = !values ||
|
||||
(Array.isArray(values) && values.length === 0) ||
|
||||
(typeof values === 'object' && !Array.isArray(values) && (
|
||||
(valueType === 'combination_attributes' && values.attributes !== undefined && Object.keys(values.attributes).length === 0) ||
|
||||
(valueType !== 'combination_attributes' && Object.keys(values).length === 0)
|
||||
));
|
||||
if (valueType !== 'none' && valueType !== 'boolean' && hasNoValues) {
|
||||
if (valueType !== 'boolean' && hasNoValues) {
|
||||
$countEl.hide();
|
||||
return null;
|
||||
}
|
||||
@@ -983,7 +1027,10 @@
|
||||
var self = this;
|
||||
|
||||
var $countEl = $row.find('.method-selector-wrapper > .condition-match-count, > .exclude-header-row .condition-match-count').first();
|
||||
if (!$countEl.length) return;
|
||||
if (!$countEl.length) {
|
||||
console.log('[updateConditionCount] No $countEl found');
|
||||
return;
|
||||
}
|
||||
|
||||
var isExclude = $row.hasClass('exclude-row');
|
||||
var $methodSelect = isExclude
|
||||
@@ -991,7 +1038,9 @@
|
||||
: $row.find('.include-method-select');
|
||||
|
||||
var method = $methodSelect.val();
|
||||
console.log('[updateConditionCount] method:', method, 'isExclude:', isExclude);
|
||||
if (!method) {
|
||||
console.log('[updateConditionCount] No method, hiding badge');
|
||||
$countEl.hide();
|
||||
return;
|
||||
}
|
||||
@@ -1001,6 +1050,104 @@
|
||||
: $row.find('.include-picker');
|
||||
|
||||
var valueType = $picker.data('valueType') || 'none';
|
||||
var searchEntity = $picker.attr('data-search-entity') || '';
|
||||
|
||||
// Get the block type to check if this is a countries block
|
||||
if (!blockType) {
|
||||
var $block = $row.closest('.target-block');
|
||||
blockType = $block.data('blockType') || 'products';
|
||||
}
|
||||
|
||||
console.log('[updateConditionCount] valueType:', valueType, 'searchEntity:', searchEntity, 'blockType:', blockType, 'method:', method);
|
||||
|
||||
// Special case: "All countries" method - fetch holidays for all countries
|
||||
if (valueType === 'none' && blockType === 'countries' && method === 'all') {
|
||||
console.log('[updateConditionCount] All countries method - fetching all country holidays');
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.removeClass('clickable no-matches country-holidays').show();
|
||||
|
||||
// First fetch all active country IDs, then get holidays
|
||||
$.ajax({
|
||||
url: self.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'searchTargetEntities',
|
||||
trait: 'EntitySelector',
|
||||
entity_type: 'countries',
|
||||
query: '',
|
||||
limit: 500
|
||||
},
|
||||
success: function(response) {
|
||||
var items = response.results || response.items || [];
|
||||
if (response && response.success && items.length > 0) {
|
||||
var allCountryIds = items.map(function(item) { return item.id; });
|
||||
console.log('[updateConditionCount] Found', allCountryIds.length, 'countries, fetching holidays');
|
||||
|
||||
// Store condition data for click handler
|
||||
$countEl.data('conditionData', {
|
||||
method: method,
|
||||
values: allCountryIds,
|
||||
blockType: blockType,
|
||||
isExclude: isExclude,
|
||||
isCountryHolidays: true,
|
||||
countryIds: allCountryIds,
|
||||
isAllCountries: true
|
||||
});
|
||||
|
||||
// Now fetch holiday count
|
||||
$.ajax({
|
||||
url: self.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'getHolidaysForCountries',
|
||||
trait: 'EntitySelector',
|
||||
country_ids: allCountryIds.join(','),
|
||||
count_only: 1
|
||||
},
|
||||
success: function(holidayResponse) {
|
||||
console.log('[updateConditionCount] All countries holiday response:', holidayResponse);
|
||||
if (holidayResponse && holidayResponse.success) {
|
||||
var count = holidayResponse.total_count || 0;
|
||||
$countEl.removeClass('no-matches clickable');
|
||||
$countEl.addClass('country-holidays');
|
||||
if (count === 0) {
|
||||
$countEl.find('.preview-count').text(count);
|
||||
$countEl.addClass('no-matches').show();
|
||||
} else {
|
||||
$countEl.find('.preview-count').text(count);
|
||||
$countEl.addClass('clickable').show();
|
||||
}
|
||||
$countEl.data('countriesInfo', holidayResponse.countries || []);
|
||||
} else {
|
||||
$countEl.hide().removeClass('clickable');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$countEl.hide().removeClass('clickable');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$countEl.hide().removeClass('clickable');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$countEl.hide().removeClass('clickable');
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide badge for other "all" type methods (valueType === 'none') since they don't filter
|
||||
if (valueType === 'none') {
|
||||
console.log('[updateConditionCount] valueType is none, hiding badge');
|
||||
$countEl.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var values = this.getPickerValues($picker, valueType);
|
||||
|
||||
var hasNoValues = !values ||
|
||||
@@ -1009,7 +1156,7 @@
|
||||
(valueType === 'combination_attributes' && values.attributes !== undefined && Object.keys(values.attributes).length === 0) ||
|
||||
(valueType !== 'combination_attributes' && Object.keys(values).length === 0)
|
||||
));
|
||||
if (valueType !== 'none' && valueType !== 'boolean' && hasNoValues) {
|
||||
if (valueType !== 'boolean' && hasNoValues) {
|
||||
$countEl.hide();
|
||||
return;
|
||||
}
|
||||
@@ -1019,9 +1166,65 @@
|
||||
blockType = $block.data('blockType') || 'products';
|
||||
}
|
||||
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.removeClass('clickable no-matches').show();
|
||||
// Check if this is a country selection - show holiday count instead
|
||||
var isCountrySelection = (searchEntity === 'countries' && valueType === 'entity_search');
|
||||
console.log('[updateConditionCount] isCountrySelection:', isCountrySelection, 'values:', values);
|
||||
|
||||
$countEl.find('.preview-count').html('<i class="icon-spinner icon-spin"></i>');
|
||||
$countEl.removeClass('clickable no-matches country-holidays').show();
|
||||
|
||||
// For countries, fetch holiday count
|
||||
if (isCountrySelection && Array.isArray(values) && values.length > 0) {
|
||||
console.log('[updateConditionCount] Fetching holiday count for countries:', values);
|
||||
$countEl.data('conditionData', {
|
||||
method: method,
|
||||
values: values,
|
||||
blockType: blockType,
|
||||
isExclude: isExclude,
|
||||
isCountryHolidays: true,
|
||||
countryIds: values
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: self.config.ajaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'getHolidaysForCountries',
|
||||
trait: 'EntitySelector',
|
||||
country_ids: values.join(','),
|
||||
count_only: 1
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('[updateConditionCount] Holiday response:', response);
|
||||
if (response && response.success) {
|
||||
var count = response.total_count || 0;
|
||||
console.log('[updateConditionCount] Holiday count:', count);
|
||||
$countEl.removeClass('no-matches clickable');
|
||||
$countEl.addClass('country-holidays');
|
||||
if (count === 0) {
|
||||
$countEl.find('.preview-count').text(count);
|
||||
$countEl.addClass('no-matches').show();
|
||||
} else {
|
||||
$countEl.find('.preview-count').text(count);
|
||||
$countEl.addClass('clickable').show();
|
||||
}
|
||||
// Store countries info for popover
|
||||
$countEl.data('countriesInfo', response.countries || []);
|
||||
} else {
|
||||
console.log('[updateConditionCount] Holiday response failed:', response);
|
||||
$countEl.hide().removeClass('clickable');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$countEl.hide().removeClass('clickable');
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Default: count entities
|
||||
$countEl.data('conditionData', {
|
||||
method: method,
|
||||
values: values,
|
||||
|
||||
@@ -233,6 +233,22 @@
|
||||
requestData.filter_active = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Countries-specific
|
||||
if (searchEntity === 'countries') {
|
||||
if (this.filters.activeOnly) {
|
||||
requestData.filter_active = 1;
|
||||
}
|
||||
if (this.filters.hasHolidays) {
|
||||
requestData.filter_has_holidays = 1;
|
||||
}
|
||||
if (this.filters.containsStates) {
|
||||
requestData.filter_contains_states = 1;
|
||||
}
|
||||
if (this.filters.zone) {
|
||||
requestData.filter_zone = this.filters.zone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
@@ -357,16 +373,22 @@
|
||||
html += 'data-name="' + self.escapeAttr(item.name) + '"';
|
||||
if (item.image) html += ' data-image="' + self.escapeAttr(item.image) + '"';
|
||||
if (item.subtitle) html += ' data-subtitle="' + self.escapeAttr(item.subtitle) + '"';
|
||||
if (item.iso_code) html += ' data-iso="' + self.escapeAttr(item.iso_code) + '"';
|
||||
html += '>';
|
||||
|
||||
html += '<span class="result-checkbox"><i class="icon-check"></i></span>';
|
||||
|
||||
if (item.image) {
|
||||
var searchEntity = self.activeGroup ? self.activeGroup.searchEntity : null;
|
||||
|
||||
// Countries show flags
|
||||
if (searchEntity === 'countries' && item.iso_code) {
|
||||
var flagUrl = 'https://flagcdn.com/w40/' + item.iso_code.toLowerCase() + '.png';
|
||||
html += '<div class="result-image result-flag"><img src="' + self.escapeAttr(flagUrl) + '" alt="' + self.escapeAttr(item.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\';"><span class="flag-fallback" style="display:none;"><i class="icon-flag"></i></span></div>';
|
||||
} else if (item.image) {
|
||||
html += '<div class="result-image"><img src="' + self.escapeAttr(item.image) + '" alt=""></div>';
|
||||
} else {
|
||||
// Entity-specific icons
|
||||
var iconClass = 'icon-cube'; // default
|
||||
var searchEntity = self.activeGroup ? self.activeGroup.searchEntity : null;
|
||||
if (searchEntity === 'categories') iconClass = 'icon-folder';
|
||||
else if (searchEntity === 'manufacturers') iconClass = 'icon-building';
|
||||
else if (searchEntity === 'suppliers') iconClass = 'icon-truck';
|
||||
@@ -628,7 +650,11 @@
|
||||
dateAddFrom: null,
|
||||
dateAddTo: null,
|
||||
lastProductFrom: null,
|
||||
lastProductTo: null
|
||||
lastProductTo: null,
|
||||
// Country-specific filters
|
||||
hasHolidays: false,
|
||||
containsStates: false,
|
||||
zone: null
|
||||
};
|
||||
|
||||
if (this.$dropdown) {
|
||||
@@ -657,6 +683,10 @@
|
||||
this.$dropdown.find('.filter-has-image').prop('checked', false);
|
||||
this.$dropdown.find('.filter-active-only').prop('checked', true);
|
||||
this.$dropdown.find('.filter-attribute-group-select, .filter-feature-group-select').val('');
|
||||
// Country filters
|
||||
this.$dropdown.find('.filter-has-holidays').prop('checked', false);
|
||||
this.$dropdown.find('.filter-contains-states').prop('checked', false);
|
||||
this.$dropdown.find('.filter-zone-select').val('');
|
||||
}
|
||||
|
||||
this.refreshSearch();
|
||||
@@ -689,7 +719,11 @@
|
||||
dateAddFrom: null,
|
||||
dateAddTo: null,
|
||||
lastProductFrom: null,
|
||||
lastProductTo: null
|
||||
lastProductTo: null,
|
||||
// Country-specific filters
|
||||
hasHolidays: false,
|
||||
containsStates: false,
|
||||
zone: null
|
||||
};
|
||||
|
||||
if (this.$dropdown) {
|
||||
@@ -716,6 +750,10 @@
|
||||
this.$dropdown.find('.filter-has-image').prop('checked', false);
|
||||
this.$dropdown.find('.filter-active-only').prop('checked', true);
|
||||
this.$dropdown.find('.filter-attribute-group-select, .filter-feature-group-select').val('');
|
||||
// Country filters
|
||||
this.$dropdown.find('.filter-has-holidays').prop('checked', false);
|
||||
this.$dropdown.find('.filter-contains-states').prop('checked', false);
|
||||
this.$dropdown.find('.filter-zone-select').val('');
|
||||
}
|
||||
// Note: Does NOT call refreshSearch() - caller handles search/load
|
||||
},
|
||||
@@ -781,6 +819,9 @@
|
||||
$panel.find('.filter-row-entity-cms').show();
|
||||
} else if (entityType === 'cms_categories') {
|
||||
$panel.find('.filter-row-entity-cms-categories').show();
|
||||
} else if (entityType === 'countries') {
|
||||
$panel.find('.filter-row-entity-countries').show();
|
||||
this.loadZonesForCountryFilter();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Entity chips, selection pills, tags
|
||||
*/
|
||||
|
||||
@use "sass:color";
|
||||
@use '../variables' as *;
|
||||
@use '../mixins' as *;
|
||||
|
||||
@@ -285,6 +286,57 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Country flag in chip
|
||||
.chip-flag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 18px;
|
||||
height: 12px;
|
||||
object-fit: cover;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.flag-fallback {
|
||||
width: 18px;
|
||||
height: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #e8eaed 0%, #dadce0 100%);
|
||||
border-radius: 2px;
|
||||
font-size: 10px;
|
||||
color: #5f6368;
|
||||
}
|
||||
}
|
||||
|
||||
// Holiday preview button in country chip
|
||||
.chip-preview-holidays {
|
||||
@include button-reset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: $es-primary;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
transition: all $es-transition-fast;
|
||||
|
||||
&:hover {
|
||||
background: rgba($es-primary, 0.15);
|
||||
color: darken($es-primary, 10%);
|
||||
}
|
||||
|
||||
i.material-icons {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.chip-text,
|
||||
.chip-name {
|
||||
// Show full name, no truncation
|
||||
@@ -344,7 +396,7 @@
|
||||
|
||||
.entity-chip.chip-warning {
|
||||
background: $es-warning-light;
|
||||
color: darken($es-warning, 20%);
|
||||
color: color.adjust($es-warning, $lightness: -20%);
|
||||
|
||||
&:hover {
|
||||
background: rgba($es-warning, 0.3);
|
||||
@@ -715,6 +767,238 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Holiday Preview Popover (Country chip eye button)
|
||||
// ==========================================================================
|
||||
|
||||
.holiday-preview-popover {
|
||||
position: absolute;
|
||||
z-index: 10001;
|
||||
width: 320px;
|
||||
max-width: 90vw;
|
||||
background: $es-white;
|
||||
border-radius: $es-radius-lg;
|
||||
box-shadow: $es-shadow-xl;
|
||||
overflow: hidden;
|
||||
|
||||
.popover-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: $es-spacing-sm;
|
||||
padding: $es-spacing-sm $es-spacing-md;
|
||||
background: $es-bg-header;
|
||||
border-bottom: 1px solid $es-border-color;
|
||||
}
|
||||
|
||||
.popover-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $es-spacing-sm;
|
||||
font-size: $es-font-size-sm;
|
||||
font-weight: $es-font-weight-semibold;
|
||||
color: $es-text-primary;
|
||||
}
|
||||
|
||||
.popover-flag {
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.popover-close {
|
||||
@include button-reset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
color: $es-text-muted;
|
||||
border-radius: $es-radius-md;
|
||||
transition: all $es-transition-fast;
|
||||
|
||||
&:hover {
|
||||
background: $es-slate-200;
|
||||
color: $es-text-secondary;
|
||||
}
|
||||
|
||||
i.material-icons {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-body {
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
padding: $es-spacing-sm;
|
||||
@include custom-scrollbar;
|
||||
}
|
||||
|
||||
// Loading state
|
||||
.holiday-preview-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $es-spacing-sm;
|
||||
padding: $es-spacing-xl 0;
|
||||
color: $es-text-muted;
|
||||
font-size: $es-font-size-sm;
|
||||
|
||||
i.material-icons {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.icon-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state
|
||||
.holiday-preview-empty {
|
||||
text-align: center;
|
||||
padding: $es-spacing-xl 0;
|
||||
color: $es-text-muted;
|
||||
|
||||
i.material-icons {
|
||||
font-size: 48px;
|
||||
opacity: 0.4;
|
||||
margin-bottom: $es-spacing-sm;
|
||||
display: block;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: $es-font-size-sm;
|
||||
}
|
||||
}
|
||||
|
||||
// Holiday list
|
||||
.holiday-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $es-spacing-xs;
|
||||
}
|
||||
|
||||
.holiday-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: $es-spacing-md;
|
||||
padding: $es-spacing-sm $es-spacing-md;
|
||||
background: $es-slate-50;
|
||||
border-radius: $es-radius-md;
|
||||
border-left: 3px solid $es-success;
|
||||
|
||||
&.holiday-type-bank,
|
||||
&.holiday-type-bank-holiday {
|
||||
border-left-color: $es-info;
|
||||
}
|
||||
|
||||
&.holiday-type-observance {
|
||||
border-left-color: $es-warning;
|
||||
}
|
||||
|
||||
&.holiday-type-regional,
|
||||
&.holiday-type-local-holiday {
|
||||
border-left-color: #8b5cf6;
|
||||
}
|
||||
}
|
||||
|
||||
.holiday-date {
|
||||
flex-shrink: 0;
|
||||
min-width: 80px;
|
||||
|
||||
.holiday-day {
|
||||
display: block;
|
||||
font-size: $es-font-size-sm;
|
||||
font-weight: $es-font-weight-semibold;
|
||||
color: $es-text-primary;
|
||||
}
|
||||
|
||||
.holiday-weekday {
|
||||
display: block;
|
||||
font-size: $es-font-size-xs;
|
||||
color: $es-text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
.holiday-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.holiday-country-flag {
|
||||
vertical-align: middle;
|
||||
margin-right: 0.25rem;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.holiday-name {
|
||||
display: inline;
|
||||
font-size: $es-font-size-sm;
|
||||
color: $es-text-primary;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.holiday-type-badge {
|
||||
display: inline-block;
|
||||
margin-left: $es-spacing-sm;
|
||||
padding: 0.125rem 0.375rem;
|
||||
font-size: 10px;
|
||||
font-weight: $es-font-weight-medium;
|
||||
text-transform: capitalize;
|
||||
background: $es-slate-200;
|
||||
color: $es-text-secondary;
|
||||
border-radius: $es-radius-sm;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.holiday-preview-note {
|
||||
margin-top: $es-spacing-md;
|
||||
font-size: $es-font-size-xs;
|
||||
color: $es-text-muted;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// Filter input
|
||||
.popover-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $es-spacing-xs;
|
||||
padding: $es-spacing-xs $es-spacing-md;
|
||||
border-bottom: 1px solid $es-border-color;
|
||||
background: $es-slate-50;
|
||||
|
||||
i.material-icons {
|
||||
font-size: 18px;
|
||||
color: $es-text-muted;
|
||||
}
|
||||
|
||||
.holiday-filter-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: $es-font-size-sm;
|
||||
color: $es-text-primary;
|
||||
outline: none;
|
||||
padding: $es-spacing-xs 0;
|
||||
|
||||
&::placeholder {
|
||||
color: $es-text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spin animation for loading icons
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap specificity overrides for chips toolbar form elements
|
||||
// PrestaShop admin uses #content .mpr-config-form... with high specificity
|
||||
// We need to match or exceed that specificity
|
||||
|
||||
@@ -208,6 +208,29 @@
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&.result-flag {
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
background: transparent;
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.flag-fallback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #e8eaed 0%, #dadce0 100%);
|
||||
font-size: 14px;
|
||||
color: #5f6368;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
|
||||
@@ -306,3 +306,64 @@
|
||||
border-radius: $es-radius-lg;
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule toggle row (form-content layout)
|
||||
.schedule-toggle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: $es-slate-100;
|
||||
border: 1px solid $es-border-color;
|
||||
border-radius: $es-radius-lg;
|
||||
|
||||
.schedule-toggle-switch {
|
||||
padding: $es-spacing-sm $es-spacing-md;
|
||||
}
|
||||
|
||||
.schedule-toggle-actions {
|
||||
padding: $es-spacing-sm $es-spacing-md;
|
||||
border-left: 1px solid $es-border-color;
|
||||
cursor: pointer;
|
||||
transition: background-color $es-transition-fast;
|
||||
|
||||
&:hover {
|
||||
background: $es-slate-200;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
color: $es-slate-400;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule summary badges (read-only indicators in header)
|
||||
.schedule-summary-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-left: auto;
|
||||
padding: 0 $es-spacing-sm;
|
||||
}
|
||||
|
||||
.schedule-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: $es-slate-200;
|
||||
color: $es-slate-600;
|
||||
font-size: $es-font-size-xs;
|
||||
font-weight: $es-font-weight-medium;
|
||||
border-radius: $es-radius-full;
|
||||
white-space: nowrap;
|
||||
|
||||
.material-icons {
|
||||
font-size: 14px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// Section hint after embedded entity selector - add margin
|
||||
.schedule-holidays .section-hint {
|
||||
margin-top: $es-spacing-md;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
}
|
||||
|
||||
// Full-width form group override using :has()
|
||||
.form-group:has(.entity-selector-trait),
|
||||
.form-group:has(.target-conditions-trait),
|
||||
.form-group:has(.condition-trait) {
|
||||
// Excludes .layout-form-group which uses standard PrestaShop form layout
|
||||
.form-group:has(.entity-selector-trait:not(.layout-form-group)),
|
||||
.form-group:has(.target-conditions-trait:not(.layout-form-group)),
|
||||
.form-group:has(.condition-trait:not(.layout-form-group)) {
|
||||
display: block;
|
||||
|
||||
> .control-label {
|
||||
@@ -55,6 +56,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// SAFEGUARD: Force label visibility for form-group layout widgets
|
||||
// This overrides any conflicting rules (including fallback class rules)
|
||||
// when the widget has layout-form-group class indicating standard form integration
|
||||
.form-group:has(.layout-form-group) > .control-label {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
// Dropdown overflow fix
|
||||
// When dropdown is open, parent containers must allow overflow
|
||||
.panel:has(.target-search-dropdown.show),
|
||||
@@ -79,3 +87,75 @@
|
||||
.target-search-wrapper:has(.target-search-dropdown.show) {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Embedded Layout
|
||||
// =============================================================================
|
||||
// Use .layout-embedded for entity selectors nested inside other components
|
||||
// Removes outer wrapper styling to avoid redundant borders/backgrounds
|
||||
|
||||
.target-conditions-trait.layout-embedded,
|
||||
.entity-selector-trait.layout-embedded {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
||||
// Remove padding from groups container when embedded
|
||||
.groups-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Remove block body padding
|
||||
.block-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Remove block footer border when embedded
|
||||
.block-footer {
|
||||
border-top: none;
|
||||
padding: $es-spacing-sm 0 0;
|
||||
}
|
||||
|
||||
// Simplify selection group when embedded - single thin border only
|
||||
.selection-group {
|
||||
background: $es-white;
|
||||
border: 1px solid $es-slate-200;
|
||||
border-radius: $es-radius-md;
|
||||
|
||||
// Lighter group header in embedded mode
|
||||
.group-header {
|
||||
background: $es-slate-50;
|
||||
border-bottom-color: $es-slate-200;
|
||||
padding: $es-spacing-xs $es-spacing-sm;
|
||||
border-radius: $es-radius-md $es-radius-md 0 0;
|
||||
}
|
||||
|
||||
// Reduce group body padding (slightly more than $es-spacing-sm for readability)
|
||||
.group-body {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
// Reduce group-include section padding
|
||||
.group-include {
|
||||
padding: $es-spacing-xs;
|
||||
margin-bottom: $es-spacing-sm;
|
||||
}
|
||||
|
||||
// Smaller modifiers section
|
||||
.group-modifiers {
|
||||
padding: $es-spacing-xs $es-spacing-sm;
|
||||
margin: $es-spacing-sm (-$es-spacing-sm) (-$es-spacing-sm);
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state - smaller padding
|
||||
.groups-empty-state {
|
||||
padding: $es-spacing-md;
|
||||
}
|
||||
|
||||
// Smaller add group button
|
||||
.btn-add-group {
|
||||
padding: 0.375rem 0.625rem;
|
||||
font-size: $es-font-size-xs;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user