chore: remove duplicated nested directories
Remove assets/assets/ and sources/sources/ artifact directories created by sync script symlink resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,184 +0,0 @@
|
|||||||
/**
|
|
||||||
* MPR Search Revolution - Universal Modal Styles
|
|
||||||
* All admin modals use .mpr-sr-admin-modal class
|
|
||||||
* Works on both Symfony pages (.show) and Legacy pages (.in)
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal.show,
|
|
||||||
.mpr-sr-admin-modal.in {
|
|
||||||
display: flex !important;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal.show .modal-dialog,
|
|
||||||
.mpr-sr-admin-modal.in .modal-dialog {
|
|
||||||
transform: none !important;
|
|
||||||
top: auto !important;
|
|
||||||
margin: 0 auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-dialog {
|
|
||||||
max-width: 480px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-content {
|
|
||||||
padding: 1.25rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-header,
|
|
||||||
.mpr-sr-admin-modal .modal-body,
|
|
||||||
.mpr-sr-admin-modal .modal-footer {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-header {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-header .modal-title {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-header .modal-title i {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-header .close,
|
|
||||||
.mpr-sr-admin-modal .modal-header .mpr-close-modal {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
padding: 5px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
line-height: 1;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-header .close:hover,
|
|
||||||
.mpr-sr-admin-modal .modal-header .mpr-close-modal:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-body {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-body .body-title {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #6b7280;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-body .modal-amount {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
color: #dc2626;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-body .tables-table {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-body .tables-table thead th {
|
|
||||||
background: #f8fafc;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.025em;
|
|
||||||
color: #64748b;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-body .tables-table tbody td {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-color: #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer {
|
|
||||||
display: flex;
|
|
||||||
padding-top: 1rem;
|
|
||||||
gap: 0.75rem;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.625rem 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 6px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn i {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .cancel-btn,
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn-secondary {
|
|
||||||
background: #f1f5f9;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .cancel-btn:hover,
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn-secondary:hover {
|
|
||||||
background: #e2e8f0;
|
|
||||||
color: #475569;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .confirm-btn,
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn-primary {
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
background: linear-gradient(135deg, #337ab7 0%, #286090 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .confirm-btn:hover:not(:disabled),
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn-primary:hover:not(:disabled) {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
background: linear-gradient(135deg, #286090 0%, #204d74 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .confirm-btn:disabled,
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn-primary:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn-danger {
|
|
||||||
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-sr-admin-modal .modal-footer .btn-danger:hover:not(:disabled) {
|
|
||||||
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,515 +0,0 @@
|
|||||||
/**
|
|
||||||
* MPR Express Checkout - Standardized Modal Helper
|
|
||||||
*
|
|
||||||
* Provides a consistent API for managing modals across the module.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* const modal = new MPRModal('my-modal-id');
|
|
||||||
* modal.show();
|
|
||||||
* modal.setHeader('success', 'icon-check', 'Operation Complete');
|
|
||||||
* modal.setBody('<p>Content here</p>');
|
|
||||||
* modal.setFooter([
|
|
||||||
* { type: 'cancel', label: 'Close' },
|
|
||||||
* { type: 'primary', label: 'Continue', icon: 'arrow-right', onClick: () => {} }
|
|
||||||
* ]);
|
|
||||||
*/
|
|
||||||
|
|
||||||
class MPRModal {
|
|
||||||
/**
|
|
||||||
* @param {string} modalId - The modal element ID (without #)
|
|
||||||
* @param {Object} options - Configuration options
|
|
||||||
* @param {Function} options.onShow - Callback when modal is shown
|
|
||||||
* @param {Function} options.onHide - Callback when modal is hidden
|
|
||||||
* @param {Function} options.onCancel - Callback when cancel/close is clicked
|
|
||||||
*/
|
|
||||||
constructor(modalId, options = {}) {
|
|
||||||
this.modalId = modalId;
|
|
||||||
this.$modal = $(`#${modalId}`);
|
|
||||||
this.$header = this.$modal.find('.mpr-modal-header');
|
|
||||||
this.$title = this.$modal.find('.mpr-modal-title');
|
|
||||||
this.$titleText = this.$modal.find('.mpr-modal-title-text');
|
|
||||||
this.$titleIcon = this.$modal.find('.mpr-modal-icon');
|
|
||||||
this.$body = this.$modal.find('.mpr-modal-body');
|
|
||||||
this.$footer = this.$modal.find('.mpr-modal-footer');
|
|
||||||
this.options = options;
|
|
||||||
this.currentView = null;
|
|
||||||
|
|
||||||
this._bindEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind modal events
|
|
||||||
*/
|
|
||||||
_bindEvents() {
|
|
||||||
this.$modal.on('shown.bs.modal', () => {
|
|
||||||
if (typeof this.options.onShow === 'function') {
|
|
||||||
this.options.onShow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$modal.on('hidden.bs.modal', () => {
|
|
||||||
if (typeof this.options.onHide === 'function') {
|
|
||||||
this.options.onHide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$modal.on('click', '[data-dismiss="modal"]', () => {
|
|
||||||
if (typeof this.options.onCancel === 'function') {
|
|
||||||
this.options.onCancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the modal
|
|
||||||
* @param {Object} options - Bootstrap modal options
|
|
||||||
*/
|
|
||||||
show(options = {}) {
|
|
||||||
if (this.$modal.length === 0) {
|
|
||||||
console.error('[MPRModal] Modal element not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$modal.modal({
|
|
||||||
backdrop: options.static ? 'static' : true,
|
|
||||||
keyboard: !options.static,
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide the modal
|
|
||||||
*/
|
|
||||||
hide() {
|
|
||||||
this.$modal.modal('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set header appearance
|
|
||||||
* @param {string} type - Header type: default, primary, success, warning, danger, dark
|
|
||||||
* @param {string} icon - Icon class without prefix (e.g., 'shield' for icon-shield)
|
|
||||||
* @param {string} title - Title text
|
|
||||||
*/
|
|
||||||
setHeader(type, icon, title) {
|
|
||||||
// Remove all header type classes
|
|
||||||
this.$header.removeClass(
|
|
||||||
'mpr-modal-header-default mpr-modal-header-primary mpr-modal-header-success ' +
|
|
||||||
'mpr-modal-header-warning mpr-modal-header-danger mpr-modal-header-dark'
|
|
||||||
);
|
|
||||||
this.$header.addClass(`mpr-modal-header-${type}`);
|
|
||||||
|
|
||||||
if (icon) {
|
|
||||||
if (this.$titleIcon.length) {
|
|
||||||
this.$titleIcon.attr('class', `mpr-icon icon-${icon} mpr-modal-icon`);
|
|
||||||
} else {
|
|
||||||
this.$title.prepend(`<i class="mpr-icon icon-${icon} mpr-modal-icon"></i>`);
|
|
||||||
this.$titleIcon = this.$modal.find('.mpr-modal-icon');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title !== undefined) {
|
|
||||||
this.$titleText.text(title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set only the header type/color
|
|
||||||
* @param {string} type - Header type: default, primary, success, warning, danger, dark
|
|
||||||
*/
|
|
||||||
setHeaderType(type) {
|
|
||||||
this.$header.removeClass(
|
|
||||||
'mpr-modal-header-default mpr-modal-header-primary mpr-modal-header-success ' +
|
|
||||||
'mpr-modal-header-warning mpr-modal-header-danger mpr-modal-header-dark'
|
|
||||||
);
|
|
||||||
this.$header.addClass(`mpr-modal-header-${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the header title
|
|
||||||
* @param {string} title - Title text
|
|
||||||
*/
|
|
||||||
setTitle(title) {
|
|
||||||
this.$titleText.text(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the header icon
|
|
||||||
* @param {string} icon - Icon class without prefix
|
|
||||||
*/
|
|
||||||
setIcon(icon) {
|
|
||||||
if (this.$titleIcon.length) {
|
|
||||||
this.$titleIcon.attr('class', `icon-${icon} mpr-modal-icon`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set modal size
|
|
||||||
* @param {string} size - Size: sm, md, lg, xl, fullwidth
|
|
||||||
*/
|
|
||||||
setSize(size) {
|
|
||||||
const $dialog = this.$modal.find('.modal-dialog');
|
|
||||||
$dialog.removeClass('modal-sm modal-lg modal-xl modal-fullwidth');
|
|
||||||
if (size === 'sm') {
|
|
||||||
$dialog.addClass('modal-sm');
|
|
||||||
} else if (size === 'lg') {
|
|
||||||
$dialog.addClass('modal-lg');
|
|
||||||
} else if (size === 'xl') {
|
|
||||||
$dialog.addClass('modal-xl');
|
|
||||||
} else if (size === 'fullwidth') {
|
|
||||||
$dialog.addClass('modal-fullwidth');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set body content
|
|
||||||
* @param {string} html - HTML content for the body
|
|
||||||
*/
|
|
||||||
setBody(html) {
|
|
||||||
this.$body.html(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append content to body
|
|
||||||
* @param {string} html - HTML content to append
|
|
||||||
*/
|
|
||||||
appendBody(html) {
|
|
||||||
this.$body.append(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set footer buttons
|
|
||||||
* @param {Array} buttons - Array of button configurations
|
|
||||||
* Each button: { type, label, icon, id, onClick, disabled, className, size }
|
|
||||||
* type: 'cancel', 'primary', 'success', 'warning', 'danger', 'default', 'dark',
|
|
||||||
* 'outline-primary', 'outline-danger', 'ghost'
|
|
||||||
* size: 'sm', 'lg' (optional)
|
|
||||||
*/
|
|
||||||
setFooter(buttons) {
|
|
||||||
this.$footer.empty();
|
|
||||||
|
|
||||||
buttons.forEach(btn => {
|
|
||||||
const btnType = btn.type === 'cancel' ? 'default' : btn.type;
|
|
||||||
let btnClass = `mpr-btn mpr-btn-${btnType}`;
|
|
||||||
|
|
||||||
if (btn.size) {
|
|
||||||
btnClass += ` mpr-btn-${btn.size}`;
|
|
||||||
}
|
|
||||||
if (btn.className) {
|
|
||||||
btnClass += ` ${btn.className}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $btn = $('<button>', {
|
|
||||||
type: 'button',
|
|
||||||
class: btnClass,
|
|
||||||
id: btn.id || undefined,
|
|
||||||
disabled: btn.disabled || false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (btn.type === 'cancel' || btn.dismiss) {
|
|
||||||
$btn.attr('data-dismiss', 'modal');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add icon - cancel buttons get 'close' icon by default if no icon specified
|
|
||||||
const iconName = btn.icon || (btn.type === 'cancel' ? 'close' : null);
|
|
||||||
if (iconName) {
|
|
||||||
$btn.append(`<i class="mpr-icon icon-${iconName}"></i> `);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support HTML in labels (for inline icons) or plain text
|
|
||||||
if (btn.html) {
|
|
||||||
$btn.append(btn.label);
|
|
||||||
} else {
|
|
||||||
$btn.append(document.createTextNode(btn.label));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof btn.onClick === 'function') {
|
|
||||||
$btn.on('click', btn.onClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$footer.append($btn);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set button loading state
|
|
||||||
* @param {string} buttonId - Button ID
|
|
||||||
* @param {boolean} loading - Loading state
|
|
||||||
*/
|
|
||||||
setButtonLoading(buttonId, loading) {
|
|
||||||
const $btn = $(`#${buttonId}`);
|
|
||||||
if (loading) {
|
|
||||||
$btn.addClass('mpr-btn-loading').prop('disabled', true);
|
|
||||||
} else {
|
|
||||||
$btn.removeClass('mpr-btn-loading').prop('disabled', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show footer
|
|
||||||
*/
|
|
||||||
showFooter() {
|
|
||||||
this.$footer.removeClass('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide footer
|
|
||||||
*/
|
|
||||||
hideFooter() {
|
|
||||||
this.$footer.addClass('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable/disable a footer button by ID
|
|
||||||
* @param {string} buttonId - Button ID
|
|
||||||
* @param {boolean} enabled - Enable or disable
|
|
||||||
*/
|
|
||||||
setButtonEnabled(buttonId, enabled) {
|
|
||||||
$(`#${buttonId}`).prop('disabled', !enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update button label
|
|
||||||
* @param {string} buttonId - Button ID
|
|
||||||
* @param {string} label - New label
|
|
||||||
* @param {string} icon - Optional new icon
|
|
||||||
*/
|
|
||||||
setButtonLabel(buttonId, label, icon = null) {
|
|
||||||
const $btn = $(`#${buttonId}`);
|
|
||||||
$btn.empty();
|
|
||||||
if (icon) {
|
|
||||||
$btn.append(`<i class="mpr-icon icon-${icon}"></i> `);
|
|
||||||
}
|
|
||||||
$btn.append(document.createTextNode(label));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch between views (for multi-step modals)
|
|
||||||
* Views should have class 'mpr-modal-view' and a data-view attribute
|
|
||||||
* @param {string} viewName - The view to show
|
|
||||||
*/
|
|
||||||
showView(viewName) {
|
|
||||||
this.$body.find('.mpr-modal-view').removeClass('active');
|
|
||||||
this.$body.find(`[data-view="${viewName}"]`).addClass('active');
|
|
||||||
this.currentView = viewName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current view name
|
|
||||||
* @returns {string|null}
|
|
||||||
*/
|
|
||||||
getCurrentView() {
|
|
||||||
return this.currentView;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and show a simple confirmation modal
|
|
||||||
* @param {Object} config - Configuration
|
|
||||||
* @param {string} config.type - Header type
|
|
||||||
* @param {string} config.icon - Header icon
|
|
||||||
* @param {string} config.title - Title
|
|
||||||
* @param {string} config.message - Body message (can be HTML)
|
|
||||||
* @param {string} config.confirmLabel - Confirm button label
|
|
||||||
* @param {string} config.confirmType - Confirm button type (primary, danger, etc.)
|
|
||||||
* @param {string} config.cancelLabel - Cancel button label
|
|
||||||
* @param {Function} config.onConfirm - Confirm callback
|
|
||||||
* @param {Function} config.onCancel - Cancel callback
|
|
||||||
*/
|
|
||||||
confirm(config) {
|
|
||||||
this.setHeader(
|
|
||||||
config.type || 'primary',
|
|
||||||
config.icon || 'question',
|
|
||||||
config.title || 'Confirm'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setBody(`
|
|
||||||
<div class="mpr-modal-center">
|
|
||||||
<p>${config.message}</p>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
|
|
||||||
this.setFooter([
|
|
||||||
{
|
|
||||||
type: 'cancel',
|
|
||||||
label: config.cancelLabel || 'Cancel',
|
|
||||||
onClick: config.onCancel
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: config.confirmType || 'primary',
|
|
||||||
label: config.confirmLabel || 'Confirm',
|
|
||||||
icon: config.confirmIcon,
|
|
||||||
onClick: () => {
|
|
||||||
if (typeof config.onConfirm === 'function') {
|
|
||||||
config.onConfirm();
|
|
||||||
}
|
|
||||||
if (config.autoClose !== false) {
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.show({ static: config.static || false });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a progress state
|
|
||||||
* @param {Object} config - Configuration
|
|
||||||
* @param {string} config.title - Progress title
|
|
||||||
* @param {string} config.subtitle - Progress subtitle
|
|
||||||
* @param {number} config.percent - Initial percentage (0-100)
|
|
||||||
*/
|
|
||||||
showProgress(config = {}) {
|
|
||||||
const title = config.title || 'Processing...';
|
|
||||||
const subtitle = config.subtitle || 'Please wait';
|
|
||||||
const percent = config.percent || 0;
|
|
||||||
|
|
||||||
this.setBody(`
|
|
||||||
<div class="mpr-modal-progress">
|
|
||||||
<i class="icon-refresh mpr-modal-progress-icon"></i>
|
|
||||||
<div class="mpr-modal-progress-title">${title}</div>
|
|
||||||
<div class="mpr-modal-progress-subtitle">${subtitle}</div>
|
|
||||||
<div class="mpr-modal-progress-bar-container">
|
|
||||||
<div class="mpr-modal-progress-bar" style="width: ${percent}%"></div>
|
|
||||||
</div>
|
|
||||||
<div class="mpr-modal-progress-percent">${percent}%</div>
|
|
||||||
<div class="mpr-modal-progress-current"></div>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
|
|
||||||
this.hideFooter();
|
|
||||||
this.setHeaderType('primary');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update progress bar
|
|
||||||
* @param {number} percent - Percentage (0-100)
|
|
||||||
* @param {string} currentItem - Current item being processed
|
|
||||||
*/
|
|
||||||
updateProgress(percent, currentItem = '') {
|
|
||||||
this.$body.find('.mpr-modal-progress-bar').css('width', `${percent}%`);
|
|
||||||
this.$body.find('.mpr-modal-progress-percent').text(`${Math.round(percent)}%`);
|
|
||||||
if (currentItem) {
|
|
||||||
this.$body.find('.mpr-modal-progress-current').text(currentItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a result state
|
|
||||||
* @param {Object} config - Configuration
|
|
||||||
* @param {string} config.type - Result type: success, warning, danger, info
|
|
||||||
* @param {string} config.icon - Icon (defaults based on type)
|
|
||||||
* @param {string} config.title - Result title
|
|
||||||
* @param {string} config.message - Result message
|
|
||||||
* @param {string} config.closeLabel - Close button label
|
|
||||||
* @param {Function} config.onClose - Close callback
|
|
||||||
*/
|
|
||||||
showResult(config) {
|
|
||||||
const iconMap = {
|
|
||||||
success: 'check-circle',
|
|
||||||
warning: 'warning',
|
|
||||||
danger: 'times-circle',
|
|
||||||
info: 'info-circle'
|
|
||||||
};
|
|
||||||
|
|
||||||
const icon = config.icon || iconMap[config.type] || 'info-circle';
|
|
||||||
|
|
||||||
this.setHeaderType(config.type === 'info' ? 'primary' : config.type);
|
|
||||||
|
|
||||||
this.setBody(`
|
|
||||||
<div class="mpr-modal-result">
|
|
||||||
<i class="icon-${icon} mpr-modal-result-icon result-${config.type}"></i>
|
|
||||||
<div class="mpr-modal-result-title">${config.title}</div>
|
|
||||||
<div class="mpr-modal-result-message">${config.message}</div>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
|
|
||||||
this.setFooter([
|
|
||||||
{
|
|
||||||
type: 'primary',
|
|
||||||
label: config.closeLabel || 'Close',
|
|
||||||
onClick: () => {
|
|
||||||
if (typeof config.onClose === 'function') {
|
|
||||||
config.onClose();
|
|
||||||
}
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.showFooter();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lock modal (prevent closing)
|
|
||||||
*/
|
|
||||||
lock() {
|
|
||||||
this.$modal.data('bs.modal').options.backdrop = 'static';
|
|
||||||
this.$modal.data('bs.modal').options.keyboard = false;
|
|
||||||
this.$modal.find('.mpr-modal-close').hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unlock modal (allow closing)
|
|
||||||
*/
|
|
||||||
unlock() {
|
|
||||||
this.$modal.data('bs.modal').options.backdrop = true;
|
|
||||||
this.$modal.data('bs.modal').options.keyboard = true;
|
|
||||||
this.$modal.find('.mpr-modal-close').show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy the modal instance
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
this.$modal.modal('dispose');
|
|
||||||
this.$modal.off();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory function to create modals dynamically
|
|
||||||
* Creates the modal HTML and appends it to the body
|
|
||||||
*
|
|
||||||
* @param {Object} config - Modal configuration
|
|
||||||
* @param {string} config.id - Modal ID
|
|
||||||
* @param {string} config.size - Modal size: sm, md, lg, xl
|
|
||||||
* @param {boolean} config.static - Static backdrop
|
|
||||||
* @returns {MPRModal}
|
|
||||||
*/
|
|
||||||
MPRModal.create = function(config) {
|
|
||||||
const id = config.id || 'mpr-modal-' + Date.now();
|
|
||||||
const sizeClass = config.size === 'sm' ? 'modal-sm' :
|
|
||||||
config.size === 'lg' ? 'modal-lg' :
|
|
||||||
config.size === 'xl' ? 'modal-xl' :
|
|
||||||
config.size === 'fullwidth' ? 'modal-fullwidth' : '';
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div class="modal fade mpr-modal" id="${id}" tabindex="-1" role="dialog"
|
|
||||||
${config.static ? 'data-backdrop="static" data-keyboard="false"' : ''}>
|
|
||||||
<div class="modal-dialog ${sizeClass}" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header mpr-modal-header mpr-modal-header-primary">
|
|
||||||
<h5 class="modal-title mpr-modal-title">
|
|
||||||
<i class="mpr-modal-icon"></i>
|
|
||||||
<span class="mpr-modal-title-text"></span>
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="close mpr-modal-close" data-dismiss="modal">
|
|
||||||
<span>×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body mpr-modal-body"></div>
|
|
||||||
<div class="modal-footer mpr-modal-footer"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
$('body').append(html);
|
|
||||||
|
|
||||||
return new MPRModal(id, config);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Export for module systems if available
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports = MPRModal;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// Placeholder - _core.js
|
|
||||||
|
|
||||||
// Placeholder - _timeline.js
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Placeholder - _core.js
|
|
||||||
|
|
||||||
// Placeholder - _timeline.js
|
|
||||||
|
|
||||||
//# sourceMappingURL=schedule-conditions.min.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"schedule-conditions.min.js","sourcesContent":[]}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); header("Location: ../"); exit;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,307 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Core Module
|
|
||||||
* Factory, initialization, state management
|
|
||||||
* @partial _core.js
|
|
||||||
*
|
|
||||||
* IMPORTANT: This file must be loaded LAST in the concatenation order
|
|
||||||
* as it combines all mixins from other partials.
|
|
||||||
*
|
|
||||||
* EXTRACTION SOURCE: assets/js/admin/entity-selector.js
|
|
||||||
* Lines: 15-55 (createTargetConditionsInstance, state variables)
|
|
||||||
* 56-110 (init method)
|
|
||||||
* 108-132 (observeNewSelects)
|
|
||||||
* 7889-7951 (Factory object, window export, document ready)
|
|
||||||
*
|
|
||||||
* Contains:
|
|
||||||
* - createTargetConditionsInstance() - Factory function
|
|
||||||
* - State variable initialization
|
|
||||||
* - init() - Main initialization method
|
|
||||||
* - observeNewSelects() - MutationObserver for dynamic selects
|
|
||||||
* - loadExistingSelections() - Restore saved state
|
|
||||||
* - TargetConditions factory object
|
|
||||||
* - window.TargetConditions export
|
|
||||||
* - Document ready auto-initialization
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new TargetConditions instance
|
|
||||||
* Each instance is independent and manages its own wrapper/state
|
|
||||||
*/
|
|
||||||
function createTargetConditionsInstance() {
|
|
||||||
// Base instance object with state variables
|
|
||||||
var instance = {
|
|
||||||
config: {},
|
|
||||||
$wrapper: null,
|
|
||||||
$dropdown: null,
|
|
||||||
activeGroup: null, // { blockType, groupIndex, section: 'include'|'exclude' }
|
|
||||||
searchTimeout: null,
|
|
||||||
searchResults: [],
|
|
||||||
searchTotal: 0,
|
|
||||||
searchOffset: 0,
|
|
||||||
searchQuery: '',
|
|
||||||
isLoading: false,
|
|
||||||
loadMoreCount: 20,
|
|
||||||
// Sort, filter, view state
|
|
||||||
viewMode: 'list',
|
|
||||||
currentSort: { field: 'name', dir: 'ASC' },
|
|
||||||
refineQuery: '',
|
|
||||||
refineNegate: false,
|
|
||||||
filters: {
|
|
||||||
inStock: false,
|
|
||||||
discounted: false,
|
|
||||||
priceMin: null,
|
|
||||||
priceMax: null,
|
|
||||||
attributes: [],
|
|
||||||
features: [],
|
|
||||||
// Entity-specific filters
|
|
||||||
productCountMin: null,
|
|
||||||
productCountMax: null,
|
|
||||||
salesMin: null,
|
|
||||||
salesMax: null,
|
|
||||||
turnoverMin: null,
|
|
||||||
turnoverMax: null,
|
|
||||||
depth: null,
|
|
||||||
hasProducts: false,
|
|
||||||
hasDescription: false,
|
|
||||||
hasImage: false,
|
|
||||||
activeOnly: true, // Default to active only
|
|
||||||
attributeGroup: null,
|
|
||||||
featureGroup: null,
|
|
||||||
dateAddFrom: null,
|
|
||||||
dateAddTo: null,
|
|
||||||
lastProductFrom: null,
|
|
||||||
lastProductTo: null,
|
|
||||||
// Country-specific filters
|
|
||||||
hasHolidays: false,
|
|
||||||
containsStates: false,
|
|
||||||
zone: null
|
|
||||||
},
|
|
||||||
filterableData: null,
|
|
||||||
// Search history
|
|
||||||
searchHistory: {},
|
|
||||||
searchHistoryMax: 10,
|
|
||||||
searchHistoryKey: 'targetConditionsSearchHistory',
|
|
||||||
// Chips visibility
|
|
||||||
maxVisibleChips: 20,
|
|
||||||
// Method dropdown references
|
|
||||||
$methodDropdownMenu: null,
|
|
||||||
$methodDropdownSelect: null,
|
|
||||||
$methodDropdownTrigger: null,
|
|
||||||
// Preview state
|
|
||||||
$previewPopover: null,
|
|
||||||
$activeBadge: null,
|
|
||||||
$previewList: null,
|
|
||||||
previewLoadedCount: 0,
|
|
||||||
previewBlockType: null,
|
|
||||||
allPreviewData: null,
|
|
||||||
// Count update timeout
|
|
||||||
countUpdateTimeout: null,
|
|
||||||
|
|
||||||
init: function(options) {
|
|
||||||
this.config = $.extend({
|
|
||||||
id: 'target-conditions',
|
|
||||||
name: 'target_conditions',
|
|
||||||
namePrefix: 'target_',
|
|
||||||
mode: 'multi', // Global mode: 'multi' or 'single'
|
|
||||||
blocks: {},
|
|
||||||
ajaxUrl: '',
|
|
||||||
trans: {}
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
this.$wrapper = $('[data-entity-selector-id="' + this.config.id + '"]');
|
|
||||||
|
|
||||||
if (!this.$wrapper.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global single mode - hide "Add Group" buttons
|
|
||||||
if (this.config.mode === 'single') {
|
|
||||||
this.$wrapper.find('.btn-add-group').hide();
|
|
||||||
this.$wrapper.find('.group-excludes').hide();
|
|
||||||
this.$wrapper.find('.group-modifiers').hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add fullwidth class to parent form-group (skip for form-group layout)
|
|
||||||
var hasLayoutFormGroup = this.$wrapper.hasClass('layout-form-group');
|
|
||||||
var $entitySelectorFormGroup = this.$wrapper.closest('.entity-selector-form-group');
|
|
||||||
|
|
||||||
if (!hasLayoutFormGroup && !$entitySelectorFormGroup.length) {
|
|
||||||
var $formGroup = this.$wrapper.closest('.form-group');
|
|
||||||
$formGroup.addClass('condition-trait-fullwidth');
|
|
||||||
$formGroup.find('.col-lg-offset-3').removeClass('col-lg-offset-3');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createDropdown();
|
|
||||||
this.bindEvents();
|
|
||||||
this.loadExistingSelections();
|
|
||||||
this.loadSearchHistory();
|
|
||||||
|
|
||||||
// Initialize styled method dropdowns
|
|
||||||
this.initMethodDropdowns();
|
|
||||||
|
|
||||||
// Watch for dynamically added selects
|
|
||||||
this.observeNewSelects();
|
|
||||||
|
|
||||||
// Update counts on page load
|
|
||||||
var self = this;
|
|
||||||
setTimeout(function() {
|
|
||||||
self.updateTabBadges();
|
|
||||||
self.updateAllConditionCounts();
|
|
||||||
}, 100);
|
|
||||||
},
|
|
||||||
|
|
||||||
observeNewSelects: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (typeof MutationObserver === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var observer = new MutationObserver(function(mutations) {
|
|
||||||
mutations.forEach(function(mutation) {
|
|
||||||
if (mutation.addedNodes.length) {
|
|
||||||
$(mutation.addedNodes).find('.include-method-select, .exclude-method-select').each(function() {
|
|
||||||
self.enhanceMethodSelect($(this));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(this.$wrapper[0], {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
loadExistingSelections: function() {
|
|
||||||
// TODO: Extract full implementation from original
|
|
||||||
// Reads JSON from hidden input and populates chips
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Merge all mixins into the instance
|
|
||||||
// Each mixin adds its methods to window._EntitySelectorMixins
|
|
||||||
var mixins = window._EntitySelectorMixins || {};
|
|
||||||
|
|
||||||
// Merge utils mixin
|
|
||||||
if (mixins.utils) {
|
|
||||||
$.extend(instance, mixins.utils);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge events mixin
|
|
||||||
if (mixins.events) {
|
|
||||||
$.extend(instance, mixins.events);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge dropdown mixin
|
|
||||||
if (mixins.dropdown) {
|
|
||||||
$.extend(instance, mixins.dropdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge search mixin
|
|
||||||
if (mixins.search) {
|
|
||||||
$.extend(instance, mixins.search);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge filters mixin
|
|
||||||
if (mixins.filters) {
|
|
||||||
$.extend(instance, mixins.filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge chips mixin
|
|
||||||
if (mixins.chips) {
|
|
||||||
$.extend(instance, mixins.chips);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge groups mixin
|
|
||||||
if (mixins.groups) {
|
|
||||||
$.extend(instance, mixins.groups);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge methods mixin
|
|
||||||
if (mixins.methods) {
|
|
||||||
$.extend(instance, mixins.methods);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge preview mixin
|
|
||||||
if (mixins.preview) {
|
|
||||||
$.extend(instance, mixins.preview);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge tree mixin
|
|
||||||
if (mixins.tree) {
|
|
||||||
$.extend(instance, mixins.tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge validation mixin
|
|
||||||
if (mixins.validation) {
|
|
||||||
$.extend(instance, mixins.validation);
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Factory object for creating and managing instances
|
|
||||||
var TargetConditions = {
|
|
||||||
instances: [],
|
|
||||||
|
|
||||||
// Create and initialize a new instance
|
|
||||||
create: function(options) {
|
|
||||||
var instance = createTargetConditionsInstance();
|
|
||||||
instance.init(options);
|
|
||||||
this.instances.push(instance);
|
|
||||||
return instance;
|
|
||||||
},
|
|
||||||
|
|
||||||
// For backwards compatibility - init creates a new instance
|
|
||||||
init: function(options) {
|
|
||||||
return this.create(options);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Validate all instances - returns true if all valid
|
|
||||||
validateAll: function() {
|
|
||||||
var allValid = true;
|
|
||||||
for (var i = 0; i < this.instances.length; i++) {
|
|
||||||
if (!this.instances[i].validate()) {
|
|
||||||
allValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allValid;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Export to window
|
|
||||||
window.TargetConditions = TargetConditions;
|
|
||||||
|
|
||||||
// Auto-initialize on document ready
|
|
||||||
$(document).ready(function() {
|
|
||||||
// Auto-initialize from data-config attributes on wrapper elements
|
|
||||||
$('[data-entity-selector-id]').each(function() {
|
|
||||||
var configData = $(this).data('config');
|
|
||||||
if (configData) {
|
|
||||||
TargetConditions.create(configData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Tips box toggle handler
|
|
||||||
$(document).on('click', '.target-tips-box .tips-header', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).closest('.target-tips-box').toggleClass('expanded');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Form submission validation for required target conditions
|
|
||||||
$(document).on('submit', 'form', function(e) {
|
|
||||||
var $form = $(this);
|
|
||||||
if ($form.find('.target-conditions-trait[data-required]').length > 0) {
|
|
||||||
if (!TargetConditions.validateAll()) {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
@@ -1,438 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Dropdown Module
|
|
||||||
* Search dropdown UI creation and positioning
|
|
||||||
* @partial _dropdown.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
window._EntitySelectorMixins = window._EntitySelectorMixins || {};
|
|
||||||
|
|
||||||
window._EntitySelectorMixins.dropdown = {
|
|
||||||
|
|
||||||
createDropdown: function() {
|
|
||||||
this.$wrapper.find('.target-search-dropdown').remove();
|
|
||||||
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
|
|
||||||
var html = '<div class="target-search-dropdown view-list">';
|
|
||||||
|
|
||||||
// Header with results count, actions, sort controls, view mode
|
|
||||||
html += '<div class="dropdown-header">';
|
|
||||||
html += '<span class="results-count">0 results</span>';
|
|
||||||
|
|
||||||
html += '<div class="dropdown-actions">';
|
|
||||||
|
|
||||||
// Select all / Clear buttons with keyboard shortcuts
|
|
||||||
html += '<button type="button" class="btn-select-all" title="' + (trans.select_all || 'Select all visible') + '">';
|
|
||||||
html += this.esIcon('check_box') + ' ' + (trans.all || 'All') + ' <kbd>Ctrl+A</kbd>';
|
|
||||||
html += '</button>';
|
|
||||||
html += '<button type="button" class="btn-clear-selection" title="' + (trans.clear_selection || 'Clear selection') + '">';
|
|
||||||
html += this.esIcon('check_box_outline_blank') + ' ' + (trans.clear || 'Clear') + ' <kbd>Ctrl+D</kbd>';
|
|
||||||
html += '</button>';
|
|
||||||
|
|
||||||
// Sort controls - options with data-entities attribute for entity-specific filtering
|
|
||||||
html += '<div class="sort-controls">';
|
|
||||||
html += '<select class="sort-field-select" title="Sort by">';
|
|
||||||
// Universal options (all entities)
|
|
||||||
html += '<option value="name">' + (trans.sort_name || 'Name') + '</option>';
|
|
||||||
html += '<option value="id">' + (trans.sort_id || 'ID') + '</option>';
|
|
||||||
html += '<option value="selected">' + (trans.sort_selected || 'Selected') + '</option>';
|
|
||||||
// Product-specific
|
|
||||||
html += '<option value="price" data-entities="products">' + (trans.sort_price || 'Price') + '</option>';
|
|
||||||
html += '<option value="stock" data-entities="products">' + (trans.sort_stock || 'Stock') + '</option>';
|
|
||||||
html += '<option value="popularity" data-entities="products">' + (trans.sort_popularity || 'Sales') + '</option>';
|
|
||||||
html += '<option value="reference" data-entities="products">' + (trans.sort_reference || 'Reference') + '</option>';
|
|
||||||
// Position-based entities (categories, cms, cms_categories, attributes)
|
|
||||||
html += '<option value="position" data-entities="categories,cms,cms_categories,attributes">' + (trans.sort_position || 'Position') + '</option>';
|
|
||||||
// Product count (categories, manufacturers, suppliers)
|
|
||||||
html += '<option value="product_count" data-entities="categories,manufacturers,suppliers">' + (trans.sort_product_count || 'Products') + '</option>';
|
|
||||||
html += '</select>';
|
|
||||||
html += '<button type="button" class="btn-sort-dir" data-dir="ASC" title="Sort direction">';
|
|
||||||
html += this.esIcon('sort_by_alpha');
|
|
||||||
html += '</button>';
|
|
||||||
|
|
||||||
// View mode selector - Tree option always present, shown for categories
|
|
||||||
html += '<select class="view-mode-select" title="View mode">';
|
|
||||||
html += '<option value="list">' + (trans.view_list || 'List') + '</option>';
|
|
||||||
html += '<option value="tree" class="tree-view-option">' + (trans.view_tree || 'Tree') + '</option>';
|
|
||||||
html += '<option value="cols-2">2 ' + (trans.cols || 'cols') + '</option>';
|
|
||||||
html += '<option value="cols-3">3 ' + (trans.cols || 'cols') + '</option>';
|
|
||||||
html += '<option value="cols-4">4 ' + (trans.cols || 'cols') + '</option>';
|
|
||||||
html += '<option value="cols-5">5 ' + (trans.cols || 'cols') + '</option>';
|
|
||||||
html += '<option value="cols-6">6 ' + (trans.cols || 'cols') + '</option>';
|
|
||||||
html += '<option value="cols-7">7 ' + (trans.cols || 'cols') + '</option>';
|
|
||||||
html += '<option value="cols-8">8 ' + (trans.cols || 'cols') + '</option>';
|
|
||||||
html += '</select>';
|
|
||||||
html += '</div>'; // End sort-controls
|
|
||||||
|
|
||||||
// Refine search
|
|
||||||
html += '<div class="refine-compact">';
|
|
||||||
html += '<button type="button" class="btn-refine-negate" title="' + (trans.exclude_matches || 'Exclude matches (NOT contains)') + '">' + this.esIcon('block') + '</button>';
|
|
||||||
html += '<input type="text" class="refine-input" placeholder="' + (trans.refine_short || 'Refine...') + '">';
|
|
||||||
html += '<button type="button" class="btn-clear-refine" style="display:none;">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Filter toggle button
|
|
||||||
html += '<button type="button" class="btn-toggle-filters" title="' + (trans.toggle_filters || 'Filters') + '">';
|
|
||||||
html += this.esIcon('filter_list');
|
|
||||||
html += '</button>';
|
|
||||||
|
|
||||||
// History button
|
|
||||||
html += '<button type="button" class="btn-show-history" title="' + (trans.recent_searches || 'Recent searches') + '">';
|
|
||||||
html += this.esIcon('schedule');
|
|
||||||
html += '</button>';
|
|
||||||
|
|
||||||
html += '</div>'; // End dropdown-actions
|
|
||||||
html += '</div>'; // End dropdown-header
|
|
||||||
|
|
||||||
// Filter panel
|
|
||||||
html += '<div class="filter-panel">';
|
|
||||||
|
|
||||||
// Quick filters row (for products)
|
|
||||||
html += '<div class="filter-row filter-row-quick" data-entity="products">';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-in-stock"> ' + (trans.in_stock || 'In stock') + '</label>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-discounted"> ' + (trans.discounted || 'On sale') + '</label>';
|
|
||||||
|
|
||||||
// Price range
|
|
||||||
html += '<div class="filter-price-range">';
|
|
||||||
html += '<span class="filter-price-label">' + (trans.price || 'Price') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-price-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="0.01">';
|
|
||||||
html += '<span class="filter-price-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-price-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="0.01">';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '<button type="button" class="btn-clear-filters" title="' + (trans.clear_filters || 'Clear filters') + '">';
|
|
||||||
html += this.esIcon('close');
|
|
||||||
html += '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Attribute/Feature filter toggles for products
|
|
||||||
html += '<div class="filter-row filter-row-attributes" data-entity="products" style="display:none;">';
|
|
||||||
html += '<span class="filter-row-label">' + this.esIcon('label') + ' ' + (trans.attributes || 'Attributes') + ':</span>';
|
|
||||||
html += '<div class="filter-attributes-container"></div>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-row filter-row-values filter-row-attr-values" data-type="attribute" style="display:none;">';
|
|
||||||
html += '<div class="filter-values-container"></div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '<div class="filter-row filter-row-features" data-entity="products" style="display:none;">';
|
|
||||||
html += '<span class="filter-row-label">' + this.esIcon('list') + ' ' + (trans.features || 'Features') + ':</span>';
|
|
||||||
html += '<div class="filter-features-container"></div>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-row filter-row-values filter-row-feat-values" data-type="feature" style="display:none;">';
|
|
||||||
html += '<div class="filter-values-container"></div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Entity-specific filters: Categories
|
|
||||||
html += '<div class="filter-row filter-row-entity-categories filter-row-multi" data-entity="categories" style="display:none;">';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-select-group">';
|
|
||||||
html += '<span class="filter-select-label">' + this.esIcon('account_tree') + ' ' + (trans.depth || 'Depth') + ':</span>';
|
|
||||||
html += '<select class="filter-depth-select">';
|
|
||||||
html += '<option value="">' + (trans.all_levels || 'All levels') + '</option>';
|
|
||||||
html += '<option value="1">' + (trans.level || 'Level') + ' 1 (' + (trans.root || 'Root') + ')</option>';
|
|
||||||
html += '<option value="2">' + (trans.level || 'Level') + ' 2</option>';
|
|
||||||
html += '<option value="3">' + (trans.level || 'Level') + ' 3</option>';
|
|
||||||
html += '<option value="4">' + (trans.level || 'Level') + ' 4+</option>';
|
|
||||||
html += '</select>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-has-products"> ' + (trans.has_products || 'Has products') + '</label>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-has-description"> ' + (trans.has_description || 'Has description') + '</label>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-has-image"> ' + (trans.has_image || 'Has image') + '</label>';
|
|
||||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Entity-specific filters: Manufacturers
|
|
||||||
html += '<div class="filter-row filter-row-entity-manufacturers filter-row-multi" data-entity="manufacturers" style="display:none;">';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-date-group">';
|
|
||||||
html += '<span class="filter-date-label">' + this.esIcon('event') + ' ' + (trans.date_added || 'Added') + ':</span>';
|
|
||||||
html += '<input type="date" class="filter-date-add-from" title="' + (trans.from || 'From') + '">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="date" class="filter-date-add-to" title="' + (trans.to || 'To') + '">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-date-group">';
|
|
||||||
html += '<span class="filter-date-label">' + this.esIcon('schedule') + ' ' + (trans.last_product || 'Last product') + ':</span>';
|
|
||||||
html += '<input type="date" class="filter-last-product-from" title="' + (trans.from || 'From') + '">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="date" class="filter-last-product-to" title="' + (trans.to || 'To') + '">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Entity-specific filters: Suppliers
|
|
||||||
html += '<div class="filter-row filter-row-entity-suppliers filter-row-multi" data-entity="suppliers" style="display:none;">';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-date-group">';
|
|
||||||
html += '<span class="filter-date-label">' + this.esIcon('event') + ' ' + (trans.date_added || 'Added') + ':</span>';
|
|
||||||
html += '<input type="date" class="filter-date-add-from" title="' + (trans.from || 'From') + '">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="date" class="filter-date-add-to" title="' + (trans.to || 'To') + '">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-date-group">';
|
|
||||||
html += '<span class="filter-date-label">' + this.esIcon('schedule') + ' ' + (trans.last_product || 'Last product') + ':</span>';
|
|
||||||
html += '<input type="date" class="filter-last-product-from" title="' + (trans.from || 'From') + '">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="date" class="filter-last-product-to" title="' + (trans.to || 'To') + '">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Entity-specific filters: Attributes
|
|
||||||
html += '<div class="filter-row filter-row-entity-attributes filter-row-multi" data-entity="attributes" style="display:none;">';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-select-group">';
|
|
||||||
html += '<span class="filter-select-label">' + this.esIcon('label') + ' ' + (trans.attribute_group || 'Group') + ':</span>';
|
|
||||||
html += '<select class="filter-attribute-group-select">';
|
|
||||||
html += '<option value="">' + (trans.all_groups || 'All groups') + '</option>';
|
|
||||||
html += '</select>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-is-color"> ' + (trans.color_only || 'Color attributes') + '</label>';
|
|
||||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Entity-specific filters: Features
|
|
||||||
html += '<div class="filter-row filter-row-entity-features filter-row-multi" data-entity="features" style="display:none;">';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-range-group">';
|
|
||||||
html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
|
|
||||||
html += '<span class="filter-range-sep">-</span>';
|
|
||||||
html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="filter-subrow">';
|
|
||||||
html += '<div class="filter-select-group">';
|
|
||||||
html += '<span class="filter-select-label">' + this.esIcon('list') + ' ' + (trans.feature_group || 'Group') + ':</span>';
|
|
||||||
html += '<select class="filter-feature-group-select">';
|
|
||||||
html += '<option value="">' + (trans.all_groups || 'All groups') + '</option>';
|
|
||||||
html += '</select>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-is-custom"> ' + (trans.custom_only || 'Custom values') + '</label>';
|
|
||||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Entity-specific filters: CMS Pages
|
|
||||||
html += '<div class="filter-row filter-row-entity-cms" data-entity="cms" style="display:none;">';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-indexable"> ' + (trans.indexable || 'Indexable') + '</label>';
|
|
||||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Entity-specific filters: CMS Categories
|
|
||||||
html += '<div class="filter-row filter-row-entity-cms-categories" data-entity="cms_categories" style="display:none;">';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
|
|
||||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Entity-specific filters: Countries
|
|
||||||
html += '<div class="filter-row filter-row-entity-countries" data-entity="countries" style="display:none;">';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-has-holidays"> ' + (trans.has_holidays || 'Has holidays') + '</label>';
|
|
||||||
html += '<label class="filter-label"><input type="checkbox" class="filter-contains-states"> ' + (trans.contains_states || 'Has states') + '</label>';
|
|
||||||
html += '<div class="filter-select-group">';
|
|
||||||
html += '<span class="filter-select-label">' + this.esIcon('language') + ' ' + (trans.zone || 'Zone') + ':</span>';
|
|
||||||
html += '<select class="filter-zone-select">';
|
|
||||||
html += '<option value="">' + (trans.all_zones || 'All zones') + '</option>';
|
|
||||||
html += '</select>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '</div>'; // End filter-panel
|
|
||||||
|
|
||||||
// Results header for list view (product columns)
|
|
||||||
html += '<div class="results-header">';
|
|
||||||
html += '<span class="header-spacer"></span>';
|
|
||||||
html += '<span class="header-col header-col-name">' + (trans.product || 'Product') + '</span>';
|
|
||||||
html += '<span class="header-col header-col-price">' + (trans.price || 'Price') + '</span>';
|
|
||||||
html += '<span class="header-col header-col-sale">' + (trans.sale || 'Sale') + '</span>';
|
|
||||||
html += '<span class="header-col header-col-stock">' + (trans.stock || 'Stock') + '</span>';
|
|
||||||
html += '<span class="header-col header-col-sales">' + (trans.sold || 'Sold') + '</span>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Results
|
|
||||||
html += '<div class="dropdown-results"></div>';
|
|
||||||
|
|
||||||
// Footer - unified load more + actions
|
|
||||||
html += '<div class="dropdown-footer">';
|
|
||||||
|
|
||||||
// Left side: load more
|
|
||||||
html += '<div class="dropdown-footer-left" style="visibility:hidden;">';
|
|
||||||
html += '<span class="load-label">' + (trans.load || 'Load') + '</span>';
|
|
||||||
html += '<select class="load-more-select">';
|
|
||||||
html += '<option value="20">20</option>';
|
|
||||||
html += '<option value="50">50</option>';
|
|
||||||
html += '<option value="100">100</option>';
|
|
||||||
html += '<option value="all">' + (trans.all || 'All') + '</option>';
|
|
||||||
html += '</select>';
|
|
||||||
html += '<span class="remaining-text">' + (trans.of || 'of') + ' <strong class="remaining-count">0</strong> ' + (trans.remaining || 'remaining') + '</span>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Right side: action buttons
|
|
||||||
html += '<div class="dropdown-footer-right">';
|
|
||||||
html += '<button type="button" class="dropdown-action-btn btn-cancel">' + this.esIcon('close') + ' ' + (trans.cancel || 'Cancel') + ' <span class="btn-shortcut">Esc</span></button>';
|
|
||||||
html += '<button type="button" class="dropdown-action-btn btn-save">' + this.esIcon('check') + ' ' + (trans.save || 'Save') + ' <span class="btn-shortcut">⏎</span></button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
this.$dropdown = $(html);
|
|
||||||
$('body').append(this.$dropdown);
|
|
||||||
},
|
|
||||||
|
|
||||||
hideDropdown: function() {
|
|
||||||
if (this.$dropdown) {
|
|
||||||
this.$dropdown.removeClass('show');
|
|
||||||
}
|
|
||||||
this.activeGroup = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
positionDropdown: function($input) {
|
|
||||||
if (!this.$dropdown) return;
|
|
||||||
|
|
||||||
var $picker = $input.closest('.value-picker');
|
|
||||||
var $searchBox = $input.closest('.entity-search-box');
|
|
||||||
|
|
||||||
// Get absolute positions (dropdown is appended to body)
|
|
||||||
var searchBoxOffset = $searchBox.offset();
|
|
||||||
var searchBoxHeight = $searchBox.outerHeight();
|
|
||||||
var pickerOffset = $picker.offset();
|
|
||||||
var pickerWidth = $picker.outerWidth();
|
|
||||||
|
|
||||||
// Calculate position relative to document
|
|
||||||
var dropdownTop = searchBoxOffset.top + searchBoxHeight + 4;
|
|
||||||
var dropdownLeft = pickerOffset.left;
|
|
||||||
var dropdownWidth = Math.max(pickerWidth, 400);
|
|
||||||
|
|
||||||
// Ensure dropdown doesn't overflow the viewport horizontally
|
|
||||||
var viewportWidth = $(window).width();
|
|
||||||
if (dropdownLeft + dropdownWidth > viewportWidth - 10) {
|
|
||||||
dropdownWidth = viewportWidth - dropdownLeft - 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure dropdown doesn't overflow viewport vertically
|
|
||||||
var viewportHeight = $(window).height();
|
|
||||||
var scrollTop = $(window).scrollTop();
|
|
||||||
var maxHeight = viewportHeight - (dropdownTop - scrollTop) - 20;
|
|
||||||
maxHeight = Math.max(maxHeight, 400);
|
|
||||||
|
|
||||||
this.$dropdown.css({
|
|
||||||
position: 'absolute',
|
|
||||||
top: dropdownTop,
|
|
||||||
left: dropdownLeft,
|
|
||||||
width: dropdownWidth,
|
|
||||||
maxHeight: maxHeight,
|
|
||||||
zIndex: 10000
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show the dropdown
|
|
||||||
this.$dropdown.addClass('show');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,397 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Filters Module
|
|
||||||
* Filter panel, filter state management
|
|
||||||
* @partial _filters.js
|
|
||||||
*
|
|
||||||
* EXTRACTION SOURCE: assets/js/admin/entity-selector.js
|
|
||||||
* Lines: 6605-6758 (filter methods)
|
|
||||||
*
|
|
||||||
* Contains:
|
|
||||||
* - clearFilters() - Reset all filters
|
|
||||||
* - resetFiltersWithoutSearch() - Reset without triggering search
|
|
||||||
* - updateFilterPanelForEntity() - Show/hide filters based on entity type
|
|
||||||
* - loadFilterableData() - Load attributes/features for filter panel
|
|
||||||
* - renderFilterDropdowns() - Render attribute/feature group toggles
|
|
||||||
* - showFilterGroupValues() - Show values for a filter group
|
|
||||||
* - hideFilterGroupValues() - Hide filter values row
|
|
||||||
* - updateFilterToggleStates() - Update toggle states based on selections
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
window._EntitySelectorMixins = window._EntitySelectorMixins || {};
|
|
||||||
|
|
||||||
window._EntitySelectorMixins.filters = {
|
|
||||||
|
|
||||||
clearFilters: function() {
|
|
||||||
this.refineQuery = '';
|
|
||||||
this.refineNegate = false;
|
|
||||||
this.filters = {
|
|
||||||
inStock: false,
|
|
||||||
discounted: false,
|
|
||||||
priceMin: null,
|
|
||||||
priceMax: null,
|
|
||||||
attributes: [],
|
|
||||||
features: [],
|
|
||||||
productCountMin: null,
|
|
||||||
productCountMax: null,
|
|
||||||
salesMin: null,
|
|
||||||
salesMax: null,
|
|
||||||
turnoverMin: null,
|
|
||||||
turnoverMax: null,
|
|
||||||
depth: null,
|
|
||||||
hasProducts: false,
|
|
||||||
hasDescription: false,
|
|
||||||
hasImage: false,
|
|
||||||
activeOnly: true,
|
|
||||||
attributeGroup: null,
|
|
||||||
featureGroup: null,
|
|
||||||
dateAddFrom: null,
|
|
||||||
dateAddTo: null,
|
|
||||||
lastProductFrom: null,
|
|
||||||
lastProductTo: null,
|
|
||||||
// Country-specific filters
|
|
||||||
hasHolidays: false,
|
|
||||||
containsStates: false,
|
|
||||||
zone: null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.$dropdown) {
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
this.$dropdown.find('.refine-input').val('');
|
|
||||||
this.$dropdown.find('.btn-refine-negate').removeClass('active');
|
|
||||||
this.$dropdown.find('.filter-in-stock').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-discounted').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-price-min, .filter-price-max').val('');
|
|
||||||
this.$dropdown.find('.filter-attr-chip, .filter-feat-chip').removeClass('active');
|
|
||||||
this.$dropdown.find('.filter-product-count-min, .filter-product-count-max').val('');
|
|
||||||
this.$dropdown.find('.filter-sales-min, .filter-sales-max').val('');
|
|
||||||
this.$dropdown.find('.filter-depth-select').val('');
|
|
||||||
this.$dropdown.find('.filter-has-products').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-active-only').prop('checked', true);
|
|
||||||
// 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();
|
|
||||||
},
|
|
||||||
|
|
||||||
resetFiltersWithoutSearch: function() {
|
|
||||||
// Same as clearFilters but doesn't trigger search
|
|
||||||
// Used when switching entity types
|
|
||||||
this.refineQuery = '';
|
|
||||||
this.refineNegate = false;
|
|
||||||
this.filters = {
|
|
||||||
inStock: false,
|
|
||||||
discounted: false,
|
|
||||||
priceMin: null,
|
|
||||||
priceMax: null,
|
|
||||||
attributes: [],
|
|
||||||
features: [],
|
|
||||||
productCountMin: null,
|
|
||||||
productCountMax: null,
|
|
||||||
salesMin: null,
|
|
||||||
salesMax: null,
|
|
||||||
turnoverMin: null,
|
|
||||||
turnoverMax: null,
|
|
||||||
depth: null,
|
|
||||||
hasProducts: false,
|
|
||||||
hasDescription: false,
|
|
||||||
hasImage: false,
|
|
||||||
activeOnly: true,
|
|
||||||
attributeGroup: null,
|
|
||||||
featureGroup: null,
|
|
||||||
dateAddFrom: null,
|
|
||||||
dateAddTo: null,
|
|
||||||
lastProductFrom: null,
|
|
||||||
lastProductTo: null,
|
|
||||||
// Country-specific filters
|
|
||||||
hasHolidays: false,
|
|
||||||
containsStates: false,
|
|
||||||
zone: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFilterPanelForEntity: function(entityType) {
|
|
||||||
if (!this.$dropdown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $panel = this.$dropdown.find('.filter-panel');
|
|
||||||
|
|
||||||
// Hide all entity-specific filter rows
|
|
||||||
$panel.find('.filter-row').hide();
|
|
||||||
|
|
||||||
// Show filters for current entity type
|
|
||||||
$panel.find('.filter-row[data-entity="' + entityType + '"]').show();
|
|
||||||
$panel.find('.filter-row-entity-' + entityType.replace('_', '-')).show();
|
|
||||||
|
|
||||||
// Show/hide tree view option based on entity type
|
|
||||||
var isCategory = (entityType === 'categories' || entityType === 'cms_categories');
|
|
||||||
this.$dropdown.find('.tree-view-option').toggle(isCategory);
|
|
||||||
|
|
||||||
// Default to tree view for categories (only if currently on list mode)
|
|
||||||
if (isCategory && this.viewMode === 'list') {
|
|
||||||
this.viewMode = 'tree';
|
|
||||||
this.$dropdown.find('.view-mode-select').val('tree');
|
|
||||||
this.$dropdown.removeClass('view-list view-cols-2 view-cols-3 view-cols-4 view-cols-5 view-cols-6 view-cols-7 view-cols-8').addClass('view-tree');
|
|
||||||
} else if (!isCategory && this.viewMode === 'tree') {
|
|
||||||
// If switching away from categories while in tree mode, switch to list
|
|
||||||
this.viewMode = 'list';
|
|
||||||
this.$dropdown.find('.view-mode-select').val('list');
|
|
||||||
this.$dropdown.removeClass('view-tree view-cols-2 view-cols-3 view-cols-4 view-cols-5 view-cols-6 view-cols-7 view-cols-8').addClass('view-list');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load zones for countries filter
|
|
||||||
if (entityType === 'countries') {
|
|
||||||
this.loadZonesForCountryFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update sort options for entity type
|
|
||||||
this.updateSortOptionsForEntity(entityType);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show/hide sort options based on entity type
|
|
||||||
* Options with data-entities attribute are only shown for matching entities
|
|
||||||
*/
|
|
||||||
updateSortOptionsForEntity: function(entityType) {
|
|
||||||
if (!this.$dropdown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $select = this.$dropdown.find('.sort-field-select');
|
|
||||||
var currentValue = $select.val();
|
|
||||||
var hasCurrentOption = false;
|
|
||||||
|
|
||||||
$select.find('option').each(function() {
|
|
||||||
var $option = $(this);
|
|
||||||
var entities = $option.data('entities');
|
|
||||||
|
|
||||||
// Options without data-entities are universal (always shown)
|
|
||||||
if (!entities) {
|
|
||||||
$option.show();
|
|
||||||
if ($option.val() === currentValue) {
|
|
||||||
hasCurrentOption = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this entity type is in the allowed list
|
|
||||||
var allowedEntities = entities.split(',');
|
|
||||||
var isAllowed = allowedEntities.indexOf(entityType) !== -1;
|
|
||||||
|
|
||||||
$option.toggle(isAllowed);
|
|
||||||
|
|
||||||
if (isAllowed && $option.val() === currentValue) {
|
|
||||||
hasCurrentOption = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If current sort field is not available for this entity, reset to 'name'
|
|
||||||
if (!hasCurrentOption) {
|
|
||||||
$select.val('name');
|
|
||||||
this.currentSort.field = 'name';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadFilterableData: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.filterableData) {
|
|
||||||
this.renderFilterDropdowns();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: this.config.ajaxUrl,
|
|
||||||
type: 'POST',
|
|
||||||
data: {
|
|
||||||
ajax: 1,
|
|
||||||
action: 'getTargetFilterableAttributes',
|
|
||||||
trait: 'EntitySelector'
|
|
||||||
},
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(response) {
|
|
||||||
if (response.success && response.data) {
|
|
||||||
self.filterableData = response.data;
|
|
||||||
self.renderFilterDropdowns();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderFilterDropdowns: function() {
|
|
||||||
if (!this.$dropdown || !this.filterableData) return;
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Render attribute group toggle buttons
|
|
||||||
var $attrContainer = this.$dropdown.find('.filter-attributes-container');
|
|
||||||
$attrContainer.empty();
|
|
||||||
|
|
||||||
if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
|
|
||||||
this.filterableData.attributes.forEach(function(group) {
|
|
||||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">';
|
|
||||||
html += '<span class="toggle-name">' + group.name + '</span>';
|
|
||||||
if (group.count !== undefined) {
|
|
||||||
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">' + self.esIcon('visibility') + ' ' + group.count + '</span>';
|
|
||||||
}
|
|
||||||
html += '</button>';
|
|
||||||
$attrContainer.append(html);
|
|
||||||
});
|
|
||||||
this.$dropdown.find('.filter-row-attributes').show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render feature group toggle buttons
|
|
||||||
var $featContainer = this.$dropdown.find('.filter-features-container');
|
|
||||||
$featContainer.empty();
|
|
||||||
|
|
||||||
if (this.filterableData.features && this.filterableData.features.length > 0) {
|
|
||||||
this.filterableData.features.forEach(function(group) {
|
|
||||||
var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">';
|
|
||||||
html += '<span class="toggle-name">' + group.name + '</span>';
|
|
||||||
if (group.count !== undefined) {
|
|
||||||
html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">' + self.esIcon('visibility') + ' ' + group.count + '</span>';
|
|
||||||
}
|
|
||||||
html += '</button>';
|
|
||||||
$featContainer.append(html);
|
|
||||||
});
|
|
||||||
this.$dropdown.find('.filter-row-features').show();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showFilterGroupValues: function(groupId, type) {
|
|
||||||
if (!this.filterableData) return;
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var groups = type === 'attribute' ? this.filterableData.attributes : this.filterableData.features;
|
|
||||||
var group = groups.find(function(g) { return g.id == groupId; });
|
|
||||||
|
|
||||||
if (!group) return;
|
|
||||||
|
|
||||||
// Hide all values rows first, then show the correct one
|
|
||||||
this.$dropdown.find('.filter-row-values').hide();
|
|
||||||
|
|
||||||
// Target the correct values row based on type
|
|
||||||
var valuesRowClass = type === 'attribute' ? '.filter-row-attr-values' : '.filter-row-feat-values';
|
|
||||||
var $filterRowValues = this.$dropdown.find(valuesRowClass);
|
|
||||||
var $valuesContainer = $filterRowValues.find('.filter-values-container');
|
|
||||||
$valuesContainer.empty();
|
|
||||||
|
|
||||||
// Add group label
|
|
||||||
var html = '<span class="filter-values-label">' + group.name + ':</span>';
|
|
||||||
|
|
||||||
// Add chips
|
|
||||||
group.values.forEach(function(val) {
|
|
||||||
var isActive = type === 'attribute'
|
|
||||||
? self.filters.attributes.indexOf(val.id) !== -1
|
|
||||||
: self.filters.features.indexOf(val.id) !== -1;
|
|
||||||
var activeClass = isActive ? ' active' : '';
|
|
||||||
var chipClass = type === 'attribute' ? 'filter-attr-chip' : 'filter-feat-chip';
|
|
||||||
var colorStyle = val.color ? ' style="--chip-color: ' + val.color + '"' : '';
|
|
||||||
var colorClass = val.color ? ' has-color' : '';
|
|
||||||
|
|
||||||
html += '<button type="button" class="filter-chip ' + chipClass + activeClass + colorClass + '" data-id="' + val.id + '" data-group-id="' + groupId + '"' + colorStyle + '>';
|
|
||||||
if (val.color) {
|
|
||||||
html += '<span class="chip-color-dot"></span>';
|
|
||||||
}
|
|
||||||
html += '<span class="chip-name">' + val.name + '</span>';
|
|
||||||
if (val.count !== undefined) {
|
|
||||||
html += '<span class="chip-count">(' + val.count + ')</span>';
|
|
||||||
}
|
|
||||||
html += '</button>';
|
|
||||||
});
|
|
||||||
|
|
||||||
$valuesContainer.html(html);
|
|
||||||
|
|
||||||
// Add close button as sibling (outside filter-values-container, inside filter-row-values)
|
|
||||||
$filterRowValues.find('.btn-close-values').remove();
|
|
||||||
$filterRowValues.append('<button type="button" class="btn-close-values">' + this.esIcon('close') + '</button>');
|
|
||||||
$filterRowValues.show();
|
|
||||||
|
|
||||||
// Scroll into view if needed
|
|
||||||
var rowValues = $filterRowValues[0];
|
|
||||||
if (rowValues) {
|
|
||||||
rowValues.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hideFilterGroupValues: function() {
|
|
||||||
this.$dropdown.find('.filter-row-values').hide();
|
|
||||||
this.$dropdown.find('.filter-group-toggle').removeClass('active');
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFilterToggleStates: function() {
|
|
||||||
if (!this.$dropdown || !this.filterableData) return;
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Update attribute group toggles
|
|
||||||
if (this.filterableData.attributes) {
|
|
||||||
this.filterableData.attributes.forEach(function(group) {
|
|
||||||
var $toggle = self.$dropdown.find('.filter-group-toggle[data-group-id="' + group.id + '"][data-type="attribute"]');
|
|
||||||
var hasActiveInGroup = group.values.some(function(val) {
|
|
||||||
return self.filters.attributes.indexOf(val.id) !== -1;
|
|
||||||
});
|
|
||||||
$toggle.toggleClass('has-selection', hasActiveInGroup);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update feature group toggles
|
|
||||||
if (this.filterableData.features) {
|
|
||||||
this.filterableData.features.forEach(function(group) {
|
|
||||||
var $toggle = self.$dropdown.find('.filter-group-toggle[data-group-id="' + group.id + '"][data-type="feature"]');
|
|
||||||
var hasActiveInGroup = group.values.some(function(val) {
|
|
||||||
return self.filters.features.indexOf(val.id) !== -1;
|
|
||||||
});
|
|
||||||
$toggle.toggleClass('has-selection', hasActiveInGroup);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load zones for country filter dropdown
|
|
||||||
*/
|
|
||||||
loadZonesForCountryFilter: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.zonesLoaded || !this.$dropdown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $select = this.$dropdown.find('.filter-zone-select');
|
|
||||||
if (!$select.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: this.config.ajaxUrl,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
ajax: 1,
|
|
||||||
action: 'getZonesForFilter',
|
|
||||||
trait: 'EntitySelector'
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
if (response.success && response.zones && response.zones.length > 0) {
|
|
||||||
var trans = self.config.trans || {};
|
|
||||||
$select.empty();
|
|
||||||
$select.append('<option value="">' + (trans.all_zones || 'All zones') + '</option>');
|
|
||||||
|
|
||||||
response.zones.forEach(function(zone) {
|
|
||||||
$select.append('<option value="' + zone.id + '">' + self.escapeHtml(zone.name) + '</option>');
|
|
||||||
});
|
|
||||||
|
|
||||||
self.zonesLoaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,878 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Methods Module
|
|
||||||
* Method dropdown rendering, value pickers, combination picker
|
|
||||||
* @partial _methods.js
|
|
||||||
*
|
|
||||||
* EXTRACTION SOURCE: assets/js/admin/entity-selector.js
|
|
||||||
* Lines: 6760-6848 (initMethodDropdowns, enhanceMethodSelect)
|
|
||||||
* 6849-7051 (showMethodDropdownMenu, buildMethodDropdownMenuHtml, closeMethodDropdownMenu)
|
|
||||||
* 7053-7138 (populateTiles, applyRangeInputConstraints, showRangeInputError)
|
|
||||||
* 7139-7380 (combination picker methods)
|
|
||||||
* 7382-7550 (updateMethodInfoPlaceholder, getBuiltInMethodHelp)
|
|
||||||
* 7748-7888 (buildSortOptions, updateModifierButtonState, updateMethodSelectorLock)
|
|
||||||
*
|
|
||||||
* Contains:
|
|
||||||
* - initMethodDropdowns() - Initialize styled dropdowns
|
|
||||||
* - enhanceMethodSelect() - Convert select to styled dropdown
|
|
||||||
* - showMethodDropdownMenu() - Show method selection menu
|
|
||||||
* - buildMethodDropdownMenuHtml() - Build menu HTML
|
|
||||||
* - closeMethodDropdownMenu() - Close dropdown menu
|
|
||||||
* - updateMethodTrigger() - Update trigger display
|
|
||||||
* - populateTiles() - Build multi-select tiles
|
|
||||||
* - applyRangeInputConstraints() - Set numeric input constraints
|
|
||||||
* - showRangeInputError() - Display validation error
|
|
||||||
* - loadCombinationAttributeGroups() - Load attribute groups for picker
|
|
||||||
* - loadCombinationAttributeValues() - Load values for attribute group
|
|
||||||
* - restoreCombinationSelections() - Restore saved combination state
|
|
||||||
* - updateCombinationData() - Save combination selection
|
|
||||||
* - updateCombinationGroupCounts() - Update selection counts
|
|
||||||
* - updateMethodInfoPlaceholder() - Show method help
|
|
||||||
* - getBuiltInMethodHelp() - Get help text for methods
|
|
||||||
* - buildSortOptions() - Build sort dropdown options
|
|
||||||
* - updateModifierButtonState() - Update modifier toggle state
|
|
||||||
* - updateMethodSelectorLock() - Lock/unlock method selector
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
window._EntitySelectorMixins = window._EntitySelectorMixins || {};
|
|
||||||
|
|
||||||
window._EntitySelectorMixins.methods = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize styled method dropdowns for all method selects
|
|
||||||
*/
|
|
||||||
initMethodDropdowns: function() {
|
|
||||||
var self = this;
|
|
||||||
this.$wrapper.find('.include-method-select').each(function() {
|
|
||||||
self.enhanceMethodSelect($(this));
|
|
||||||
});
|
|
||||||
this.$wrapper.find('.exclude-method-select').each(function() {
|
|
||||||
self.enhanceMethodSelect($(this));
|
|
||||||
});
|
|
||||||
this.initMethodInfoPlaceholders();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize info placeholders for all existing method selects
|
|
||||||
*/
|
|
||||||
initMethodInfoPlaceholders: function() {
|
|
||||||
var self = this;
|
|
||||||
this.$wrapper.find('.selection-group').each(function() {
|
|
||||||
var $group = $(this);
|
|
||||||
var $block = $group.closest('.target-block');
|
|
||||||
var blockType = $block.data('blockType') || 'products';
|
|
||||||
|
|
||||||
// Include method info
|
|
||||||
var includeMethod = $group.find('.include-method-select').val() || 'all';
|
|
||||||
self.updateMethodInfoPlaceholder($group.find('.method-selector-wrapper'), includeMethod, blockType);
|
|
||||||
|
|
||||||
// Exclude methods info
|
|
||||||
$group.find('.exclude-row').each(function() {
|
|
||||||
var $row = $(this);
|
|
||||||
var excludeMethod = $row.find('.exclude-method-select').val();
|
|
||||||
if (excludeMethod) {
|
|
||||||
self.updateMethodInfoPlaceholder($row.find('.method-selector-wrapper'), excludeMethod, blockType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhance a single method select with styled dropdown
|
|
||||||
*/
|
|
||||||
enhanceMethodSelect: function($select) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (!$select.length || $select.data('methodDropdownInit')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$select.data('methodDropdownInit', true);
|
|
||||||
|
|
||||||
$select.addClass('method-select-hidden');
|
|
||||||
|
|
||||||
var $selectedOption = $select.find('option:selected');
|
|
||||||
var selectedIcon = $selectedOption.data('icon') || 'arrow_drop_down';
|
|
||||||
var selectedLabel = $selectedOption.text();
|
|
||||||
|
|
||||||
var triggerHtml = '<div class="method-dropdown-trigger">';
|
|
||||||
triggerHtml += this.esIcon(selectedIcon, 'method-trigger-icon');
|
|
||||||
triggerHtml += '<span class="method-trigger-label">' + this.escapeHtml(selectedLabel) + '</span>';
|
|
||||||
triggerHtml += this.esIcon('arrow_drop_down', 'method-trigger-caret');
|
|
||||||
triggerHtml += '</div>';
|
|
||||||
|
|
||||||
var $trigger = $(triggerHtml);
|
|
||||||
$select.after($trigger);
|
|
||||||
|
|
||||||
$trigger.on('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
var $wrapper = $select.closest('.method-selector-wrapper');
|
|
||||||
if ($wrapper.hasClass('selector-locked')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.showMethodDropdownMenu($select, $trigger);
|
|
||||||
});
|
|
||||||
|
|
||||||
$select.on('change.methodDropdown', function() {
|
|
||||||
self.updateMethodTrigger($select, $trigger);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the trigger display to match current selection
|
|
||||||
*/
|
|
||||||
updateMethodTrigger: function($select, $trigger) {
|
|
||||||
var $selectedOption = $select.find('option:selected');
|
|
||||||
var selectedIcon = $selectedOption.data('icon') || 'arrow_drop_down';
|
|
||||||
var selectedLabel = $selectedOption.text();
|
|
||||||
|
|
||||||
$trigger.find('.method-trigger-icon').replaceWith(this.esIcon(selectedIcon, 'method-trigger-icon'));
|
|
||||||
$trigger.find('.method-trigger-label').text(selectedLabel);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the method dropdown menu
|
|
||||||
*/
|
|
||||||
showMethodDropdownMenu: function($select, $trigger) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.closeMethodDropdownMenu();
|
|
||||||
|
|
||||||
var menuHtml = this.buildMethodDropdownMenuHtml($select);
|
|
||||||
var $menu = $(menuHtml);
|
|
||||||
|
|
||||||
var triggerOffset = $trigger.offset();
|
|
||||||
var triggerWidth = $trigger.outerWidth();
|
|
||||||
var triggerHeight = $trigger.outerHeight();
|
|
||||||
|
|
||||||
$menu.css({
|
|
||||||
position: 'absolute',
|
|
||||||
top: triggerOffset.top + triggerHeight + 2,
|
|
||||||
left: triggerOffset.left,
|
|
||||||
minWidth: triggerWidth,
|
|
||||||
zIndex: 10001
|
|
||||||
});
|
|
||||||
|
|
||||||
$('body').append($menu);
|
|
||||||
this.$methodDropdownMenu = $menu;
|
|
||||||
this.$methodDropdownSelect = $select;
|
|
||||||
this.$methodDropdownTrigger = $trigger;
|
|
||||||
|
|
||||||
$menu.on('click', '.method-dropdown-item', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
var value = $(this).data('value');
|
|
||||||
$select.val(value).trigger('change');
|
|
||||||
self.closeMethodDropdownMenu();
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on('click.methodDropdown', function(e) {
|
|
||||||
if (!$(e.target).closest('.method-dropdown-menu, .method-dropdown-trigger').length) {
|
|
||||||
self.closeMethodDropdownMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on('keydown.methodDropdown', function(e) {
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
self.closeMethodDropdownMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the dropdown menu HTML
|
|
||||||
*/
|
|
||||||
buildMethodDropdownMenuHtml: function($select) {
|
|
||||||
var self = this;
|
|
||||||
var html = '<div class="method-dropdown-menu">';
|
|
||||||
|
|
||||||
// Render ungrouped options first
|
|
||||||
$select.children('option').each(function() {
|
|
||||||
var $el = $(this);
|
|
||||||
var icon = $el.data('icon') || 'star';
|
|
||||||
var label = $el.text();
|
|
||||||
var value = $el.val();
|
|
||||||
var isSelected = $el.is(':selected');
|
|
||||||
|
|
||||||
html += '<div class="method-dropdown-item' + (isSelected ? ' selected' : '') + '" data-value="' + self.escapeAttr(value) + '">';
|
|
||||||
html += this.esIcon(icon, 'method-item-icon');
|
|
||||||
html += '<span class="method-item-label">' + self.escapeHtml(label) + '</span>';
|
|
||||||
if (isSelected) {
|
|
||||||
html += self.esIcon('check', 'method-item-check');
|
|
||||||
}
|
|
||||||
html += '</div>';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Render optgroups
|
|
||||||
$select.children('optgroup').each(function() {
|
|
||||||
var $optgroup = $(this);
|
|
||||||
var groupLabel = $optgroup.attr('label') || '';
|
|
||||||
|
|
||||||
html += '<div class="method-dropdown-optgroup">';
|
|
||||||
html += '<div class="method-optgroup-label">' + self.escapeHtml(groupLabel) + '</div>';
|
|
||||||
html += '<div class="method-optgroup-items">';
|
|
||||||
|
|
||||||
$optgroup.children('option').each(function() {
|
|
||||||
var $el = $(this);
|
|
||||||
var icon = $el.data('icon') || 'settings';
|
|
||||||
var label = $el.text();
|
|
||||||
var value = $el.val();
|
|
||||||
var isSelected = $el.is(':selected');
|
|
||||||
|
|
||||||
html += '<div class="method-dropdown-item' + (isSelected ? ' selected' : '') + '" data-value="' + self.escapeAttr(value) + '">';
|
|
||||||
html += self.esIcon(icon, 'method-item-icon');
|
|
||||||
html += '<span class="method-item-label">' + self.escapeHtml(label) + '</span>';
|
|
||||||
if (isSelected) {
|
|
||||||
html += self.esIcon('check', 'method-item-check');
|
|
||||||
}
|
|
||||||
html += '</div>';
|
|
||||||
});
|
|
||||||
|
|
||||||
html += '</div>'; // close items
|
|
||||||
html += '</div>'; // close optgroup
|
|
||||||
});
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the method dropdown menu
|
|
||||||
*/
|
|
||||||
closeMethodDropdownMenu: function() {
|
|
||||||
if (this.$methodDropdownMenu) {
|
|
||||||
this.$methodDropdownMenu.remove();
|
|
||||||
this.$methodDropdownMenu = null;
|
|
||||||
}
|
|
||||||
this.$methodDropdownSelect = null;
|
|
||||||
this.$methodDropdownTrigger = null;
|
|
||||||
$(document).off('click.methodDropdown keydown.methodDropdown');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate tiles for multi_select_tiles value picker
|
|
||||||
*/
|
|
||||||
populateTiles: function($picker, options, exclusive) {
|
|
||||||
var self = this;
|
|
||||||
var $container = $picker.find('.multi-select-tiles');
|
|
||||||
$container.empty();
|
|
||||||
|
|
||||||
if (exclusive) {
|
|
||||||
$container.attr('data-exclusive', 'true');
|
|
||||||
} else {
|
|
||||||
$container.removeAttr('data-exclusive');
|
|
||||||
}
|
|
||||||
|
|
||||||
$.each(options, function(key, optData) {
|
|
||||||
var label = typeof optData === 'object' ? optData.label : optData;
|
|
||||||
var icon = typeof optData === 'object' && optData.icon ? optData.icon : null;
|
|
||||||
var color = typeof optData === 'object' && optData.color ? optData.color : null;
|
|
||||||
|
|
||||||
var tileClass = 'tile-option';
|
|
||||||
if (color) {
|
|
||||||
tileClass += ' tile-color-' + color;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $tile = $('<button>', {
|
|
||||||
type: 'button',
|
|
||||||
class: tileClass,
|
|
||||||
'data-value': key
|
|
||||||
});
|
|
||||||
|
|
||||||
if (icon) {
|
|
||||||
$tile.append($('<i>', { class: icon }));
|
|
||||||
}
|
|
||||||
$tile.append($('<span>', { class: 'tile-label', text: label }));
|
|
||||||
|
|
||||||
$container.append($tile);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply step/min constraints to numeric range inputs
|
|
||||||
*/
|
|
||||||
applyRangeInputConstraints: function($picker, step, min) {
|
|
||||||
var $inputs = $picker.find('.range-min-input, .range-max-input');
|
|
||||||
|
|
||||||
if (typeof step !== 'undefined' && step !== null) {
|
|
||||||
$inputs.attr('step', step);
|
|
||||||
} else {
|
|
||||||
$inputs.attr('step', 'any');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof min !== 'undefined' && min !== null) {
|
|
||||||
$inputs.attr('min', min);
|
|
||||||
} else {
|
|
||||||
$inputs.removeAttr('min');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show error message on range input
|
|
||||||
*/
|
|
||||||
showRangeInputError: function($input, message) {
|
|
||||||
var $container = $input.closest('.multi-range-input-row');
|
|
||||||
|
|
||||||
$container.find('.range-input-error').remove();
|
|
||||||
$container.find('.range-min-input, .range-max-input').removeClass('has-error');
|
|
||||||
|
|
||||||
$input.addClass('has-error');
|
|
||||||
var $error = $('<span>', {
|
|
||||||
class: 'range-input-error',
|
|
||||||
text: message
|
|
||||||
});
|
|
||||||
$container.append($error);
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
$input.removeClass('has-error');
|
|
||||||
$error.fadeOut(200, function() {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
}, 3000);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load attribute groups for combination picker
|
|
||||||
*/
|
|
||||||
loadCombinationAttributeGroups: function($picker) {
|
|
||||||
var self = this;
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
var $container = $picker.find('.combination-groups-container');
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: this.config.ajaxUrl,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
ajax: 1,
|
|
||||||
action: 'getAttributeGroups',
|
|
||||||
trait: 'TargetConditions'
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
$container.empty();
|
|
||||||
|
|
||||||
if (!response.success || !response.groups || response.groups.length === 0) {
|
|
||||||
$container.html('<span class="combination-empty">' +
|
|
||||||
self.escapeHtml(trans.no_attribute_groups || 'No attribute groups found') +
|
|
||||||
'</span>');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.groups.forEach(function(group) {
|
|
||||||
var $groupDiv = $('<div>', {
|
|
||||||
class: 'comb-attr-group',
|
|
||||||
'data-group-id': group.id
|
|
||||||
});
|
|
||||||
|
|
||||||
var $groupHeader = $('<div>', { class: 'comb-attr-group-header' });
|
|
||||||
$groupHeader.append($('<span>', {
|
|
||||||
class: 'comb-attr-group-name',
|
|
||||||
text: group.name
|
|
||||||
}));
|
|
||||||
$groupHeader.append($('<span>', {
|
|
||||||
class: 'comb-attr-group-count',
|
|
||||||
text: '0'
|
|
||||||
}));
|
|
||||||
|
|
||||||
var $toolbar = $('<div>', { class: 'comb-attr-toolbar' });
|
|
||||||
$toolbar.append($('<button>', {
|
|
||||||
type: 'button',
|
|
||||||
class: 'comb-toolbar-btn comb-select-all',
|
|
||||||
title: trans.select_all || 'Select all',
|
|
||||||
html: self.esIcon('check_box')
|
|
||||||
}));
|
|
||||||
$toolbar.append($('<button>', {
|
|
||||||
type: 'button',
|
|
||||||
class: 'comb-toolbar-btn comb-select-none',
|
|
||||||
title: trans.clear || 'Clear',
|
|
||||||
html: self.esIcon('check_box_outline_blank')
|
|
||||||
}));
|
|
||||||
$toolbar.append($('<input>', {
|
|
||||||
type: 'text',
|
|
||||||
class: 'comb-attr-search',
|
|
||||||
placeholder: trans.filter_results || 'Filter...'
|
|
||||||
}));
|
|
||||||
|
|
||||||
var $valuesContainer = $('<div>', {
|
|
||||||
class: 'comb-attr-values',
|
|
||||||
'data-loaded': 'false'
|
|
||||||
});
|
|
||||||
$valuesContainer.append($('<span>', {
|
|
||||||
class: 'comb-attr-loading',
|
|
||||||
html: self.esIcon('progress_activity', 'es-spin')
|
|
||||||
}));
|
|
||||||
|
|
||||||
$groupDiv.append($groupHeader);
|
|
||||||
$groupDiv.append($toolbar);
|
|
||||||
$groupDiv.append($valuesContainer);
|
|
||||||
$container.append($groupDiv);
|
|
||||||
|
|
||||||
self.loadCombinationAttributeValues($picker, group.id, $valuesContainer);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
$container.html('<span class="combination-error">' +
|
|
||||||
self.escapeHtml(trans.error_loading || 'Error loading attribute groups') +
|
|
||||||
'</span>');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load attribute values for a specific group
|
|
||||||
*/
|
|
||||||
loadCombinationAttributeValues: function($picker, groupId, $container) {
|
|
||||||
var self = this;
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: this.config.ajaxUrl,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
ajax: 1,
|
|
||||||
action: 'getAttributeValues',
|
|
||||||
trait: 'TargetConditions',
|
|
||||||
id_attribute_group: groupId
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
$container.empty();
|
|
||||||
$container.attr('data-loaded', 'true');
|
|
||||||
|
|
||||||
if (!response.success || !response.values || response.values.length === 0) {
|
|
||||||
$container.html('<span class="comb-attr-empty">' +
|
|
||||||
self.escapeHtml(trans.no_values || 'No values') +
|
|
||||||
'</span>');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.values.forEach(function(value) {
|
|
||||||
var productCount = parseInt(value.product_count) || 0;
|
|
||||||
var $valueBtn = $('<button>', {
|
|
||||||
type: 'button',
|
|
||||||
class: 'comb-attr-value',
|
|
||||||
'data-value-id': value.id,
|
|
||||||
'data-group-id': groupId,
|
|
||||||
'data-name': value.name.toLowerCase()
|
|
||||||
});
|
|
||||||
$valueBtn.append($('<span>', {
|
|
||||||
class: 'comb-attr-value-name',
|
|
||||||
text: value.name
|
|
||||||
}));
|
|
||||||
if (productCount > 0) {
|
|
||||||
$valueBtn.append($('<span>', {
|
|
||||||
class: 'comb-attr-value-count',
|
|
||||||
text: productCount
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
$container.append($valueBtn);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.restoreCombinationSelections($picker);
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
$container.html('<span class="comb-attr-error">' +
|
|
||||||
self.escapeHtml(trans.error_loading || 'Error') +
|
|
||||||
'</span>');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore previously selected combination values from hidden input
|
|
||||||
*/
|
|
||||||
restoreCombinationSelections: function($picker) {
|
|
||||||
var $dataInput = $picker.find('.include-values-data, .exclude-values-data').first();
|
|
||||||
var dataStr = $dataInput.val() || '{}';
|
|
||||||
var data;
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(dataStr);
|
|
||||||
} catch (e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var attributes = data.attributes || data;
|
|
||||||
var mode = data.mode || 'products';
|
|
||||||
|
|
||||||
$picker.find('.comb-mode-radio[value="' + mode + '"]').prop('checked', true);
|
|
||||||
|
|
||||||
$.each(attributes, function(groupId, valueIds) {
|
|
||||||
if (!Array.isArray(valueIds)) return;
|
|
||||||
|
|
||||||
valueIds.forEach(function(valueId) {
|
|
||||||
$picker.find('.comb-attr-value[data-group-id="' + groupId + '"][data-value-id="' + valueId + '"]')
|
|
||||||
.addClass('selected');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateCombinationGroupCounts($picker);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update hidden input with current combination selections
|
|
||||||
*/
|
|
||||||
updateCombinationData: function($picker) {
|
|
||||||
var attributes = {};
|
|
||||||
|
|
||||||
$picker.find('.comb-attr-value.selected').each(function() {
|
|
||||||
var groupId = $(this).data('groupId').toString();
|
|
||||||
var valueId = $(this).data('valueId');
|
|
||||||
|
|
||||||
if (!attributes[groupId]) {
|
|
||||||
attributes[groupId] = [];
|
|
||||||
}
|
|
||||||
attributes[groupId].push(valueId);
|
|
||||||
});
|
|
||||||
|
|
||||||
var $combPicker = $picker.find('.combination-attributes-picker');
|
|
||||||
var configMode = $combPicker.data('combinationMode') || this.config.combinationMode || 'products';
|
|
||||||
var mode;
|
|
||||||
|
|
||||||
if (configMode === 'toggle') {
|
|
||||||
mode = $picker.find('.comb-mode-radio:checked').val() || 'products';
|
|
||||||
} else {
|
|
||||||
mode = configMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
mode: mode,
|
|
||||||
attributes: attributes
|
|
||||||
};
|
|
||||||
|
|
||||||
var $dataInput = $picker.find('.include-values-data, .exclude-values-data').first();
|
|
||||||
$dataInput.val(JSON.stringify(data));
|
|
||||||
|
|
||||||
this.updateCombinationGroupCounts($picker);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the count badges on each attribute group
|
|
||||||
*/
|
|
||||||
updateCombinationGroupCounts: function($picker) {
|
|
||||||
$picker.find('.comb-attr-group').each(function() {
|
|
||||||
var $group = $(this);
|
|
||||||
var count = $group.find('.comb-attr-value.selected').length;
|
|
||||||
$group.find('.comb-attr-group-count').text(count);
|
|
||||||
|
|
||||||
if (count > 0) {
|
|
||||||
$group.addClass('has-selections');
|
|
||||||
} else {
|
|
||||||
$group.removeClass('has-selections');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the info placeholder based on method and block type
|
|
||||||
*/
|
|
||||||
updateMethodInfoPlaceholder: function($headerRow, method, blockType) {
|
|
||||||
var $placeholder = $headerRow.find('.method-info-placeholder');
|
|
||||||
if (!$placeholder.length) return;
|
|
||||||
|
|
||||||
$placeholder.empty();
|
|
||||||
|
|
||||||
var methodHelp = this.config.methodHelp || {};
|
|
||||||
var blockHelp = methodHelp[blockType] || methodHelp['products'] || {};
|
|
||||||
var helpContent = blockHelp[method] || this.getBuiltInMethodHelp(method);
|
|
||||||
|
|
||||||
if (helpContent) {
|
|
||||||
var $infoWrapper = $('<span>', {
|
|
||||||
class: 'mpr-info-wrapper',
|
|
||||||
'data-details': helpContent
|
|
||||||
});
|
|
||||||
$infoWrapper.append($(this.esIcon('info')));
|
|
||||||
$placeholder.append($infoWrapper);
|
|
||||||
|
|
||||||
// Let prestashop-admin info-tooltip.js handle this element
|
|
||||||
if (window.MPRInfoTooltip) {
|
|
||||||
window.MPRInfoTooltip.init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get built-in help content for targeting methods
|
|
||||||
*/
|
|
||||||
getBuiltInMethodHelp: function(method) {
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
var html = '';
|
|
||||||
|
|
||||||
switch (method) {
|
|
||||||
case 'all':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_all_title || 'All Items') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_all_desc || 'Selects all items without any filtering.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'specific':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_specific_title || 'Specific Items') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_specific_desc || 'Search and select individual items by name, reference, or ID.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_category':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_category_title || 'By Category') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_category_desc || 'Select items belonging to specific categories. Includes subcategories.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_manufacturer':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_manufacturer_title || 'By Manufacturer') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_manufacturer_desc || 'Select items from specific manufacturers/brands.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_supplier':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_supplier_title || 'By Supplier') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_supplier_desc || 'Select items from specific suppliers.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_tag':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_tag_title || 'By Tag') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_tag_desc || 'Select items with specific tags assigned.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_attribute':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_attribute_title || 'By Attribute') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_attribute_desc || 'Select items with specific attribute values (e.g., Color: Red).') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_feature':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_feature_title || 'By Feature') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_feature_desc || 'Select items with specific feature values (e.g., Material: Cotton).') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_combination':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_combination_title || 'Combination Targeting') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_combination_desc || 'Select items by combination attributes.') + '</p>';
|
|
||||||
html += '<p><strong>' + this.escapeHtml(trans.help_combination_logic || 'Logic:') + '</strong></p>';
|
|
||||||
html += '<ul>';
|
|
||||||
html += '<li>' + this.escapeHtml(trans.help_combination_within || 'Within group: OR (Red OR Blue)') + '</li>';
|
|
||||||
html += '<li>' + this.escapeHtml(trans.help_combination_between || 'Between groups: AND (Color AND Size)') + '</li>';
|
|
||||||
html += '</ul>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_carrier':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_carrier_title || 'By Carrier') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_carrier_desc || 'Select items available with specific carriers.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_condition':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_condition_title || 'By Condition') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_condition_desc || 'Filter by product condition: New, Used, or Refurbished.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_visibility':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_visibility_title || 'By Visibility') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_visibility_desc || 'Filter by where products are visible in the store.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_active_status':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_active_title || 'By Active Status') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_active_desc || 'Filter by whether products are enabled or disabled.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_stock_status':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_stock_title || 'By Stock Status') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_stock_desc || 'Filter by stock availability: In stock, Out of stock, or Low stock.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_on_sale':
|
|
||||||
case 'by_has_specific_price':
|
|
||||||
case 'by_is_virtual':
|
|
||||||
case 'by_is_pack':
|
|
||||||
case 'by_has_combinations':
|
|
||||||
case 'by_available_for_order':
|
|
||||||
case 'by_online_only':
|
|
||||||
case 'by_has_related':
|
|
||||||
case 'by_has_customization':
|
|
||||||
case 'by_has_attachments':
|
|
||||||
case 'by_has_additional_shipping':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_boolean_title || 'Yes/No Filter') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_boolean_desc || 'Filter products by this property.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_name_pattern':
|
|
||||||
case 'by_reference_pattern':
|
|
||||||
case 'by_description_pattern':
|
|
||||||
case 'by_long_description_pattern':
|
|
||||||
case 'by_ean13_pattern':
|
|
||||||
case 'by_upc_pattern':
|
|
||||||
case 'by_isbn_pattern':
|
|
||||||
case 'by_mpn_pattern':
|
|
||||||
case 'by_meta_title_pattern':
|
|
||||||
case 'by_meta_description_pattern':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_pattern_title || 'Pattern Matching') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_pattern_desc || 'Match text using patterns with wildcards.') + '</p>';
|
|
||||||
html += '<div><code>*</code> ' + this.escapeHtml(trans.help_pattern_wildcard || 'any text') + '</div>';
|
|
||||||
html += '<div><code>{number}</code> ' + this.escapeHtml(trans.help_pattern_number || 'any number') + '</div>';
|
|
||||||
html += '<div><code>{letter}</code> ' + this.escapeHtml(trans.help_pattern_letter || 'single letter A-Z') + '</div>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_id_range':
|
|
||||||
case 'by_price_range':
|
|
||||||
case 'by_weight_range':
|
|
||||||
case 'by_quantity_range':
|
|
||||||
case 'by_position_range':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_range_title || 'Numeric Range') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_range_desc || 'Filter by numeric values within specified ranges.') + '</p>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_range_tip || 'Leave min or max empty for open-ended ranges.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'by_date_added':
|
|
||||||
case 'by_date_updated':
|
|
||||||
html = '<strong>' + this.escapeHtml(trans.help_date_title || 'Date Range') + '</strong>';
|
|
||||||
html += '<p>' + this.escapeHtml(trans.help_date_desc || 'Filter by date within a specific period.') + '</p>';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build sort options HTML for a specific block type
|
|
||||||
*/
|
|
||||||
buildSortOptions: function(blockType) {
|
|
||||||
var options = [];
|
|
||||||
|
|
||||||
switch (blockType) {
|
|
||||||
case 'products':
|
|
||||||
options = [
|
|
||||||
{ value: 'sales', label: 'Best sellers' },
|
|
||||||
{ value: 'date_add', label: 'Newest' },
|
|
||||||
{ value: 'price', label: 'Price' },
|
|
||||||
{ value: 'name', label: 'Name' },
|
|
||||||
{ value: 'position', label: 'Position' },
|
|
||||||
{ value: 'quantity', label: 'Stock quantity' },
|
|
||||||
{ value: 'random', label: 'Random' }
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'categories':
|
|
||||||
options = [
|
|
||||||
{ value: 'name', label: 'Name' },
|
|
||||||
{ value: 'position', label: 'Position' },
|
|
||||||
{ value: 'product_count', label: 'Product count' },
|
|
||||||
{ value: 'total_sales', label: 'Best sellers' },
|
|
||||||
{ value: 'newest_products', label: 'Newest products' },
|
|
||||||
{ value: 'date_add', label: 'Creation date' },
|
|
||||||
{ value: 'random', label: 'Random' }
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'manufacturers':
|
|
||||||
case 'suppliers':
|
|
||||||
options = [
|
|
||||||
{ value: 'name', label: 'Name' },
|
|
||||||
{ value: 'product_count', label: 'Product count' },
|
|
||||||
{ value: 'total_sales', label: 'Best sellers' },
|
|
||||||
{ value: 'newest_products', label: 'Newest products' },
|
|
||||||
{ value: 'random', label: 'Random' }
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'cms':
|
|
||||||
case 'cms_categories':
|
|
||||||
options = [
|
|
||||||
{ value: 'name', label: 'Name' },
|
|
||||||
{ value: 'position', label: 'Position' },
|
|
||||||
{ value: 'random', label: 'Random' }
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
options = [
|
|
||||||
{ value: 'name', label: 'Name' },
|
|
||||||
{ value: 'random', label: 'Random' }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
var html = '';
|
|
||||||
for (var i = 0; i < options.length; i++) {
|
|
||||||
html += '<option value="' + this.escapeAttr(options[i].value) + '">' +
|
|
||||||
this.escapeHtml(options[i].label) + '</option>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the modifier toggle button state
|
|
||||||
*/
|
|
||||||
updateModifierButtonState: function($group) {
|
|
||||||
var limit = $group.find('.group-modifier-limit').val();
|
|
||||||
var sortBy = $group.find('.group-modifier-sort').val();
|
|
||||||
var $modifiers = $group.find('.group-modifiers');
|
|
||||||
var $btn = $group.find('.btn-toggle-modifiers');
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
|
|
||||||
$btn.find('.modifier-summary').remove();
|
|
||||||
|
|
||||||
if (limit || sortBy) {
|
|
||||||
$modifiers.addClass('has-values');
|
|
||||||
|
|
||||||
var summary = [];
|
|
||||||
if (limit) {
|
|
||||||
summary.push((trans.top || 'Top') + ' ' + limit);
|
|
||||||
}
|
|
||||||
if (sortBy) {
|
|
||||||
var sortLabel = $group.find('.group-modifier-sort option:selected').text();
|
|
||||||
summary.push(sortLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
var $arrow = $btn.find('.toggle-arrow');
|
|
||||||
$('<span class="modifier-summary">' + this.escapeHtml(summary.join(', ')) + '</span>').insertBefore($arrow);
|
|
||||||
} else {
|
|
||||||
$modifiers.removeClass('has-values');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lock/unlock method selector when excludes are present
|
|
||||||
*/
|
|
||||||
updateMethodSelectorLock: function($group, locked) {
|
|
||||||
var $select = $group.find('.include-method-select');
|
|
||||||
var $wrapper = $select.closest('.method-selector-wrapper');
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
|
|
||||||
if (locked) {
|
|
||||||
$select.prop('disabled', true);
|
|
||||||
|
|
||||||
if (!$wrapper.length) {
|
|
||||||
$select.wrap('<div class="method-selector-wrapper"></div>');
|
|
||||||
$wrapper = $select.parent('.method-selector-wrapper');
|
|
||||||
}
|
|
||||||
|
|
||||||
$wrapper.addClass('selector-locked');
|
|
||||||
if (!$wrapper.find('.lock-indicator').length) {
|
|
||||||
var lockHtml = '<span class="mpr-info-wrapper lock-indicator">' +
|
|
||||||
self.esIcon('lock') +
|
|
||||||
'<span class="mpr-tooltip">' +
|
|
||||||
(trans.remove_excludes_first || 'Remove all exceptions to change selection type') +
|
|
||||||
'</span>' +
|
|
||||||
'</span>';
|
|
||||||
var $countEl = $wrapper.find('.condition-match-count');
|
|
||||||
if ($countEl.length) {
|
|
||||||
$countEl.before(lockHtml);
|
|
||||||
} else {
|
|
||||||
$wrapper.append(lockHtml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$select.prop('disabled', false);
|
|
||||||
if ($wrapper.length) {
|
|
||||||
$wrapper.removeClass('selector-locked');
|
|
||||||
$wrapper.find('.mpr-info-wrapper.lock-indicator').remove();
|
|
||||||
} else {
|
|
||||||
$select.siblings('.mpr-info-wrapper.lock-indicator').remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,881 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Search Module
|
|
||||||
* AJAX search, results rendering, category tree, filters, search history
|
|
||||||
* @partial _search.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
window._EntitySelectorMixins = window._EntitySelectorMixins || {};
|
|
||||||
|
|
||||||
window._EntitySelectorMixins.search = {
|
|
||||||
|
|
||||||
// Category tree cache
|
|
||||||
categoryTreeCache: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform AJAX search for entities
|
|
||||||
*/
|
|
||||||
performSearch: function(appendMode) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (!this.activeGroup) return;
|
|
||||||
|
|
||||||
this.isLoading = true;
|
|
||||||
|
|
||||||
var searchEntity = this.activeGroup.searchEntity;
|
|
||||||
|
|
||||||
// Build request data with sort and filter params
|
|
||||||
var limit = appendMode && this.loadMoreCount ? this.loadMoreCount : 20;
|
|
||||||
var requestData = {
|
|
||||||
ajax: 1,
|
|
||||||
action: 'searchTargetEntities',
|
|
||||||
trait: 'EntitySelector',
|
|
||||||
entity_type: searchEntity,
|
|
||||||
q: this.searchQuery,
|
|
||||||
limit: limit,
|
|
||||||
offset: appendMode ? this.searchOffset : 0,
|
|
||||||
sort_by: this.currentSort ? this.currentSort.field : 'name',
|
|
||||||
sort_dir: this.currentSort ? this.currentSort.dir : 'ASC'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add refine query if present
|
|
||||||
if (this.refineQuery) {
|
|
||||||
requestData.refine = this.refineQuery;
|
|
||||||
if (this.refineNegate) {
|
|
||||||
requestData.refine_negate = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add product-specific filters
|
|
||||||
if (searchEntity === 'products' && this.filters) {
|
|
||||||
if (this.filters.inStock) {
|
|
||||||
requestData.filter_in_stock = 1;
|
|
||||||
}
|
|
||||||
if (this.filters.discounted) {
|
|
||||||
requestData.filter_discounted = 1;
|
|
||||||
}
|
|
||||||
if (this.filters.priceMin !== null && this.filters.priceMin !== '') {
|
|
||||||
requestData.filter_price_min = this.filters.priceMin;
|
|
||||||
}
|
|
||||||
if (this.filters.priceMax !== null && this.filters.priceMax !== '') {
|
|
||||||
requestData.filter_price_max = this.filters.priceMax;
|
|
||||||
}
|
|
||||||
if (this.filters.attributes && this.filters.attributes.length > 0) {
|
|
||||||
requestData.filter_attributes = JSON.stringify(this.filters.attributes);
|
|
||||||
}
|
|
||||||
if (this.filters.features && this.filters.features.length > 0) {
|
|
||||||
requestData.filter_features = JSON.stringify(this.filters.features);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add entity-specific filters for non-product entities
|
|
||||||
if (searchEntity !== 'products' && this.filters) {
|
|
||||||
// Product count range (categories, manufacturers, suppliers, attributes, features)
|
|
||||||
if (this.filters.productCountMin !== null && this.filters.productCountMin !== '') {
|
|
||||||
requestData.filter_product_count_min = this.filters.productCountMin;
|
|
||||||
}
|
|
||||||
if (this.filters.productCountMax !== null && this.filters.productCountMax !== '') {
|
|
||||||
requestData.filter_product_count_max = this.filters.productCountMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Category-specific
|
|
||||||
if (searchEntity === 'categories') {
|
|
||||||
if (this.filters.depth) {
|
|
||||||
requestData.filter_depth = this.filters.depth;
|
|
||||||
}
|
|
||||||
if (this.filters.hasProducts) {
|
|
||||||
requestData.filter_has_products = 1;
|
|
||||||
}
|
|
||||||
if (this.filters.hasDescription) {
|
|
||||||
requestData.filter_has_description = 1;
|
|
||||||
}
|
|
||||||
if (this.filters.hasImage) {
|
|
||||||
requestData.filter_has_image = 1;
|
|
||||||
}
|
|
||||||
if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
|
|
||||||
requestData.filter_sales_min = this.filters.salesMin;
|
|
||||||
}
|
|
||||||
if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
|
|
||||||
requestData.filter_sales_max = this.filters.salesMax;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
|
|
||||||
requestData.filter_turnover_min = this.filters.turnoverMin;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
|
|
||||||
requestData.filter_turnover_max = this.filters.turnoverMax;
|
|
||||||
}
|
|
||||||
if (this.filters.activeOnly) {
|
|
||||||
requestData.filter_active = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manufacturer-specific
|
|
||||||
if (searchEntity === 'manufacturers') {
|
|
||||||
if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
|
|
||||||
requestData.filter_sales_min = this.filters.salesMin;
|
|
||||||
}
|
|
||||||
if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
|
|
||||||
requestData.filter_sales_max = this.filters.salesMax;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
|
|
||||||
requestData.filter_turnover_min = this.filters.turnoverMin;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
|
|
||||||
requestData.filter_turnover_max = this.filters.turnoverMax;
|
|
||||||
}
|
|
||||||
if (this.filters.dateAddFrom) {
|
|
||||||
requestData.filter_date_add_from = this.filters.dateAddFrom;
|
|
||||||
}
|
|
||||||
if (this.filters.dateAddTo) {
|
|
||||||
requestData.filter_date_add_to = this.filters.dateAddTo;
|
|
||||||
}
|
|
||||||
if (this.filters.lastProductFrom) {
|
|
||||||
requestData.filter_last_product_from = this.filters.lastProductFrom;
|
|
||||||
}
|
|
||||||
if (this.filters.lastProductTo) {
|
|
||||||
requestData.filter_last_product_to = this.filters.lastProductTo;
|
|
||||||
}
|
|
||||||
if (this.filters.activeOnly) {
|
|
||||||
requestData.filter_active = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supplier-specific
|
|
||||||
if (searchEntity === 'suppliers') {
|
|
||||||
if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
|
|
||||||
requestData.filter_sales_min = this.filters.salesMin;
|
|
||||||
}
|
|
||||||
if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
|
|
||||||
requestData.filter_sales_max = this.filters.salesMax;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
|
|
||||||
requestData.filter_turnover_min = this.filters.turnoverMin;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
|
|
||||||
requestData.filter_turnover_max = this.filters.turnoverMax;
|
|
||||||
}
|
|
||||||
if (this.filters.dateAddFrom) {
|
|
||||||
requestData.filter_date_add_from = this.filters.dateAddFrom;
|
|
||||||
}
|
|
||||||
if (this.filters.dateAddTo) {
|
|
||||||
requestData.filter_date_add_to = this.filters.dateAddTo;
|
|
||||||
}
|
|
||||||
if (this.filters.lastProductFrom) {
|
|
||||||
requestData.filter_last_product_from = this.filters.lastProductFrom;
|
|
||||||
}
|
|
||||||
if (this.filters.lastProductTo) {
|
|
||||||
requestData.filter_last_product_to = this.filters.lastProductTo;
|
|
||||||
}
|
|
||||||
if (this.filters.activeOnly) {
|
|
||||||
requestData.filter_active = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute-specific
|
|
||||||
if (searchEntity === 'attributes') {
|
|
||||||
if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
|
|
||||||
requestData.filter_sales_min = this.filters.salesMin;
|
|
||||||
}
|
|
||||||
if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
|
|
||||||
requestData.filter_sales_max = this.filters.salesMax;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
|
|
||||||
requestData.filter_turnover_min = this.filters.turnoverMin;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
|
|
||||||
requestData.filter_turnover_max = this.filters.turnoverMax;
|
|
||||||
}
|
|
||||||
if (this.filters.attributeGroup) {
|
|
||||||
requestData.filter_attribute_group = this.filters.attributeGroup;
|
|
||||||
}
|
|
||||||
if (this.filters.isColor) {
|
|
||||||
requestData.filter_is_color = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feature-specific
|
|
||||||
if (searchEntity === 'features') {
|
|
||||||
if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
|
|
||||||
requestData.filter_sales_min = this.filters.salesMin;
|
|
||||||
}
|
|
||||||
if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
|
|
||||||
requestData.filter_sales_max = this.filters.salesMax;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
|
|
||||||
requestData.filter_turnover_min = this.filters.turnoverMin;
|
|
||||||
}
|
|
||||||
if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
|
|
||||||
requestData.filter_turnover_max = this.filters.turnoverMax;
|
|
||||||
}
|
|
||||||
if (this.filters.featureGroup) {
|
|
||||||
requestData.filter_feature_group = this.filters.featureGroup;
|
|
||||||
}
|
|
||||||
if (this.filters.isCustom) {
|
|
||||||
requestData.filter_is_custom = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CMS-specific
|
|
||||||
if (searchEntity === 'cms') {
|
|
||||||
if (this.filters.activeOnly) {
|
|
||||||
requestData.filter_active = 1;
|
|
||||||
}
|
|
||||||
if (this.filters.indexable) {
|
|
||||||
requestData.filter_indexable = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CMS Categories-specific
|
|
||||||
if (searchEntity === 'cms_categories') {
|
|
||||||
if (this.filters.activeOnly) {
|
|
||||||
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({
|
|
||||||
url: this.config.ajaxUrl,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: requestData,
|
|
||||||
success: function(response) {
|
|
||||||
self.isLoading = false;
|
|
||||||
|
|
||||||
if (!response.success) return;
|
|
||||||
|
|
||||||
// Save to search history if query is not empty and has results
|
|
||||||
if (self.searchQuery && self.searchQuery.length >= 2 && response.total > 0) {
|
|
||||||
self.addToSearchHistory(searchEntity, self.searchQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appendMode) {
|
|
||||||
self.searchResults = self.searchResults.concat(response.results || []);
|
|
||||||
} else {
|
|
||||||
self.searchResults = response.results || [];
|
|
||||||
}
|
|
||||||
self.searchTotal = response.total || 0;
|
|
||||||
self.searchOffset = appendMode ? self.searchOffset + (response.results || []).length : (response.results || []).length;
|
|
||||||
|
|
||||||
self.renderSearchResults(appendMode);
|
|
||||||
self.$dropdown.addClass('show');
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
self.isLoading = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render search results in the dropdown
|
|
||||||
*/
|
|
||||||
renderSearchResults: function(appendMode) {
|
|
||||||
var self = this;
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
var $container = this.$dropdown.find('.dropdown-results');
|
|
||||||
|
|
||||||
// Get selected IDs from current picker (to mark as selected)
|
|
||||||
// and hidden IDs from sibling exclude pickers with same entity type (to hide completely)
|
|
||||||
var selectedIds = [];
|
|
||||||
var hiddenIds = [];
|
|
||||||
if (this.activeGroup) {
|
|
||||||
var $block = this.$wrapper.find('.target-block[data-block-type="' + this.activeGroup.blockType + '"]');
|
|
||||||
var $group = $block.find('.selection-group[data-group-index="' + this.activeGroup.groupIndex + '"]');
|
|
||||||
var currentSearchEntity = this.activeGroup.searchEntity;
|
|
||||||
var currentExcludeIndex = this.activeGroup.excludeIndex;
|
|
||||||
|
|
||||||
if (this.activeGroup.section === 'include') {
|
|
||||||
// For include section, just get current picker's selections
|
|
||||||
var $picker = $group.find('.include-picker');
|
|
||||||
$picker.find('.entity-chip').each(function() {
|
|
||||||
selectedIds.push(String($(this).data('id')));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// For exclude section, get current picker's selections AND
|
|
||||||
// collect IDs from sibling exclude rows with same entity type to hide
|
|
||||||
var $currentExcludeRow = $group.find('.exclude-row[data-exclude-index="' + currentExcludeIndex + '"]');
|
|
||||||
var $currentPicker = $currentExcludeRow.find('.exclude-picker');
|
|
||||||
|
|
||||||
// Get selected IDs from current exclude row
|
|
||||||
$currentPicker.find('.entity-chip').each(function() {
|
|
||||||
selectedIds.push(String($(this).data('id')));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get hidden IDs from OTHER exclude rows with the same entity type
|
|
||||||
$group.find('.exclude-row').each(function() {
|
|
||||||
var $row = $(this);
|
|
||||||
var rowIndex = parseInt($row.data('excludeIndex'), 10);
|
|
||||||
|
|
||||||
// Skip current exclude row
|
|
||||||
if (rowIndex === currentExcludeIndex) return;
|
|
||||||
|
|
||||||
var $picker = $row.find('.exclude-picker');
|
|
||||||
var rowEntityType = $picker.attr('data-search-entity') || self.activeGroup.blockType;
|
|
||||||
|
|
||||||
// Only collect if same entity type
|
|
||||||
if (rowEntityType === currentSearchEntity) {
|
|
||||||
$picker.find('.entity-chip').each(function() {
|
|
||||||
hiddenIds.push(String($(this).data('id')));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is a product search
|
|
||||||
var isProductSearch = this.activeGroup && this.activeGroup.searchEntity === 'products';
|
|
||||||
var isListView = this.viewMode === 'list';
|
|
||||||
|
|
||||||
// Show/hide results header for products in list view
|
|
||||||
this.$dropdown.find('.results-header').toggle(isProductSearch && isListView);
|
|
||||||
|
|
||||||
// Build HTML - filter out items that are hidden (selected in sibling exclude rows)
|
|
||||||
var visibleResults = this.searchResults.filter(function(item) {
|
|
||||||
return hiddenIds.indexOf(String(item.id)) === -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update count (show visible count and total, noting hidden items if any)
|
|
||||||
var hiddenCount = this.searchResults.length - visibleResults.length;
|
|
||||||
var countText = visibleResults.length + ' / ' + this.searchTotal + ' results';
|
|
||||||
if (hiddenCount > 0) {
|
|
||||||
countText += ' (' + hiddenCount + ' hidden)';
|
|
||||||
}
|
|
||||||
this.$dropdown.find('.results-count').text(countText);
|
|
||||||
|
|
||||||
var html = '';
|
|
||||||
if (visibleResults.length === 0 && !appendMode) {
|
|
||||||
html = '<div class="no-results">' + this.esIcon('search') + ' ' + (trans.no_results || 'No results found') + '</div>';
|
|
||||||
} else {
|
|
||||||
visibleResults.forEach(function(item) {
|
|
||||||
var isSelected = selectedIds.indexOf(String(item.id)) !== -1;
|
|
||||||
var itemClass = 'dropdown-item' + (isSelected ? ' selected' : '');
|
|
||||||
if (item.type === 'product') itemClass += ' result-item-product';
|
|
||||||
|
|
||||||
html += '<div class="' + itemClass + '" ';
|
|
||||||
html += 'data-id="' + self.escapeAttr(item.id) + '" ';
|
|
||||||
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">' + self.esIcon('check') + '</span>';
|
|
||||||
|
|
||||||
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;">' + self.esIcon('flag') + '</span></div>';
|
|
||||||
} else if (item.image) {
|
|
||||||
html += '<div class="result-image"><img src="' + self.escapeAttr(item.image) + '" alt=""></div>';
|
|
||||||
} else {
|
|
||||||
// Entity-specific icons
|
|
||||||
var iconName = 'widgets'; // default
|
|
||||||
if (searchEntity === 'categories') iconName = 'folder';
|
|
||||||
else if (searchEntity === 'manufacturers') iconName = 'business';
|
|
||||||
else if (searchEntity === 'suppliers') iconName = 'local_shipping';
|
|
||||||
else if (searchEntity === 'attributes') iconName = 'brush';
|
|
||||||
else if (searchEntity === 'features') iconName = 'list';
|
|
||||||
else if (searchEntity === 'cms') iconName = 'description';
|
|
||||||
else if (searchEntity === 'cms_categories') iconName = 'folder';
|
|
||||||
html += '<div class="result-icon">' + self.esIcon(iconName) + '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<div class="result-info">';
|
|
||||||
html += '<div class="result-name">' + self.escapeHtml(item.name) + '</div>';
|
|
||||||
if (item.subtitle) {
|
|
||||||
// Split multi-line subtitles into separate divs for styling
|
|
||||||
var subtitleLines = item.subtitle.split('\n');
|
|
||||||
html += '<div class="result-subtitle">';
|
|
||||||
subtitleLines.forEach(function(line, idx) {
|
|
||||||
var lineClass = idx === 0 ? 'subtitle-line subtitle-line-primary' : 'subtitle-line subtitle-line-secondary';
|
|
||||||
html += '<div class="' + lineClass + '">' + self.escapeHtml(line) + '</div>';
|
|
||||||
});
|
|
||||||
html += '</div>';
|
|
||||||
}
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Add product-specific columns (price, sale price, stock, sold)
|
|
||||||
if (item.type === 'product') {
|
|
||||||
if (isListView) {
|
|
||||||
// List view: full columns
|
|
||||||
// Regular price
|
|
||||||
html += '<div class="result-col result-col-price">';
|
|
||||||
html += '<span class="col-value">' + (item.regular_price_formatted || item.price_formatted || '') + '</span>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Sale price (only if discounted)
|
|
||||||
if (item.has_discount) {
|
|
||||||
html += '<div class="result-col result-col-sale">';
|
|
||||||
html += '<span class="col-value">' + (item.price_formatted || '') + '</span>';
|
|
||||||
html += '</div>';
|
|
||||||
} else {
|
|
||||||
html += '<div class="result-col result-col-sale"></div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stock column
|
|
||||||
var stockClass = item.stock_status === 'out_of_stock' ? 'stock-out' :
|
|
||||||
(item.stock_status === 'low_stock' ? 'stock-low' : 'stock-ok');
|
|
||||||
html += '<div class="result-col result-col-stock">';
|
|
||||||
html += '<span class="col-value ' + stockClass + '">' + (item.stock_qty !== undefined ? item.stock_qty : '') + '</span>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Sales column
|
|
||||||
html += '<div class="result-col result-col-sales">';
|
|
||||||
html += '<span class="col-value">' + (item.sales_qty !== undefined ? item.sales_qty : '0') + '</span>';
|
|
||||||
html += '</div>';
|
|
||||||
} else {
|
|
||||||
// Grid view: compact info line
|
|
||||||
var gridStockClass = item.stock_status === 'out_of_stock' ? 'stock-out' :
|
|
||||||
(item.stock_status === 'low_stock' ? 'stock-low' : '');
|
|
||||||
html += '<div class="result-grid-info">';
|
|
||||||
html += '<span class="grid-price">' + (item.price_formatted || '') + '</span>';
|
|
||||||
if (item.stock_qty !== undefined) {
|
|
||||||
html += '<span class="grid-stock ' + gridStockClass + '">' + item.stock_qty + ' qty</span>';
|
|
||||||
}
|
|
||||||
if (item.has_discount) {
|
|
||||||
html += '<span class="grid-discount">-' + (item.discount_percent || '') + '%</span>';
|
|
||||||
}
|
|
||||||
html += '</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appendMode) {
|
|
||||||
$container.append(html);
|
|
||||||
} else {
|
|
||||||
$container.html(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show/hide load more controls and update remaining count
|
|
||||||
var hasMore = this.searchResults.length < this.searchTotal;
|
|
||||||
var $loadMoreControls = this.$dropdown.find('.load-more-controls');
|
|
||||||
$loadMoreControls.toggle(hasMore);
|
|
||||||
|
|
||||||
if (hasMore) {
|
|
||||||
var remaining = this.searchTotal - this.searchResults.length;
|
|
||||||
$loadMoreControls.find('.remaining-count').text(remaining);
|
|
||||||
|
|
||||||
// Update "All" option in dropdown
|
|
||||||
var $select = $loadMoreControls.find('.load-more-select');
|
|
||||||
var $allOption = $select.find('option[data-all="true"]');
|
|
||||||
if ($allOption.length) {
|
|
||||||
$allOption.val(remaining).text((trans.all || 'All') + ' (' + remaining + ')');
|
|
||||||
} else {
|
|
||||||
$select.find('option:last').after('<option value="' + remaining + '" data-all="true">' + (trans.all || 'All') + ' (' + remaining + ')</option>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure dropdown-actions are visible and history button is deactivated
|
|
||||||
this.$dropdown.find('.dropdown-actions').show();
|
|
||||||
this.$dropdown.find('.btn-show-history').removeClass('active');
|
|
||||||
|
|
||||||
// Disable history button if no search history for current entity type
|
|
||||||
var entityType = this.activeGroup ? this.activeGroup.searchEntity : null;
|
|
||||||
var hasHistory = entityType && this.getSearchHistory(entityType).length > 0;
|
|
||||||
this.$dropdown.find('.btn-show-history').prop('disabled', !hasHistory);
|
|
||||||
},
|
|
||||||
|
|
||||||
// NOTE: Tree methods (loadCategoryTree, renderCategoryTree, filterCategoryTree,
|
|
||||||
// findTreeDescendants, findTreeAncestors, updateSelectChildrenButtons) are
|
|
||||||
// defined in _tree.js which is merged later and takes precedence.
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// Search History
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
loadSearchHistory: function() {
|
|
||||||
try {
|
|
||||||
var stored = localStorage.getItem(this.searchHistoryKey);
|
|
||||||
this.searchHistory = stored ? JSON.parse(stored) : {};
|
|
||||||
} catch (e) {
|
|
||||||
this.searchHistory = {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
saveSearchHistory: function() {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(this.searchHistoryKey, JSON.stringify(this.searchHistory));
|
|
||||||
} catch (e) {
|
|
||||||
// localStorage might be full or unavailable
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addToSearchHistory: function(entityType, query) {
|
|
||||||
if (!query || query.length < 2) return;
|
|
||||||
|
|
||||||
if (!this.searchHistory[entityType]) {
|
|
||||||
this.searchHistory[entityType] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var history = this.searchHistory[entityType];
|
|
||||||
|
|
||||||
// Remove if already exists (will re-add at top)
|
|
||||||
var existingIndex = history.indexOf(query);
|
|
||||||
if (existingIndex !== -1) {
|
|
||||||
history.splice(existingIndex, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add at beginning
|
|
||||||
history.unshift(query);
|
|
||||||
|
|
||||||
// Trim to max
|
|
||||||
if (history.length > this.searchHistoryMax) {
|
|
||||||
history = history.slice(0, this.searchHistoryMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.searchHistory[entityType] = history;
|
|
||||||
this.saveSearchHistory();
|
|
||||||
},
|
|
||||||
|
|
||||||
removeFromSearchHistory: function(entityType, query) {
|
|
||||||
if (!this.searchHistory[entityType]) return;
|
|
||||||
|
|
||||||
var index = this.searchHistory[entityType].indexOf(query);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.searchHistory[entityType].splice(index, 1);
|
|
||||||
this.saveSearchHistory();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getSearchHistory: function(entityType) {
|
|
||||||
return this.searchHistory[entityType] || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
showSearchHistory: function(entityType) {
|
|
||||||
var history = this.getSearchHistory(entityType);
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
var $container = this.$dropdown.find('.dropdown-results');
|
|
||||||
|
|
||||||
// Update header
|
|
||||||
this.$dropdown.find('.results-count').text(trans.recent_searches || 'Recent searches');
|
|
||||||
|
|
||||||
// Hide filters, actions, and results header for history view
|
|
||||||
this.$dropdown.find('.dropdown-actions').hide();
|
|
||||||
this.$dropdown.find('.filter-panel').removeClass('show');
|
|
||||||
this.$dropdown.find('.btn-toggle-filters').removeClass('active');
|
|
||||||
this.$dropdown.find('.results-header').hide();
|
|
||||||
|
|
||||||
if (!history.length) {
|
|
||||||
// No history - just do a regular search
|
|
||||||
this.performSearch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build history items
|
|
||||||
var html = '<div class="search-history-list">';
|
|
||||||
for (var i = 0; i < history.length; i++) {
|
|
||||||
var query = history[i];
|
|
||||||
html += '<div class="history-item" data-query="' + this.escapeAttr(query) + '">';
|
|
||||||
html += this.esIcon('schedule');
|
|
||||||
html += '<span class="history-query">' + this.escapeHtml(query) + '</span>';
|
|
||||||
html += '<button type="button" class="btn-delete-history" title="' + (trans.remove || 'Remove') + '">';
|
|
||||||
html += this.esIcon('close');
|
|
||||||
html += '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
}
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
$container.html(html);
|
|
||||||
this.$dropdown.addClass('show');
|
|
||||||
},
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// Filter Methods
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
refreshSearch: function() {
|
|
||||||
// In tree view mode, re-filter the tree instead of doing a flat AJAX search
|
|
||||||
if (this.viewMode === 'tree') {
|
|
||||||
this.filterCategoryTree(this.searchQuery || '');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.searchOffset = 0;
|
|
||||||
this.loadMoreCount = 20;
|
|
||||||
// Reset load more select to default
|
|
||||||
if (this.$dropdown) {
|
|
||||||
this.$dropdown.find('.load-more-select').val('20');
|
|
||||||
// Remove the dynamic "All" option
|
|
||||||
this.$dropdown.find('.load-more-select option[data-all="true"]').remove();
|
|
||||||
}
|
|
||||||
this.performSearch(false);
|
|
||||||
},
|
|
||||||
|
|
||||||
clearFilters: function() {
|
|
||||||
this.refineQuery = '';
|
|
||||||
this.refineNegate = false;
|
|
||||||
this.filters = {
|
|
||||||
inStock: false,
|
|
||||||
discounted: false,
|
|
||||||
priceMin: null,
|
|
||||||
priceMax: null,
|
|
||||||
attributes: [],
|
|
||||||
features: [],
|
|
||||||
// Entity-specific filters
|
|
||||||
productCountMin: null,
|
|
||||||
productCountMax: null,
|
|
||||||
salesMin: null,
|
|
||||||
salesMax: null,
|
|
||||||
turnoverMin: null,
|
|
||||||
turnoverMax: null,
|
|
||||||
depth: null,
|
|
||||||
hasProducts: false,
|
|
||||||
hasDescription: false,
|
|
||||||
hasImage: false,
|
|
||||||
activeOnly: true,
|
|
||||||
attributeGroup: null,
|
|
||||||
featureGroup: null,
|
|
||||||
dateAddFrom: null,
|
|
||||||
dateAddTo: null,
|
|
||||||
lastProductFrom: null,
|
|
||||||
lastProductTo: null,
|
|
||||||
// Country-specific filters
|
|
||||||
hasHolidays: false,
|
|
||||||
containsStates: false,
|
|
||||||
zone: null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.$dropdown) {
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
this.$dropdown.find('.refine-input').val('').attr('placeholder', trans.refine_short || 'Refine...');
|
|
||||||
this.$dropdown.find('.btn-clear-refine').hide();
|
|
||||||
this.$dropdown.find('.btn-refine-negate').removeClass('active');
|
|
||||||
this.$dropdown.find('.filter-in-stock').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-discounted').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-price-min').val('');
|
|
||||||
this.$dropdown.find('.filter-price-max').val('');
|
|
||||||
this.$dropdown.find('.filter-attr-chip').removeClass('active');
|
|
||||||
this.$dropdown.find('.filter-feat-chip').removeClass('active');
|
|
||||||
this.$dropdown.find('.filter-group-toggle').removeClass('active has-selection');
|
|
||||||
this.$dropdown.find('.filter-row-values').hide();
|
|
||||||
|
|
||||||
// Clear entity-specific filter inputs
|
|
||||||
this.$dropdown.find('.filter-product-count-min, .filter-product-count-max').val('');
|
|
||||||
this.$dropdown.find('.filter-sales-min, .filter-sales-max').val('');
|
|
||||||
this.$dropdown.find('.filter-turnover-min, .filter-turnover-max').val('');
|
|
||||||
this.$dropdown.find('.filter-date-add-from, .filter-date-add-to').val('');
|
|
||||||
this.$dropdown.find('.filter-last-product-from, .filter-last-product-to').val('');
|
|
||||||
this.$dropdown.find('.filter-depth-select').val('');
|
|
||||||
this.$dropdown.find('.filter-has-products').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-has-description').prop('checked', false);
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Reset filters without triggering a search (used when switching entity types)
|
|
||||||
resetFiltersWithoutSearch: function() {
|
|
||||||
this.refineQuery = '';
|
|
||||||
this.refineNegate = false;
|
|
||||||
this.filters = {
|
|
||||||
inStock: false,
|
|
||||||
discounted: false,
|
|
||||||
priceMin: null,
|
|
||||||
priceMax: null,
|
|
||||||
attributes: [],
|
|
||||||
features: [],
|
|
||||||
productCountMin: null,
|
|
||||||
productCountMax: null,
|
|
||||||
salesMin: null,
|
|
||||||
salesMax: null,
|
|
||||||
turnoverMin: null,
|
|
||||||
turnoverMax: null,
|
|
||||||
depth: null,
|
|
||||||
hasProducts: false,
|
|
||||||
hasDescription: false,
|
|
||||||
hasImage: false,
|
|
||||||
activeOnly: true,
|
|
||||||
attributeGroup: null,
|
|
||||||
featureGroup: null,
|
|
||||||
dateAddFrom: null,
|
|
||||||
dateAddTo: null,
|
|
||||||
lastProductFrom: null,
|
|
||||||
lastProductTo: null,
|
|
||||||
// Country-specific filters
|
|
||||||
hasHolidays: false,
|
|
||||||
containsStates: false,
|
|
||||||
zone: null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.$dropdown) {
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
this.$dropdown.find('.refine-input').val('').attr('placeholder', trans.refine_short || 'Refine...');
|
|
||||||
this.$dropdown.find('.btn-clear-refine').hide();
|
|
||||||
this.$dropdown.find('.btn-refine-negate').removeClass('active');
|
|
||||||
this.$dropdown.find('.filter-in-stock').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-discounted').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-price-min').val('');
|
|
||||||
this.$dropdown.find('.filter-price-max').val('');
|
|
||||||
this.$dropdown.find('.filter-attr-chip').removeClass('active');
|
|
||||||
this.$dropdown.find('.filter-feat-chip').removeClass('active');
|
|
||||||
this.$dropdown.find('.filter-group-toggle').removeClass('active has-selection');
|
|
||||||
this.$dropdown.find('.filter-row-values').hide();
|
|
||||||
this.$dropdown.find('.filter-product-count-min, .filter-product-count-max').val('');
|
|
||||||
this.$dropdown.find('.filter-sales-min, .filter-sales-max').val('');
|
|
||||||
this.$dropdown.find('.filter-turnover-min, .filter-turnover-max').val('');
|
|
||||||
this.$dropdown.find('.filter-date-add-from, .filter-date-add-to').val('');
|
|
||||||
this.$dropdown.find('.filter-last-product-from, .filter-last-product-to').val('');
|
|
||||||
this.$dropdown.find('.filter-depth-select').val('');
|
|
||||||
this.$dropdown.find('.filter-has-products').prop('checked', false);
|
|
||||||
this.$dropdown.find('.filter-has-description').prop('checked', false);
|
|
||||||
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
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFilterPanelForEntity: function(entityType) {
|
|
||||||
if (!this.$dropdown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $panel = this.$dropdown.find('.filter-panel');
|
|
||||||
|
|
||||||
// Hide all filter rows first
|
|
||||||
$panel.find('.filter-row').hide();
|
|
||||||
|
|
||||||
// Show/hide tree view option based on entity type
|
|
||||||
var $treeOption = this.$dropdown.find('.view-mode-select option.tree-view-option');
|
|
||||||
if (entityType === 'categories' || entityType === 'cms_categories') {
|
|
||||||
$treeOption.prop('disabled', false).prop('hidden', false);
|
|
||||||
// Auto-switch to tree view for categories
|
|
||||||
if (this.viewMode !== 'tree') {
|
|
||||||
this.viewMode = 'tree';
|
|
||||||
this.$dropdown.find('.view-mode-select').val('tree');
|
|
||||||
this.$dropdown.removeClass('view-list view-cols-2 view-cols-3 view-cols-4 view-cols-5 view-cols-6 view-cols-7 view-cols-8').addClass('view-tree');
|
|
||||||
this.loadCategoryTree();
|
|
||||||
} else {
|
|
||||||
this.loadCategoryTree();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$treeOption.prop('disabled', true).prop('hidden', true);
|
|
||||||
// If currently in tree mode, switch back to list
|
|
||||||
if (this.viewMode === 'tree') {
|
|
||||||
this.viewMode = 'list';
|
|
||||||
this.$dropdown.find('.view-mode-select').val('list');
|
|
||||||
this.$dropdown.removeClass('view-tree').addClass('view-list');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show entity-specific filter row (prepare visibility, but don't auto-expand panel)
|
|
||||||
if (entityType === 'products') {
|
|
||||||
// Prepare the correct rows to be visible when panel is shown
|
|
||||||
$panel.find('.filter-row-quick').show();
|
|
||||||
// Show attribute/feature rows if we have cached data
|
|
||||||
if (this.filterableData) {
|
|
||||||
if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
|
|
||||||
this.$dropdown.find('.filter-row-attributes').show();
|
|
||||||
}
|
|
||||||
if (this.filterableData.features && this.filterableData.features.length > 0) {
|
|
||||||
this.$dropdown.find('.filter-row-features').show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (entityType === 'categories') {
|
|
||||||
$panel.find('.filter-row-entity-categories').show();
|
|
||||||
} else if (entityType === 'manufacturers') {
|
|
||||||
$panel.find('.filter-row-entity-manufacturers').show();
|
|
||||||
} else if (entityType === 'suppliers') {
|
|
||||||
$panel.find('.filter-row-entity-suppliers').show();
|
|
||||||
} else if (entityType === 'attributes') {
|
|
||||||
$panel.find('.filter-row-entity-attributes').show();
|
|
||||||
this.loadAttributeGroups();
|
|
||||||
} else if (entityType === 'features') {
|
|
||||||
$panel.find('.filter-row-entity-features').show();
|
|
||||||
} else if (entityType === 'cms') {
|
|
||||||
$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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadAttributeGroups: function() {
|
|
||||||
var self = this;
|
|
||||||
var $select = this.$dropdown.find('.filter-attribute-group-select');
|
|
||||||
|
|
||||||
// Already loaded?
|
|
||||||
if ($select.find('option').length > 1) return;
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: this.config.ajaxUrl,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
ajax: 1,
|
|
||||||
action: 'getAttributeGroups',
|
|
||||||
trait: 'EntitySelector'
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
if (response.success && response.groups) {
|
|
||||||
$.each(response.groups, function(i, group) {
|
|
||||||
$select.append('<option value="' + group.id + '">' + self.escapeHtml(group.name) + ' (' + group.count + ')</option>');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
loadFeatureGroups: function() {
|
|
||||||
var self = this;
|
|
||||||
var $select = this.$dropdown.find('.filter-feature-group-select');
|
|
||||||
|
|
||||||
// Already loaded?
|
|
||||||
if ($select.find('option').length > 1) return;
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: this.config.ajaxUrl,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
ajax: 1,
|
|
||||||
action: 'getFeatureGroups',
|
|
||||||
trait: 'EntitySelector'
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
if (response.success && response.groups) {
|
|
||||||
$.each(response.groups, function(i, group) {
|
|
||||||
$select.append('<option value="' + group.id + '">' + self.escapeHtml(group.name) + ' (' + group.count + ')</option>');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
@@ -1,359 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Category Tree Module
|
|
||||||
* Hierarchical tree view for category selection inside the dropdown
|
|
||||||
* @partial _tree.js
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* - Expand/collapse individual nodes
|
|
||||||
* - Expand all / Collapse all
|
|
||||||
* - Select parent with all children button
|
|
||||||
* - Visual tree with indentation
|
|
||||||
* - Product count display
|
|
||||||
* - Search/filter within tree
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Create mixin namespace
|
|
||||||
window._EntitySelectorMixins = window._EntitySelectorMixins || {};
|
|
||||||
|
|
||||||
// Tree mixin
|
|
||||||
window._EntitySelectorMixins.tree = {
|
|
||||||
|
|
||||||
// Tree state
|
|
||||||
treeData: null,
|
|
||||||
treeFlatData: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load and display category tree in the dropdown
|
|
||||||
* Called when view mode is changed to "tree"
|
|
||||||
*/
|
|
||||||
loadCategoryTree: function() {
|
|
||||||
var self = this;
|
|
||||||
var $results = this.$dropdown.find('.dropdown-results');
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
var searchEntity = this.activeGroup ? this.activeGroup.searchEntity : 'categories';
|
|
||||||
|
|
||||||
// Show loading
|
|
||||||
$results.html('<div class="tree-loading">' + this.esIcon('progress_activity', 'es-spin') + ' ' +
|
|
||||||
this.escapeHtml(trans.loading || 'Loading...') + '</div>');
|
|
||||||
|
|
||||||
// Fetch tree data
|
|
||||||
$.ajax({
|
|
||||||
url: this.config.ajaxUrl,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
ajax: 1,
|
|
||||||
action: 'getCategoryTree',
|
|
||||||
trait: 'EntitySelector',
|
|
||||||
entity_type: searchEntity
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
if (response.success && response.categories && response.categories.length > 0) {
|
|
||||||
self.treeFlatData = response.categories;
|
|
||||||
self.treeData = self.buildTreeStructure(response.categories);
|
|
||||||
self.renderCategoryTree($results, searchEntity);
|
|
||||||
} else {
|
|
||||||
$results.html('<div class="dropdown-empty">' +
|
|
||||||
self.escapeHtml(trans.no_categories || 'No categories found') + '</div>');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
$results.html('<div class="dropdown-error">' +
|
|
||||||
self.escapeHtml(trans.error_loading || 'Failed to load categories') + '</div>');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build nested tree structure from flat array
|
|
||||||
* @param {Array} flatData - Flat array with parent_id references
|
|
||||||
* @returns {Array} Nested tree structure
|
|
||||||
*/
|
|
||||||
buildTreeStructure: function(flatData) {
|
|
||||||
var lookup = {};
|
|
||||||
var tree = [];
|
|
||||||
|
|
||||||
// Create lookup and initialize children arrays
|
|
||||||
flatData.forEach(function(item) {
|
|
||||||
lookup[item.id] = $.extend({}, item, { children: [] });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build tree by assigning children to parents
|
|
||||||
flatData.forEach(function(item) {
|
|
||||||
var node = lookup[item.id];
|
|
||||||
var parentId = parseInt(item.parent_id, 10);
|
|
||||||
|
|
||||||
if (parentId && lookup[parentId]) {
|
|
||||||
lookup[parentId].children.push(node);
|
|
||||||
} else {
|
|
||||||
tree.push(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return tree;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the category tree inside dropdown results
|
|
||||||
* @param {jQuery} $container - The dropdown-results container
|
|
||||||
* @param {string} entityType - 'categories' or 'cms_categories'
|
|
||||||
*/
|
|
||||||
renderCategoryTree: function($container, entityType) {
|
|
||||||
var self = this;
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
|
|
||||||
// Get currently selected IDs from chips
|
|
||||||
var selectedIds = this.getSelectedIdsFromChips();
|
|
||||||
|
|
||||||
// Build tree HTML
|
|
||||||
var html = '<div class="category-tree" data-entity-type="' + this.escapeAttr(entityType) + '">';
|
|
||||||
|
|
||||||
// Tree toolbar
|
|
||||||
html += '<div class="tree-toolbar">';
|
|
||||||
html += '<button type="button" class="btn-expand-all" title="' +
|
|
||||||
this.escapeAttr(trans.expand_all || 'Expand all') + '">';
|
|
||||||
html += this.esIcon('add_box') + ' ' + this.escapeHtml(trans.expand_all || 'Expand all');
|
|
||||||
html += '</button>';
|
|
||||||
html += '<button type="button" class="btn-collapse-all" title="' +
|
|
||||||
this.escapeAttr(trans.collapse_all || 'Collapse all') + '">';
|
|
||||||
html += this.esIcon('indeterminate_check_box') + ' ' + this.escapeHtml(trans.collapse_all || 'Collapse all');
|
|
||||||
html += '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Tree items
|
|
||||||
html += '<div class="tree-items">';
|
|
||||||
html += this.renderTreeItems(this.treeData, 0, selectedIds);
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
$container.html(html);
|
|
||||||
|
|
||||||
// Update count
|
|
||||||
var totalCount = this.treeFlatData ? this.treeFlatData.length : 0;
|
|
||||||
var selectedCount = selectedIds.length;
|
|
||||||
var categoryLabel = entityType === 'cms_categories' ? 'CMS categories' : 'categories';
|
|
||||||
var countText = totalCount + ' ' + categoryLabel;
|
|
||||||
if (selectedCount > 0) {
|
|
||||||
countText += ' (' + selectedCount + ' selected)';
|
|
||||||
}
|
|
||||||
this.$dropdown.find('.results-count').text(countText);
|
|
||||||
|
|
||||||
// Update select children button states
|
|
||||||
this.updateSelectChildrenButtons(this.$dropdown.find('.tree-item'));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render tree items recursively
|
|
||||||
* @param {Array} nodes - Tree nodes
|
|
||||||
* @param {number} level - Current depth level
|
|
||||||
* @param {Array} selectedIds - Currently selected IDs
|
|
||||||
* @returns {string} HTML string
|
|
||||||
*/
|
|
||||||
renderTreeItems: function(nodes, level, selectedIds) {
|
|
||||||
var self = this;
|
|
||||||
var html = '';
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
|
|
||||||
nodes.forEach(function(node) {
|
|
||||||
var hasChildren = node.children && node.children.length > 0;
|
|
||||||
var isSelected = selectedIds.indexOf(parseInt(node.id, 10)) !== -1;
|
|
||||||
var indent = level * 20;
|
|
||||||
|
|
||||||
var itemClass = 'tree-item';
|
|
||||||
if (hasChildren) itemClass += ' has-children';
|
|
||||||
if (isSelected) itemClass += ' selected';
|
|
||||||
if (!node.active) itemClass += ' inactive';
|
|
||||||
|
|
||||||
html += '<div class="' + itemClass + '" data-id="' + node.id + '" ';
|
|
||||||
html += 'data-name="' + self.escapeAttr(node.name) + '" ';
|
|
||||||
html += 'data-level="' + level + '" ';
|
|
||||||
html += 'data-parent-id="' + (node.parent_id || 0) + '">';
|
|
||||||
|
|
||||||
// Indentation
|
|
||||||
html += '<span class="tree-indent" style="width: ' + indent + 'px;"></span>';
|
|
||||||
|
|
||||||
// Toggle button (expand/collapse)
|
|
||||||
if (hasChildren) {
|
|
||||||
html += '<span class="tree-toggle">' + self.esIcon('arrow_drop_down') + '</span>';
|
|
||||||
// Select with children button (next to toggle on the left)
|
|
||||||
html += '<button type="button" class="btn-select-children" title="' +
|
|
||||||
self.escapeAttr(trans.select_with_children || 'Select with all children') + '">';
|
|
||||||
html += self.esIcon('check_box');
|
|
||||||
html += '</button>';
|
|
||||||
} else {
|
|
||||||
html += '<span class="tree-toggle tree-leaf"></span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checkbox indicator
|
|
||||||
html += '<span class="tree-checkbox">' + this.esIcon('check') + '</span>';
|
|
||||||
|
|
||||||
// Category icon
|
|
||||||
html += '<span class="tree-icon">' + this.esIcon('folder') + '</span>';
|
|
||||||
|
|
||||||
// Name
|
|
||||||
html += '<span class="tree-name">' + self.escapeHtml(node.name) + '</span>';
|
|
||||||
|
|
||||||
// Product/page count with clickable preview
|
|
||||||
var itemCount = node.product_count || node.page_count || 0;
|
|
||||||
if (itemCount > 0) {
|
|
||||||
var countLabel = node.page_count ? (trans.pages || 'pages') : (trans.products || 'products');
|
|
||||||
html += '<span class="tree-count clickable" data-category-id="' + node.id + '" ';
|
|
||||||
html += 'title="' + self.escapeAttr(itemCount + ' ' + countLabel) + '">';
|
|
||||||
html += self.esIcon('visibility') + ' ' + itemCount;
|
|
||||||
html += '</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inactive badge
|
|
||||||
if (!node.active) {
|
|
||||||
html += '<span class="tree-badge inactive">' +
|
|
||||||
self.escapeHtml(trans.inactive || 'Inactive') + '</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Render children
|
|
||||||
if (hasChildren) {
|
|
||||||
html += '<div class="tree-children">';
|
|
||||||
html += self.renderTreeItems(node.children, level + 1, selectedIds);
|
|
||||||
html += '</div>';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return html;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get selected IDs from the current picker's chips
|
|
||||||
* @returns {Array} Array of selected IDs
|
|
||||||
*/
|
|
||||||
getSelectedIdsFromChips: function() {
|
|
||||||
var selectedIds = [];
|
|
||||||
|
|
||||||
if (!this.activeGroup) return selectedIds;
|
|
||||||
|
|
||||||
var $block = this.$wrapper.find('.target-block[data-block-type="' + this.activeGroup.blockType + '"]');
|
|
||||||
var $group = $block.find('.selection-group[data-group-index="' + this.activeGroup.groupIndex + '"]');
|
|
||||||
var $picker;
|
|
||||||
|
|
||||||
if (this.activeGroup.section === 'include') {
|
|
||||||
$picker = $group.find('.include-picker');
|
|
||||||
} else {
|
|
||||||
var $excludeRow = $group.find('.exclude-row[data-exclude-index="' + this.activeGroup.excludeIndex + '"]');
|
|
||||||
$picker = $excludeRow.find('.exclude-picker');
|
|
||||||
}
|
|
||||||
|
|
||||||
$picker.find('.entity-chip').each(function() {
|
|
||||||
selectedIds.push(parseInt($(this).data('id'), 10));
|
|
||||||
});
|
|
||||||
|
|
||||||
return selectedIds;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter category tree by search query
|
|
||||||
* @param {string} query - Search query
|
|
||||||
*/
|
|
||||||
filterCategoryTree: function(query) {
|
|
||||||
var $tree = this.$dropdown.find('.category-tree');
|
|
||||||
if (!$tree.length) return;
|
|
||||||
|
|
||||||
var $items = $tree.find('.tree-item');
|
|
||||||
var $children = $tree.find('.tree-children');
|
|
||||||
query = (query || '').toLowerCase().trim();
|
|
||||||
|
|
||||||
// Remove any inline display styles set by jQuery .toggle()
|
|
||||||
$items.css('display', '');
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
$items.removeClass('filtered-out filter-match');
|
|
||||||
$children.removeClass('filter-expanded');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark all as filtered out first
|
|
||||||
$items.addClass('filtered-out').removeClass('filter-match');
|
|
||||||
|
|
||||||
// Find matching items and show them with their parents
|
|
||||||
$items.each(function() {
|
|
||||||
var $item = $(this);
|
|
||||||
var name = ($item.data('name') || '').toLowerCase();
|
|
||||||
|
|
||||||
if (name.indexOf(query) !== -1) {
|
|
||||||
$item.removeClass('filtered-out');
|
|
||||||
|
|
||||||
// Show parent containers
|
|
||||||
$item.parents('.tree-children').addClass('filter-expanded');
|
|
||||||
$item.parents('.tree-item').removeClass('filtered-out');
|
|
||||||
|
|
||||||
// Show children of matching item
|
|
||||||
$item.next('.tree-children').find('.tree-item').removeClass('filtered-out');
|
|
||||||
$item.next('.tree-children').addClass('filter-expanded');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all descendant tree items of a given item
|
|
||||||
* @param {jQuery} $item - Parent tree item
|
|
||||||
* @param {jQuery} $allItems - All tree items (for performance)
|
|
||||||
* @returns {Array} Array of descendant jQuery elements
|
|
||||||
*/
|
|
||||||
findTreeDescendants: function($item, $allItems) {
|
|
||||||
var descendants = [];
|
|
||||||
var parentId = parseInt($item.data('id'), 10);
|
|
||||||
var level = parseInt($item.data('level'), 10);
|
|
||||||
|
|
||||||
// Find immediate children first
|
|
||||||
var $next = $item.next('.tree-children');
|
|
||||||
if ($next.length) {
|
|
||||||
$next.find('.tree-item').each(function() {
|
|
||||||
descendants.push(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return descendants;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state of select-children buttons based on selection
|
|
||||||
* @param {jQuery} $allItems - All tree items
|
|
||||||
*/
|
|
||||||
updateSelectChildrenButtons: function($allItems) {
|
|
||||||
var self = this;
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
|
|
||||||
$allItems.filter('.has-children').each(function() {
|
|
||||||
var $item = $(this);
|
|
||||||
var $btn = $item.find('.btn-select-children');
|
|
||||||
if (!$btn.length) return;
|
|
||||||
|
|
||||||
var $children = $item.next('.tree-children');
|
|
||||||
if (!$children.length) return;
|
|
||||||
|
|
||||||
var $childItems = $children.find('.tree-item');
|
|
||||||
var isParentSelected = $item.hasClass('selected');
|
|
||||||
var allChildrenSelected = true;
|
|
||||||
|
|
||||||
$childItems.each(function() {
|
|
||||||
if (!$(this).hasClass('selected')) {
|
|
||||||
allChildrenSelected = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isParentSelected && allChildrenSelected) {
|
|
||||||
$btn.find('i').replaceWith(self.esIcon('indeterminate_check_box'));
|
|
||||||
$btn.attr('title', trans.deselect_with_children || 'Deselect with all children');
|
|
||||||
} else {
|
|
||||||
$btn.find('i').replaceWith(self.esIcon('add_box'));
|
|
||||||
$btn.attr('title', trans.select_with_children || 'Select with all children');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Utilities Module
|
|
||||||
* Helper functions: escape, validation, icons, search history
|
|
||||||
* @partial _utils.js (must be loaded first)
|
|
||||||
*
|
|
||||||
* EXTRACTION SOURCE: assets/js/admin/entity-selector.js
|
|
||||||
* Lines: 7552-7570 (escapeHtml, escapeAttr)
|
|
||||||
* 7577-7590 (getEntityTypeLabel)
|
|
||||||
* 6289-6350 (validate, showValidationError, clearValidationError)
|
|
||||||
* 7115-7137 (showRangeInputError)
|
|
||||||
* 7728-7745 (getBlockMode, isBlockSingleMode)
|
|
||||||
* 7707-7723 (getCurrentSingleSelection)
|
|
||||||
* 5411-5467 (search history methods)
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Create mixin namespace
|
|
||||||
window._EntitySelectorMixins = window._EntitySelectorMixins || {};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
// Icon framework detection & FA4 mapping (module-level singleton)
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
var _iconMode = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Material Icons → FontAwesome 4 class mapping.
|
|
||||||
* FA4 uses class-based icons (icon-name), Material uses text content.
|
|
||||||
*/
|
|
||||||
var FA4_MAP = {
|
|
||||||
'account_tree': 'icon-sitemap',
|
|
||||||
'add': 'icon-plus',
|
|
||||||
'add_box': 'icon-plus-square',
|
|
||||||
'arrow_downward': 'icon-sort-desc',
|
|
||||||
'arrow_drop_down': 'icon-caret-down',
|
|
||||||
'arrow_right': 'icon-chevron-right',
|
|
||||||
'arrow_upward': 'icon-sort-asc',
|
|
||||||
'block': 'icon-ban',
|
|
||||||
'brush': 'icon-paint-brush',
|
|
||||||
'business': 'icon-building',
|
|
||||||
'check': 'icon-check',
|
|
||||||
'check_box': 'icon-check-square',
|
|
||||||
'check_box_outline_blank': 'icon-square-o',
|
|
||||||
'check_circle': 'icon-check-circle',
|
|
||||||
'close': 'icon-times',
|
|
||||||
'delete': 'icon-trash',
|
|
||||||
'description': 'icon-file-text',
|
|
||||||
'error': 'icon-exclamation-circle',
|
|
||||||
'event': 'icon-calendar',
|
|
||||||
'event_busy': 'icon-calendar-times-o',
|
|
||||||
'expand_less': 'icon-chevron-up',
|
|
||||||
'expand_more': 'icon-chevron-down',
|
|
||||||
'filter_list': 'icon-filter',
|
|
||||||
'flag': 'icon-flag',
|
|
||||||
'folder': 'icon-folder',
|
|
||||||
'folder_open': 'icon-folder-open',
|
|
||||||
'indeterminate_check_box': 'icon-minus-square',
|
|
||||||
'info': 'icon-info-circle',
|
|
||||||
'inventory_2': 'icon-archive',
|
|
||||||
'label': 'icon-tag',
|
|
||||||
'language': 'icon-globe',
|
|
||||||
'lightbulb': 'icon-lightbulb-o',
|
|
||||||
'list': 'icon-list',
|
|
||||||
'list_alt': 'icon-list-alt',
|
|
||||||
'local_shipping': 'icon-truck',
|
|
||||||
'lock': 'icon-lock',
|
|
||||||
'my_location': 'icon-crosshairs',
|
|
||||||
'open_in_full': 'icon-expand',
|
|
||||||
'payments': 'icon-credit-card',
|
|
||||||
'progress_activity': 'icon-circle-o-notch',
|
|
||||||
'schedule': 'icon-clock-o',
|
|
||||||
'search': 'icon-search',
|
|
||||||
'shopping_cart': 'icon-shopping-cart',
|
|
||||||
'shuffle': 'icon-random',
|
|
||||||
'sort': 'icon-sort',
|
|
||||||
'sort_by_alpha': 'icon-sort-alpha-asc',
|
|
||||||
'star': 'icon-star',
|
|
||||||
'sync': 'icon-refresh',
|
|
||||||
'tune': 'icon-sliders',
|
|
||||||
'visibility': 'icon-eye',
|
|
||||||
'warning': 'icon-warning',
|
|
||||||
'widgets': 'icon-th-large'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse map: FontAwesome 4 class → Material Icons name.
|
|
||||||
* Built once lazily from FA4_MAP + extra mappings for FA4 names
|
|
||||||
* that don't appear as values in FA4_MAP.
|
|
||||||
*/
|
|
||||||
var _REVERSE_FA4_MAP = null;
|
|
||||||
|
|
||||||
var EXTRA_REVERSE_MAPPINGS = {
|
|
||||||
'icon-cube': 'inventory',
|
|
||||||
'icon-folder-o': 'folder',
|
|
||||||
'icon-file-text-o': 'description',
|
|
||||||
'icon-briefcase': 'work',
|
|
||||||
'icon-user': 'person',
|
|
||||||
'icon-users': 'group',
|
|
||||||
'icon-money': 'payments',
|
|
||||||
'icon-tasks': 'checklist',
|
|
||||||
'icon-calculator': 'calculate',
|
|
||||||
'icon-asterisk': 'star',
|
|
||||||
'icon-bar-chart': 'bar_chart',
|
|
||||||
'icon-cogs': 'settings',
|
|
||||||
'icon-cog': 'settings',
|
|
||||||
'icon-tags': 'label',
|
|
||||||
'icon-list-ul': 'list',
|
|
||||||
'icon-th': 'grid_view',
|
|
||||||
'icon-certificate': 'verified',
|
|
||||||
'icon-power-off': 'power_settings_new',
|
|
||||||
'icon-circle-o': 'radio_button_unchecked'
|
|
||||||
};
|
|
||||||
|
|
||||||
function getReverseFa4Map() {
|
|
||||||
if (_REVERSE_FA4_MAP !== null) return _REVERSE_FA4_MAP;
|
|
||||||
_REVERSE_FA4_MAP = {};
|
|
||||||
// Invert FA4_MAP: value → key
|
|
||||||
for (var material in FA4_MAP) {
|
|
||||||
if (FA4_MAP.hasOwnProperty(material)) {
|
|
||||||
var fa4Class = FA4_MAP[material];
|
|
||||||
if (!_REVERSE_FA4_MAP[fa4Class]) {
|
|
||||||
_REVERSE_FA4_MAP[fa4Class] = material;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Merge extras
|
|
||||||
for (var fa4 in EXTRA_REVERSE_MAPPINGS) {
|
|
||||||
if (EXTRA_REVERSE_MAPPINGS.hasOwnProperty(fa4) && !_REVERSE_FA4_MAP[fa4]) {
|
|
||||||
_REVERSE_FA4_MAP[fa4] = EXTRA_REVERSE_MAPPINGS[fa4];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _REVERSE_FA4_MAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize an icon name — handles both Material and FA4 class names.
|
|
||||||
* @param {string} name - Icon name (Material or FA4 format)
|
|
||||||
* @param {string} mode - 'material' or 'fa4'
|
|
||||||
* @returns {object} { name: string, extra: string, rawFa4: boolean }
|
|
||||||
*/
|
|
||||||
function normalizeIconName(name, mode) {
|
|
||||||
var extra = '';
|
|
||||||
var rawFa4 = false;
|
|
||||||
|
|
||||||
if (name.indexOf('icon-') === 0) {
|
|
||||||
// FA4 class name input — may have extra classes (e.g. "icon-power-off text-success")
|
|
||||||
var spaceIdx = name.indexOf(' ');
|
|
||||||
var fa4Class = (spaceIdx !== -1) ? name.substring(0, spaceIdx) : name;
|
|
||||||
if (spaceIdx !== -1) extra = name.substring(spaceIdx + 1);
|
|
||||||
|
|
||||||
if (mode === 'material') {
|
|
||||||
var reverseMap = getReverseFa4Map();
|
|
||||||
var materialName = reverseMap[fa4Class];
|
|
||||||
if (materialName) {
|
|
||||||
return { name: materialName, extra: extra, rawFa4: false };
|
|
||||||
}
|
|
||||||
// Fallback: strip 'icon-' and convert hyphens to underscores
|
|
||||||
var fallback = fa4Class.substring(5).replace(/-/g, '_');
|
|
||||||
return { name: fallback, extra: extra, rawFa4: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
// FA4 mode + FA4 input — use as-is
|
|
||||||
return { name: fa4Class, extra: extra, rawFa4: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Material Icons name — pass through
|
|
||||||
return { name: name, extra: extra, rawFa4: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect icon framework: 'material' (PS 8+/9+) or 'fa4' (PS 1.6/1.7).
|
|
||||||
* Checks PHP-set data attribute first, falls back to font detection.
|
|
||||||
*/
|
|
||||||
function detectIconMode() {
|
|
||||||
if (_iconMode !== null) return _iconMode;
|
|
||||||
|
|
||||||
// 1. PHP sets data-icon-mode on the wrapper
|
|
||||||
var $w = $('.entity-selector-trait[data-icon-mode], .target-conditions-trait[data-icon-mode]').first();
|
|
||||||
if ($w.length && $w.data('icon-mode')) {
|
|
||||||
_iconMode = $w.data('icon-mode');
|
|
||||||
return _iconMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Fallback: probe whether Material Icons font is loaded
|
|
||||||
var test = document.createElement('i');
|
|
||||||
test.className = 'material-icons';
|
|
||||||
test.style.cssText = 'position:absolute;left:-9999px;top:-9999px;font-size:16px;pointer-events:none';
|
|
||||||
test.textContent = 'check';
|
|
||||||
(document.body || document.documentElement).appendChild(test);
|
|
||||||
var family = (window.getComputedStyle(test).fontFamily || '').toLowerCase();
|
|
||||||
test.parentNode.removeChild(test);
|
|
||||||
|
|
||||||
_iconMode = (family.indexOf('material') !== -1) ? 'material' : 'fa4';
|
|
||||||
return _iconMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility functions mixin
|
|
||||||
window._EntitySelectorMixins.utils = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debounce function - delays execution until after wait milliseconds
|
|
||||||
* @param {Function} func - Function to debounce
|
|
||||||
* @param {number} wait - Milliseconds to wait
|
|
||||||
* @returns {Function} Debounced function
|
|
||||||
*/
|
|
||||||
debounce: function(func, wait) {
|
|
||||||
var timeout;
|
|
||||||
return function() {
|
|
||||||
var context = this;
|
|
||||||
var args = arguments;
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(function() {
|
|
||||||
func.apply(context, args);
|
|
||||||
}, wait);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
escapeHtml: function(str) {
|
|
||||||
if (str === null || str === undefined) return '';
|
|
||||||
return String(str)
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
},
|
|
||||||
|
|
||||||
escapeAttr: function(str) {
|
|
||||||
if (str === null || str === undefined) return '';
|
|
||||||
return String(str)
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon helper — returns HTML for an icon that works on PS 1.6 through 9.x.
|
|
||||||
* Accepts both Material Icons names and FA4 class names as input.
|
|
||||||
* Automatically uses Material Icons (PS 8+/9+) or FontAwesome 4 (PS 1.6/1.7).
|
|
||||||
*
|
|
||||||
* @param {string} name - Icon name (Material or FA4 format, e.g. 'search', 'icon-cube')
|
|
||||||
* @param {string} [extraClass] - Additional CSS class(es) (e.g. 'es-spin', 'method-trigger-icon')
|
|
||||||
* @returns {string} HTML string for an <i> element
|
|
||||||
*/
|
|
||||||
esIcon: function(name, extraClass) {
|
|
||||||
var mode = detectIconMode();
|
|
||||||
var normalized = normalizeIconName(name, mode);
|
|
||||||
var iconName = normalized.name;
|
|
||||||
// Merge extra classes from normalization
|
|
||||||
if (normalized.extra) {
|
|
||||||
extraClass = extraClass ? extraClass + ' ' + normalized.extra : normalized.extra;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === 'material') {
|
|
||||||
var cls = 'material-icons es-icon';
|
|
||||||
if (extraClass) cls += ' ' + extraClass;
|
|
||||||
return '<i class="' + cls + '">' + iconName + '</i>';
|
|
||||||
}
|
|
||||||
// FA4 mode
|
|
||||||
if (normalized.rawFa4) {
|
|
||||||
// Input was already an FA4 class — use directly
|
|
||||||
var cls = iconName + ' es-icon';
|
|
||||||
} else {
|
|
||||||
// Input was Material — map to FA4
|
|
||||||
var cls = (FA4_MAP[iconName] || 'icon-circle') + ' es-icon';
|
|
||||||
}
|
|
||||||
if (extraClass) cls += ' ' + extraClass;
|
|
||||||
return '<i class="' + cls + '"></i>';
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing <i> icon element to show a different icon.
|
|
||||||
* Handles both Material Icons and FA4 modes.
|
|
||||||
* Accepts both Material Icons names and FA4 class names as input.
|
|
||||||
*
|
|
||||||
* @param {jQuery} $el - The <i> element to update
|
|
||||||
* @param {string} name - Icon name (Material or FA4 format)
|
|
||||||
* @param {string} [extraClass] - Additional CSS class(es) to preserve
|
|
||||||
*/
|
|
||||||
esIconUpdate: function($el, name, extraClass) {
|
|
||||||
var mode = detectIconMode();
|
|
||||||
var normalized = normalizeIconName(name, mode);
|
|
||||||
var iconName = normalized.name;
|
|
||||||
if (normalized.extra) {
|
|
||||||
extraClass = extraClass ? extraClass + ' ' + normalized.extra : normalized.extra;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === 'material') {
|
|
||||||
var cls = 'material-icons es-icon';
|
|
||||||
if (extraClass) cls += ' ' + extraClass;
|
|
||||||
$el.attr('class', cls).text(iconName);
|
|
||||||
} else {
|
|
||||||
if (normalized.rawFa4) {
|
|
||||||
var cls = iconName + ' es-icon';
|
|
||||||
} else {
|
|
||||||
var cls = (FA4_MAP[iconName] || 'icon-circle') + ' es-icon';
|
|
||||||
}
|
|
||||||
if (extraClass) cls += ' ' + extraClass;
|
|
||||||
$el.attr('class', cls).text('');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getEntityTypeIcon: function(entityType) {
|
|
||||||
var icons = {
|
|
||||||
'products': 'shopping_cart',
|
|
||||||
'categories': 'folder_open',
|
|
||||||
'manufacturers': 'business',
|
|
||||||
'suppliers': 'local_shipping',
|
|
||||||
'attributes': 'list_alt',
|
|
||||||
'features': 'label',
|
|
||||||
'cms': 'description',
|
|
||||||
'cms_categories': 'folder'
|
|
||||||
};
|
|
||||||
return icons[entityType] || 'widgets';
|
|
||||||
},
|
|
||||||
|
|
||||||
getEntityTypeLabel: function(entityType) {
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
var labels = {
|
|
||||||
'products': trans.product || 'Product',
|
|
||||||
'categories': trans.category || 'Category',
|
|
||||||
'manufacturers': trans.manufacturer || 'Manufacturer',
|
|
||||||
'suppliers': trans.supplier || 'Supplier',
|
|
||||||
'attributes': trans.attribute || 'Attribute',
|
|
||||||
'features': trans.feature || 'Feature',
|
|
||||||
'cms': trans.cms_page || 'CMS Page',
|
|
||||||
'cms_categories': trans.cms_category || 'CMS Category'
|
|
||||||
};
|
|
||||||
return labels[entityType] || entityType;
|
|
||||||
},
|
|
||||||
|
|
||||||
validate: function() {
|
|
||||||
var isRequired = this.$wrapper.data('required') === 1 || this.$wrapper.data('required') === '1';
|
|
||||||
if (!isRequired) return true;
|
|
||||||
|
|
||||||
var hasData = false;
|
|
||||||
this.$wrapper.find('.target-block').each(function() {
|
|
||||||
if ($(this).find('.selection-group').length > 0) {
|
|
||||||
hasData = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasData) {
|
|
||||||
this.showValidationError();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.clearValidationError();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
showValidationError: function() {
|
|
||||||
this.$wrapper.addClass('has-validation-error');
|
|
||||||
var message = this.$wrapper.data('required-message') || 'Please select at least one item';
|
|
||||||
this.$wrapper.find('.trait-validation-error').remove();
|
|
||||||
var $error = $('<div>', {
|
|
||||||
class: 'trait-validation-error',
|
|
||||||
html: this.esIcon('warning') + ' ' + message
|
|
||||||
});
|
|
||||||
this.$wrapper.find('.condition-trait-header').after($error);
|
|
||||||
$('html, body').animate({ scrollTop: this.$wrapper.offset().top - 100 }, 300);
|
|
||||||
if (!this.$wrapper.find('.condition-trait-body').is(':visible')) {
|
|
||||||
this.$wrapper.find('.condition-trait-body').slideDown(200);
|
|
||||||
this.$wrapper.removeClass('collapsed');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
clearValidationError: function() {
|
|
||||||
this.$wrapper.removeClass('has-validation-error');
|
|
||||||
this.$wrapper.find('.trait-validation-error').remove();
|
|
||||||
},
|
|
||||||
|
|
||||||
getBlockMode: function(blockType) {
|
|
||||||
var blockDef = this.config.blocks[blockType];
|
|
||||||
return (blockDef && blockDef.mode) ? blockDef.mode : 'multi';
|
|
||||||
},
|
|
||||||
|
|
||||||
isBlockSingleMode: function(blockType) {
|
|
||||||
return this.getBlockMode(blockType) === 'single';
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrentSingleSelection: function() {
|
|
||||||
if ((this.config.mode || 'multi') !== 'single') return null;
|
|
||||||
var $chip = this.$wrapper.find('.entity-chips .entity-chip').first();
|
|
||||||
if ($chip.length) {
|
|
||||||
var $block = $chip.closest('.target-block');
|
|
||||||
return {
|
|
||||||
name: $chip.find('.chip-name').text() || $chip.data('id'),
|
|
||||||
entityType: $block.data('block-type') || 'item'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if entity type supports tree browsing
|
|
||||||
*/
|
|
||||||
supportsTreeBrowsing: function(entityType) {
|
|
||||||
return entityType === 'categories' || entityType === 'cms_categories';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
@@ -1,365 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Validation Module
|
|
||||||
* Conflict detection and prevention for entity selections
|
|
||||||
* @partial _validation.js
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* - Same entity in include & exclude detection
|
|
||||||
* - Parent-child conflict detection for tree entities
|
|
||||||
* - Redundant selection detection
|
|
||||||
* - Error message display
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
window._EntitySelectorMixins = window._EntitySelectorMixins || {};
|
|
||||||
|
|
||||||
window._EntitySelectorMixins.validation = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a selection before adding it
|
|
||||||
* Returns { valid: true } or { valid: false, error: 'message', type: 'conflict_type' }
|
|
||||||
*
|
|
||||||
* @param {number|string} id - Entity ID to validate
|
|
||||||
* @param {string} name - Entity name (for error messages)
|
|
||||||
* @param {string} section - 'include' or 'exclude'
|
|
||||||
* @param {Object} data - Additional data (parent_id, etc.)
|
|
||||||
* @returns {Object} Validation result
|
|
||||||
*/
|
|
||||||
validateSelection: function(id, name, section, data) {
|
|
||||||
if (!this.activeGroup) {
|
|
||||||
return { valid: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
id = parseInt(id, 10);
|
|
||||||
|
|
||||||
var $block = this.$wrapper.find('.target-block[data-block-type="' + this.activeGroup.blockType + '"]');
|
|
||||||
var $group = $block.find('.selection-group[data-group-index="' + this.activeGroup.groupIndex + '"]');
|
|
||||||
|
|
||||||
// Get include chips
|
|
||||||
var includeIds = this.getChipIds($group.find('.include-picker'));
|
|
||||||
|
|
||||||
// Get all exclude chips (from all exclude rows)
|
|
||||||
var excludeIds = [];
|
|
||||||
$group.find('.exclude-row').each(function() {
|
|
||||||
var $excludePicker = $(this).find('.exclude-picker');
|
|
||||||
var ids = [];
|
|
||||||
$excludePicker.find('.entity-chip').each(function() {
|
|
||||||
ids.push(parseInt($(this).data('id'), 10));
|
|
||||||
});
|
|
||||||
excludeIds = excludeIds.concat(ids);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1. Check for same entity in include & exclude
|
|
||||||
var conflictResult = this.checkIncludeExcludeConflict(id, name, section, includeIds, excludeIds, trans);
|
|
||||||
if (!conflictResult.valid) {
|
|
||||||
return conflictResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check for redundant selection (already selected in same section)
|
|
||||||
var redundantResult = this.checkRedundantSelection(id, name, section, includeIds, excludeIds, trans);
|
|
||||||
if (!redundantResult.valid) {
|
|
||||||
return redundantResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Check for parent-child conflicts (only for tree entities)
|
|
||||||
var searchEntity = this.activeGroup.searchEntity;
|
|
||||||
if (searchEntity === 'categories' || searchEntity === 'cms_categories') {
|
|
||||||
var treeResult = this.checkTreeConflicts(id, name, section, data, includeIds, excludeIds, trans);
|
|
||||||
if (!treeResult.valid) {
|
|
||||||
return treeResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: true };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if entity is in both include and exclude
|
|
||||||
*/
|
|
||||||
checkIncludeExcludeConflict: function(id, name, section, includeIds, excludeIds, trans) {
|
|
||||||
if (section === 'include' && excludeIds.indexOf(id) !== -1) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
type: 'include_exclude_conflict',
|
|
||||||
error: (trans.error_in_exclude || '"{name}" is already in the exclude list. Remove it from exclude first.').replace('{name}', name)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (section === 'exclude' && includeIds.indexOf(id) !== -1) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
type: 'include_exclude_conflict',
|
|
||||||
error: (trans.error_in_include || '"{name}" is already in the include list. Remove it from include first.').replace('{name}', name)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: true };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for redundant selection (already selected)
|
|
||||||
*/
|
|
||||||
checkRedundantSelection: function(id, name, section, includeIds, excludeIds, trans) {
|
|
||||||
if (section === 'include' && includeIds.indexOf(id) !== -1) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
type: 'redundant',
|
|
||||||
error: (trans.error_already_selected || '"{name}" is already selected.').replace('{name}', name)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (section === 'exclude' && excludeIds.indexOf(id) !== -1) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
type: 'redundant',
|
|
||||||
error: (trans.error_already_excluded || '"{name}" is already in an exclude list.').replace('{name}', name)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: true };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for parent-child conflicts in tree entities
|
|
||||||
*/
|
|
||||||
checkTreeConflicts: function(id, name, section, data, includeIds, excludeIds, trans) {
|
|
||||||
// Need tree data for parent-child lookups
|
|
||||||
if (!this.treeFlatData) {
|
|
||||||
return { valid: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
var parentId = data && data.parentId ? parseInt(data.parentId, 10) : null;
|
|
||||||
|
|
||||||
// Build lookup for quick access
|
|
||||||
var lookup = {};
|
|
||||||
this.treeFlatData.forEach(function(item) {
|
|
||||||
lookup[parseInt(item.id, 10)] = item;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get all ancestor IDs
|
|
||||||
var ancestorIds = this.getAncestorIds(id, lookup);
|
|
||||||
|
|
||||||
// Get all descendant IDs
|
|
||||||
var descendantIds = this.getDescendantIds(id, lookup);
|
|
||||||
|
|
||||||
if (section === 'include') {
|
|
||||||
// Check if any ancestor is excluded
|
|
||||||
for (var i = 0; i < ancestorIds.length; i++) {
|
|
||||||
if (excludeIds.indexOf(ancestorIds[i]) !== -1) {
|
|
||||||
var ancestorName = lookup[ancestorIds[i]] ? lookup[ancestorIds[i]].name : 'Parent';
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
type: 'parent_excluded',
|
|
||||||
error: (trans.error_parent_excluded || 'Cannot include "{name}" because its parent "{parent}" is excluded.').replace('{name}', name).replace('{parent}', ancestorName)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if any descendant is excluded
|
|
||||||
for (var j = 0; j < descendantIds.length; j++) {
|
|
||||||
if (excludeIds.indexOf(descendantIds[j]) !== -1) {
|
|
||||||
var descendantName = lookup[descendantIds[j]] ? lookup[descendantIds[j]].name : 'Child';
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
type: 'child_excluded',
|
|
||||||
error: (trans.error_child_excluded || 'Cannot include "{name}" because its child "{child}" is excluded. Remove the child from exclude first.').replace('{name}', name).replace('{child}', descendantName)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (section === 'exclude') {
|
|
||||||
// Check if any ancestor is included
|
|
||||||
for (var k = 0; k < ancestorIds.length; k++) {
|
|
||||||
if (includeIds.indexOf(ancestorIds[k]) !== -1) {
|
|
||||||
var parentName = lookup[ancestorIds[k]] ? lookup[ancestorIds[k]].name : 'Parent';
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
type: 'parent_included',
|
|
||||||
error: (trans.error_parent_included || 'Cannot exclude "{name}" because its parent "{parent}" is included. This would create a contradiction.').replace('{name}', name).replace('{parent}', parentName)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if any descendant is included (warning about implicit exclusion)
|
|
||||||
var includedDescendants = [];
|
|
||||||
for (var m = 0; m < descendantIds.length; m++) {
|
|
||||||
if (includeIds.indexOf(descendantIds[m]) !== -1) {
|
|
||||||
var childName = lookup[descendantIds[m]] ? lookup[descendantIds[m]].name : 'Child';
|
|
||||||
includedDescendants.push(childName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includedDescendants.length > 0) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
type: 'children_included',
|
|
||||||
error: (trans.error_children_included || 'Cannot exclude "{name}" because its children ({children}) are included. Remove them from include first.').replace('{name}', name).replace('{children}', includedDescendants.slice(0, 3).join(', ') + (includedDescendants.length > 3 ? '...' : ''))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: true };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all ancestor IDs for a given item
|
|
||||||
*/
|
|
||||||
getAncestorIds: function(id, lookup) {
|
|
||||||
var ancestors = [];
|
|
||||||
var current = lookup[id];
|
|
||||||
|
|
||||||
while (current && current.parent_id) {
|
|
||||||
var parentId = parseInt(current.parent_id, 10);
|
|
||||||
if (parentId && lookup[parentId]) {
|
|
||||||
ancestors.push(parentId);
|
|
||||||
current = lookup[parentId];
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ancestors;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all descendant IDs for a given item
|
|
||||||
*/
|
|
||||||
getDescendantIds: function(id, lookup) {
|
|
||||||
var descendants = [];
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Find direct children
|
|
||||||
Object.keys(lookup).forEach(function(key) {
|
|
||||||
var item = lookup[key];
|
|
||||||
if (parseInt(item.parent_id, 10) === id) {
|
|
||||||
var childId = parseInt(item.id, 10);
|
|
||||||
descendants.push(childId);
|
|
||||||
// Recursively get children's descendants
|
|
||||||
var childDescendants = self.getDescendantIds(childId, lookup);
|
|
||||||
descendants = descendants.concat(childDescendants);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return descendants;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get chip IDs from a picker
|
|
||||||
*/
|
|
||||||
getChipIds: function($picker) {
|
|
||||||
var ids = [];
|
|
||||||
$picker.find('.entity-chip').each(function() {
|
|
||||||
ids.push(parseInt($(this).data('id'), 10));
|
|
||||||
});
|
|
||||||
return ids;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate pending selections (for tree view bulk operations)
|
|
||||||
* Returns array of invalid items
|
|
||||||
*/
|
|
||||||
validatePendingSelections: function(pendingSelections, section) {
|
|
||||||
var self = this;
|
|
||||||
var errors = [];
|
|
||||||
|
|
||||||
if (!pendingSelections || !pendingSelections.length) {
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingSelections.forEach(function(sel) {
|
|
||||||
var result = self.validateSelection(sel.id, sel.name, section, sel.data || {});
|
|
||||||
if (!result.valid) {
|
|
||||||
errors.push({
|
|
||||||
id: sel.id,
|
|
||||||
name: sel.name,
|
|
||||||
error: result.error,
|
|
||||||
type: result.type
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show validation error toast
|
|
||||||
*/
|
|
||||||
showValidationError: function(message) {
|
|
||||||
var trans = this.config.trans || {};
|
|
||||||
var title = trans.validation_error || 'Selection Conflict';
|
|
||||||
|
|
||||||
// Remove existing toast
|
|
||||||
$('.es-validation-toast').remove();
|
|
||||||
|
|
||||||
// Create toast HTML
|
|
||||||
var html = '<div class="es-validation-toast">';
|
|
||||||
html += '<div class="es-toast-icon">' + this.esIcon('warning') + '</div>';
|
|
||||||
html += '<div class="es-toast-content">';
|
|
||||||
html += '<div class="es-toast-title">' + this.escapeHtml(title) + '</div>';
|
|
||||||
html += '<div class="es-toast-message">' + this.escapeHtml(message) + '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '<button type="button" class="es-toast-close">' + this.esIcon('close') + '</button>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
var $toast = $(html);
|
|
||||||
$('body').append($toast);
|
|
||||||
|
|
||||||
// Position near dropdown if visible
|
|
||||||
if (this.$dropdown && this.$dropdown.hasClass('show')) {
|
|
||||||
var dropdownOffset = this.$dropdown.offset();
|
|
||||||
$toast.css({
|
|
||||||
position: 'fixed',
|
|
||||||
top: dropdownOffset.top - $toast.outerHeight() - 10,
|
|
||||||
left: dropdownOffset.left,
|
|
||||||
zIndex: 10001
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$toast.css({
|
|
||||||
position: 'fixed',
|
|
||||||
top: 20,
|
|
||||||
right: 20,
|
|
||||||
zIndex: 10001
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animate in
|
|
||||||
$toast.hide().fadeIn(200);
|
|
||||||
|
|
||||||
// Auto-dismiss after 5 seconds
|
|
||||||
setTimeout(function() {
|
|
||||||
$toast.fadeOut(200, function() {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
// Close button
|
|
||||||
$toast.on('click', '.es-toast-close', function() {
|
|
||||||
$toast.fadeOut(200, function() {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate and add selection (wrapper that validates before adding)
|
|
||||||
* Returns true if added successfully, false if validation failed
|
|
||||||
*/
|
|
||||||
validateAndAddSelection: function($picker, id, name, data, section) {
|
|
||||||
var result = this.validateSelection(id, name, section, data || {});
|
|
||||||
|
|
||||||
if (!result.valid) {
|
|
||||||
this.showValidationError(result.error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation passed, add the selection
|
|
||||||
this.addSelection($picker, id, name, data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); header("Location: ../"); exit;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); header("Location: ../"); exit;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Placeholder - _core.js
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Placeholder - _timeline.js
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); header("Location: ../"); exit;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); header("Location: ../"); exit;
|
|
||||||
@@ -1,351 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector Mixins
|
|
||||||
* Reusable patterns - prefer Bootstrap utilities in HTML where possible
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use "sass:color";
|
|
||||||
@use 'variables' as *;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Layout
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
@mixin flex-center {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin flex-between {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin grid-columns($cols) {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat($cols, 1fr);
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Text
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
@mixin text-truncate {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Interactive Elements
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
// Reset button styles
|
|
||||||
@mixin button-reset {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font: inherit;
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus ring (Bootstrap 4 style)
|
|
||||||
@mixin focus-ring($color: $es-primary) {
|
|
||||||
outline: 0;
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba($color, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interactive hover state
|
|
||||||
@mixin interactive-item {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color $es-transition-fast, color $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $es-bg-hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Cards & Containers
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
@mixin card {
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dropdown-container {
|
|
||||||
position: absolute;
|
|
||||||
z-index: $es-z-dropdown;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
box-shadow: $es-shadow-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Form Elements
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
// Reset input styles (for inputs in custom wrappers)
|
|
||||||
@mixin input-reset {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font: inherit;
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin input-base {
|
|
||||||
width: 100%;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
line-height: $es-line-height-normal;
|
|
||||||
color: $es-text-primary;
|
|
||||||
background-color: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
transition: border-color $es-transition-fast, box-shadow $es-transition-fast;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: $es-primary;
|
|
||||||
@include focus-ring($es-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: $es-text-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Scrollbar
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
@mixin custom-scrollbar {
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: $es-gray-100;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: $es-gray-300;
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-gray-400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Badges & Chips
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
@mixin badge($bg: $es-gray-200, $color: $es-gray-700) {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0.125rem 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
line-height: 1;
|
|
||||||
background-color: $bg;
|
|
||||||
color: $color;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count badge with preview icon (used for tab badges, match counts, totals)
|
|
||||||
// Note: Eye icon is provided in HTML via <i class="material-icons">visibility</i>
|
|
||||||
@mixin count-badge($bg: $es-primary) {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
min-width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
background: $bg;
|
|
||||||
color: $es-white;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.05);
|
|
||||||
box-shadow: 0 2px 8px rgba($bg, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus state - maintain styled appearance
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px rgba($bg, 0.3), 0 2px 8px rgba($bg, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading state - spinner icon replaces eye
|
|
||||||
&.loading {
|
|
||||||
cursor: wait;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 10px;
|
|
||||||
animation: spin 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inactive/empty state
|
|
||||||
&.inactive,
|
|
||||||
&.no-matches {
|
|
||||||
background: $es-slate-400;
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popover open state
|
|
||||||
&.popover-open {
|
|
||||||
background: color.adjust($bg, $lightness: -10%);
|
|
||||||
box-shadow: 0 2px 8px rgba($bg, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Icon inside badge (eye, spinner, etc.)
|
|
||||||
i {
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 1;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover i {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-count {
|
|
||||||
font-weight: $es-font-weight-bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin-pulse {
|
|
||||||
0% { transform: rotate(0deg); opacity: 1; }
|
|
||||||
50% { opacity: 0.4; }
|
|
||||||
100% { transform: rotate(360deg); opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global spin utility classes (Material Icons replacement for icon-spin / icon-spin-pulse)
|
|
||||||
.es-spin {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.es-spin-pulse {
|
|
||||||
animation: spin-pulse 1s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin chip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
padding: $es-spacing-xs $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
background: $es-gray-200;
|
|
||||||
color: $es-gray-700;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
|
|
||||||
.chip-remove {
|
|
||||||
@include button-reset;
|
|
||||||
@include flex-center;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
font-size: 10px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Toggle Switch
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
@mixin toggle-switch($width: 36px, $height: 20px) {
|
|
||||||
position: relative;
|
|
||||||
width: $width;
|
|
||||||
height: $height;
|
|
||||||
border-radius: $height;
|
|
||||||
background: $es-gray-400;
|
|
||||||
transition: background-color $es-transition-normal;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
width: $height - 4px;
|
|
||||||
height: $height - 4px;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
||||||
transition: transform $es-transition-normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: $es-success;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
transform: translateX($width - $height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Screen Reader Only
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
@mixin sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector Variables
|
|
||||||
* Bootstrap 4 compatible values for PrestaShop admin theme
|
|
||||||
*
|
|
||||||
* Imports shared variables from prestashop-admin package
|
|
||||||
* and maps them to $es-* prefixed aliases for this package
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Import shared variables from prestashop-admin
|
|
||||||
@use '../../../prestashop-admin/assets/scss/variables' as admin;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Base Colors
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-white: #ffffff !default;
|
|
||||||
$es-black: #000000 !default;
|
|
||||||
|
|
||||||
// Primary (from prestashop-admin)
|
|
||||||
$es-primary: admin.$primary !default;
|
|
||||||
$es-primary-hover: #1a9ab7 !default;
|
|
||||||
$es-primary-light: rgba(37, 185, 215, 0.1) !default;
|
|
||||||
|
|
||||||
// Semantic colors (from prestashop-admin)
|
|
||||||
$es-success: admin.$success !default;
|
|
||||||
$es-success-light: #d4edda !default;
|
|
||||||
$es-success-dark: #1e7e34 !default;
|
|
||||||
|
|
||||||
$es-danger: admin.$danger !default;
|
|
||||||
$es-danger-light: #f8d7da !default;
|
|
||||||
$es-danger-dark: #bd2130 !default;
|
|
||||||
|
|
||||||
$es-warning: admin.$warning !default;
|
|
||||||
$es-warning-light: #fff3cd !default;
|
|
||||||
|
|
||||||
$es-info: admin.$info !default;
|
|
||||||
$es-info-light: #d1ecf1 !default;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Gray Scale (Bootstrap 4)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-gray-100: admin.$light !default;
|
|
||||||
$es-gray-200: #e9ecef !default;
|
|
||||||
$es-gray-300: admin.$border-color !default;
|
|
||||||
$es-gray-400: #ced4da !default;
|
|
||||||
$es-gray-500: #adb5bd !default;
|
|
||||||
$es-gray-600: admin.$secondary !default;
|
|
||||||
$es-gray-700: #495057 !default;
|
|
||||||
$es-gray-800: admin.$dark !default;
|
|
||||||
$es-gray-900: #212529 !default;
|
|
||||||
|
|
||||||
// Slate (subtle variations)
|
|
||||||
$es-slate-50: #f8fafc !default;
|
|
||||||
$es-slate-100: #f1f5f9 !default;
|
|
||||||
$es-slate-200: #e2e8f0 !default;
|
|
||||||
$es-slate-300: #cbd5e1 !default;
|
|
||||||
$es-slate-400: #94a3b8 !default;
|
|
||||||
$es-slate-500: #64748b !default;
|
|
||||||
$es-slate-600: #475569 !default;
|
|
||||||
$es-slate-700: #334155 !default;
|
|
||||||
$es-slate-800: #1e293b !default;
|
|
||||||
$es-slate-900: #0f172a !default;
|
|
||||||
|
|
||||||
// Cyan
|
|
||||||
$es-cyan-50: #ecfeff !default;
|
|
||||||
$es-cyan-100: #cffafe !default;
|
|
||||||
$es-cyan-200: #a5f3fc !default;
|
|
||||||
$es-cyan-500: #06b6d4 !default;
|
|
||||||
$es-cyan-600: #0891b2 !default;
|
|
||||||
$es-cyan-700: #0e7490 !default;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Semantic Aliases
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-bg-header: $es-gray-100 !default;
|
|
||||||
$es-bg-hover: $es-gray-200 !default;
|
|
||||||
$es-bg-active: $es-gray-200 !default;
|
|
||||||
$es-bg-body: $es-white !default;
|
|
||||||
|
|
||||||
$es-border-color: admin.$border-color !default;
|
|
||||||
$es-border-light: $es-gray-200 !default;
|
|
||||||
$es-border-dark: $es-gray-400 !default;
|
|
||||||
|
|
||||||
$es-text-primary: $es-gray-900 !default;
|
|
||||||
$es-text-secondary: $es-gray-700 !default;
|
|
||||||
$es-text-muted: $es-gray-600 !default;
|
|
||||||
$es-text-light: $es-gray-500 !default;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Spacing (Bootstrap 4 compatible, derived from admin.$spacer)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-spacing-xs: admin.$spacer * 0.25 !default; // 4px
|
|
||||||
$es-spacing-sm: admin.$spacer * 0.5 !default; // 8px
|
|
||||||
$es-spacing-md: admin.$spacer !default; // 16px
|
|
||||||
$es-spacing-lg: admin.$spacer * 1.5 !default; // 24px
|
|
||||||
$es-spacing-xl: admin.$spacer * 2 !default; // 32px
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Border Radius (from prestashop-admin)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-radius-sm: admin.$border-radius-sm !default;
|
|
||||||
$es-radius-md: admin.$border-radius !default;
|
|
||||||
$es-radius-lg: admin.$border-radius-lg !default;
|
|
||||||
$es-radius-xl: 0.5rem !default;
|
|
||||||
$es-radius-full: 50rem !default;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Box Shadows (Bootstrap 4 compatible)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !default;
|
|
||||||
$es-shadow-md: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !default;
|
|
||||||
$es-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175) !default;
|
|
||||||
$es-shadow-xl: 0 1.5rem 4rem rgba(0, 0, 0, 0.2) !default;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Transitions
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-transition-fast: 0.15s ease-in-out !default;
|
|
||||||
$es-transition-normal: 0.2s ease-in-out !default;
|
|
||||||
$es-transition-slow: 0.3s ease-in-out !default;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Z-Index (below Bootstrap modal)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-z-dropdown: 1000 !default;
|
|
||||||
$es-z-modal: 1050 !default;
|
|
||||||
$es-z-popover: 1060 !default;
|
|
||||||
$es-z-tooltip: 1070 !default;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Typography
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$es-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
|
|
||||||
|
|
||||||
$es-font-size-xs: 0.75rem !default; // 12px
|
|
||||||
$es-font-size-sm: 0.875rem !default; // 14px
|
|
||||||
$es-font-size-base: 1rem !default; // 16px
|
|
||||||
$es-font-size-lg: 1.125rem !default; // 18px
|
|
||||||
|
|
||||||
$es-font-weight-normal: 400 !default;
|
|
||||||
$es-font-weight-medium: 500 !default;
|
|
||||||
$es-font-weight-semibold: 600 !default;
|
|
||||||
$es-font-weight-bold: 700 !default;
|
|
||||||
|
|
||||||
$es-line-height-tight: 1.25 !default;
|
|
||||||
$es-line-height-normal: 1.5 !default;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,396 +0,0 @@
|
|||||||
/**
|
|
||||||
* Combination Attributes Picker Component
|
|
||||||
* Product attribute combination selection styles
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use "sass:color";
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
|
|
||||||
// Main container
|
|
||||||
.combination-attributes-picker {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode toggle (Any/All)
|
|
||||||
.combination-mode-toggle {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.125rem;
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combination-mode-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 11px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
padding: 0.25rem 0.625rem;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
input[type="radio"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mode-label {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $es-primary;
|
|
||||||
background: rgba($es-primary, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:has(input[type="radio"]:checked) {
|
|
||||||
background: $es-primary;
|
|
||||||
color: $es-white;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Groups container
|
|
||||||
.combination-groups-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading/Empty/Error states
|
|
||||||
.combination-loading,
|
|
||||||
.combination-empty,
|
|
||||||
.combination-error {
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-style: italic;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combination-error {
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section header
|
|
||||||
.combinations-section {
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combinations-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combinations-label {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combinations-help {
|
|
||||||
font-size: 11px;
|
|
||||||
color: $es-slate-400;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle combinations button
|
|
||||||
.btn-toggle-combinations {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-primary;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid $es-primary;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-remove-combinations {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-danger;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Attribute Group
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.comb-attr-group {
|
|
||||||
flex: none;
|
|
||||||
min-width: 120px;
|
|
||||||
max-width: 200px;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-gray-300;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.has-selections {
|
|
||||||
border-color: $es-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.comb-attr-group-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.375rem 0.625rem;
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-bottom: 1px solid $es-gray-300;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-slate-800;
|
|
||||||
|
|
||||||
.comb-attr-group.has-selections & {
|
|
||||||
background: $es-cyan-50;
|
|
||||||
border-bottom-color: $es-cyan-200;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.comb-attr-group-name {
|
|
||||||
flex: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comb-attr-group-count {
|
|
||||||
flex-shrink: 0;
|
|
||||||
min-width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
padding: 0 0.25rem;
|
|
||||||
background: $es-gray-300;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
line-height: 18px;
|
|
||||||
text-align: center;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
.comb-attr-group.has-selections & {
|
|
||||||
background: $es-primary;
|
|
||||||
color: $es-white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toolbar
|
|
||||||
.comb-attr-toolbar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0.375rem;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-bottom: 1px solid $es-slate-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comb-toolbar-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
padding: 0;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-gray-300;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
color: $es-text-muted;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-color: $es-slate-400;
|
|
||||||
color: $es-slate-800;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.comb-attr-search {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 60px;
|
|
||||||
padding: 0.125rem 0.375rem;
|
|
||||||
border: 1px solid $es-gray-300;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
font-size: 11px;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: $es-slate-400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Values container
|
|
||||||
.comb-attr-values {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.375rem;
|
|
||||||
max-height: 150px;
|
|
||||||
overflow-y: auto;
|
|
||||||
@include custom-scrollbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comb-attr-loading,
|
|
||||||
.comb-attr-empty,
|
|
||||||
.comb-attr-error {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
color: $es-slate-400;
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comb-attr-error {
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Individual value
|
|
||||||
.comb-attr-value {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.125rem 0.5rem;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-slate-400;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
font-size: 11px;
|
|
||||||
color: $es-slate-600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: $es-primary;
|
|
||||||
border-color: $es-primary-hover;
|
|
||||||
color: $es-white;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-hover;
|
|
||||||
border-color: color.adjust($es-primary-hover, $lightness: -5%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.comb-attr-value-count {
|
|
||||||
font-size: 9px;
|
|
||||||
color: $es-slate-400;
|
|
||||||
background: $es-slate-100;
|
|
||||||
padding: 1px 0.25rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
min-width: 14px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.comb-attr-value.selected & {
|
|
||||||
color: $es-white;
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Combination Conditions (Row-based)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.combination-conditions-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combination-condition-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combination-group-select,
|
|
||||||
.combination-values-select {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combination-equals {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
padding: 0 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-add-combination-condition {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-primary;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px dashed $es-primary;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-remove-combination-row {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba($es-danger, 0.1);
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,358 +0,0 @@
|
|||||||
/**
|
|
||||||
* Condition Trait Base Styles
|
|
||||||
* Shared styling for all condition trait components
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
// Base condition trait container
|
|
||||||
.condition-trait {
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
margin-bottom: $es-spacing-lg;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collapsed state
|
|
||||||
.condition-trait.collapsed {
|
|
||||||
.condition-trait-header {
|
|
||||||
border-bottom-color: transparent;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-icon {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Trait Header
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.condition-trait-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: $es-spacing-lg;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 0.875rem $es-spacing-lg;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg $es-radius-lg 0 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-header-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-icon {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
color: $es-text-muted;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-title-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.125rem;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-title {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-slate-800;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-subtitle {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule summary (shows current config at a glance)
|
|
||||||
.trait-summary {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.375rem;
|
|
||||||
padding: 0.25rem 0.625rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-primary;
|
|
||||||
background: rgba($es-primary, 0.08);
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-left: $es-spacing-md;
|
|
||||||
flex-shrink: 0;
|
|
||||||
max-width: 320px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-header-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-header-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collapse icon
|
|
||||||
.collapse-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-text-muted;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $es-primary;
|
|
||||||
background: rgba($es-primary, 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show all toggle
|
|
||||||
.trait-show-all-toggle {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-primary;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trait total count badge (global fallback)
|
|
||||||
.trait-total-count {
|
|
||||||
@include count-badge($es-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required indicator
|
|
||||||
.trait-required {
|
|
||||||
color: $es-danger;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation error
|
|
||||||
.trait-validation-error {
|
|
||||||
color: $es-danger;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trait toggle button
|
|
||||||
.trait-toggle {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-color: $es-gray-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: $es-primary;
|
|
||||||
border-color: $es-primary;
|
|
||||||
background: $es-primary-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Trait Body
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.condition-trait-body {
|
|
||||||
padding: $es-spacing-lg;
|
|
||||||
border-radius: 0 0 $es-radius-lg $es-radius-lg;
|
|
||||||
background: $es-white;
|
|
||||||
animation: slideDown 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Condition trait collapsed - hide body
|
|
||||||
.condition-trait.collapsed .condition-trait-body {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Section Styles
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.schedule-section,
|
|
||||||
.context-section {
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
padding-bottom: 1.25rem;
|
|
||||||
border-bottom: 1px solid $es-slate-100;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-slate-600;
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-slate-400;
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-content {
|
|
||||||
// Container for section content
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-hint {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
font-size: 11px;
|
|
||||||
color: $es-slate-400;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Full-width Form Group Override
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.form-group.condition-trait-fullwidth {
|
|
||||||
display: block !important;
|
|
||||||
|
|
||||||
> .control-label {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .col-lg-8,
|
|
||||||
> .col-lg-8.col-lg-offset-3 {
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
flex: 0 0 100% !important;
|
|
||||||
padding-left: $es-spacing-lg !important;
|
|
||||||
padding-right: $es-spacing-lg !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Condition traits group label
|
|
||||||
.condition-traits-group-label {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-slate-700;
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.condition-traits-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Collapse Header (form-content layout)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.entity-selector-collapse-header {
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: $es-spacing-sm;
|
|
||||||
|
|
||||||
.btn-collapse-toggle {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: $es-primary;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: color $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $es-primary-hover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-icon {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-label {
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When collapsed, rotate icon
|
|
||||||
.condition-trait.collapsed .entity-selector-collapse-header {
|
|
||||||
.collapse-icon {
|
|
||||||
// Icon already shows expand_more when collapsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Animations
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
@keyframes slideDown {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,516 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Item - Shared Base Component
|
|
||||||
* Unified styling for entity items in chips, lists, and previews
|
|
||||||
*
|
|
||||||
* Variants:
|
|
||||||
* - .entity-item (base) - default list-row style
|
|
||||||
* - .entity-item.chip-style - pill/chip style (compact)
|
|
||||||
* - .entity-item.card-style - card/grid style
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Entity Item Sizing
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
$entity-item-image-sm: 20px;
|
|
||||||
$entity-item-image-md: 32px;
|
|
||||||
$entity-item-image-lg: 48px;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Base Entity Item (list-row layout)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.entity-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: background $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-bg-hover;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clickable variant
|
|
||||||
&.clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selected state
|
|
||||||
&.selected {
|
|
||||||
background: $es-primary-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Entity Item Image
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
.entity-item-image {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: $entity-item-image-md;
|
|
||||||
height: $entity-item-image-md;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
background: $es-slate-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size variants
|
|
||||||
.entity-item-image--sm {
|
|
||||||
width: $entity-item-image-sm;
|
|
||||||
height: $entity-item-image-sm;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-image--lg {
|
|
||||||
width: $entity-item-image-lg;
|
|
||||||
height: $entity-item-image-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No-image placeholder
|
|
||||||
.entity-item-no-image {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: $entity-item-image-md;
|
|
||||||
height: $entity-item-image-md;
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
|
|
||||||
&--sm {
|
|
||||||
width: $entity-item-image-sm;
|
|
||||||
height: $entity-item-image-sm;
|
|
||||||
font-size: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Entity Item Info (name + meta)
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
.entity-item-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-name {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-primary;
|
|
||||||
@include text-truncate;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-meta {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
@include text-truncate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Entity Item Badge/Price (right side)
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
.entity-item-badge {
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 0.125rem 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-price {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Entity Item Actions (remove button, etc.)
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
.entity-item-action {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Chip Style Variant (compact pill)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.entity-item.chip-style {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: 0.375rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-image {
|
|
||||||
width: $entity-item-image-sm;
|
|
||||||
height: $entity-item-image-sm;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-no-image {
|
|
||||||
width: $entity-item-image-sm;
|
|
||||||
height: $entity-item-image-sm;
|
|
||||||
font-size: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-info {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-name {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-meta {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-item-action {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin-left: 0.125rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// List Style Variant (bordered rows)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.entity-item.list-style {
|
|
||||||
padding: $es-spacing-sm 0;
|
|
||||||
background: transparent;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
border-radius: 0;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-bg-hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Entity Item Container (wrapper for multiple items)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.entity-items-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toolbar (filter, sort, count, clear)
|
|
||||||
.entity-items-toolbar {
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
padding-bottom: 0;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&.has-items {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter input
|
|
||||||
.entity-items-filter {
|
|
||||||
all: unset;
|
|
||||||
display: block;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 80px;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
padding: 0.2rem 0.5rem 0.2rem 1.5rem;
|
|
||||||
background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") no-repeat 0.375rem center;
|
|
||||||
background-size: 10px;
|
|
||||||
border: 1px solid $es-slate-300;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.4;
|
|
||||||
color: $es-text-primary;
|
|
||||||
box-sizing: border-box;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: $es-primary;
|
|
||||||
box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort dropdown
|
|
||||||
.entity-items-sort {
|
|
||||||
all: unset;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
padding: 0.2rem 1.25rem 0.2rem 0.5rem;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cpath fill='%23666' d='M0 2l4 4 4-4z'/%3E%3C/svg%3E") no-repeat right 0.375rem center;
|
|
||||||
background-size: 8px;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 1.4;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
cursor: pointer;
|
|
||||||
box-sizing: border-box;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: $es-primary;
|
|
||||||
box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count badge
|
|
||||||
.entity-items-count {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
gap: 0.125rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
white-space: nowrap;
|
|
||||||
line-height: 1.4;
|
|
||||||
|
|
||||||
&.has-filter {
|
|
||||||
background: $es-cyan-100;
|
|
||||||
color: $es-cyan-700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear button
|
|
||||||
.entity-items-clear {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
color: $es-danger;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
background: rgba($es-danger, 0.1);
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
white-space: nowrap;
|
|
||||||
line-height: 1.4;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-danger;
|
|
||||||
color: $es-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 9px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Items list area
|
|
||||||
.entity-items-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md $es-spacing-md;
|
|
||||||
min-height: 40px;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
@include custom-scrollbar;
|
|
||||||
|
|
||||||
&:empty {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// List layout (vertical)
|
|
||||||
&.list-layout {
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 0;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load more section
|
|
||||||
.entity-items-load-more {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: transparent;
|
|
||||||
border-top: 1px dashed $es-border-color;
|
|
||||||
|
|
||||||
.load-more-label {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-select {
|
|
||||||
appearance: none;
|
|
||||||
padding: 0.25rem 1.75rem 0.25rem 0.5rem;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cpath fill='%23666' d='M0 2l4 4 4-4z'/%3E%3C/svg%3E") no-repeat right 0.5rem center;
|
|
||||||
background-size: 8px;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-primary;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
height: auto;
|
|
||||||
min-height: 0;
|
|
||||||
line-height: 1.3;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: $es-primary;
|
|
||||||
background-color: $es-primary-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: $es-primary;
|
|
||||||
box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-remaining {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
.remaining-count {
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-load-more {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: $es-spacing-xs;
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
color: $es-primary;
|
|
||||||
background: $es-primary-light;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
font: inherit;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba($es-primary, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.loading {
|
|
||||||
cursor: wait;
|
|
||||||
|
|
||||||
i {
|
|
||||||
animation: spin 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Empty & Loading States
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.entity-items-empty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-xl;
|
|
||||||
text-align: center;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 2rem;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-items-loading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: $es-spacing-xl;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 20px;
|
|
||||||
animation: spin 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector - Main Component Styles
|
|
||||||
* Wrapper, header, body, tabs, blocks
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
// Main wrapper (supports both .target-conditions-trait and .entity-selector-trait)
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
position: relative;
|
|
||||||
overflow: visible;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
|
|
||||||
// Base Material Icons sizing — 18px throughout entity-selector
|
|
||||||
// !important needed to override .bootstrap .material-icons:not(.js-mobile-menu) { font-size: inherit }
|
|
||||||
.material-icons {
|
|
||||||
font-size: 18px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trait Header (collapsible)
|
|
||||||
.condition-trait-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: 0.875rem $es-spacing-md;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg $es-radius-lg 0 0;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
transition: background-color $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-bg-hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-header-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-icon {
|
|
||||||
font-size: $es-font-size-lg;
|
|
||||||
color: $es-text-muted;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-title-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.125rem;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-title {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-subtitle {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Total count badge in header
|
|
||||||
.trait-total-count {
|
|
||||||
@include count-badge($es-primary);
|
|
||||||
margin-left: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show all toggle switch
|
|
||||||
.trait-show-all-toggle {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
padding: 0.25rem $es-spacing-sm;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
transition: background-color $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-label {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-all-checkbox {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-slider {
|
|
||||||
position: relative;
|
|
||||||
width: 36px;
|
|
||||||
height: 20px;
|
|
||||||
background: $es-slate-300;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
transition: background-color $es-transition-normal;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
||||||
transition: transform $es-transition-normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-all-checkbox:checked + .toggle-slider {
|
|
||||||
background: $es-success;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
transform: translateX(16px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation error states
|
|
||||||
&.has-validation-error {
|
|
||||||
border-color: $es-danger;
|
|
||||||
box-shadow: 0 0 0 3px rgba($es-danger, 0.1);
|
|
||||||
|
|
||||||
.condition-trait-header {
|
|
||||||
border-bottom-color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-validation-error {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: 0.625rem $es-spacing-md;
|
|
||||||
background: $es-danger-light;
|
|
||||||
color: #b91c1c;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
border-bottom: 1px solid #fecaca;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required indicator
|
|
||||||
&.trait-required .trait-title::after {
|
|
||||||
content: ' *';
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Body
|
|
||||||
.condition-trait-body {
|
|
||||||
padding: 0;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: 0 0 $es-radius-lg $es-radius-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block type tabs
|
|
||||||
.target-block-tabs {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0;
|
|
||||||
padding: 0;
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-block-tab {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
flex: none;
|
|
||||||
min-width: 0;
|
|
||||||
padding: 0.625rem $es-spacing-md;
|
|
||||||
margin-bottom: -1px;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-slate-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: $es-white;
|
|
||||||
border-bottom-color: $es-cyan-500;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
i.material-icons {
|
|
||||||
font-size: 18px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-label {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-badge {
|
|
||||||
@include count-badge($es-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-data:not(.active) .tab-badge {
|
|
||||||
@include count-badge($es-slate-400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tabs row with actions (form-content layout)
|
|
||||||
.entity-selector-tabs-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg $es-radius-lg 0 0;
|
|
||||||
|
|
||||||
.target-block-tabs {
|
|
||||||
flex: 1;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-radius: $es-radius-lg 0 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand/collapse groups button (standalone, in tabs row)
|
|
||||||
.entity-selector-actions:not(.btn-toggle-blocks) {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: $es-spacing-xs $es-spacing-md;
|
|
||||||
border-left: 1px solid $es-border-color;
|
|
||||||
|
|
||||||
.btn-toggle-groups {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
padding: 0;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-slate-400;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
color: $es-slate-700;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-light;
|
|
||||||
color: $es-primary;
|
|
||||||
border-color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
font-size: 18px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand/collapse toggle area (entire section is clickable)
|
|
||||||
.entity-selector-actions.btn-toggle-blocks {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 $es-spacing-md;
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-left: 1px solid $es-border-color;
|
|
||||||
color: $es-slate-400;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
font-size: 20px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When expanded - highlight the toggle area
|
|
||||||
.entity-selector-trait:not(.blocks-collapsed) .entity-selector-actions.btn-toggle-blocks {
|
|
||||||
background: $es-primary-light;
|
|
||||||
border-left-color: $es-primary;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blocks content wrapper (for form-content layout collapse)
|
|
||||||
.entity-selector-blocks-content {
|
|
||||||
// Inherits styles from condition-trait-body context
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block container
|
|
||||||
.target-block-container {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-block-content {
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-block-groups {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block header (for standalone blocks)
|
|
||||||
.target-block-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty state
|
|
||||||
.target-block-empty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-xl;
|
|
||||||
text-align: center;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 2rem !important;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collapse toggle
|
|
||||||
.trait-collapse-toggle,
|
|
||||||
.collapse-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform $es-transition-normal;
|
|
||||||
|
|
||||||
&.collapsed {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header actions
|
|
||||||
.trait-header-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collapsed state
|
|
||||||
&.collapsed {
|
|
||||||
.condition-trait-body {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.condition-trait-header {
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single mode specific styles
|
|
||||||
.target-conditions-trait.single-mode,
|
|
||||||
.entity-selector-trait.single-mode {
|
|
||||||
.target-block-tabs {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-block-container {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header action buttons
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
.header-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-action-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-muted;
|
|
||||||
background: transparent;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,982 +0,0 @@
|
|||||||
/**
|
|
||||||
* Groups Component
|
|
||||||
* Selection groups, include/exclude sections, method selectors
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
|
|
||||||
// Group container
|
|
||||||
.target-group {
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group header
|
|
||||||
.target-group-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-group-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;
|
|
||||||
|
|
||||||
.group-number {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
padding: 0 0.25rem;
|
|
||||||
background: $es-primary;
|
|
||||||
color: $es-white;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-bold;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-group-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-action-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.danger:hover {
|
|
||||||
background: $es-danger-light;
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group body
|
|
||||||
.target-group-body,
|
|
||||||
.group-body {
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include section
|
|
||||||
.include-section {
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
margin-bottom: $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
|
|
||||||
&.label-include {
|
|
||||||
color: $es-success-dark;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.label-exclude {
|
|
||||||
color: $es-danger;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method selector
|
|
||||||
.method-selector {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
margin-bottom: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-selector-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-select {
|
|
||||||
@include input-base;
|
|
||||||
padding-right: 2rem;
|
|
||||||
cursor: pointer;
|
|
||||||
appearance: none;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
|
|
||||||
background-position: right 0.5rem center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 1.5em 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-help-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value picker (search trigger)
|
|
||||||
.value-picker {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value-picker-trigger {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
width: 100%;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
text-align: left;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: $es-slate-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: $es-primary;
|
|
||||||
@include focus-ring($es-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-text-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pattern input (text input for patterns)
|
|
||||||
.pattern-input-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pattern-input {
|
|
||||||
@include input-base;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pattern-add-btn {
|
|
||||||
@include button-reset;
|
|
||||||
position: absolute;
|
|
||||||
right: 0.25rem;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
color: $es-primary;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-range input (price ranges)
|
|
||||||
.multi-range-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-input {
|
|
||||||
@include input-base;
|
|
||||||
width: 100px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-separator {
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-remove-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-danger-light;
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-add-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
color: $es-primary;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-select tiles (stock status, etc.)
|
|
||||||
.multi-select-tiles {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multi-select-tile {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: $es-primary-light;
|
|
||||||
color: $es-primary;
|
|
||||||
border-color: $es-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exclude section
|
|
||||||
.exclude-section {
|
|
||||||
margin-top: $es-spacing-md;
|
|
||||||
padding-top: $es-spacing-md;
|
|
||||||
border-top: 1px dashed $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy exclude-rows (if used elsewhere)
|
|
||||||
.exclude-rows {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.exclude-row-content {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.exclude-remove-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-danger-light;
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-exclude-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
margin-top: $es-spacing-sm;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
color: $es-danger;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
border: 1px dashed $es-danger;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-danger-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add group button (used in block-footer)
|
|
||||||
.btn-add-group {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.375rem;
|
|
||||||
padding: 0.5rem 0.875rem;
|
|
||||||
color: $es-primary;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
background: rgba($es-primary, 0.05);
|
|
||||||
border: 1px dashed $es-primary;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba($es-primary, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block footer
|
|
||||||
.block-footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
border-top: 1px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block body
|
|
||||||
.block-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Groups container
|
|
||||||
.groups-container {
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Groups empty state
|
|
||||||
.groups-empty-state {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: $es-spacing-xl;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selection group
|
|
||||||
.selection-group {
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsed {
|
|
||||||
.group-body {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-collapse-toggle i {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group header
|
|
||||||
.group-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg $es-radius-lg 0 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.group-header-single {
|
|
||||||
padding: $es-spacing-xs $es-spacing-md;
|
|
||||||
background: transparent;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-collapse-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 20px !important;
|
|
||||||
transition: transform $es-transition-fast;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-name-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-name-input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background: $es-white;
|
|
||||||
border-color: $es-border-color;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-count-badge {
|
|
||||||
@include count-badge($es-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-remove-group {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-danger-light;
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group include section - green accent to distinguish from exclude
|
|
||||||
.group-include {
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
background: rgba($es-success, 0.03);
|
|
||||||
border: 1px solid rgba($es-success, 0.2);
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method selector wrapper (from PHP)
|
|
||||||
.method-selector-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-info-placeholder {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.include-method-select,
|
|
||||||
.exclude-method-select {
|
|
||||||
flex: 1;
|
|
||||||
@include input-base;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock indicator for method selector (when excludes are present)
|
|
||||||
.selector-locked {
|
|
||||||
.include-method-select {
|
|
||||||
opacity: 0.7;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-indicator {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: $es-warning;
|
|
||||||
cursor: help;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-tooltip {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
bottom: calc(100% + 8px);
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
padding: $es-spacing-xs $es-spacing-sm;
|
|
||||||
background: $es-slate-800;
|
|
||||||
color: $es-white;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .mpr-tooltip {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group excludes section
|
|
||||||
.group-excludes {
|
|
||||||
margin-top: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.except-separator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
margin: 0 0 $es-spacing-sm 0;
|
|
||||||
|
|
||||||
// Lines on both sides
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
flex: 1;
|
|
||||||
height: 1px;
|
|
||||||
background: rgba($es-danger, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.except-label {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
background: $es-danger-light;
|
|
||||||
color: $es-danger;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.exclude-rows-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.exclude-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
background: rgba($es-danger, 0.03);
|
|
||||||
border: 1px solid rgba($es-danger, 0.15);
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
|
|
||||||
// Value picker inside exclude row - full width
|
|
||||||
.value-picker {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.exclude-header-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.method-selector-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete button at the far right
|
|
||||||
.btn-remove-exclude-row {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-remove-exclude-row {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-danger-light;
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-add-exclude,
|
|
||||||
.btn-add-another-exclude {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
margin-top: $es-spacing-sm;
|
|
||||||
padding: 0.375rem 0.625rem;
|
|
||||||
color: $es-danger;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px dashed rgba($es-danger, 0.5);
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-danger-light;
|
|
||||||
border-color: $es-danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group modifiers (inline version from PHP)
|
|
||||||
// Uses negative margins to break out of .group-body padding
|
|
||||||
// Elements use .mpr-input-compact class to opt out of admin.css global sizing
|
|
||||||
.group-modifiers {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
margin: $es-spacing-md (-$es-spacing-md) (-$es-spacing-md);
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-top: 1px solid $es-border-color;
|
|
||||||
border-radius: 0 0 $es-radius-lg $es-radius-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modifier-inline {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.375rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common height for all modifier controls
|
|
||||||
$modifier-height: 26px;
|
|
||||||
|
|
||||||
.group-modifier-limit {
|
|
||||||
width: 50px;
|
|
||||||
max-width: 50px;
|
|
||||||
min-width: 50px;
|
|
||||||
height: $modifier-height;
|
|
||||||
padding: 0 0.375rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: $es-primary;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort modifier - input group style (select + button glued together)
|
|
||||||
.modifier-sort {
|
|
||||||
gap: 0; // Remove gap to glue select + button together
|
|
||||||
|
|
||||||
.modifier-label {
|
|
||||||
margin-right: 0.375rem; // Keep space between label and input group
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-modifier-sort {
|
|
||||||
width: auto;
|
|
||||||
height: $modifier-height;
|
|
||||||
padding: 0 1.25rem 0 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-sm 0 0 $es-radius-sm;
|
|
||||||
border-right: none;
|
|
||||||
cursor: pointer;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: $es-primary;
|
|
||||||
outline: none;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sort-dir {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: $modifier-height;
|
|
||||||
height: $modifier-height;
|
|
||||||
color: $es-text-muted;
|
|
||||||
background: $es-slate-100;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: 0 $es-radius-sm $es-radius-sm 0;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for elements outside .modifier-sort context
|
|
||||||
.group-modifier-sort {
|
|
||||||
height: $modifier-height;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: $es-primary;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sort-dir {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: $modifier-height;
|
|
||||||
height: $modifier-height;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-preview-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&.clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-light;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OR separator between groups
|
|
||||||
.group-separator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: $es-spacing-sm 0;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.1em;
|
|
||||||
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
flex: 1;
|
|
||||||
height: 1px;
|
|
||||||
background: $es-border-color;
|
|
||||||
margin: 0 $es-spacing-md;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group modifiers (limit, sort)
|
|
||||||
.group-modifiers {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding-top: $es-spacing-md;
|
|
||||||
margin-top: $es-spacing-md;
|
|
||||||
border-top: 1px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modifier-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modifier-label {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-muted;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modifier-input {
|
|
||||||
@include input-base;
|
|
||||||
width: 80px;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modifier-select {
|
|
||||||
@include input-base;
|
|
||||||
width: auto;
|
|
||||||
padding: 0.25rem 1.5rem 0.25rem 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
cursor: pointer;
|
|
||||||
appearance: none;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
|
|
||||||
background-position: right 0.25rem center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 1.25em 1.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Condition match count badge
|
|
||||||
.condition-match-count {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.125rem 0.375rem;
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-results {
|
|
||||||
background: $es-primary-light;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Country holidays variant - use calendar icon style
|
|
||||||
&.country-holidays {
|
|
||||||
background: rgba(139, 92, 246, 0.1);
|
|
||||||
color: #8b5cf6;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(139, 92, 246, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.clickable {
|
|
||||||
background: rgba(139, 92, 246, 0.15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,644 +0,0 @@
|
|||||||
/**
|
|
||||||
* List Preview Component
|
|
||||||
* Popover and modal views for entity preview
|
|
||||||
*
|
|
||||||
* Uses shared entity-item base for item styling.
|
|
||||||
* This file only contains popover/modal container styles.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview Popover Container
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.target-preview-popover,
|
|
||||||
.target-list-preview-popover {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10000;
|
|
||||||
min-width: 320px;
|
|
||||||
max-width: 480px;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
box-shadow: $es-shadow-lg;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
// Arrow pointing to badge
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -8px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-left: 8px solid transparent;
|
|
||||||
border-right: 8px solid transparent;
|
|
||||||
border-bottom: 8px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-left: 6px solid transparent;
|
|
||||||
border-right: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid $es-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Positioned to the right - arrow on left
|
|
||||||
&.position-right {
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
left: 20px;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Positioned to the left - arrow on right
|
|
||||||
&.position-left {
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
left: auto;
|
|
||||||
right: 20px;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview Header
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.preview-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
|
|
||||||
.preview-title {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-close {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview Tabs (entity type switcher)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.preview-tabs {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0;
|
|
||||||
padding: 0;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-tab {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.375rem;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
margin-bottom: -1px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: $es-white;
|
|
||||||
border-bottom-color: $es-primary;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview Filter
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.preview-filter {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-white;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-filter-input {
|
|
||||||
all: unset;
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-primary;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview Contents (tabbed content areas)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.preview-contents {
|
|
||||||
max-height: 350px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-content {
|
|
||||||
display: none;
|
|
||||||
max-height: 350px;
|
|
||||||
overflow-y: auto;
|
|
||||||
@include custom-scrollbar;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview Items Container
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.preview-items {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: $es-spacing-xs $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview Item - Uses entity-item patterns
|
|
||||||
// Maps legacy classes to shared styling
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.preview-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: background $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-bg-hover;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clickable items
|
|
||||||
&[data-id] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image - matches chip image sizing for consistency
|
|
||||||
.preview-item-image {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
background: $es-slate-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No-image placeholder
|
|
||||||
.preview-item-no-image {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info container
|
|
||||||
.preview-item-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name
|
|
||||||
.preview-item-name {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-primary;
|
|
||||||
@include text-truncate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Meta/ref (category, email, etc.)
|
|
||||||
.preview-item-ref,
|
|
||||||
.preview-item-meta {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
@include text-truncate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Price badge
|
|
||||||
.preview-item-price {
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-primary;
|
|
||||||
background: $es-primary-light;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview Footer (load more)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.preview-footer {
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-top: 1px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
.load-more-label {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-select {
|
|
||||||
appearance: none;
|
|
||||||
padding: 0.25rem 1.75rem 0.25rem 0.5rem;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
background: $es-white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cpath fill='%23666' d='M0 2l4 4 4-4z'/%3E%3C/svg%3E") no-repeat right 0.5rem center;
|
|
||||||
background-size: 8px;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-primary;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
height: auto;
|
|
||||||
min-height: 0;
|
|
||||||
line-height: 1.3;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: $es-primary;
|
|
||||||
background-color: $es-primary-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: $es-primary;
|
|
||||||
box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-of {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remaining-count {
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-load-more {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: $es-spacing-xs;
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
color: $es-primary;
|
|
||||||
background: $es-primary-light;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
font: inherit;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba($es-primary, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.loading {
|
|
||||||
cursor: wait;
|
|
||||||
|
|
||||||
i {
|
|
||||||
animation: spin 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Preview States
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.preview-empty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-xl;
|
|
||||||
text-align: center;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 2rem;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-loading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: $es-spacing-xl;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 20px;
|
|
||||||
color: $es-primary;
|
|
||||||
animation: spin 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Total Summary Popover (header total badge click)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.total-preview-popover {
|
|
||||||
min-width: 240px;
|
|
||||||
max-width: 320px;
|
|
||||||
|
|
||||||
.preview-popover-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
|
|
||||||
.preview-popover-title {
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-popover-count {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-muted;
|
|
||||||
background: $es-slate-200;
|
|
||||||
padding: 0.125rem 0.5rem;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-popover-body {
|
|
||||||
padding: $es-spacing-xs 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-summary-list {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-summary-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.15s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
width: 18px;
|
|
||||||
text-align: center;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-item-label {
|
|
||||||
flex: 1;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-text-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-item-count {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-primary;
|
|
||||||
background: rgba($es-primary, 0.1);
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make trait-total-count clickable
|
|
||||||
.trait-total-count {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.popover-open {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Schedule Dropdown Preview
|
|
||||||
// Inline dropdown for schedule details in admin list
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.mpr-dropdown-preview {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
margin-top: 6px;
|
|
||||||
z-index: 1000;
|
|
||||||
padding: 0.625rem 0.75rem;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
box-shadow: $es-shadow-lg;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: left;
|
|
||||||
text-transform: none;
|
|
||||||
font-weight: normal;
|
|
||||||
white-space: nowrap; // Allow dropdown to grow as needed
|
|
||||||
|
|
||||||
&.is-open {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-dropdown-preview__item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.375rem 0;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.4;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid rgba($es-border-color, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Icon styles
|
|
||||||
> .material-icons,
|
|
||||||
> i:first-child {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 16px;
|
|
||||||
font-size: 14px !important;
|
|
||||||
color: #999;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-dropdown-preview__muted {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule inline layout
|
|
||||||
.mpr-dropdown-preview__schedule {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-dropdown-preview__schedule-row {
|
|
||||||
display: inline;
|
|
||||||
|
|
||||||
&:not(:last-child)::after {
|
|
||||||
content: ',';
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-dropdown-preview__day {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-dropdown-preview__hours {
|
|
||||||
color: $es-primary;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Badge trigger for preview dropdown/popover
|
|
||||||
.mpr-badge--preview {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
position: relative;
|
|
||||||
min-width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 50rem;
|
|
||||||
cursor: pointer;
|
|
||||||
text-transform: none;
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
font-size: 12px !important;
|
|
||||||
line-height: 1;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-badge--preview-primary {
|
|
||||||
background: $es-primary;
|
|
||||||
color: $es-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-badge--preview-success {
|
|
||||||
background: #d4edda;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-badge--preview-warning {
|
|
||||||
background: #fff3cd;
|
|
||||||
color: #856404;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-badge--preview-muted {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
/**
|
|
||||||
* Method Dropdown Component
|
|
||||||
* Custom select dropdown with icons for method selection
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
|
|
||||||
// Method dropdown trigger button
|
|
||||||
.method-dropdown-trigger {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
height: 36px;
|
|
||||||
padding: 0 $es-spacing-md;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
background: $es-white;
|
|
||||||
color: $es-slate-800;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
min-width: 180px;
|
|
||||||
max-width: 320px;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-color: $es-gray-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
outline: none;
|
|
||||||
border-color: $es-primary;
|
|
||||||
box-shadow: 0 0 0 3px rgba($es-primary, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-trigger-icon {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-text-muted;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 18px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-trigger-label {
|
|
||||||
flex: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-trigger-caret {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-slate-400;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locked state
|
|
||||||
.selector-locked .method-dropdown-trigger {
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-slate-400;
|
|
||||||
cursor: not-allowed;
|
|
||||||
border-color: $es-border-color;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-color: $es-border-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method selector wrapper
|
|
||||||
.method-selector-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hidden select (for form submission)
|
|
||||||
.method-select-hidden {
|
|
||||||
position: absolute !important;
|
|
||||||
opacity: 0 !important;
|
|
||||||
pointer-events: none !important;
|
|
||||||
width: 0 !important;
|
|
||||||
height: 0 !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global fallback for hidden method selects
|
|
||||||
.method-select-hidden {
|
|
||||||
position: absolute !important;
|
|
||||||
opacity: 0 !important;
|
|
||||||
pointer-events: none !important;
|
|
||||||
width: 0 !important;
|
|
||||||
height: 0 !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Method Dropdown Menu (appended to body, outside trait wrappers)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.method-dropdown-menu {
|
|
||||||
position: absolute;
|
|
||||||
z-index: $es-z-dropdown + 1;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 360px;
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
padding: 0.375rem 0;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
|
||||||
animation: methodDropdownFadeIn 0.15s ease;
|
|
||||||
@include custom-scrollbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes methodDropdownFadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-4px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method dropdown item
|
|
||||||
.method-dropdown-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.625rem;
|
|
||||||
padding: 0.5rem $es-spacing-md;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.1s;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: rgba($es-primary, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-item-icon {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-text-muted;
|
|
||||||
width: 18px;
|
|
||||||
text-align: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected .method-item-icon {
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-item-label {
|
|
||||||
flex: 1;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-slate-700;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected .method-item-label {
|
|
||||||
color: $es-cyan-700;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-item-check {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method dropdown optgroup
|
|
||||||
.method-dropdown-optgroup {
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-optgroup-label {
|
|
||||||
padding: 0.5rem $es-spacing-md;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-muted;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-top: 1px solid $es-slate-100;
|
|
||||||
border-bottom: 1px solid $es-slate-100;
|
|
||||||
|
|
||||||
.method-dropdown-optgroup:first-child & {
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-optgroup-items {
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
|
|
||||||
.method-dropdown-item {
|
|
||||||
padding-left: $es-spacing-lg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method info placeholder
|
|
||||||
.method-info-placeholder {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
@@ -1,488 +0,0 @@
|
|||||||
/**
|
|
||||||
* Modal Component
|
|
||||||
* Preview modals, confirmation dialogs
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use "sass:color";
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
// Modal backdrop
|
|
||||||
.mpr-modal-backdrop {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: $es-z-modal;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity $es-transition-normal;
|
|
||||||
|
|
||||||
&.show {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal container
|
|
||||||
.mpr-modal {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%) scale(0.95);
|
|
||||||
z-index: $es-z-modal + 1;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 600px;
|
|
||||||
max-height: 90vh;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: $es-radius-xl;
|
|
||||||
box-shadow: $es-shadow-xl;
|
|
||||||
opacity: 0;
|
|
||||||
transition: all $es-transition-normal;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
&.show {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translate(-50%, -50%) scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.modal-sm {
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.modal-lg {
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.modal-xl {
|
|
||||||
max-width: 1000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.modal-fullscreen {
|
|
||||||
width: 95%;
|
|
||||||
max-width: none;
|
|
||||||
height: 90vh;
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal header
|
|
||||||
.mpr-modal-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: $es-spacing-md $es-spacing-lg;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-title {
|
|
||||||
font-size: $es-font-size-base;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-close {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: $es-font-size-lg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal body
|
|
||||||
.mpr-modal-body {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: $es-spacing-lg;
|
|
||||||
@include custom-scrollbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal footer
|
|
||||||
.mpr-modal-footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-md $es-spacing-lg;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-top: 1px solid $es-border-color;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-btn {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&.btn-secondary {
|
|
||||||
color: $es-text-secondary;
|
|
||||||
background: $es-slate-100;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.btn-primary {
|
|
||||||
color: $es-white;
|
|
||||||
background: $es-primary;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.btn-danger {
|
|
||||||
color: $es-white;
|
|
||||||
background: $es-danger;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: color.adjust($es-danger, $lightness: -10%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preview popover styles moved to _list-preview.scss
|
|
||||||
|
|
||||||
.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;
|
|
||||||
border-radius: $es-radius-lg $es-radius-lg 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-title {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-close {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-body {
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
@include custom-scrollbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-footer {
|
|
||||||
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-top: 1px solid $es-border-color;
|
|
||||||
border-radius: 0 0 $es-radius-lg $es-radius-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-info {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-load-more {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-primary;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popover arrow
|
|
||||||
.popover-arrow {
|
|
||||||
position: absolute;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
|
|
||||||
&.arrow-top {
|
|
||||||
top: -7px;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -6px;
|
|
||||||
border-right: none;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.arrow-bottom {
|
|
||||||
bottom: -7px;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -6px;
|
|
||||||
border-left: none;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================================================================
|
|
||||||
// Holiday Preview Modal
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
#mpr-holiday-preview-modal {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: $es-z-modal;
|
|
||||||
|
|
||||||
&.show {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-backdrop {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-dialog {
|
|
||||||
position: relative;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 480px;
|
|
||||||
max-height: 80vh;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
box-shadow: $es-shadow-xl;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: $es-spacing-md $es-spacing-lg;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-base;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
i.material-icons {
|
|
||||||
font-size: 20px !important;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-close {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpr-modal-body {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: $es-spacing-lg;
|
|
||||||
@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 {
|
|
||||||
font-size: $es-font-size-lg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty state
|
|
||||||
.holiday-preview-empty {
|
|
||||||
text-align: center;
|
|
||||||
padding: $es-spacing-xl 0;
|
|
||||||
color: $es-text-muted;
|
|
||||||
|
|
||||||
i.material-icons {
|
|
||||||
font-size: 48px !important;
|
|
||||||
opacity: 0.5;
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0 0 $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Holiday list
|
|
||||||
.holiday-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
|
||||||
border-left-color: $es-info;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.holiday-type-observance {
|
|
||||||
border-left-color: $es-warning;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.holiday-type-regional {
|
|
||||||
border-left-color: #8b5cf6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.holiday-date {
|
|
||||||
flex-shrink: 0;
|
|
||||||
min-width: 100px;
|
|
||||||
|
|
||||||
.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-name {
|
|
||||||
display: block;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-text-primary;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.holiday-type-badge {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: $es-spacing-xs;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.holiday-preview-note {
|
|
||||||
margin-top: $es-spacing-md;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,369 +0,0 @@
|
|||||||
/**
|
|
||||||
* Schedule Conditions Component
|
|
||||||
* DateTime picker, weekly timeline, holidays
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
// Schedule conditions wrapper
|
|
||||||
.schedule-conditions-trait {
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule header
|
|
||||||
.schedule-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: 0.875rem $es-spacing-md;
|
|
||||||
background: $es-bg-header;
|
|
||||||
border-bottom: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg $es-radius-lg 0 0;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
transition: background-color $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-bg-hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule body
|
|
||||||
.schedule-body {
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule section
|
|
||||||
.schedule-section {
|
|
||||||
margin-bottom: $es-spacing-lg;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-section-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
margin-bottom: $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-text-primary;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-section-description {
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DateTime range picker
|
|
||||||
.datetime-range {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetime-field {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetime-label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetime-input {
|
|
||||||
@include input-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Weekly schedule
|
|
||||||
.weekly-schedule {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.day-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.day-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.day-checkbox {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.day-name {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timeline slider
|
|
||||||
.timeline-slider {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
height: 24px;
|
|
||||||
background: $es-slate-200;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-fill {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
background: $es-primary;
|
|
||||||
border-radius: $es-radius-full;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-handle {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background: $es-white;
|
|
||||||
border: 2px solid $es-primary;
|
|
||||||
border-radius: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
cursor: grab;
|
|
||||||
box-shadow: $es-shadow-sm;
|
|
||||||
transition: box-shadow $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: $es-shadow-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.handle-start {
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.handle-end {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time display
|
|
||||||
.day-times {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
min-width: 120px;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-family: monospace;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-separator {
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Holiday exclusions
|
|
||||||
.holiday-section {
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.holiday-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
margin-bottom: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.holiday-checkbox {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.holiday-label {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.holiday-countries {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.holiday-country-chip {
|
|
||||||
@include chip;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: $es-primary-light;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server time display
|
|
||||||
.server-time {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-info-light;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-info;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-value {
|
|
||||||
font-family: monospace;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule summary
|
|
||||||
.schedule-summary {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
|
|
||||||
.summary-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-success;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inactive i {
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collapsed state
|
|
||||||
.schedule-conditions-trait.collapsed {
|
|
||||||
.schedule-body {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-header {
|
|
||||||
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 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 !important;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section hint after embedded entity selector - add margin
|
|
||||||
.schedule-holidays .section-hint {
|
|
||||||
margin-top: $es-spacing-md;
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tips Box Component
|
|
||||||
* Pro tips and help information display
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
|
|
||||||
// Tips box container
|
|
||||||
.target-tips-box {
|
|
||||||
margin: $es-spacing-lg $es-spacing-md $es-spacing-md;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-lg;
|
|
||||||
overflow: hidden;
|
|
||||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tips header (clickable to expand/collapse)
|
|
||||||
.tips-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.625rem;
|
|
||||||
padding: $es-spacing-md $es-spacing-lg;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
transition: background-color $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lightbulb icon
|
|
||||||
> i:first-child {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: $es-warning;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title text
|
|
||||||
> span {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-slate-600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle chevron icon
|
|
||||||
.tips-toggle {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-slate-400;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expanded state
|
|
||||||
.target-tips-box.expanded {
|
|
||||||
.tips-toggle {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tips-content {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tips content (hidden by default)
|
|
||||||
.tips-content {
|
|
||||||
display: none;
|
|
||||||
padding: 0 $es-spacing-lg $es-spacing-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tips grid layout
|
|
||||||
.tips-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Individual tip item
|
|
||||||
.tip-item {
|
|
||||||
display: flex;
|
|
||||||
gap: $es-spacing-md;
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tip icon
|
|
||||||
.tip-icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: $es-primary-light;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
color: $es-primary;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tip text content
|
|
||||||
.tip-text {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
strong {
|
|
||||||
display: block;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-slate-700;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 11px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
line-height: 1.625;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tips footer
|
|
||||||
.tips-footer {
|
|
||||||
margin-top: $es-spacing-md;
|
|
||||||
padding: 0.625rem $es-spacing-md;
|
|
||||||
background: $es-white;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
border: 1px dashed $es-gray-300;
|
|
||||||
font-size: 11px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
line-height: 1.625;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $es-primary;
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tooltip Component
|
|
||||||
* Info tooltips for method help
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Info Wrapper (tooltip trigger)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.mpr-info-wrapper {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
|
||||||
cursor: help;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
font-size: 16px !important;
|
|
||||||
color: $es-text-muted;
|
|
||||||
transition: color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .material-icons {
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Fixed Tooltip (appended to body on hover)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
.mpr-tooltip-fixed {
|
|
||||||
position: fixed;
|
|
||||||
background: $es-white;
|
|
||||||
color: $es-slate-800;
|
|
||||||
padding: $es-spacing-md $es-spacing-lg;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.5;
|
|
||||||
white-space: normal;
|
|
||||||
z-index: 10500;
|
|
||||||
max-width: 320px;
|
|
||||||
min-width: 180px;
|
|
||||||
text-align: left;
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 1px 0px,
|
|
||||||
rgba(64, 68, 82, 0.16) 0px 0px 0px 1px,
|
|
||||||
rgba(64, 68, 82, 0.08) 0px 2px 5px 0px;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
// Pinned tooltip allows interaction
|
|
||||||
&.pinned {
|
|
||||||
pointer-events: auto;
|
|
||||||
padding-right: $es-spacing-xl + 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.375rem;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0.5rem 0 0;
|
|
||||||
padding-left: 1.25rem;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: 0.25rem 0;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close button for pinned tooltips
|
|
||||||
.mpr-tooltip-close {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.375rem;
|
|
||||||
right: 0.375rem;
|
|
||||||
padding: 0.125rem;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
line-height: 1;
|
|
||||||
transition: background-color 0.15s ease;
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
font-size: 16px !important;
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
color: $es-slate-700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
/**
|
|
||||||
* Category Tree Component
|
|
||||||
* Hierarchical tree view for category selection inside dropdown
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use "sass:color";
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
// Category tree container (inside dropdown)
|
|
||||||
.category-tree {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree toolbar inside dropdown
|
|
||||||
.category-tree .tree-toolbar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-xs $es-spacing-sm;
|
|
||||||
background: $es-slate-50;
|
|
||||||
border-bottom: 1px solid $es-border-light;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
.btn-expand-all,
|
|
||||||
.btn-collapse-all {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: $es-spacing-xs $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
border-color: $es-slate-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree items container
|
|
||||||
.category-tree .tree-items {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree item
|
|
||||||
.tree-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
padding: $es-spacing-xs $es-spacing-sm;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background $es-transition-fast;
|
|
||||||
border-radius: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: $es-primary-light;
|
|
||||||
|
|
||||||
.tree-name {
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-checkbox {
|
|
||||||
color: $es-primary;
|
|
||||||
|
|
||||||
i {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inactive {
|
|
||||||
opacity: 0.6;
|
|
||||||
|
|
||||||
.tree-name {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.filtered-out {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.filter-match {
|
|
||||||
background: $es-warning-light;
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: $es-primary-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All tree element styles nested under .category-tree for specificity
|
|
||||||
.category-tree {
|
|
||||||
// Tree indentation
|
|
||||||
.tree-indent {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree toggle (expand/collapse)
|
|
||||||
.tree-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-200;
|
|
||||||
color: $es-text-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.tree-leaf {
|
|
||||||
cursor: default;
|
|
||||||
visibility: hidden;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 10px;
|
|
||||||
transition: transform $es-transition-fast;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-item.collapsed > .tree-toggle i {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree checkbox indicator - 12x12 to match PrestaShop admin standards
|
|
||||||
.tree-checkbox {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: $es-white;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 8px;
|
|
||||||
opacity: 0;
|
|
||||||
color: $es-white;
|
|
||||||
transition: opacity $es-transition-fast;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-item.selected .tree-checkbox {
|
|
||||||
background: $es-primary;
|
|
||||||
border-color: $es-primary;
|
|
||||||
|
|
||||||
i {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree icon
|
|
||||||
.tree-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: $es-text-muted;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px; // match visual weight of other icons
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-item.selected .tree-icon {
|
|
||||||
color: $es-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree name
|
|
||||||
.tree-name {
|
|
||||||
flex: 1;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-text-primary;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree product/page count with preview
|
|
||||||
.tree-count {
|
|
||||||
@include count-badge($es-primary);
|
|
||||||
height: 18px;
|
|
||||||
min-width: 18px;
|
|
||||||
padding: 0 $es-spacing-sm;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.clickable {
|
|
||||||
&.loading {
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
i {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.popover-open {
|
|
||||||
background: color.adjust($es-primary, $lightness: -10%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select children button - positioned on the left next to toggle
|
|
||||||
.btn-select-children {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
opacity: 0.3;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px; // larger to visually match other icons
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $es-primary;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-item:hover .btn-select-children {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree badge (inactive, etc.)
|
|
||||||
.tree-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.125rem $es-spacing-xs;
|
|
||||||
font-size: 9px;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.025em;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&.inactive {
|
|
||||||
color: $es-warning;
|
|
||||||
background: $es-warning-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree children container
|
|
||||||
.tree-children {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
&.filter-expanded {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-item.collapsed + .tree-children {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtering - must be inside .category-tree for specificity
|
|
||||||
.tree-item.filtered-out {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
} // end .category-tree
|
|
||||||
|
|
||||||
// Loading/empty/error states
|
|
||||||
.category-tree .tree-loading,
|
|
||||||
.category-tree .dropdown-empty,
|
|
||||||
.category-tree .dropdown-error {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: $es-spacing-xl;
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
|
|
||||||
i {
|
|
||||||
margin-right: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-tree .dropdown-error {
|
|
||||||
color: $es-danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree view mode in dropdown
|
|
||||||
.target-search-dropdown.view-tree {
|
|
||||||
.dropdown-results {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-tree {
|
|
||||||
max-height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
@include custom-scrollbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-items {
|
|
||||||
max-height: calc(100% - 40px);
|
|
||||||
overflow-y: auto;
|
|
||||||
@include custom-scrollbar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/**
|
|
||||||
* Validation Toast Component
|
|
||||||
* Error notifications for selection conflicts
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
// Validation error toast
|
|
||||||
.es-validation-toast {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-md;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-danger;
|
|
||||||
border-left: 4px solid $es-danger;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
box-shadow: $es-shadow-lg;
|
|
||||||
max-width: 400px;
|
|
||||||
animation: es-toast-slide-in 0.2s ease-out;
|
|
||||||
|
|
||||||
.es-toast-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: $es-danger;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.es-toast-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.es-toast-title {
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-semibold;
|
|
||||||
color: $es-danger;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.es-toast-message {
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
color: $es-text-secondary;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.es-toast-close {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
color: $es-text-muted;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-slate-100;
|
|
||||||
color: $es-text-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes es-toast-slide-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,281 +0,0 @@
|
|||||||
/**
|
|
||||||
* Value Picker Component
|
|
||||||
* Search boxes, input types, range inputs
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
@use '../mixins' as *;
|
|
||||||
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
|
|
||||||
// Value picker container
|
|
||||||
.value-picker {
|
|
||||||
padding: $es-spacing-sm 0;
|
|
||||||
|
|
||||||
&[style*="display: none"],
|
|
||||||
&[style*="display:none"] {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.include-picker,
|
|
||||||
.exclude-picker {
|
|
||||||
// Section-specific picker styles
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entity search box
|
|
||||||
.entity-search-box {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
padding: $es-spacing-xs;
|
|
||||||
background: $es-white;
|
|
||||||
border: 1px solid $es-border-color;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
border-color: $es-primary;
|
|
||||||
box-shadow: 0 0 0 2px rgba($es-primary, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separation between chips and search box
|
|
||||||
.chips-wrapper + .entity-search-box {
|
|
||||||
margin-top: $es-spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity-search-icon {
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: 14px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override parent form's max-width on search input
|
|
||||||
input.entity-search-input,
|
|
||||||
input.entity-search-input[type="text"] {
|
|
||||||
@include input-reset;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
width: auto !important;
|
|
||||||
max-width: none !important;
|
|
||||||
padding: 0.375rem;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
color: $es-text-primary;
|
|
||||||
border: none !important;
|
|
||||||
background: transparent !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: $es-text-muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-loading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: $es-primary;
|
|
||||||
|
|
||||||
i {
|
|
||||||
animation: spin 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Browse tree button (for categories)
|
|
||||||
.btn-browse-tree {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin-left: auto;
|
|
||||||
color: $es-primary;
|
|
||||||
background: $es-primary-light;
|
|
||||||
border-radius: $es-radius-sm;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary;
|
|
||||||
color: $es-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numeric range box
|
|
||||||
.numeric-range-box,
|
|
||||||
.multi-range-input-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-min-input,
|
|
||||||
.range-max-input {
|
|
||||||
@include input-base;
|
|
||||||
width: 100px;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
text-align: center;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
|
|
||||||
&::-webkit-inner-spin-button,
|
|
||||||
&::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-separator {
|
|
||||||
color: $es-text-muted;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-add-range {
|
|
||||||
@include button-reset;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
color: $es-white;
|
|
||||||
background: $es-primary;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $es-primary-hover;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-range container
|
|
||||||
.multi-range-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date range box
|
|
||||||
.date-range-box {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-from-input,
|
|
||||||
.date-to-input {
|
|
||||||
@include input-base;
|
|
||||||
width: 140px;
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-select tiles
|
|
||||||
.multi-select-tiles {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $es-spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile-option {
|
|
||||||
@include button-reset;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
color: $es-text-muted;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px dashed $es-border-color;
|
|
||||||
border-radius: 100px; // Pill shape
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
font-weight: $es-font-weight-normal;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all $es-transition-fast;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $es-text-secondary;
|
|
||||||
border-color: $es-slate-400;
|
|
||||||
border-style: solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
color: $es-primary;
|
|
||||||
background: $es-primary-light;
|
|
||||||
border: 1px solid $es-primary;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 11px;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected i {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile-label {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select input box
|
|
||||||
.select-input-box {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-value-input {
|
|
||||||
@include input-base;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean input box
|
|
||||||
.boolean-input-box {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: $es-spacing-sm $es-spacing-md;
|
|
||||||
background: $es-success-light;
|
|
||||||
color: $es-success-dark;
|
|
||||||
border-radius: $es-radius-md;
|
|
||||||
font-size: $es-font-size-sm;
|
|
||||||
font-weight: $es-font-weight-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.boolean-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '\2713';
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Condition match count badge
|
|
||||||
.condition-match-count {
|
|
||||||
@include count-badge($es-primary);
|
|
||||||
margin-left: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); header("Location: ../"); exit;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); header("Location: ../"); exit;
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
/**
|
|
||||||
* Form Integration Styles
|
|
||||||
* Handles PrestaShop admin form layout overrides
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
|
|
||||||
// Base border reset for all entity-selector elements
|
|
||||||
.target-conditions-trait,
|
|
||||||
.target-conditions-trait *,
|
|
||||||
.entity-selector-trait,
|
|
||||||
.entity-selector-trait *,
|
|
||||||
.method-dropdown-menu,
|
|
||||||
.method-dropdown-menu *,
|
|
||||||
.target-preview-popover,
|
|
||||||
.target-preview-popover * {
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 0;
|
|
||||||
border-color: $es-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full-width form group override using :has()
|
|
||||||
// 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 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .col-lg-8 {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
padding-left: $es-spacing-md;
|
|
||||||
padding-right: $es-spacing-md;
|
|
||||||
flex: 0 0 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback class for browsers without :has() support
|
|
||||||
.form-group.condition-trait-fullwidth {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
> .control-label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .col-lg-8 {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
padding-left: $es-spacing-md;
|
|
||||||
padding-right: $es-spacing-md;
|
|
||||||
flex: 0 0 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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),
|
|
||||||
.card:has(.target-search-dropdown.show),
|
|
||||||
.form-wrapper:has(.target-search-dropdown.show),
|
|
||||||
.panel-body:has(.target-search-dropdown.show),
|
|
||||||
.card-body:has(.target-search-dropdown.show),
|
|
||||||
.form-group:has(.target-search-dropdown.show),
|
|
||||||
.col-lg-8:has(.target-search-dropdown.show),
|
|
||||||
.col-lg-12:has(.target-search-dropdown.show) {
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Target conditions wrapper hierarchy overflow fix
|
|
||||||
.target-conditions-trait:has(.target-search-dropdown.show),
|
|
||||||
.entity-selector-trait:has(.target-search-dropdown.show),
|
|
||||||
.condition-trait-body:has(.target-search-dropdown.show),
|
|
||||||
.target-block-content:has(.target-search-dropdown.show),
|
|
||||||
.target-block-groups:has(.target-search-dropdown.show),
|
|
||||||
.target-group:has(.target-search-dropdown.show),
|
|
||||||
.target-group-body:has(.target-search-dropdown.show),
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/**
|
|
||||||
* Responsive Styles
|
|
||||||
* Media query adjustments for different screen sizes
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../variables' as *;
|
|
||||||
|
|
||||||
// Tablet and below
|
|
||||||
@media (max-width: 991px) {
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
.condition-trait-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: $es-spacing-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-header-right {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-block-tabs {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
.target-block-tab {
|
|
||||||
padding: $es-spacing-sm;
|
|
||||||
font-size: $es-font-size-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-group-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-search-dropdown {
|
|
||||||
width: 100% !important;
|
|
||||||
left: 0 !important;
|
|
||||||
right: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-results-grid {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// High-resolution displays
|
|
||||||
@media (min-width: 1600px) {
|
|
||||||
.target-conditions-trait,
|
|
||||||
.entity-selector-trait {
|
|
||||||
.dropdown-results-grid.view-grid-3 {
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<?php header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); header("Location: ../"); exit;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Entity Selector Styles
|
|
||||||
* @package prestashop-entity-selector
|
|
||||||
* @version 2.0.0
|
|
||||||
*
|
|
||||||
* Compiles to: assets/css/admin/entity-selector.css
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Foundation
|
|
||||||
@use 'variables' as *;
|
|
||||||
@use 'mixins' as *;
|
|
||||||
|
|
||||||
// Layouts
|
|
||||||
@use 'layouts/form-integration';
|
|
||||||
@use 'layouts/responsive';
|
|
||||||
|
|
||||||
// Components
|
|
||||||
@use 'components/entity-selector';
|
|
||||||
@use 'components/entity-item'; // Shared base for chips and list items
|
|
||||||
@use 'components/dropdown';
|
|
||||||
@use 'components/chips';
|
|
||||||
@use 'components/groups';
|
|
||||||
@use 'components/value-picker';
|
|
||||||
@use 'components/modal';
|
|
||||||
@use 'components/list-preview';
|
|
||||||
@use 'components/schedule';
|
|
||||||
@use 'components/tips';
|
|
||||||
@use 'components/condition-trait';
|
|
||||||
@use 'components/combinations';
|
|
||||||
@use 'components/method-dropdown';
|
|
||||||
@use 'components/tooltip';
|
|
||||||
@use 'components/tree';
|
|
||||||
@use 'components/validation';
|
|
||||||
Reference in New Issue
Block a user