Add schedule preview dropdown styles and various improvements
- Add .schedule-preview-dropdown and .schedule-preview-item CSS classes - Add .btn-schedule-preview badge styling - Add preview functionality for entity list views - Improve modal and dropdown styling - Various JS and SCSS enhancements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -281,6 +281,15 @@ trait EntitySelector
|
||||
case 'previewCategoryPages':
|
||||
$this->ajaxPreviewCategoryPages();
|
||||
return true;
|
||||
case 'getHolidaysPreview':
|
||||
$this->ajaxGetHolidaysPreview();
|
||||
return true;
|
||||
case 'getHolidaysForCountries':
|
||||
$this->ajaxGetHolidaysForCountries();
|
||||
return true;
|
||||
case 'getZonesForFilter':
|
||||
$this->ajaxGetZonesForFilter();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -652,6 +661,40 @@ trait EntitySelector
|
||||
|
||||
$finalCount = count($matchingIds);
|
||||
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => true,
|
||||
'include_count' => $includeCount,
|
||||
'exclude_count' => $excludeCount,
|
||||
'final_count' => $finalCount,
|
||||
]));
|
||||
} elseif ($blockType === 'countries') {
|
||||
// For countries, count selected countries (not holidays)
|
||||
$includeMethod = $groupData['include']['method'] ?? 'all';
|
||||
$includeValues = $groupData['include']['values'] ?? [];
|
||||
|
||||
// For 'specific' method, count is the number of selected countries
|
||||
// For 'all', we'd need to count all active countries
|
||||
if ($includeMethod === 'specific') {
|
||||
$includeCount = count($includeValues);
|
||||
} elseif ($includeMethod === 'all') {
|
||||
// Count all active countries
|
||||
$includeCount = (int) Db::getInstance()->getValue(
|
||||
'SELECT COUNT(*) FROM ' . _DB_PREFIX_ . 'country WHERE active = 1'
|
||||
);
|
||||
} else {
|
||||
$includeCount = 0;
|
||||
}
|
||||
|
||||
$excludeCount = 0;
|
||||
if (!empty($groupData['excludes']) && is_array($groupData['excludes'])) {
|
||||
foreach ($groupData['excludes'] as $exclude) {
|
||||
$excludeValues = $exclude['values'] ?? [];
|
||||
$excludeCount += count($excludeValues);
|
||||
}
|
||||
}
|
||||
|
||||
$finalCount = max(0, $includeCount - $excludeCount);
|
||||
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => true,
|
||||
'include_count' => $includeCount,
|
||||
@@ -1193,6 +1236,11 @@ trait EntitySelector
|
||||
$filterIsCustom = (bool) Tools::getValue('filter_is_custom', 0);
|
||||
$filterIndexable = (bool) Tools::getValue('filter_indexable', 0);
|
||||
|
||||
// Country-specific filters
|
||||
$filterHasHolidays = (bool) Tools::getValue('filter_has_holidays', 0);
|
||||
$filterContainsStates = (bool) Tools::getValue('filter_contains_states', 0);
|
||||
$filterZone = Tools::getValue('filter_zone', '');
|
||||
|
||||
$filters = [
|
||||
'attributes' => $filterAttributes ? json_decode($filterAttributes, true) : [],
|
||||
'features' => $filterFeatures ? json_decode($filterFeatures, true) : [],
|
||||
@@ -1224,6 +1272,10 @@ trait EntitySelector
|
||||
'is_color' => $filterIsColor,
|
||||
'is_custom' => $filterIsCustom,
|
||||
'indexable' => $filterIndexable,
|
||||
// Country-specific
|
||||
'has_holidays' => $filterHasHolidays,
|
||||
'contains_states' => $filterContainsStates,
|
||||
'zone' => $filterZone !== '' ? (int) $filterZone : null,
|
||||
// Sorting
|
||||
'sort_by' => $sortBy,
|
||||
'sort_dir' => $sortDir,
|
||||
@@ -1275,6 +1327,8 @@ trait EntitySelector
|
||||
protected function ajaxGetTargetEntitiesByIdsBulk()
|
||||
{
|
||||
$entitiesParam = Tools::getValue('entities', '');
|
||||
\PrestaShopLogger::addLog('[EntitySelector] ajaxGetTargetEntitiesByIdsBulk called, raw param: ' . substr($entitiesParam, 0, 500), 1);
|
||||
|
||||
if (is_string($entitiesParam)) {
|
||||
$entitiesParam = json_decode($entitiesParam, true);
|
||||
}
|
||||
@@ -1282,16 +1336,21 @@ trait EntitySelector
|
||||
$entitiesParam = [];
|
||||
}
|
||||
|
||||
\PrestaShopLogger::addLog('[EntitySelector] Parsed entities param: ' . json_encode($entitiesParam), 1);
|
||||
|
||||
$result = [];
|
||||
$searchEngine = $this->getEntitySearchEngine();
|
||||
|
||||
foreach ($entitiesParam as $entityType => $ids) {
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
\PrestaShopLogger::addLog('[EntitySelector] Skipping entityType=' . $entityType . ' - empty or not array', 1);
|
||||
continue;
|
||||
}
|
||||
// Deduplicate and sanitize IDs
|
||||
$ids = array_unique(array_map('intval', $ids));
|
||||
\PrestaShopLogger::addLog('[EntitySelector] Fetching entityType=' . $entityType . ' ids=' . implode(',', $ids), 1);
|
||||
$result[$entityType] = $searchEngine->getByIds($entityType, $ids);
|
||||
\PrestaShopLogger::addLog('[EntitySelector] Got ' . count($result[$entityType]) . ' results for ' . $entityType, 1);
|
||||
}
|
||||
|
||||
$this->ajaxDie(json_encode([
|
||||
@@ -3605,4 +3664,248 @@ trait EntitySelector
|
||||
'groups' => $groups ?: [],
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Get holidays preview for a country
|
||||
*/
|
||||
protected function ajaxGetHolidaysPreview()
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$idCountry = (int) Tools::getValue('id_country');
|
||||
|
||||
if (!$idCountry) {
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Country ID is required.',
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if PublicHoliday class exists
|
||||
if (!class_exists('MyPrestaRocks\\PublicHolidays\\PublicHoliday')) {
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Holiday system not available.',
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
$idLang = (int) $this->context->language->id;
|
||||
$currentYear = (int) date('Y');
|
||||
|
||||
// Get country info
|
||||
$country = new \Country($idCountry, $idLang);
|
||||
if (!\Validate::isLoadedObject($country)) {
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Country not found.',
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get holidays for current and next year
|
||||
$holidays = [];
|
||||
try {
|
||||
$holidaysThisYear = \MyPrestaRocks\PublicHolidays\PublicHoliday::getHolidaysForCountry(
|
||||
$idCountry,
|
||||
$currentYear,
|
||||
$idLang
|
||||
);
|
||||
$holidaysNextYear = \MyPrestaRocks\PublicHolidays\PublicHoliday::getHolidaysForCountry(
|
||||
$idCountry,
|
||||
$currentYear + 1,
|
||||
$idLang
|
||||
);
|
||||
|
||||
$allHolidays = array_merge($holidaysThisYear ?: [], $holidaysNextYear ?: []);
|
||||
|
||||
// Filter to show only future holidays and format
|
||||
$today = date('Y-m-d');
|
||||
foreach ($allHolidays as $holiday) {
|
||||
if ($holiday['holiday_date'] >= $today) {
|
||||
$holidays[] = [
|
||||
'date' => $holiday['holiday_date'],
|
||||
'date_formatted' => \Tools::displayDate($holiday['holiday_date'], null, false),
|
||||
'day_of_week' => date('l', strtotime($holiday['holiday_date'])),
|
||||
'name' => $holiday['display_name'] ?? $holiday['name'],
|
||||
'type' => $holiday['type'],
|
||||
'recurring' => (bool) $holiday['recurring'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by date
|
||||
usort($holidays, function ($a, $b) {
|
||||
return strcmp($a['date'], $b['date']);
|
||||
});
|
||||
|
||||
// Limit to next 20 holidays
|
||||
$holidays = array_slice($holidays, 0, 20);
|
||||
} catch (\Exception $e) {
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => true,
|
||||
'country' => [
|
||||
'id' => $idCountry,
|
||||
'name' => $country->name,
|
||||
'iso_code' => $country->iso_code,
|
||||
],
|
||||
'holidays' => $holidays,
|
||||
'total_count' => count($holidays),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Get holidays for multiple countries
|
||||
* Used for the condition-match-count badge and combined preview
|
||||
*/
|
||||
protected function ajaxGetHolidaysForCountries()
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$countryIds = Tools::getValue('country_ids');
|
||||
$countOnly = (bool) Tools::getValue('count_only', false);
|
||||
|
||||
// Parse country IDs (can be comma-separated string or array)
|
||||
if (is_string($countryIds)) {
|
||||
$countryIds = array_filter(array_map('intval', explode(',', $countryIds)));
|
||||
} elseif (is_array($countryIds)) {
|
||||
$countryIds = array_filter(array_map('intval', $countryIds));
|
||||
} else {
|
||||
$countryIds = [];
|
||||
}
|
||||
|
||||
if (empty($countryIds)) {
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => true,
|
||||
'holidays' => [],
|
||||
'total_count' => 0,
|
||||
'countries' => [],
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if PublicHoliday class exists
|
||||
if (!class_exists('MyPrestaRocks\\PublicHolidays\\PublicHoliday')) {
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Holiday system not available.',
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
$idLang = (int) $this->context->language->id;
|
||||
$currentYear = (int) date('Y');
|
||||
$today = date('Y-m-d');
|
||||
|
||||
$allHolidays = [];
|
||||
$countriesInfo = [];
|
||||
|
||||
try {
|
||||
foreach ($countryIds as $idCountry) {
|
||||
$country = new \Country($idCountry, $idLang);
|
||||
if (!\Validate::isLoadedObject($country)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$countriesInfo[$idCountry] = [
|
||||
'id' => $idCountry,
|
||||
'name' => $country->name,
|
||||
'iso_code' => $country->iso_code,
|
||||
];
|
||||
|
||||
// Get holidays for current and next year
|
||||
$holidaysThisYear = \MyPrestaRocks\PublicHolidays\PublicHoliday::getHolidaysForCountry(
|
||||
$idCountry,
|
||||
$currentYear,
|
||||
$idLang
|
||||
);
|
||||
$holidaysNextYear = \MyPrestaRocks\PublicHolidays\PublicHoliday::getHolidaysForCountry(
|
||||
$idCountry,
|
||||
$currentYear + 1,
|
||||
$idLang
|
||||
);
|
||||
|
||||
$countryHolidays = array_merge($holidaysThisYear ?: [], $holidaysNextYear ?: []);
|
||||
|
||||
// Filter to show only future holidays
|
||||
foreach ($countryHolidays as $holiday) {
|
||||
if ($holiday['holiday_date'] >= $today) {
|
||||
$allHolidays[] = [
|
||||
'date' => $holiday['holiday_date'],
|
||||
'date_formatted' => \Tools::displayDate($holiday['holiday_date'], null, false),
|
||||
'day_of_week' => date('l', strtotime($holiday['holiday_date'])),
|
||||
'name' => $holiday['display_name'] ?? $holiday['name'],
|
||||
'type' => $holiday['type'],
|
||||
'recurring' => (bool) $holiday['recurring'],
|
||||
'country_id' => $idCountry,
|
||||
'country_name' => $country->name,
|
||||
'country_iso' => $country->iso_code,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by date
|
||||
usort($allHolidays, function ($a, $b) {
|
||||
return strcmp($a['date'], $b['date']);
|
||||
});
|
||||
|
||||
$totalCount = count($allHolidays);
|
||||
|
||||
// If only count requested, return early
|
||||
if ($countOnly) {
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => true,
|
||||
'total_count' => $totalCount,
|
||||
'countries' => array_values($countriesInfo),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit to next 50 holidays for full preview
|
||||
$allHolidays = array_slice($allHolidays, 0, 50);
|
||||
} catch (\Exception $e) {
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => true,
|
||||
'holidays' => $allHolidays,
|
||||
'total_count' => $totalCount,
|
||||
'countries' => array_values($countriesInfo),
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Get zones for country filter dropdown
|
||||
*/
|
||||
protected function ajaxGetZonesForFilter()
|
||||
{
|
||||
$idLang = (int) $this->context->language->id;
|
||||
|
||||
$zones = \Zone::getZones(true);
|
||||
$result = [];
|
||||
|
||||
foreach ($zones as $zone) {
|
||||
$result[] = [
|
||||
'id' => (int) $zone['id_zone'],
|
||||
'name' => $zone['name'],
|
||||
];
|
||||
}
|
||||
|
||||
$this->ajaxDie(json_encode([
|
||||
'success' => true,
|
||||
'zones' => $result,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2082,9 +2082,8 @@ class EntitySearchEngine
|
||||
/**
|
||||
* Search countries
|
||||
*/
|
||||
public function searchTargetCountries($query, $idLang, $idShop, array $filters = [])
|
||||
public function searchTargetCountries($query, $idLang, $idShop, $limit = 20, $offset = 0, array $filters = [])
|
||||
{
|
||||
$limit = isset($filters['limit']) ? (int) $filters['limit'] : 20;
|
||||
|
||||
$sql = new DbQuery();
|
||||
$sql->select('DISTINCT c.id_country, cl.name, c.iso_code, c.active, c.id_zone, c.contains_states, c.need_zip_code');
|
||||
@@ -2112,6 +2111,29 @@ class EntitySearchEngine
|
||||
$sql->where('c.active = 1');
|
||||
}
|
||||
|
||||
// Zone filter
|
||||
if (!empty($filters['zone'])) {
|
||||
$sql->where('c.id_zone = ' . (int) $filters['zone']);
|
||||
}
|
||||
|
||||
// Contains states filter
|
||||
if (!empty($filters['contains_states'])) {
|
||||
$sql->where('c.contains_states = 1');
|
||||
}
|
||||
|
||||
// Has holidays filter - check if PublicHoliday class exists and country has holidays
|
||||
if (!empty($filters['has_holidays'])) {
|
||||
if (class_exists('MyPrestaRocks\\PublicHolidays\\PublicHoliday')) {
|
||||
$countriesWithHolidays = \MyPrestaRocks\PublicHolidays\PublicHoliday::getCountriesWithHolidays();
|
||||
if (!empty($countriesWithHolidays)) {
|
||||
$sql->where('c.id_country IN (' . implode(',', array_map('intval', $countriesWithHolidays)) . ')');
|
||||
} else {
|
||||
// No countries with holidays, return empty
|
||||
$sql->where('1 = 0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting
|
||||
$countrySortMap = [
|
||||
'name' => 'cl.name',
|
||||
@@ -2120,7 +2142,7 @@ class EntitySearchEngine
|
||||
'iso_code' => 'c.iso_code',
|
||||
];
|
||||
$sql->orderBy($this->buildOrderBy('countries', $filters, $countrySortMap));
|
||||
$sql->limit($limit);
|
||||
$sql->limit($limit, $offset);
|
||||
|
||||
$results = Db::getInstance()->executeS($sql);
|
||||
|
||||
@@ -2171,6 +2193,28 @@ class EntitySearchEngine
|
||||
$sql->where('c.active = 1');
|
||||
}
|
||||
|
||||
// Zone filter
|
||||
if (!empty($filters['zone'])) {
|
||||
$sql->where('c.id_zone = ' . (int) $filters['zone']);
|
||||
}
|
||||
|
||||
// Contains states filter
|
||||
if (!empty($filters['contains_states'])) {
|
||||
$sql->where('c.contains_states = 1');
|
||||
}
|
||||
|
||||
// Has holidays filter
|
||||
if (!empty($filters['has_holidays'])) {
|
||||
if (class_exists('MyPrestaRocks\\PublicHolidays\\PublicHoliday')) {
|
||||
$countriesWithHolidays = \MyPrestaRocks\PublicHolidays\PublicHoliday::getCountriesWithHolidays();
|
||||
if (!empty($countriesWithHolidays)) {
|
||||
$sql->where('c.id_country IN (' . implode(',', array_map('intval', $countriesWithHolidays)) . ')');
|
||||
} else {
|
||||
$sql->where('1 = 0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (int) Db::getInstance()->getValue($sql);
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ class EntitySelectorRenderer
|
||||
'required_message' => '',
|
||||
'show_modifiers' => true,
|
||||
'show_all_toggle' => false,
|
||||
'layout' => 'standalone', // 'standalone' (default), 'form-group' (full form-group), or 'form-content' (just content for existing form-group)
|
||||
];
|
||||
|
||||
$config = array_merge($defaults, $config);
|
||||
@@ -195,7 +196,21 @@ class EntitySelectorRenderer
|
||||
$jsConfigJson = $this->escapeAttr(json_encode($jsConfig));
|
||||
|
||||
$requiredClass = !empty($config['required']) ? ' trait-required' : '';
|
||||
$html = '<div class="condition-trait target-conditions-trait' . $collapsedClass . $singleModeClass . $requiredClass . '"';
|
||||
$layout = $config['layout'] ?? 'standalone';
|
||||
$layoutClass = $layout === 'form-group' ? ' layout-form-group' : '';
|
||||
|
||||
// Form-group layout: wrap in PrestaShop form structure
|
||||
if ($layout === 'form-group') {
|
||||
return $this->renderFormGroupLayout($config, $enabledBlocks, $activeBlock, $savedData, $globalMode, $jsConfigJson);
|
||||
}
|
||||
|
||||
// Form-content layout: just content for existing form-group (no wrapper, no extra label)
|
||||
if ($layout === 'form-content') {
|
||||
return $this->renderFormContentLayout($config, $enabledBlocks, $activeBlock, $savedData, $globalMode, $jsConfigJson);
|
||||
}
|
||||
|
||||
// Standalone layout (default)
|
||||
$html = '<div class="condition-trait target-conditions-trait' . $collapsedClass . $singleModeClass . $requiredClass . $layoutClass . '"';
|
||||
$html .= ' data-entity-selector-id="' . $this->escapeAttr($config['id']) . '"';
|
||||
$html .= ' data-mode="' . $this->escapeAttr($globalMode) . '"';
|
||||
if (!empty($config['required'])) {
|
||||
@@ -232,6 +247,152 @@ class EntitySelectorRenderer
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render form-group layout (matches standard PrestaShop form fields)
|
||||
*
|
||||
* @param array $config Configuration
|
||||
* @param array $enabledBlocks Enabled blocks
|
||||
* @param string $activeBlock Active block type
|
||||
* @param array $savedData Saved data
|
||||
* @param string $globalMode Selection mode
|
||||
* @param string $jsConfigJson JSON-encoded JS config
|
||||
* @return string HTML
|
||||
*/
|
||||
protected function renderFormGroupLayout($config, $enabledBlocks, $activeBlock, $savedData, $globalMode, $jsConfigJson)
|
||||
{
|
||||
$requiredClass = !empty($config['required']) ? ' trait-required' : '';
|
||||
$singleModeClass = $globalMode === 'single' ? ' single-mode' : '';
|
||||
|
||||
$html = '<div class="form-group entity-selector-form-group">';
|
||||
|
||||
// Label column (col-lg-3) - standard PrestaShop form field markup
|
||||
$html .= '<label class="control-label col-lg-3">';
|
||||
$html .= '<span class="mpr-label-text">' . $this->escapeAttr($config['title']) . '</span>';
|
||||
if (!empty($config['required'])) {
|
||||
$html .= ' <span class="text-danger">*</span>';
|
||||
}
|
||||
$html .= '</label>';
|
||||
|
||||
// Content column (col-lg-9)
|
||||
$html .= '<div class="col-lg-9">';
|
||||
|
||||
// Entity selector container (without traditional header)
|
||||
$html .= '<div class="condition-trait target-conditions-trait layout-form-group' . $singleModeClass . $requiredClass . '"';
|
||||
$html .= ' data-entity-selector-id="' . $this->escapeAttr($config['id']) . '"';
|
||||
$html .= ' data-mode="' . $this->escapeAttr($globalMode) . '"';
|
||||
if (!empty($config['required'])) {
|
||||
$html .= ' data-required="1"';
|
||||
$requiredMsg = !empty($config['required_message'])
|
||||
? $config['required_message']
|
||||
: $this->trans('Please select at least one item');
|
||||
$html .= ' data-required-message="' . $this->escapeAttr($requiredMsg) . '"';
|
||||
}
|
||||
$html .= ' data-config=\'' . $jsConfigJson . '\'>';
|
||||
|
||||
// Body (always visible in form-group layout)
|
||||
$html .= '<div class="condition-trait-body">';
|
||||
|
||||
// Tabs row with expand button
|
||||
$html .= '<div class="entity-selector-tabs-row">';
|
||||
$html .= $this->renderTabs($enabledBlocks, $activeBlock, $savedData, $config);
|
||||
|
||||
// Expand/collapse button for groups (multi mode only)
|
||||
if ($globalMode !== 'single') {
|
||||
$html .= '<div class="entity-selector-actions">';
|
||||
$html .= '<button type="button" class="btn-toggle-groups" data-state="collapsed" title="' . $this->trans('Expand all groups') . '">';
|
||||
$html .= '<i class="material-icons" style="font-size:18px;">unfold_more</i>';
|
||||
$html .= '</button>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
$html .= '</div>'; // End tabs-row
|
||||
|
||||
// Blocks
|
||||
$html .= $this->renderBlocks($enabledBlocks, $activeBlock, $savedData, $config, $globalMode);
|
||||
|
||||
// Tips box
|
||||
$html .= $this->renderTipsBox();
|
||||
|
||||
// Hidden input
|
||||
$html .= '<input type="hidden" name="' . $this->escapeAttr($config['name']) . '" value="' . $this->escapeAttr(json_encode($savedData)) . '">';
|
||||
|
||||
$html .= '</div>'; // End condition-trait-body
|
||||
$html .= '</div>'; // End target-conditions-trait
|
||||
|
||||
// Subtitle as help text
|
||||
if (!empty($config['subtitle'])) {
|
||||
$html .= '<small class="form-text text-muted">' . $this->escapeAttr($config['subtitle']) . '</small>';
|
||||
}
|
||||
|
||||
$html .= '</div>'; // End col-lg-9
|
||||
$html .= '</div>'; // End form-group
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render form-content layout (just content, no form-group wrapper)
|
||||
* Use this when entity selector is inside an existing PrestaShop form type='html' field
|
||||
*
|
||||
* @param array $config Configuration
|
||||
* @param array $enabledBlocks Enabled blocks
|
||||
* @param string $activeBlock Active block type
|
||||
* @param array $savedData Saved data
|
||||
* @param string $globalMode Selection mode
|
||||
* @param string $jsConfigJson JSON-encoded JS config
|
||||
* @return string HTML
|
||||
*/
|
||||
protected function renderFormContentLayout($config, $enabledBlocks, $activeBlock, $savedData, $globalMode, $jsConfigJson)
|
||||
{
|
||||
$requiredClass = !empty($config['required']) ? ' trait-required' : '';
|
||||
$singleModeClass = $globalMode === 'single' ? ' single-mode' : '';
|
||||
|
||||
// Check if blocks collapsed by default (default: true for form-content layout)
|
||||
$collapsed = isset($config['collapsed']) ? $config['collapsed'] : true;
|
||||
$collapsedClass = $collapsed ? ' blocks-collapsed' : '';
|
||||
|
||||
// Entity selector container (without form-group wrapper, without header)
|
||||
$html = '<div class="condition-trait target-conditions-trait layout-form-group' . $singleModeClass . $requiredClass . $collapsedClass . '"';
|
||||
$html .= ' data-entity-selector-id="' . $this->escapeAttr($config['id']) . '"';
|
||||
$html .= ' data-mode="' . $this->escapeAttr($globalMode) . '"';
|
||||
if (!empty($config['required'])) {
|
||||
$html .= ' data-required="1"';
|
||||
$requiredMsg = !empty($config['required_message'])
|
||||
? $config['required_message']
|
||||
: $this->trans('Please select at least one item');
|
||||
$html .= ' data-required-message="' . $this->escapeAttr($requiredMsg) . '"';
|
||||
}
|
||||
$html .= ' data-config=\'' . $jsConfigJson . '\'>';
|
||||
|
||||
// Tabs row (always visible) with collapse toggle
|
||||
$html .= '<div class="entity-selector-tabs-row">';
|
||||
$html .= $this->renderTabs($enabledBlocks, $activeBlock, $savedData, $config);
|
||||
|
||||
// Actions: expand/collapse toggle (entire area is clickable)
|
||||
$html .= '<div class="entity-selector-actions btn-toggle-blocks" title="' . $this->trans('Show/hide details') . '">';
|
||||
$html .= '<i class="material-icons">' . ($collapsed ? 'expand_more' : 'expand_less') . '</i>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>'; // End tabs-row
|
||||
|
||||
// Blocks content (hidden when collapsed)
|
||||
$blocksStyle = $collapsed ? ' style="display:none;"' : '';
|
||||
$html .= '<div class="entity-selector-blocks-content"' . $blocksStyle . '>';
|
||||
|
||||
// Blocks
|
||||
$html .= $this->renderBlocks($enabledBlocks, $activeBlock, $savedData, $config, $globalMode);
|
||||
|
||||
// Tips box
|
||||
$html .= $this->renderTipsBox();
|
||||
|
||||
$html .= '</div>'; // End blocks-content
|
||||
|
||||
// Hidden input (outside collapsed area)
|
||||
$html .= '<input type="hidden" name="' . $this->escapeAttr($config['name']) . '" value="' . $this->escapeAttr(json_encode($savedData)) . '">';
|
||||
|
||||
$html .= '</div>'; // End target-conditions-trait
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render widget header
|
||||
*
|
||||
@@ -544,8 +705,8 @@ class EntitySelectorRenderer
|
||||
$html .= '<button type="button" class="btn-add-group">';
|
||||
$html .= '<i class="icon-plus"></i> ' . $this->trans('Add selection group');
|
||||
$html .= '</button>';
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($groupsTooltip) . '">';
|
||||
$html .= '<i class="material-icons" style="font-size:16px;color:#5bc0de;cursor:pointer;vertical-align:middle">info</i>';
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($groupsTooltip) . '" data-toggle="none">';
|
||||
$html .= '<i class="material-icons">info</i>';
|
||||
$html .= '</span>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
@@ -606,8 +767,8 @@ class EntitySelectorRenderer
|
||||
$html .= '<i class="icon-sliders"></i> ';
|
||||
$html .= '<span class="result-modifiers-title">' . $this->trans('Result modifiers') . '</span>';
|
||||
$html .= '<span class="result-modifiers-hint">' . $this->trans('(optional)') . '</span>';
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($modifiersTooltip) . '">';
|
||||
$html .= '<i class="material-icons" style="font-size:16px;color:#5bc0de;cursor:pointer;vertical-align:middle">info</i>';
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($modifiersTooltip) . '" data-toggle="none">';
|
||||
$html .= '<i class="material-icons">info</i>';
|
||||
$html .= '</span>';
|
||||
$html .= '</div>';
|
||||
|
||||
@@ -773,8 +934,8 @@ class EntitySelectorRenderer
|
||||
$methodHelp = $this->getMethodHelpTooltip($includeMethod, $blockType);
|
||||
$html .= '<span class="method-info-placeholder">';
|
||||
if (!empty($methodHelp)) {
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($methodHelp) . '">';
|
||||
$html .= '<i class="material-icons" style="font-size:16px;color:#5bc0de;cursor:pointer;vertical-align:middle">info</i>';
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($methodHelp) . '" data-toggle="none">';
|
||||
$html .= '<i class="material-icons">info</i>';
|
||||
$html .= '</span>';
|
||||
}
|
||||
$html .= '</span>';
|
||||
@@ -990,8 +1151,8 @@ class EntitySelectorRenderer
|
||||
$methodHelp = $this->getMethodHelpTooltip($excludeMethod, $blockType);
|
||||
$html .= '<span class="method-info-placeholder">';
|
||||
if (!empty($methodHelp)) {
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($methodHelp) . '">';
|
||||
$html .= '<i class="material-icons" style="font-size:16px;color:#5bc0de;cursor:pointer;vertical-align:middle">info</i>';
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($methodHelp) . '" data-toggle="none">';
|
||||
$html .= '<i class="material-icons">info</i>';
|
||||
$html .= '</span>';
|
||||
}
|
||||
$html .= '</span>';
|
||||
|
||||
@@ -209,6 +209,18 @@ trait ScheduleConditions
|
||||
$html .= '<span class="trait-title">' . htmlspecialchars($config['title']) . '</span>';
|
||||
$html .= '<span class="trait-subtitle">' . htmlspecialchars($config['subtitle']) . '</span>';
|
||||
$html .= '</div>';
|
||||
// Schedule summary (shows current configuration at a glance)
|
||||
$scheduleDescData = [
|
||||
'enabled' => $scheduleEnabled,
|
||||
'datetime_start' => isset($savedData['datetime_start']) ? $savedData['datetime_start'] : '',
|
||||
'datetime_end' => isset($savedData['datetime_end']) ? $savedData['datetime_end'] : '',
|
||||
'weekly_schedule' => $weeklySchedule,
|
||||
'exclude_holidays' => $excludeHolidays,
|
||||
'holiday_countries' => $holidayCountries,
|
||||
];
|
||||
$summaryText = $this->getScheduleDescription($scheduleDescData);
|
||||
$summaryVisible = $scheduleEnabled ? '' : ' style="display:none;"';
|
||||
$html .= '<span class="trait-summary"' . $summaryVisible . '>' . htmlspecialchars($summaryText) . '</span>';
|
||||
$html .= '</div>';
|
||||
// Expand/collapse all toggle - determine initial state based on section states
|
||||
$datetimeSectionCollapsed = empty($datetimeStart) && empty($datetimeEnd);
|
||||
@@ -788,7 +800,7 @@ trait ScheduleConditions
|
||||
|
||||
$jsConfigJson = htmlspecialchars(json_encode($jsConfig), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$html = '<div class="target-conditions-trait holiday-countries-target"';
|
||||
$html = '<div class="target-conditions-trait holiday-countries-target layout-form-group"';
|
||||
$html .= ' data-entity-selector-id="holiday-countries-target"';
|
||||
$html .= ' data-mode="multi"';
|
||||
$html .= ' data-config=\'' . $jsConfigJson . '\'>';
|
||||
@@ -843,8 +855,8 @@ trait ScheduleConditions
|
||||
$html .= '<button type="button" class="btn-add-group">';
|
||||
$html .= '<i class="icon-plus"></i> ' . $this->transScheduleConditions('Add selection group');
|
||||
$html .= '</button>';
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . htmlspecialchars($groupsTooltip) . '">';
|
||||
$html .= '<i class="material-icons" style="font-size:16px;color:#5bc0de;cursor:pointer;vertical-align:middle">info</i>';
|
||||
$html .= '<span class="mpr-info-wrapper" data-details="' . htmlspecialchars($groupsTooltip) . '" data-toggle="none">';
|
||||
$html .= '<i class="material-icons">info</i>';
|
||||
$html .= '</span>';
|
||||
$html .= '</div>';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user