Forked from prestashop-target-conditions Renamed all references from target-conditions to entity-selector
1101 lines
46 KiB
JavaScript
Executable File
1101 lines
46 KiB
JavaScript
Executable File
/**
|
|
* Schedule Conditions - JavaScript
|
|
* Handles the interactive functionality for schedule-based conditions.
|
|
* Features:
|
|
* - Datetime range inputs
|
|
* - Visual per-day timelines with draggable handles
|
|
*/
|
|
|
|
(function($) {
|
|
'use strict';
|
|
|
|
var ScheduleConditions = {
|
|
config: {},
|
|
dragging: null,
|
|
|
|
init: function(options) {
|
|
console.log('[ScheduleConditions] Initializing...');
|
|
this.config = $.extend({
|
|
id: 'schedule-conditions',
|
|
namePrefix: 'schedule_',
|
|
trans: {}
|
|
}, options);
|
|
|
|
// Add fullwidth class to all condition-trait parent form-groups (fallback for browsers without :has())
|
|
console.log('[ScheduleConditions] DEBUG: Found condition-trait elements:', $('.condition-trait').length);
|
|
$('.condition-trait').each(function() {
|
|
var $formGroup = $(this).closest('.form-group');
|
|
console.log('[ScheduleConditions] DEBUG: Adding fullwidth to form-group', $formGroup.length, $formGroup.attr('class'));
|
|
$formGroup.addClass('condition-trait-fullwidth');
|
|
// Also remove the offset class that causes margin-left: 25%
|
|
$formGroup.find('.col-lg-offset-3').removeClass('col-lg-offset-3');
|
|
console.log('[ScheduleConditions] DEBUG: After addClass:', $formGroup.attr('class'));
|
|
});
|
|
|
|
this.bindEvents();
|
|
this.initTimelines();
|
|
this.initCollapsibleSections();
|
|
|
|
// Debug: Check if toggle button exists
|
|
var $toggleBtn = $('.schedule-conditions .btn-toggle-groups');
|
|
console.log('[ScheduleConditions] Toggle button found:', $toggleBtn.length);
|
|
if ($toggleBtn.length) {
|
|
console.log('[ScheduleConditions] Toggle button data-state:', $toggleBtn.attr('data-state'));
|
|
}
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
|
|
// Toggle enabled/disabled (Always = 0/on, Scheduled = 1/off)
|
|
$(document).on('change', '.schedule-enabled-toggle', function() {
|
|
var $wrapper = $(this).closest('.schedule-conditions');
|
|
var $body = $wrapper.find('.condition-trait-body');
|
|
// value="1" means schedule is enabled (show body)
|
|
var isScheduled = $(this).val() === '1' && $(this).is(':checked');
|
|
|
|
if (isScheduled) {
|
|
$body.slideDown(200);
|
|
$wrapper.removeClass('collapsed');
|
|
} else {
|
|
$body.slideUp(200);
|
|
$wrapper.addClass('collapsed');
|
|
}
|
|
});
|
|
|
|
// Header click to toggle collapse (except when clicking switch)
|
|
// Handles Schedule (.schedule-conditions) and Context (.context-conditions-trait)
|
|
// Note: TargetConditions (.target-conditions-trait) has its own handler in target-conditions.js
|
|
$(document).on('click', '.condition-trait .condition-trait-header', function(e) {
|
|
// Don't toggle if clicking the switch or other interactive elements
|
|
if ($(e.target).closest('.prestashop-switch, .trait-header-actions').length) {
|
|
return;
|
|
}
|
|
|
|
var $wrapper = $(this).closest('.condition-trait');
|
|
|
|
// Skip if this is TargetConditions - it has its own handler in target-conditions.js
|
|
if ($wrapper.hasClass('target-conditions-trait')) {
|
|
return;
|
|
}
|
|
|
|
var $body = $wrapper.find('.condition-trait-body');
|
|
|
|
// Use visibility check and stop any pending animations
|
|
$body.stop(true, true);
|
|
if ($body.is(':visible')) {
|
|
$body.slideUp(200);
|
|
$wrapper.addClass('collapsed');
|
|
} else {
|
|
$body.slideDown(200);
|
|
$wrapper.removeClass('collapsed');
|
|
}
|
|
});
|
|
|
|
// Holiday exclusion toggle (radio button: value="1" = enabled)
|
|
$(document).on('change', '.holiday-exclude-toggle', function() {
|
|
var $wrapper = $(this).closest('.schedule-holidays');
|
|
var $countriesWrapper = $wrapper.find('.holiday-countries-wrapper');
|
|
// Radio button: value="1" and checked means enabled
|
|
var isEnabled = $(this).val() === '1' && $(this).is(':checked');
|
|
|
|
if (isEnabled) {
|
|
$countriesWrapper.slideDown(200);
|
|
} else {
|
|
$countriesWrapper.slideUp(200);
|
|
}
|
|
});
|
|
|
|
// Collapsible section header click
|
|
$(document).on('click', '.collapsible-section .section-header', function(e) {
|
|
// Don't toggle if clicking the switch or holiday toggle
|
|
if ($(e.target).closest('.prestashop-switch, .holiday-enable-toggle').length) {
|
|
return;
|
|
}
|
|
|
|
var $section = $(this).closest('.collapsible-section');
|
|
var $content = $section.find('.section-content');
|
|
|
|
// Use visibility check instead of class - more reliable
|
|
if ($content.is(':visible')) {
|
|
$content.stop(true, true).slideUp(200);
|
|
$section.addClass('collapsed');
|
|
} else {
|
|
$content.stop(true, true).slideDown(200);
|
|
$section.removeClass('collapsed');
|
|
}
|
|
|
|
// Update expand/collapse all button state
|
|
self.updateToggleSectionsButton($section.closest('.condition-trait-body'));
|
|
});
|
|
|
|
// Expand/collapse all sections button
|
|
$(document).on('click', '.schedule-conditions .btn-toggle-groups', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
var $btn = $(this);
|
|
var $wrapper = $btn.closest('.schedule-conditions');
|
|
var $body = $wrapper.find('.condition-trait-body');
|
|
var $sections = $body.find('.collapsible-section');
|
|
var currentState = $btn.attr('data-state') || 'collapsed';
|
|
|
|
console.log('[ScheduleConditions] btn-toggle-groups clicked, state:', currentState, 'sections:', $sections.length);
|
|
|
|
if (currentState === 'expanded') {
|
|
// Collapse all
|
|
$sections.each(function() {
|
|
var $section = $(this);
|
|
var $content = $section.find('.section-content');
|
|
$content.stop(true, true).slideUp(200);
|
|
$section.addClass('collapsed');
|
|
});
|
|
$btn.attr('data-state', 'collapsed');
|
|
$btn.attr('title', 'Expand all sections');
|
|
$btn.find('i').removeClass('icon-compress').addClass('icon-expand');
|
|
} else {
|
|
// Expand all
|
|
$sections.each(function() {
|
|
var $section = $(this);
|
|
var $content = $section.find('.section-content');
|
|
$content.stop(true, true).slideDown(200);
|
|
$section.removeClass('collapsed');
|
|
});
|
|
$btn.attr('data-state', 'expanded');
|
|
$btn.attr('title', 'Collapse all sections');
|
|
$btn.find('i').removeClass('icon-expand').addClass('icon-compress');
|
|
}
|
|
});
|
|
|
|
// Holiday country picker - initialize data
|
|
self.initHolidayCountryPickers();
|
|
|
|
// Holiday country search input focus
|
|
$(document).on('focus', '.holiday-country-search-input', function() {
|
|
var $picker = $(this).closest('.holiday-country-picker');
|
|
self.showHolidayCountryDropdown($picker, $(this).val());
|
|
});
|
|
|
|
// Holiday country search input typing
|
|
$(document).on('input', '.holiday-country-search-input', function() {
|
|
var $picker = $(this).closest('.holiday-country-picker');
|
|
self.showHolidayCountryDropdown($picker, $(this).val());
|
|
});
|
|
|
|
// Holiday country dropdown item click
|
|
$(document).on('click', '.holiday-country-dropdown .dropdown-item', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $item = $(this);
|
|
var $picker = $item.closest('.holiday-country-picker');
|
|
var id = parseInt($item.data('id'), 10);
|
|
var isSelected = $item.hasClass('selected');
|
|
|
|
if (isSelected) {
|
|
self.removeHolidayCountry($picker, id);
|
|
} else {
|
|
self.addHolidayCountry($picker, id);
|
|
}
|
|
|
|
$item.toggleClass('selected');
|
|
});
|
|
|
|
// Holiday country chip remove
|
|
$(document).on('click', '.holiday-country-picker .chip-remove', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $chip = $(this).closest('.input-chip');
|
|
var $picker = $(this).closest('.holiday-country-picker');
|
|
var id = parseInt($chip.data('id'), 10);
|
|
|
|
self.removeHolidayCountry($picker, id);
|
|
|
|
// Update dropdown if visible
|
|
var $dropdown = $picker.find('.holiday-country-dropdown');
|
|
if ($dropdown.hasClass('show')) {
|
|
$dropdown.find('.dropdown-item[data-id="' + id + '"]').removeClass('selected');
|
|
}
|
|
});
|
|
|
|
// Holiday dropdown filter tabs
|
|
$(document).on('click', '.holiday-country-dropdown .tab-btn', function(e) {
|
|
e.preventDefault();
|
|
var $btn = $(this);
|
|
var $picker = $btn.closest('.holiday-country-picker');
|
|
var filter = $btn.data('filter');
|
|
|
|
$btn.addClass('active').siblings().removeClass('active');
|
|
$picker.data('filter', filter);
|
|
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
self.showHolidayCountryDropdown($picker, query);
|
|
});
|
|
|
|
// Close holiday dropdown on outside click
|
|
$(document).on('click', function(e) {
|
|
if (!$(e.target).closest('.holiday-country-picker').length) {
|
|
$('.holiday-country-dropdown.show').removeClass('show');
|
|
}
|
|
});
|
|
|
|
// Holiday dropdown - Select All button
|
|
$(document).on('click', '.holiday-country-dropdown .btn-select-all', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $picker = $(this).closest('.holiday-country-picker');
|
|
self.selectAllHolidayCountries($picker);
|
|
});
|
|
|
|
// Holiday dropdown - Clear selection button
|
|
$(document).on('click', '.holiday-country-dropdown .btn-clear-selection', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $picker = $(this).closest('.holiday-country-picker');
|
|
self.clearHolidayCountries($picker);
|
|
});
|
|
|
|
// Holiday dropdown - Sort field change
|
|
$(document).on('change', '.holiday-country-dropdown .sort-field-select', function(e) {
|
|
e.stopPropagation();
|
|
var $picker = $(this).closest('.holiday-country-picker');
|
|
$picker.data('sortField', $(this).val());
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
self.showHolidayCountryDropdown($picker, query);
|
|
});
|
|
|
|
// Holiday dropdown - Sort direction toggle
|
|
$(document).on('click', '.holiday-country-dropdown .btn-sort-dir', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $btn = $(this);
|
|
var $picker = $btn.closest('.holiday-country-picker');
|
|
var currentDir = $picker.data('sortDir') || 'ASC';
|
|
var newDir = currentDir === 'ASC' ? 'DESC' : 'ASC';
|
|
|
|
$picker.data('sortDir', newDir);
|
|
$btn.data('dir', newDir);
|
|
|
|
// Update icon
|
|
var $icon = $btn.find('i');
|
|
if (newDir === 'ASC') {
|
|
$icon.removeClass('icon-sort-alpha-desc').addClass('icon-sort-alpha-asc');
|
|
} else {
|
|
$icon.removeClass('icon-sort-alpha-asc').addClass('icon-sort-alpha-desc');
|
|
}
|
|
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
self.showHolidayCountryDropdown($picker, query);
|
|
});
|
|
|
|
// Holiday dropdown - View mode change
|
|
$(document).on('change', '.holiday-country-dropdown .view-mode-select', function(e) {
|
|
e.stopPropagation();
|
|
var $picker = $(this).closest('.holiday-country-picker');
|
|
$picker.data('viewMode', $(this).val());
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
self.showHolidayCountryDropdown($picker, query);
|
|
});
|
|
|
|
// Holiday dropdown - Refine input
|
|
$(document).on('input', '.holiday-country-dropdown .refine-input', function(e) {
|
|
e.stopPropagation();
|
|
var $input = $(this);
|
|
var $picker = $input.closest('.holiday-country-picker');
|
|
var $clearBtn = $picker.find('.btn-clear-refine');
|
|
var value = $input.val().toLowerCase().trim();
|
|
|
|
$picker.data('refineQuery', value);
|
|
$clearBtn.toggle(value.length > 0);
|
|
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
self.showHolidayCountryDropdown($picker, query);
|
|
});
|
|
|
|
// Holiday dropdown - Refine negate toggle
|
|
$(document).on('click', '.holiday-country-dropdown .btn-refine-negate', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $btn = $(this);
|
|
var $picker = $btn.closest('.holiday-country-picker');
|
|
var isNegate = !$picker.data('refineNegate');
|
|
|
|
$picker.data('refineNegate', isNegate);
|
|
$btn.toggleClass('active', isNegate);
|
|
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
self.showHolidayCountryDropdown($picker, query);
|
|
});
|
|
|
|
// Holiday dropdown - Clear refine
|
|
$(document).on('click', '.holiday-country-dropdown .btn-clear-refine', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $picker = $(this).closest('.holiday-country-picker');
|
|
var $input = $picker.find('.refine-input');
|
|
|
|
$input.val('');
|
|
$picker.data('refineQuery', '');
|
|
$(this).hide();
|
|
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
self.showHolidayCountryDropdown($picker, query);
|
|
});
|
|
|
|
// Holiday dropdown - Cancel button (close without changes)
|
|
$(document).on('click', '.holiday-country-dropdown .btn-cancel-dropdown', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $dropdown = $(this).closest('.holiday-country-dropdown');
|
|
$dropdown.removeClass('show');
|
|
});
|
|
|
|
// Holiday dropdown - Done button (confirm and close)
|
|
$(document).on('click', '.holiday-country-dropdown .btn-confirm-dropdown', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $dropdown = $(this).closest('.holiday-country-dropdown');
|
|
$dropdown.removeClass('show');
|
|
});
|
|
|
|
// Holiday dropdown - Keyboard shortcuts
|
|
$(document).on('keydown', '.holiday-country-picker', function(e) {
|
|
var $picker = $(this);
|
|
var $dropdown = $picker.find('.holiday-country-dropdown');
|
|
|
|
if (!$dropdown.hasClass('show')) {
|
|
return;
|
|
}
|
|
|
|
// Escape - close dropdown
|
|
if (e.keyCode === 27) {
|
|
e.preventDefault();
|
|
$dropdown.removeClass('show');
|
|
return;
|
|
}
|
|
|
|
// Enter - confirm and close
|
|
if (e.keyCode === 13) {
|
|
e.preventDefault();
|
|
$dropdown.removeClass('show');
|
|
return;
|
|
}
|
|
|
|
// Ctrl+A - select all
|
|
if (e.ctrlKey && (e.keyCode === 65 || e.keyCode === 97)) {
|
|
e.preventDefault();
|
|
self.selectAllHolidayCountries($picker);
|
|
return;
|
|
}
|
|
|
|
// Ctrl+D - clear selection
|
|
if (e.ctrlKey && (e.keyCode === 68 || e.keyCode === 100)) {
|
|
e.preventDefault();
|
|
self.clearHolidayCountries($picker);
|
|
return;
|
|
}
|
|
});
|
|
|
|
// Datetime validation
|
|
$(document).on('change', '.schedule-datetime-range input[type="datetime-local"]', function() {
|
|
var $container = $(this).closest('.schedule-datetime-range');
|
|
var $startInput = $container.find('input[name$="datetime_start"]');
|
|
var $endInput = $container.find('input[name$="datetime_end"]');
|
|
|
|
var startDatetime = $startInput.val();
|
|
var endDatetime = $endInput.val();
|
|
|
|
if (startDatetime && endDatetime && endDatetime < startDatetime) {
|
|
if ($(this).attr('name').indexOf('datetime_start') !== -1) {
|
|
$endInput.val(startDatetime);
|
|
} else {
|
|
$startInput.val(endDatetime);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Day enabled checkbox
|
|
$(document).on('change', '.day-enabled', function() {
|
|
var $timeline = $(this).closest('.day-timeline');
|
|
var $track = $timeline.find('.timeline-track');
|
|
var $inputs = $timeline.find('.time-input');
|
|
var isEnabled = $(this).is(':checked');
|
|
|
|
$track.toggleClass('disabled', !isEnabled);
|
|
$inputs.prop('disabled', !isEnabled);
|
|
self.updateWeeklyScheduleData($timeline.closest('.schedule-weekly'));
|
|
});
|
|
|
|
// Time input change
|
|
$(document).on('change blur', '.time-input', function() {
|
|
var $input = $(this);
|
|
var $timeline = $input.closest('.day-timeline');
|
|
var value = $input.val().trim();
|
|
|
|
// Parse time value
|
|
var minutes = self.parseTimeInput(value);
|
|
if (minutes === null) {
|
|
// Invalid - reset to previous value
|
|
var isStart = $input.hasClass('time-input-start');
|
|
var currentStart = self.getHandlePosition($timeline.find('.handle-start'));
|
|
var currentEnd = self.getHandlePosition($timeline.find('.handle-end'));
|
|
$input.val(self.formatMinutes(isStart ? currentStart : currentEnd));
|
|
return;
|
|
}
|
|
|
|
var isStart = $input.hasClass('time-input-start');
|
|
var startMinutes = self.getHandlePosition($timeline.find('.handle-start'));
|
|
var endMinutes = self.getHandlePosition($timeline.find('.handle-end'));
|
|
|
|
if (isStart) {
|
|
minutes = Math.min(minutes, endMinutes - 15);
|
|
self.setTimelineRange($timeline, minutes, endMinutes);
|
|
} else {
|
|
minutes = Math.max(minutes, startMinutes + 15);
|
|
self.setTimelineRange($timeline, startMinutes, minutes);
|
|
}
|
|
|
|
self.updateWeeklyScheduleData($timeline.closest('.schedule-weekly'));
|
|
});
|
|
|
|
// Copy Monday to all days
|
|
$(document).on('click', '.btn-copy-first-day', function(e) {
|
|
e.preventDefault();
|
|
var $section = $(this).closest('.schedule-weekly');
|
|
var $mondayTimeline = $section.find('.day-timeline[data-day="1"]');
|
|
|
|
if (!$mondayTimeline.length) return;
|
|
|
|
var mondayEnabled = $mondayTimeline.find('.day-enabled').is(':checked');
|
|
var mondayStart = self.getHandlePosition($mondayTimeline.find('.handle-start'));
|
|
var mondayEnd = self.getHandlePosition($mondayTimeline.find('.handle-end'));
|
|
|
|
$section.find('.day-timeline').each(function() {
|
|
var $timeline = $(this);
|
|
if ($timeline.data('day') === 1) return;
|
|
|
|
$timeline.find('.day-enabled').prop('checked', mondayEnabled).trigger('change');
|
|
self.setTimelineRange($timeline, mondayStart, mondayEnd);
|
|
});
|
|
|
|
self.updateWeeklyScheduleData($section);
|
|
});
|
|
|
|
// Reset all days
|
|
$(document).on('click', '.btn-reset-schedule', function(e) {
|
|
e.preventDefault();
|
|
var $section = $(this).closest('.schedule-weekly');
|
|
|
|
$section.find('.day-timeline').each(function() {
|
|
var $timeline = $(this);
|
|
$timeline.find('.day-enabled').prop('checked', true);
|
|
$timeline.find('.timeline-track').removeClass('disabled');
|
|
self.setTimelineRange($timeline, 0, 1440);
|
|
});
|
|
|
|
self.updateWeeklyScheduleData($section);
|
|
});
|
|
|
|
// Timeline handle drag
|
|
$(document).on('mousedown touchstart', '.timeline-handle', function(e) {
|
|
e.preventDefault();
|
|
var $handle = $(this);
|
|
var $track = $handle.closest('.timeline-track');
|
|
|
|
if ($track.hasClass('disabled')) return;
|
|
|
|
self.dragging = {
|
|
handle: $handle,
|
|
timeline: $handle.closest('.day-timeline'),
|
|
track: $track,
|
|
isStart: $handle.hasClass('handle-start'),
|
|
startX: e.type === 'touchstart' ? e.originalEvent.touches[0].clientX : e.clientX
|
|
};
|
|
|
|
$(document).on('mousemove.timeline touchmove.timeline', function(e) {
|
|
self.handleDrag(e);
|
|
});
|
|
|
|
$(document).on('mouseup.timeline touchend.timeline', function() {
|
|
self.handleDragEnd();
|
|
});
|
|
});
|
|
|
|
// Click on track to set position
|
|
$(document).on('click', '.timeline-track', function(e) {
|
|
var $track = $(this);
|
|
if ($track.hasClass('disabled')) return;
|
|
if ($(e.target).hasClass('timeline-handle')) return;
|
|
|
|
var $timeline = $track.closest('.day-timeline');
|
|
var trackRect = $track[0].getBoundingClientRect();
|
|
var clickX = e.clientX - trackRect.left;
|
|
var percent = Math.max(0, Math.min(1, clickX / trackRect.width));
|
|
var minutes = Math.round(percent * 1440);
|
|
|
|
var startMinutes = self.getHandlePosition($timeline.find('.handle-start'));
|
|
var endMinutes = self.getHandlePosition($timeline.find('.handle-end'));
|
|
var midPoint = (startMinutes + endMinutes) / 2;
|
|
|
|
if (minutes < midPoint) {
|
|
self.setTimelineRange($timeline, minutes, endMinutes);
|
|
} else {
|
|
self.setTimelineRange($timeline, startMinutes, minutes);
|
|
}
|
|
|
|
self.updateWeeklyScheduleData($timeline.closest('.schedule-weekly'));
|
|
});
|
|
},
|
|
|
|
initTimelines: function() {
|
|
var self = this;
|
|
$('.schedule-weekly').each(function() {
|
|
var $section = $(this);
|
|
var $input = $section.find('.weekly-schedule-data');
|
|
var data = {};
|
|
|
|
try {
|
|
data = JSON.parse($input.val() || '{}');
|
|
} catch (e) {
|
|
data = {};
|
|
}
|
|
|
|
$section.find('.day-timeline').each(function() {
|
|
var $timeline = $(this);
|
|
var dayNum = parseInt($timeline.data('day'), 10);
|
|
var dayData = data[dayNum] || {};
|
|
|
|
var enabled = dayData.enabled !== false;
|
|
var start = typeof dayData.start === 'number' ? dayData.start : 0;
|
|
var end = typeof dayData.end === 'number' ? dayData.end : 1440;
|
|
|
|
$timeline.find('.day-enabled').prop('checked', enabled);
|
|
$timeline.find('.timeline-track').toggleClass('disabled', !enabled);
|
|
self.setTimelineRange($timeline, start, end);
|
|
});
|
|
});
|
|
},
|
|
|
|
handleDrag: function(e) {
|
|
if (!this.dragging) return;
|
|
|
|
var clientX = e.type === 'touchmove' ? e.originalEvent.touches[0].clientX : e.clientX;
|
|
var trackRect = this.dragging.track[0].getBoundingClientRect();
|
|
var offsetX = clientX - trackRect.left;
|
|
var percent = Math.max(0, Math.min(1, offsetX / trackRect.width));
|
|
var minutes = Math.round(percent * 1440);
|
|
|
|
// Snap to 15-minute intervals
|
|
minutes = Math.round(minutes / 15) * 15;
|
|
|
|
var $timeline = this.dragging.timeline;
|
|
var startMinutes = this.getHandlePosition($timeline.find('.handle-start'));
|
|
var endMinutes = this.getHandlePosition($timeline.find('.handle-end'));
|
|
|
|
if (this.dragging.isStart) {
|
|
minutes = Math.min(minutes, endMinutes - 15);
|
|
this.setTimelineRange($timeline, minutes, endMinutes);
|
|
} else {
|
|
minutes = Math.max(minutes, startMinutes + 15);
|
|
this.setTimelineRange($timeline, startMinutes, minutes);
|
|
}
|
|
},
|
|
|
|
handleDragEnd: function() {
|
|
if (this.dragging) {
|
|
this.updateWeeklyScheduleData(this.dragging.timeline.closest('.schedule-weekly'));
|
|
}
|
|
this.dragging = null;
|
|
$(document).off('.timeline');
|
|
},
|
|
|
|
getHandlePosition: function($handle) {
|
|
var leftPercent = parseFloat($handle.css('left')) / $handle.parent().width();
|
|
if (isNaN(leftPercent)) {
|
|
leftPercent = parseFloat($handle[0].style.left) / 100;
|
|
}
|
|
return Math.round(leftPercent * 1440);
|
|
},
|
|
|
|
setTimelineRange: function($timeline, startMinutes, endMinutes) {
|
|
startMinutes = Math.max(0, Math.min(1440, startMinutes));
|
|
endMinutes = Math.max(0, Math.min(1440, endMinutes));
|
|
|
|
if (startMinutes > endMinutes) {
|
|
var tmp = startMinutes;
|
|
startMinutes = endMinutes;
|
|
endMinutes = tmp;
|
|
}
|
|
|
|
var startPercent = (startMinutes / 1440) * 100;
|
|
var endPercent = (endMinutes / 1440) * 100;
|
|
var widthPercent = endPercent - startPercent;
|
|
|
|
$timeline.find('.handle-start').css('left', startPercent + '%');
|
|
$timeline.find('.handle-end').css('left', endPercent + '%');
|
|
$timeline.find('.timeline-range').css({
|
|
left: startPercent + '%',
|
|
width: widthPercent + '%'
|
|
});
|
|
|
|
// Update both legacy text display and new input fields
|
|
$timeline.find('.time-start').text(this.formatMinutes(startMinutes));
|
|
$timeline.find('.time-end').text(this.formatMinutes(endMinutes));
|
|
$timeline.find('.time-input-start').val(this.formatMinutes(startMinutes));
|
|
$timeline.find('.time-input-end').val(this.formatMinutes(endMinutes));
|
|
},
|
|
|
|
formatMinutes: function(minutes) {
|
|
var hours = Math.floor(minutes / 60) % 24;
|
|
var mins = minutes % 60;
|
|
return (hours < 10 ? '0' : '') + hours + ':' + (mins < 10 ? '0' : '') + mins;
|
|
},
|
|
|
|
parseTimeInput: function(value) {
|
|
// Accept formats: HH:MM, H:MM, HHMM, HMM
|
|
var match = value.match(/^(\d{1,2}):?(\d{2})$/);
|
|
if (!match) return null;
|
|
|
|
var hours = parseInt(match[1], 10);
|
|
var mins = parseInt(match[2], 10);
|
|
|
|
if (hours < 0 || hours > 24 || mins < 0 || mins > 59) return null;
|
|
if (hours === 24 && mins > 0) return null;
|
|
|
|
return (hours * 60) + mins;
|
|
},
|
|
|
|
updateWeeklyScheduleData: function($section) {
|
|
var self = this;
|
|
var data = {};
|
|
|
|
$section.find('.day-timeline').each(function() {
|
|
var $timeline = $(this);
|
|
var dayNum = parseInt($timeline.data('day'), 10);
|
|
|
|
data[dayNum] = {
|
|
enabled: $timeline.find('.day-enabled').is(':checked'),
|
|
start: self.getHandlePosition($timeline.find('.handle-start')),
|
|
end: self.getHandlePosition($timeline.find('.handle-end'))
|
|
};
|
|
});
|
|
|
|
$section.find('.weekly-schedule-data').val(JSON.stringify(data));
|
|
},
|
|
|
|
getData: function($wrapper) {
|
|
if (!$wrapper) {
|
|
$wrapper = $('.schedule-conditions');
|
|
}
|
|
|
|
var prefix = this.config.namePrefix;
|
|
var data = {
|
|
// value="1" means "Scheduled" (enabled), value="0" means "Always" (disabled)
|
|
enabled: $wrapper.find('.schedule-enabled-toggle[value="1"]').is(':checked'),
|
|
datetime_start: $wrapper.find('input[name="' + prefix + 'datetime_start"]').val() || null,
|
|
datetime_end: $wrapper.find('input[name="' + prefix + 'datetime_end"]').val() || null,
|
|
weekly_schedule: {}
|
|
};
|
|
|
|
var weeklyJson = $wrapper.find('.weekly-schedule-data').val();
|
|
if (weeklyJson) {
|
|
try {
|
|
data.weekly_schedule = JSON.parse(weeklyJson);
|
|
} catch (e) {
|
|
data.weekly_schedule = {};
|
|
}
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
setData: function($wrapper, data) {
|
|
if (!data) return;
|
|
|
|
var self = this;
|
|
var prefix = this.config.namePrefix;
|
|
|
|
if (data.enabled) {
|
|
$wrapper.find('.schedule-enabled-toggle[value="1"]').prop('checked', true).trigger('change');
|
|
} else {
|
|
$wrapper.find('.schedule-enabled-toggle[value="0"]').prop('checked', true).trigger('change');
|
|
}
|
|
|
|
if (data.datetime_start) {
|
|
$wrapper.find('input[name="' + prefix + 'datetime_start"]').val(data.datetime_start);
|
|
}
|
|
|
|
if (data.datetime_end) {
|
|
$wrapper.find('input[name="' + prefix + 'datetime_end"]').val(data.datetime_end);
|
|
}
|
|
|
|
if (data.weekly_schedule) {
|
|
var $section = $wrapper.find('.schedule-weekly');
|
|
$section.find('.weekly-schedule-data').val(JSON.stringify(data.weekly_schedule));
|
|
|
|
$section.find('.day-timeline').each(function() {
|
|
var $timeline = $(this);
|
|
var dayNum = parseInt($timeline.data('day'), 10);
|
|
var dayData = data.weekly_schedule[dayNum] || {};
|
|
|
|
var enabled = dayData.enabled !== false;
|
|
var start = typeof dayData.start === 'number' ? dayData.start : 0;
|
|
var end = typeof dayData.end === 'number' ? dayData.end : 1440;
|
|
|
|
$timeline.find('.day-enabled').prop('checked', enabled);
|
|
$timeline.find('.timeline-track').toggleClass('disabled', !enabled);
|
|
self.setTimelineRange($timeline, start, end);
|
|
});
|
|
}
|
|
},
|
|
|
|
// =====================================================
|
|
// Holiday Country Picker Methods
|
|
// =====================================================
|
|
|
|
initHolidayCountryPickers: function() {
|
|
var self = this;
|
|
|
|
$('.holiday-country-picker').each(function() {
|
|
var $picker = $(this);
|
|
|
|
// Parse countries data from data attribute
|
|
var countriesJson = $picker.attr('data-countries');
|
|
if (countriesJson) {
|
|
try {
|
|
var countriesData = JSON.parse(countriesJson);
|
|
|
|
$picker.data('countriesData', countriesData);
|
|
|
|
// Build country map for quick lookup
|
|
var countryMap = {};
|
|
countriesData.forEach(function(c) {
|
|
countryMap[c.id] = c;
|
|
});
|
|
$picker.data('countryMap', countryMap);
|
|
} catch (e) {
|
|
console.warn('ScheduleConditions: Failed to parse countries data', e);
|
|
}
|
|
}
|
|
|
|
// Initialize selected IDs from existing chips
|
|
var selectedIds = [];
|
|
$picker.find('.input-chip').each(function() {
|
|
selectedIds.push(parseInt($(this).data('id'), 10));
|
|
});
|
|
$picker.data('selectedIds', selectedIds);
|
|
|
|
// Initialize state
|
|
$picker.data('filter', 'with-holidays');
|
|
$picker.data('sortField', 'name');
|
|
$picker.data('sortDir', 'ASC');
|
|
$picker.data('viewMode', 'list');
|
|
$picker.data('refineQuery', '');
|
|
$picker.data('refineNegate', false);
|
|
});
|
|
},
|
|
|
|
showHolidayCountryDropdown: function($picker, query) {
|
|
var self = this;
|
|
var $dropdown = $picker.find('.holiday-country-dropdown');
|
|
var countriesData = $picker.data('countriesData') || [];
|
|
var selectedIds = $picker.data('selectedIds') || [];
|
|
var filter = $picker.data('filter') || 'with-holidays';
|
|
var sortField = $picker.data('sortField') || 'name';
|
|
var sortDir = $picker.data('sortDir') || 'ASC';
|
|
var viewMode = $picker.data('viewMode') || 'list';
|
|
var refineQuery = $picker.data('refineQuery') || '';
|
|
var refineNegate = $picker.data('refineNegate') || false;
|
|
|
|
query = (query || '').toLowerCase().trim();
|
|
|
|
// Filter countries
|
|
var filtered = countriesData.filter(function(c) {
|
|
// Filter by tab
|
|
if (filter === 'with-holidays' && c.holidayCount === 0) {
|
|
return false;
|
|
}
|
|
|
|
// Filter by search query
|
|
if (query) {
|
|
var name = (c.name || '').toLowerCase();
|
|
var iso = (c.iso || '').toLowerCase();
|
|
var zone = (c.zone || '').toLowerCase();
|
|
if (!(name.indexOf(query) !== -1 || iso.indexOf(query) !== -1 || zone.indexOf(query) !== -1)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Apply refine filter
|
|
if (refineQuery) {
|
|
var name = (c.name || '').toLowerCase();
|
|
var iso = (c.iso || '').toLowerCase();
|
|
var zone = (c.zone || '').toLowerCase();
|
|
var matches = name.indexOf(refineQuery) !== -1 || iso.indexOf(refineQuery) !== -1 || zone.indexOf(refineQuery) !== -1;
|
|
if (refineNegate) {
|
|
if (matches) return false;
|
|
} else {
|
|
if (!matches) return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// Sort countries
|
|
filtered.sort(function(a, b) {
|
|
var aVal, bVal;
|
|
switch (sortField) {
|
|
case 'holidays':
|
|
aVal = a.holidayCount || 0;
|
|
bVal = b.holidayCount || 0;
|
|
break;
|
|
case 'zone':
|
|
aVal = a.zone || '';
|
|
bVal = b.zone || '';
|
|
break;
|
|
case 'selected':
|
|
aVal = selectedIds.indexOf(a.id) !== -1 ? 0 : 1;
|
|
bVal = selectedIds.indexOf(b.id) !== -1 ? 0 : 1;
|
|
break;
|
|
default: // name
|
|
aVal = a.name || '';
|
|
bVal = b.name || '';
|
|
}
|
|
|
|
var result;
|
|
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
result = aVal - bVal;
|
|
} else {
|
|
result = String(aVal).localeCompare(String(bVal));
|
|
}
|
|
|
|
return sortDir === 'DESC' ? -result : result;
|
|
});
|
|
|
|
// Store filtered for select all
|
|
$picker.data('filteredCountries', filtered);
|
|
|
|
// Build dropdown items
|
|
var html = '';
|
|
var colsClass = '';
|
|
if (viewMode.indexOf('cols-') === 0) {
|
|
var cols = parseInt(viewMode.replace('cols-', ''), 10);
|
|
colsClass = ' view-' + viewMode;
|
|
}
|
|
|
|
filtered.forEach(function(c) {
|
|
var isSelected = selectedIds.indexOf(c.id) !== -1;
|
|
var hasHolidays = c.holidayCount > 0;
|
|
var itemClass = 'dropdown-item' + (hasHolidays ? '' : ' no-holidays') + (isSelected ? ' selected' : '');
|
|
|
|
html += '<div class="' + itemClass + '" data-id="' + c.id + '">';
|
|
html += '<span class="item-checkbox"><i class="icon-' + (isSelected ? 'check-square' : 'square-o') + '"></i></span>';
|
|
html += '<span class="item-flag"><img src="' + self.escapeHtml(c.flag) + '" alt="' + self.escapeHtml(c.iso) + '" onerror="this.style.display=\'none\'"></span>';
|
|
html += '<span class="item-name">' + self.escapeHtml(c.name) + '</span>';
|
|
if (c.zone) {
|
|
html += '<span class="item-zone">' + self.escapeHtml(c.zone) + '</span>';
|
|
}
|
|
if (hasHolidays) {
|
|
html += '<span class="item-badge">' + c.holidayCount + '</span>';
|
|
}
|
|
html += '</div>';
|
|
});
|
|
|
|
if (filtered.length === 0) {
|
|
html = '<div class="dropdown-empty">No countries found</div>';
|
|
}
|
|
|
|
// Update dropdown
|
|
var $items = $dropdown.find('.dropdown-items');
|
|
$items.html(html);
|
|
$items.removeClass('view-cols-2 view-cols-3 view-cols-4').addClass(colsClass);
|
|
|
|
// Update results count
|
|
var selectedCount = selectedIds.length;
|
|
var countText = filtered.length + ' countries';
|
|
if (selectedCount > 0) {
|
|
countText += ' (' + selectedCount + ' selected)';
|
|
}
|
|
$dropdown.find('.results-count').text(countText);
|
|
|
|
$dropdown.addClass('show');
|
|
},
|
|
|
|
addHolidayCountry: function($picker, id) {
|
|
var self = this;
|
|
var countryMap = $picker.data('countryMap') || {};
|
|
var selectedIds = $picker.data('selectedIds') || [];
|
|
var country = countryMap[id];
|
|
|
|
if (!country || selectedIds.indexOf(id) !== -1) {
|
|
return;
|
|
}
|
|
|
|
selectedIds.push(id);
|
|
$picker.data('selectedIds', selectedIds);
|
|
|
|
// Add chip
|
|
var $chips = $picker.find('.entity-input-chips');
|
|
var hasHolidays = country.holidayCount > 0;
|
|
var chipClass = 'input-chip' + (hasHolidays ? ' has-holidays' : '');
|
|
|
|
var chipHtml = '<span class="' + chipClass + '" data-id="' + id + '">';
|
|
chipHtml += '<span class="chip-flag"><img src="' + self.escapeHtml(country.flag) + '" alt="' + self.escapeHtml(country.iso) + '" onerror="this.style.display=\'none\'"></span>';
|
|
chipHtml += '<span class="chip-name">' + self.escapeHtml(country.name) + '</span>';
|
|
if (hasHolidays) {
|
|
chipHtml += '<span class="chip-badge">' + country.holidayCount + '</span>';
|
|
}
|
|
chipHtml += '<button type="button" class="chip-remove" title="Remove"><i class="icon-times"></i></button>';
|
|
chipHtml += '</span>';
|
|
|
|
$chips.append(chipHtml);
|
|
|
|
// Add hidden input
|
|
var prefix = this.config.namePrefix || 'schedule_';
|
|
$picker.prepend('<input type="hidden" name="' + prefix + 'holiday_countries[]" value="' + id + '" class="holiday-country-input">');
|
|
},
|
|
|
|
removeHolidayCountry: function($picker, id) {
|
|
var selectedIds = $picker.data('selectedIds') || [];
|
|
var index = selectedIds.indexOf(id);
|
|
|
|
if (index === -1) {
|
|
return;
|
|
}
|
|
|
|
selectedIds.splice(index, 1);
|
|
$picker.data('selectedIds', selectedIds);
|
|
|
|
// Remove chip
|
|
$picker.find('.input-chip[data-id="' + id + '"]').remove();
|
|
|
|
// Remove hidden input
|
|
$picker.find('.holiday-country-input[value="' + id + '"]').remove();
|
|
},
|
|
|
|
selectAllHolidayCountries: function($picker) {
|
|
var self = this;
|
|
var filtered = $picker.data('filteredCountries') || [];
|
|
var selectedIds = $picker.data('selectedIds') || [];
|
|
|
|
filtered.forEach(function(c) {
|
|
if (selectedIds.indexOf(c.id) === -1) {
|
|
self.addHolidayCountry($picker, c.id);
|
|
}
|
|
});
|
|
|
|
// Refresh dropdown
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
this.showHolidayCountryDropdown($picker, query);
|
|
},
|
|
|
|
clearHolidayCountries: function($picker) {
|
|
var self = this;
|
|
var selectedIds = ($picker.data('selectedIds') || []).slice();
|
|
|
|
selectedIds.forEach(function(id) {
|
|
self.removeHolidayCountry($picker, id);
|
|
});
|
|
|
|
// Refresh dropdown
|
|
var query = $picker.find('.holiday-country-search-input').val();
|
|
this.showHolidayCountryDropdown($picker, query);
|
|
},
|
|
|
|
escapeHtml: function(str) {
|
|
if (!str) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = str;
|
|
return div.innerHTML;
|
|
},
|
|
|
|
// =====================================================
|
|
// Collapsible Sections Helper
|
|
// =====================================================
|
|
|
|
initCollapsibleSections: function() {
|
|
var self = this;
|
|
|
|
// Initialize toggle button state based on current collapsed sections
|
|
$('.condition-trait-body').each(function() {
|
|
self.updateToggleSectionsButton($(this));
|
|
});
|
|
},
|
|
|
|
updateToggleSectionsButton: function($body) {
|
|
// Button is now in header, find it via wrapper
|
|
var $wrapper = $body.closest('.schedule-conditions');
|
|
var $btn = $wrapper.find('.btn-toggle-groups');
|
|
if (!$btn.length) return;
|
|
|
|
var $sections = $body.find('.collapsible-section');
|
|
var totalSections = $sections.length;
|
|
var collapsedCount = $sections.filter('.collapsed').length;
|
|
|
|
// Update button state based on majority
|
|
if (collapsedCount === totalSections) {
|
|
// All collapsed - show expand icon
|
|
$btn.attr('data-state', 'collapsed');
|
|
$btn.attr('title', 'Expand all sections');
|
|
$btn.find('i').removeClass('icon-compress').addClass('icon-expand');
|
|
} else if (collapsedCount === 0) {
|
|
// All expanded - show collapse icon
|
|
$btn.attr('data-state', 'expanded');
|
|
$btn.attr('title', 'Collapse all sections');
|
|
$btn.find('i').removeClass('icon-expand').addClass('icon-compress');
|
|
}
|
|
// Mixed state - keep current icon (user can toggle to collapse or expand all)
|
|
},
|
|
|
|
// =====================================================
|
|
// Clear Method
|
|
// =====================================================
|
|
|
|
clear: function($wrapper) {
|
|
if (!$wrapper) {
|
|
$wrapper = $('.schedule-conditions');
|
|
}
|
|
|
|
var self = this;
|
|
var prefix = this.config.namePrefix;
|
|
|
|
// Set to "Always" (value=0, which is the "on" position)
|
|
$wrapper.find('.schedule-enabled-toggle[value="0"]').prop('checked', true).trigger('change');
|
|
$wrapper.find('input[name="' + prefix + 'datetime_start"]').val('');
|
|
$wrapper.find('input[name="' + prefix + 'datetime_end"]').val('');
|
|
|
|
$wrapper.find('.day-timeline').each(function() {
|
|
var $timeline = $(this);
|
|
$timeline.find('.day-enabled').prop('checked', true);
|
|
$timeline.find('.timeline-track').removeClass('disabled');
|
|
self.setTimelineRange($timeline, 0, 1440);
|
|
});
|
|
|
|
$wrapper.find('.weekly-schedule-data').val('{}');
|
|
|
|
// Clear holiday countries
|
|
$wrapper.find('.holiday-country-picker').each(function() {
|
|
var $picker = $(this);
|
|
$picker.data('selectedIds', []);
|
|
$picker.find('.entity-input-chips').empty();
|
|
$picker.find('.holiday-country-input').remove();
|
|
});
|
|
// Set holiday toggle to "No" (value="0")
|
|
$wrapper.find('.holiday-exclude-toggle[value="0"]').prop('checked', true).trigger('change');
|
|
}
|
|
};
|
|
|
|
// Export
|
|
window.ScheduleConditions = ScheduleConditions;
|
|
|
|
// Auto-init if config is available
|
|
$(document).ready(function() {
|
|
if (typeof scheduleConditionsConfig !== 'undefined' && scheduleConditionsConfig) {
|
|
ScheduleConditions.init(scheduleConditionsConfig);
|
|
} else {
|
|
ScheduleConditions.init({});
|
|
}
|
|
});
|
|
|
|
})(jQuery);
|