From c99b324c9ba8a37f43ac7b01d98a32eefea6af1e Mon Sep 17 00:00:00 2001 From: maltayyar2 Date: Mon, 29 Dec 2025 11:45:35 +0300 Subject: [PATCH 1/2] [IMP] system_dashboard_classic: automatic update Auto-generated commit based on local changes. --- .../system_dashboard_classic/models/config.py | 13 + .../system_dashboard_classic/models/models.py | 74 ++++ .../src/js/system_dashboard_self_service.js | 280 +++++++++++++-- .../static/src/scss/core.scss | 318 ++++++++++++++++++ .../views/dashboard_settings.xml | 17 + 5 files changed, 676 insertions(+), 26 deletions(-) diff --git a/odex25_base/system_dashboard_classic/models/config.py b/odex25_base/system_dashboard_classic/models/config.py index 61857cf13..762e3e26f 100644 --- a/odex25_base/system_dashboard_classic/models/config.py +++ b/odex25_base/system_dashboard_classic/models/config.py @@ -581,6 +581,14 @@ class DashboardConfigSettings(models.TransientModel): config_parameter='system_dashboard_classic.refresh_interval', help='How often to refresh data (minimum: 30 seconds, maximum: 3600 seconds / 1 hour)' ) + + # Work Timer Settings + dashboard_show_work_timer = fields.Boolean( + string='Show Work Timer', + default=False, + help='Display live work hour countdown showing remaining time until end of planned shift. Updates every second and shows progress bar.' + ) + @@ -607,9 +615,11 @@ class DashboardConfigSettings(models.TransientModel): dashboard_show_attendance_hours=get_bool_param('system_dashboard_classic.show_attendance_hours'), dashboard_show_attendance_section=get_bool_param('system_dashboard_classic.show_attendance_section'), dashboard_enable_attendance_button=get_bool_param('system_dashboard_classic.enable_attendance_button', 'False'), + dashboard_show_work_timer=get_bool_param('system_dashboard_classic.show_work_timer'), ) return res + def set_values(self): """Override to explicitly store Boolean visibility settings as strings @@ -632,9 +642,12 @@ class DashboardConfigSettings(models.TransientModel): 'True' if self.dashboard_show_attendance_section else 'False') ICPSudo.set_param('system_dashboard_classic.enable_attendance_button', 'True' if self.dashboard_enable_attendance_button else 'False') + ICPSudo.set_param('system_dashboard_classic.show_work_timer', + 'True' if self.dashboard_show_work_timer else 'False') return res + @api.model def get_stats_visibility(self): """API method to get statistics visibility settings for JavaScript diff --git a/odex25_base/system_dashboard_classic/models/models.py b/odex25_base/system_dashboard_classic/models/models.py index 84aba6f41..56ebb4b6b 100644 --- a/odex25_base/system_dashboard_classic/models/models.py +++ b/odex25_base/system_dashboard_classic/models/models.py @@ -1021,3 +1021,77 @@ class SystemDashboard(models.Model): result['approval_count'] = approval_count return result + + @api.model + def get_work_timer_data(self): + """ + Returns live work timer data for countdown display + Leverages existing attendance.attendance model and logic + """ + employee = self.env.user.employee_id + + if not employee: + return {'enabled': False, 'reason': 'no_employee'} + + # Check if feature is enabled in settings + ICP = self.env['ir.config_parameter'].sudo() + + is_enabled = ICP.get_param('system_dashboard_classic.show_work_timer', 'True') == 'True' + + if not is_enabled: + return {'enabled': False, 'reason': 'disabled_in_settings'} + + today = fields.Date.today() + + # Get last attendance record today using same model as attendance_duration + last_att = self.env['attendance.attendance'].sudo().search([ + ('employee_id', '=', employee.id), + ('action_date', '=', today) + ], order='name DESC', limit=1) + + if not last_att: + return { + 'enabled': True, + 'status': 'not_checked_in', + 'message': _('لم تسجل دخول بعد') + } + + # Get planned hours from resource calendar + calendar = employee.resource_calendar_id + planned_hours = 8.0 # Default fallback + + if calendar: + day_of_week = today.weekday() + attendance_lines = calendar.attendance_ids.filtered( + lambda a: int(a.dayofweek) == day_of_week + ) + if attendance_lines: + planned_hours = sum(line.hour_to - line.hour_from for line in attendance_lines) + + # If sign_out - use attendance_duration directly (existing logic) + if last_att.action == 'sign_out': + return { + 'enabled': True, + 'status': 'checked_out', + 'hours_worked': last_att.attendance_duration, + 'hours_worked_formatted': last_att.attendance_duration_hhmmss, + 'planned_hours': planned_hours + } + + # If sign_in - return start time for client-side countdown + # Use same timezone logic as get_refresh_data + import pytz + user_tz = pytz.timezone(self.env.context.get('tz', 'Asia/Riyadh') or self.env.user.tz or 'UTC') + + # Convert UTC datetime to user timezone + sign_in_utc = last_att.name + sign_in_local = pytz.utc.localize(sign_in_utc).astimezone(user_tz) + + return { + 'enabled': True, + 'status': 'checked_in', + 'sign_in_time': sign_in_local.isoformat(), # ISO format for JS Date parsing + 'planned_hours': planned_hours, + 'planned_seconds': int(planned_hours * 3600) + } + diff --git a/odex25_base/system_dashboard_classic/static/src/js/system_dashboard_self_service.js b/odex25_base/system_dashboard_classic/static/src/js/system_dashboard_self_service.js index 5d1e1fb31..37783e3f1 100644 --- a/odex25_base/system_dashboard_classic/static/src/js/system_dashboard_self_service.js +++ b/odex25_base/system_dashboard_classic/static/src/js/system_dashboard_self_service.js @@ -111,7 +111,7 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require if (type === 'birthday') { emoji = '🎂'; badgeClass = 'birthday-mode'; - title = isRtl ? 'عيد ميلاد سعيد!' : 'Happy Birthday!'; + title = isRtl ? 'يوم ميلاد سعيد!' : 'Happy Birthday!'; // Gendered pronouns: لك (male) / لكِ (female) var genderInfo = window.dashboardGenderInfo || {pronoun_you: 'ك'}; var forYou = 'ل' + genderInfo.pronoun_you; // لك or لكِ @@ -309,7 +309,7 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require var greetingHtml = '' + greetingWithHonorific + ''; if (result.celebration && result.celebration.is_birthday) { - var birthdayText = isRtl ? 'عيد ميلاد سعيد!' : 'Happy Birthday!'; + var birthdayText = isRtl ? 'يوم ميلاد سعيد!' : 'Happy Birthday!'; greetingHtml = '🎂 ' + birthdayText + ''; } else if (result.celebration && result.celebration.is_anniversary) { var anniversaryText = isRtl ? 'شكراً لعطائك!' : 'Thank You!'; @@ -562,7 +562,7 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require if ($approveContainer.length === 0 || $trackContainer.length === 0) { // DOM not ready - retry after 100ms if (retryCount < maxRetries) { - console.log('[Dashboard] Waiting for DOM... attempt ' + (retryCount + 1)); + // console.log('[Dashboard] Waiting for DOM... attempt ' + (retryCount + 1)); setTimeout(function() { buildApprovalCards(resultData, retryCount + 1); }, 100); @@ -621,9 +621,8 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require view_type: 'list', views: [[list_view, 'list'], [form_view, 'form']], domain: domain, - target: 'main', - flags: { reload: true } }, { on_reverse_breadcrumb: function() { return self.reload(); } }); + }); // Update pending count badge - Sum ACTUAL pending requests from data @@ -680,6 +679,23 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require // Initial build of approval cards (pass full result object) buildApprovalCards(result); + // ============================================================ + // ACTIVE TAB HANDLING - Activate correct tab from context + // Used when returning from approval record view via breadcrumb + // Note: 'context' here is actually the action object + // ============================================================ + var activeTab = (context && context.params && context.params.active_tab) || + (context && context.context && context.context.active_tab); + if (activeTab === 'to_approve') { + setTimeout(function() { + $('a[href="#to_approve"]').tab('show'); + }, 100); + } else if (activeTab === 'to_track') { + setTimeout(function() { + $('a[href="#to_track"]').tab('show'); + }, 100); + } + // ============================================================ // TAB CLICK REFRESH - Refresh data when clicking approval tabs // Only refreshes when coming FROM Self Services TO approval tabs @@ -705,14 +721,14 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require // Only refresh when coming from Self Services tab // Not when switching between To Approve ↔ To Track if (lastActiveTab === 'approval') { - console.log('[Dashboard] Already in approval section, no refresh needed'); + // console.log('[Dashboard] Already in approval section, no refresh needed'); lastActiveTab = 'approval'; // Keep it as approval return; } // Prevent double refresh if (isRefreshing) { - console.log('[Dashboard] Refresh already in progress, skipping'); + // console.log('[Dashboard] Refresh already in progress, skipping'); return; } isRefreshing = true; @@ -789,27 +805,30 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require var model = $(this).attr('data-model'); var name = $(this).attr('data-name'); var domain = $(this).attr('data-domain'); - var form_view = parseInt($(this).attr('data-form-view')) || false; - var list_view = parseInt($(this).attr('data-list-view')) || false; - var count = parseInt($(this).attr('data-count')) || 0; - var context = $(this).attr('data-context') || '{}'; + var form_view = parseInt($(this).attr('form-view')) || false; + var list_view = parseInt($(this).attr('list-view')) || false; - try { context = JSON.parse(context.replace(/'/g, '"')); } - catch(e) { context = {}; } + if (domain === undefined || domain === '') { + domain = false; + } else { + try { domain = JSON.parse(domain); } + catch(e) { domain = false; } + } + if (isNaN(form_view)) form_view = false; + if (isNaN(list_view)) list_view = false; - if (count > 0 && domain) { + if (domain) { return self.do_action({ name: _t(name), type: 'ir.actions.act_window', res_model: model, view_mode: 'tree,form', + view_type: 'list', views: [[list_view, 'list'], [form_view, 'form']], - context: context, - domain: JSON.parse(domain.replace(/'/g, '"')), - target: 'current', - flags: { reload: true } + domain: domain, }, { on_reverse_breadcrumb: function() { return self.reload(); } }); } + }); }, 100); @@ -831,7 +850,7 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require $('.pending-count-badge').hide(); } - console.log('[Dashboard] Approval cards refreshed on tab click'); + // console.log('[Dashboard] Approval cards refreshed on tab click'); // Reset flag after a short delay to allow for any animation setTimeout(function() { isRefreshing = false; }, 500); @@ -897,16 +916,16 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require function startPeriodicRefresh() { if (!refreshEnabled || refreshInterval <= 0) { - console.log('[Dashboard] Periodic refresh disabled'); + // console.log('[Dashboard] Periodic refresh disabled'); return; } - console.log('[Dashboard] Starting periodic refresh (interval: ' + (refreshInterval/1000) + 's)'); + // console.log('[Dashboard] Starting periodic refresh (interval: ' + (refreshInterval/1000) + 's)'); self.periodicRefreshId = setInterval(function() { // Skip if page is hidden (browser tab switch) if (document.hidden) { - console.log('[Dashboard] Skipping refresh - tab hidden'); + // console.log('[Dashboard] Skipping refresh - tab hidden'); return; } @@ -917,7 +936,7 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require }, []).then(function(data) { updateAttendanceStatus(data.attendance); updateApprovalCount(data.approval_count); - console.log('[Dashboard] Periodic refresh completed - Count: ' + data.approval_count); + // console.log('[Dashboard] Periodic refresh completed - Count: ' + data.approval_count); }).catch(function(err) { console.warn('[Dashboard] Refresh failed:', err); }); @@ -927,6 +946,194 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require // Start polling startPeriodicRefresh(); + // ===== WORK TIMER WIDGET ===== + // Live countdown timer for remaining work hours + var WorkTimer = { + enabled: false, + intervalId: null, + signInTime: null, + plannedSeconds: 0, + + init: function() { + var widget = this; + + // console.log('[WorkTimer] ===== INITIALIZATION START ====='); + // console.log('[WorkTimer] self object available:', typeof self !== 'undefined'); + // console.log('[WorkTimer] self._rpc available:', typeof self._rpc); + // console.log('[WorkTimer] DOM container exists:', $('.attendance-section-body').length); + + self._rpc({ + model: 'system_dashboard_classic.dashboard', + method: 'get_work_timer_data', + }).then(function(data) { + // console.log('[WorkTimer] RPC Response received:', data); + + if (!data.enabled) { + // console.log('[WorkTimer] Feature disabled -', data.reason); + return; + } + + widget.enabled = true; + // console.log('[WorkTimer] Feature enabled, status:', data.status); + + if (data.status === 'not_checked_in') { + // console.log('[WorkTimer] Rendering: Not Checked In'); + widget.renderNotCheckedIn(data.message); + } else if (data.status === 'checked_out') { + // console.log('[WorkTimer] Rendering: Checked Out'); + widget.renderCheckedOut(data); + } else if (data.status === 'checked_in') { + // console.log('[WorkTimer] Rendering: Live Countdown'); + // Parse ISO datetime to JavaScript Date + widget.signInTime = new Date(data.sign_in_time); + widget.plannedSeconds = data.planned_seconds; + // console.log('[WorkTimer] Sign-in time:', widget.signInTime); + // console.log('[WorkTimer] Planned seconds:', widget.plannedSeconds); + widget.startCountdown(); + } + }).catch(function(err) { + console.error('[WorkTimer] !!!!! RPC FAILED !!!!!'); + console.error('[WorkTimer] Error details:', err); + }); + }, + + startCountdown: function() { + var widget = this; + + // Clear existing interval + if (this.intervalId) { + clearInterval(this.intervalId); + } + + // Update immediately + this.tick(); + + // Update every second with page visibility check + this.intervalId = setInterval(function() { + if (!document.hidden) { // Best practice: Page Visibility API + widget.tick(); + } + }, 1000); + }, + + tick: function() { + var now = new Date(); + var elapsedSeconds = Math.floor((now - this.signInTime) / 1000); + var remainingSeconds = Math.max(0, this.plannedSeconds - elapsedSeconds); + + var progress = Math.min(100, (elapsedSeconds / this.plannedSeconds) * 100); + var isOvertime = elapsedSeconds > this.plannedSeconds; + + this.updateUI({ + elapsed: this.formatTime(elapsedSeconds), + remaining: this.formatTime(remainingSeconds), + progress: progress.toFixed(1), + isOvertime: isOvertime + }); + }, + + formatTime: function(seconds) { + var h = Math.floor(seconds / 3600); + var m = Math.floor((seconds % 3600) / 60); + var s = Math.floor(seconds % 60); + return h.toString().padStart(2, '0') + ':' + + m.toString().padStart(2, '0') + ':' + + s.toString().padStart(2, '0'); + }, + + updateUI: function(data) { + var $container = $('.work-timer-compact'); + + if ($container.length === 0) { + this.renderCountdown(data); + } else { + $container.find('.timer-value').text(data.remaining); + $container.find('.timer-elapsed').text(($ ('body').hasClass('o_rtl') ? 'منقضي' : 'Elapsed') + ': ' + data.elapsed); + $container.find('.timer-progress-fill').css('width', data.progress + '%'); + $container.toggleClass('overtime', data.isOvertime); + } + }, + + renderCountdown: function(data) { + var isRtl = $('body').hasClass('o_rtl'); + + // console.log('[WorkTimer] Rendering countdown widget...'); + // console.log('[WorkTimer] Data:', data); + // console.log('[WorkTimer] RTL mode:', isRtl); + + // Compact inline design - integrated with attendance info + var html = ` +
+
+ +
+
${data.remaining}
+
${isRtl ? 'متبقي' : 'remaining'}
+
+
+
+
+
+
${isRtl ? 'منقضي' : 'Elapsed'}: ${data.elapsed}
+
+ `; + + // Insert after attendance title + var $target = $('.attendance-title'); + // console.log('[WorkTimer] Target (title) found:', $target.length); + + if ($target.length > 0) { + $target.after(html); + // console.log('[WorkTimer] ✅ Widget HTML injected after title!'); + } else { + console.error('[WorkTimer] ❌ Attendance title NOT FOUND'); + } + }, + + renderNotCheckedIn: function(message) { + var html = ` +
+
+ + ${message} +
+
+ `; + $('.attendance-title').after(html); + }, + + renderCheckedOut: function(data) { + var isRtl = $('body').hasClass('o_rtl'); + var html = ` +
+
+ +
+
${data.hours_worked_formatted}
+
${isRtl ? 'إجمالي ساعات العمل' : 'Total Hours'}
+
+
+
+ `; + $('.attendance-title').after(html); + }, + + destroy: function() { + // Best practice: Clear interval on destroy + if (this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = null; + } + $('.work-timer-compact').remove(); + } + }; + + // Initialize work timer after short delay + setTimeout(function() { + WorkTimer.init(); + }, 100); + + // Chart Settings setTimeout(function() { window.check = false; @@ -1077,6 +1284,16 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require setTimeout(function() { $notification.remove(); }, 400); }, 5500); + // ===== REFRESH WORK TIMER AFTER CHECK-IN/OUT ===== + // console.log('[WorkTimer] Attendance changed - will refresh widget...'); + setTimeout(function() { + if (typeof WorkTimer !== 'undefined' && WorkTimer.destroy && WorkTimer.init) { + // console.log('[WorkTimer] Refreshing after attendance change...'); + WorkTimer.destroy(); + WorkTimer.init(); + } + }, 1500); + }).guardedCatch(function(error) { // Handle unexpected server errors only var errorMsg = isRtl ? 'حدث خطأ غير متوقع. حاول مرة أخرى' : 'An unexpected error occurred. Please try again.'; @@ -1642,8 +1859,19 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require $(".o_control_panel").addClass("o_hidden"); var self = this; }, - reload: function() { - window.location.href = this.href; + reload: function(options) { + // Properly reload dashboard by triggering the action again + // options.active_tab can be passed to specify which tab to open + var activeTab = (options && options.active_tab) || 'to_approve'; + this.do_action({ + name: _t('Self-Service'), + type: 'ir.actions.client', + tag: 'system_dashboard_classic.dashboard_self_services', + target: 'main', + params: { + active_tab: activeTab + } + }); }, /** * Cleanup on widget destruction - prevents memory leak @@ -1659,7 +1887,7 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require if (this.periodicRefreshId) { clearInterval(this.periodicRefreshId); this.periodicRefreshId = null; - console.log('[Dashboard] Periodic refresh stopped'); + // console.log('[Dashboard] Periodic refresh stopped'); } // Call parent destroy this._super.apply(this, arguments); diff --git a/odex25_base/system_dashboard_classic/static/src/scss/core.scss b/odex25_base/system_dashboard_classic/static/src/scss/core.scss index 8099d4598..736e9abed 100644 --- a/odex25_base/system_dashboard_classic/static/src/scss/core.scss +++ b/odex25_base/system_dashboard_classic/static/src/scss/core.scss @@ -724,3 +724,321 @@ $border-color_1: #2eac96; } } } + +/* ==================================== + WORK TIMER WIDGET + Compact, professional design using dashboard colors + ==================================== */ + +.work-timer-widget { + // Use dashboard primary color with intelligent gradient + background: linear-gradient(135deg, + var(--dash-primary, #667eea) 0%, + var(--dash-primary-dark, #5a67d8) 100% + ); + border-radius: 6px; + padding: 10px 12px; + color: white; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); + margin-bottom: 12px; + transition: all 0.3s ease; + width: 100%; + max-width: 100%; + box-sizing: border-box; + + &.overtime { + // Use warning color for overtime + background: linear-gradient(135deg, + var(--dash-warning, #f59e0b) 0%, + #dc2626 100% + ); + animation: pulse-timer-warning 2s infinite; + } + + &.inactive, &.completed { + // Use secondary color for inactive states + background: linear-gradient(135deg, + var(--dash-secondary, #64748b) 0%, + var(--dash-secondary-dark, #475569) 100% + ); + text-align: center; + padding: 12px; + + i { + font-size: 18px; + margin-bottom: 4px; + display: block; + } + + p { + margin: 0; + opacity: 0.95; + font-size: 11px; + } + } +} + +@keyframes pulse-timer-warning { + 0%, 100% { + box-shadow: 0 2px 6px rgba(245, 158, 11, 0.3); + } + 50% { + box-shadow: 0 2px 12px rgba(245, 158, 11, 0.5); + } +} + +.timer-header { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; + justify-content: center; + + i { + font-size: 14px; + opacity: 0.9; + } + + span { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.95; + } +} + +.timer-body { + display: flex; + flex-direction: column; + gap: 6px; +} + +.timer-main { + text-align: center; + + .timer-value { + font-size: 22px; + font-weight: 700; + font-family: 'Courier New', monospace; + letter-spacing: 1px; + line-height: 1.1; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + } + + .timer-label { + font-size: 9px; + opacity: 0.85; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-top: 1px; + } +} + +.timer-progress-bar { + background: rgba(255, 255, 255, 0.2); + border-radius: 8px; + height: 4px; + overflow: hidden; + margin: 2px 0; + + .timer-progress-fill { + // Use success color for progress + background: linear-gradient(90deg, + var(--dash-success, #10b981), + var(--dash-success-light, #34d399) + ); + height: 100%; + border-radius: 8px; + transition: width 0.5s ease; + } +} + +.work-timer-widget.overtime .timer-progress-fill { + // Use warning gradient for overtime + background: linear-gradient(90deg, + var(--dash-warning, #fbbf24), + #f97316 + ); +} + +.timer-info { + text-align: center; + font-size: 10px; + opacity: 0.9; + + strong { + font-family: 'Courier New', monospace; + font-weight: 600; + } +} + +.timer-summary { + text-align: center; + padding: 4px 0; + + .summary-value { + font-size: 24px; + font-weight: 700; + font-family: 'Courier New', monospace; + line-height: 1.1; + margin-bottom: 4px; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + } + + .summary-label { + font-size: 10px; + opacity: 0.9; + letter-spacing: 0.3px; + } +} + +/* RTL Support */ +body.o_rtl { + .timer-header { + flex-direction: row-reverse; + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .work-timer-widget { + padding: 8px 10px; + + .timer-value { + font-size: 20px !important; + } + + .summary-value { + font-size: 20px !important; + } + } +} + + +/* ==================================== + WORK TIMER - COMPACT INLINE DESIGN + Integrated genius layout within attendance section + ==================================== */ + +.work-timer-compact { + background: linear-gradient(135deg, + var(--dash-primary, #667eea) 0%, + var(--dash-primary-dark, #5a67d8) 100% + ); + border-radius: 6px; + padding: 8px 10px; + margin: 8px 0 12px 0; + color: white; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + + &.overtime { + background: linear-gradient(135deg, + var(--dash-warning, #f59e0b) 0%, + #dc2626 100% + ); + } + + &.completed { + background: linear-gradient(135deg, + var(--dash-success, #10b981) 0%, + var(--dash-success-dark, #059669) 100% + ); + } + + &.inactive { + background: linear-gradient(135deg, + var(--dash-secondary, #64748b) 0%, + var(--dash-secondary-dark, #475569) 100% + ); + padding: 10px; + + .timer-info-msg { + display: flex; + align-items: center; + gap: 8px; + justify-content: center; + font-size: 11px; + + i { + font-size: 14px; + opacity: 0.9; + } + } + } +} + +.timer-row { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 6px; +} + +.timer-icon { + font-size: 18px; + opacity: 0.9; + flex-shrink: 0; +} + +.timer-content { + flex: 1; + display: flex; + align-items: baseline; + gap: 8px; +} + +.work-timer-compact .timer-value { + font-size: 20px; + font-weight: 700; + font-family: 'Courier New', monospace; + letter-spacing: 0.5px; + line-height: 1; +} + +.work-timer-compact .timer-label { + font-size: 9px; + opacity: 0.85; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +.work-timer-compact .timer-progress-bar { + background: rgba(255, 255, 255, 0.2); + border-radius: 6px; + height: 3px; + overflow: hidden; + margin: 4px 0; +} + +.work-timer-compact .timer-progress-fill { + background: linear-gradient(90deg, + var(--dash-success, #10b981), + var(--dash-success-light, #34d399) + ); + height: 100%; + border-radius: 6px; + transition: width 0.5s ease; +} + +.work-timer-compact.overtime .timer-progress-fill { + background: linear-gradient(90deg, + var(--dash-warning, #fbbf24), + #f97316 + ); +} + +.work-timer-compact .timer-elapsed { + font-size: 9px; + opacity: 0.85; + text-align: center; + display: block; + margin-top: 2px; +} + +/* RTL Support */ +body.o_rtl { + .timer-row { + flex-direction: row-reverse; + } +} diff --git a/odex25_base/system_dashboard_classic/views/dashboard_settings.xml b/odex25_base/system_dashboard_classic/views/dashboard_settings.xml index 0ec8b3a66..bfff35a64 100644 --- a/odex25_base/system_dashboard_classic/views/dashboard_settings.xml +++ b/odex25_base/system_dashboard_classic/views/dashboard_settings.xml @@ -233,6 +233,23 @@ + + +

Work Timer Settings

+
+
+
+ +
+
+
+
+
From 40ab27d1b77941372736d39a1ac569eb37717a7e Mon Sep 17 00:00:00 2001 From: maltayyar2 Date: Mon, 29 Dec 2025 11:45:35 +0300 Subject: [PATCH 2/2] [IMP] tour_genius: automatic update Auto-generated commit based on local changes. --- .../tour_genius/static/src/js/dashboard.js | 8 ---- .../static/src/js/genius_celebration.js | 1 - .../static/src/js/genius_quiz_popup.js | 7 --- .../tour_genius/static/src/js/genius_tip.js | 1 - .../static/src/js/recorder_panel.js | 21 +-------- .../static/src/js/smart_systray.js | 1 - .../static/src/js/tour_client_action.js | 4 -- .../tour_genius/static/src/js/tour_loader.js | 47 ------------------- 8 files changed, 1 insertion(+), 89 deletions(-) diff --git a/odex25_base/tour_genius/static/src/js/dashboard.js b/odex25_base/tour_genius/static/src/js/dashboard.js index f7808a8af..4776a47f3 100644 --- a/odex25_base/tour_genius/static/src/js/dashboard.js +++ b/odex25_base/tour_genius/static/src/js/dashboard.js @@ -120,7 +120,6 @@ odoo.define('tour_genius.Dashboard', function (require) { var tourId = $(ev.currentTarget).data('tour-id'); var tourName = 'genius_tour_' + tourId; - console.log('[Tour Genius] Running tour:', tourName); // ============================================================ // PHASE 1: Complete cleanup of ALL tour state @@ -140,7 +139,6 @@ odoo.define('tour_genius.Dashboard', function (require) { } } } catch (e) { - console.warn('[Tour Genius] Error cleaning tooltip:', e); } }); tour.active_tooltips = {}; @@ -183,7 +181,6 @@ odoo.define('tour_genius.Dashboard', function (require) { }); } - console.log('[Tour Genius] Cleanup complete, removed', keysToRemove.length, 'localStorage items'); // ============================================================ // PHASE 2: Activate the requested tour with forced reload @@ -230,7 +227,6 @@ odoo.define('tour_genius.Dashboard', function (require) { activateTour(); } else { // Tour not found - try reloading tour definitions - console.log('[Tour Genius] Tour not found, reloading definitions...'); var tourLoader = require('tour_genius.tour_loader'); if (tourLoader && tourLoader.registerAllTours) { @@ -393,7 +389,6 @@ odoo.define('tour_genius.Dashboard', function (require) { // Find tour data var tour = this.tours.find(function(t) { return t.id === tourId; }); if (!tour || !tour.quiz_id) { - console.warn('[Tour Genius] No quiz found for tour:', tourId); return; } @@ -409,7 +404,6 @@ odoo.define('tour_genius.Dashboard', function (require) { }); popup.appendTo(document.body).then(function() { - console.log('[Tour Genius] Quiz popup opened for tour:', tour.name); }); }, @@ -426,7 +420,6 @@ odoo.define('tour_genius.Dashboard', function (require) { // Find tour data var tour = this.tours.find(function(t) { return t.id === tourId; }); if (!tour || !tour.quiz_id) { - console.warn('[Tour Genius] No quiz found for tour:', tourId); return; } @@ -442,7 +435,6 @@ odoo.define('tour_genius.Dashboard', function (require) { }); popup.appendTo(document.body).then(function() { - console.log('[Tour Genius] Certificate view opened for tour:', tour.name); }); }, }); diff --git a/odex25_base/tour_genius/static/src/js/genius_celebration.js b/odex25_base/tour_genius/static/src/js/genius_celebration.js index f9fe5c003..54121f3b8 100644 --- a/odex25_base/tour_genius/static/src/js/genius_celebration.js +++ b/odex25_base/tour_genius/static/src/js/genius_celebration.js @@ -141,7 +141,6 @@ odoo.define('tour_genius.GeniusCelebration', function (require) { // Open PDF in new tab window.open('/tour_genius/certificate/view/' + data.attempt_id, '_blank'); } else { - console.warn('[GeniusCelebration] No certificate found'); } // Close the celebration self.destroy(); diff --git a/odex25_base/tour_genius/static/src/js/genius_quiz_popup.js b/odex25_base/tour_genius/static/src/js/genius_quiz_popup.js index 3fd5e882a..e9840bcc9 100644 --- a/odex25_base/tour_genius/static/src/js/genius_quiz_popup.js +++ b/odex25_base/tour_genius/static/src/js/genius_quiz_popup.js @@ -81,18 +81,13 @@ odoo.define('tour_genius.GeniusQuizPopup', function (require) { start: function () { var self = this; - console.log('[GeniusQuizPopup] Start called. Template:', this.template); - console.log('[GeniusQuizPopup] $el:', this.$el); return this._super.apply(this, arguments).then(function () { - console.log('🚀 ANTIGRAVITY: GeniusQuizPopup Loaded v2 - Ghost Code Exorcised'); if (self.showCertificate) { - // console.log('[GeniusQuizPopup] Showing certificate'); // self._renderCertificate(); // REMOVED: Ghost code causing legacy UI // Fallback to results if certificate requested but method missing self._renderResults(); } else { - console.log('[GeniusQuizPopup] Rendering Question'); self._renderQuestion(); self._startTimer(); } @@ -835,7 +830,6 @@ odoo.define('tour_genius.GeniusQuizPopup', function (require) { var quizId = recordData.id; var quizName = recordData.name; - console.log('[GeniusButton] Starting Test Mode for Quiz:', quizId); // Instantiate Popup directly var popup = new GeniusQuizPopup(self, { @@ -845,7 +839,6 @@ odoo.define('tour_genius.GeniusQuizPopup', function (require) { }); popup.appendTo(document.body).then(function() { - console.log('[GeniusButton] Popup opened successfully'); }); }, }); diff --git a/odex25_base/tour_genius/static/src/js/genius_tip.js b/odex25_base/tour_genius/static/src/js/genius_tip.js index b8660c129..356a32e19 100644 --- a/odex25_base/tour_genius/static/src/js/genius_tip.js +++ b/odex25_base/tour_genius/static/src/js/genius_tip.js @@ -330,7 +330,6 @@ var GeniusTip = Tip.extend({ } // Hand remains visible - point stored for cleanup by next step - console.log('[GeniusTip] UI hidden, hand remains. Waiting for user action on target.'); }, _onSkipTour: function (ev) { diff --git a/odex25_base/tour_genius/static/src/js/recorder_panel.js b/odex25_base/tour_genius/static/src/js/recorder_panel.js index 19389afd9..151d9c5c2 100644 --- a/odex25_base/tour_genius/static/src/js/recorder_panel.js +++ b/odex25_base/tour_genius/static/src/js/recorder_panel.js @@ -58,7 +58,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { this.dragOffsetX = 0; this.dragOffsetY = 0; - console.log('[Recorder] Init - isNewTour:', this.isNewTour, 'topicId:', this.topicId); }, start: function () { @@ -72,7 +71,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { self._loadModulesDropdown(); } - console.log('[Recorder] Started - isNewTour:', self.isNewTour); }); }, @@ -115,10 +113,8 @@ odoo.define('tour_genius.RecorderPanel', function (require) { var selectedText = $(this).val(); var moduleId = self._moduleMap[selectedText] || ''; $hiddenSelect.val(moduleId); - console.log('[Recorder] Module input:', selectedText, '-> ID:', moduleId); }); - console.log('[Recorder] Loaded', modules.length, 'modules for search'); }).catch(function (err) { console.error('[Recorder] Module load error:', err); }); @@ -190,7 +186,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { }, _startTracking: function () { - console.log('[Recorder] Start Tracking'); this.isTracking = true; this.$('.btn-track-on').addClass('active').html(' Stop Tracking'); @@ -213,7 +208,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { }, _stopTracking: function () { - console.log('[Recorder] Stop Tracking'); this.isTracking = false; this.$('.btn-track-on').removeClass('active').html(' Track On'); @@ -265,7 +259,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { return; } - console.log('[Recorder] Element Clicked:', target); // Stop event! ev.preventDefault(); @@ -273,7 +266,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { // Generate CSS selector var selector = this._generateCSSSelector(target); - console.log('[Recorder] Generated Selector:', selector); // Update current step this.currentStep.css_selector = selector; @@ -545,7 +537,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { _onSaveStep: function () { var self = this; - console.log('[Recorder] Save Step Clicked'); // Validate via UI values, NOT stored values (to allow manual edits) var selector = this.$('.css-selector-input').val() || ''; @@ -581,8 +572,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { var moduleId = this.$('.tour-module-select').val(); // DEBUG: Log module selection details - console.log('[Recorder] Tour Name:', tourName); - console.log('[Recorder] Module ID retrieved:', moduleId); // Validate Tour Name if (!tourName || tourName.trim() === '') { @@ -600,12 +589,9 @@ odoo.define('tour_genius.RecorderPanel', function (require) { // Only add module_id if it's a valid number if (moduleId && moduleId !== '' && !isNaN(parseInt(moduleId))) { tourVals.module_id = parseInt(moduleId); - console.log('[Recorder] Module ID added to vals:', tourVals.module_id); } else { - console.log('[Recorder] No valid module_id to add'); } - console.log('[Recorder] Creating Tour with vals:', JSON.stringify(tourVals)); createTourPromise = ajax.jsonRpc('/web/dataset/call_kw/genius.topic/create', 'call', { model: 'genius.topic', @@ -613,7 +599,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { args: [tourVals], kwargs: { context: session.user_context } }).then(function (newTopicId) { - console.log('[Recorder] Tour Created:', newTopicId); self.topicId = newTopicId; self.topicName = tourName.trim(); self.isNewTour = false; // No longer new @@ -644,7 +629,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { sequence: (self.steps.length + 1) * 10, }; - console.log('[Recorder] Saving Step:', stepData); return ajax.jsonRpc('/web/dataset/call_kw/genius.topic.step/create', 'call', { model: 'genius.topic.step', @@ -652,7 +636,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { args: [stepData], kwargs: { context: session.user_context } }).then(function (stepId) { - console.log('[Recorder] Step Saved ID:', stepId); // Update local steps array stepData.id = stepId; @@ -691,7 +674,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { } } - console.log('[Recorder] Captured starting_url:', relativeUrl); var modelName = ''; @@ -730,7 +712,6 @@ odoo.define('tour_genius.RecorderPanel', function (require) { }); } } - } catch(e) { console.warn('[Recorder] Context detection failed', e); } var updateVals = { 'starting_url': relativeUrl }; if (modelName) { @@ -745,8 +726,8 @@ odoo.define('tour_genius.RecorderPanel', function (require) { args: [[parseInt(self.topicId)], updateVals], kwargs: { context: session.user_context } }).then(function() { - console.log('[Recorder] Topic updated with starting_url:', relativeUrl); }); + } catch(e) {} } self._updateStepCounter(); diff --git a/odex25_base/tour_genius/static/src/js/smart_systray.js b/odex25_base/tour_genius/static/src/js/smart_systray.js index f72f11b07..7d17df71a 100644 --- a/odex25_base/tour_genius/static/src/js/smart_systray.js +++ b/odex25_base/tour_genius/static/src/js/smart_systray.js @@ -83,7 +83,6 @@ odoo.define('tour_genius.smart_systray', function (require) { self.badge_count = result.new_count || 0; self._updateBadge(); }).catch(function (err) { - // console.warn('[Tour Genius] Failed to fetch contextual training', err); self.tours = []; self.badge_count = 0; self._updateBadge(); diff --git a/odex25_base/tour_genius/static/src/js/tour_client_action.js b/odex25_base/tour_genius/static/src/js/tour_client_action.js index d3c759c0a..222c66ccf 100644 --- a/odex25_base/tour_genius/static/src/js/tour_client_action.js +++ b/odex25_base/tour_genius/static/src/js/tour_client_action.js @@ -22,7 +22,6 @@ odoo.define('tour_genius.client_action', function (require) { return Promise.reject('No tour_name provided'); } - console.log('[Tour Genius] Running tour via client action:', tourName); // ============================================================ // COMPREHENSIVE CLEANUP @@ -85,7 +84,6 @@ odoo.define('tour_genius.client_action', function (require) { if (testMode && tourData && tourData.steps) { // Dynamic registration for test mode (draft tours) - console.log('[Tour Genius] Test Mode: Storing tour data for post-redirect registration:', tourName); // Store tour data in localStorage for post-redirect registration // This is needed because draft tours aren't in get_tours_for_registration() @@ -102,7 +100,6 @@ odoo.define('tour_genius.client_action', function (require) { wait_for: Promise.resolve(), }, tourData.steps); - console.log('[Tour Genius] Tour registered dynamically:', tourName); } var targetTour = tour.tours && tour.tours[tourName]; @@ -130,7 +127,6 @@ odoo.define('tour_genius.client_action', function (require) { return Promise.resolve(); } else { console.error('[Tour Genius] Tour not found or has no steps:', tourName); - console.log('[Tour Genius] Available tours:', Object.keys(tour.tours || {})); return Promise.reject('Tour not found: ' + tourName); } } diff --git a/odex25_base/tour_genius/static/src/js/tour_loader.js b/odex25_base/tour_genius/static/src/js/tour_loader.js index 823144452..a6119e5c6 100644 --- a/odex25_base/tour_genius/static/src/js/tour_loader.js +++ b/odex25_base/tour_genius/static/src/js/tour_loader.js @@ -33,7 +33,6 @@ odoo.define('tour_genius.tour_loader', function (require) { var geniusToursData = {}; function registerAllTours() { - console.log('[Tour Genius] Starting tour registration...'); // GENIUS FIX: Pre-emptive check for pending genius tours to prevent overlap // This prevents standard tours from flashing while we fetch our tours @@ -52,13 +51,11 @@ odoo.define('tour_genius.tour_loader', function (require) { } if (isGeniusPending) { - console.log('[Tour Genius] Pending genius tour detected. Aggressively clearing others.'); deactivateNonGeniusTours(); // Repeat deactivation every 100ms until RPC returns to catch any stragglers antiFlashInterval = setInterval(deactivateNonGeniusTours, 100); } } catch(e) { - console.warn('[Tour Genius] Pre-emptive check failed:', e); } rpc.query({ @@ -66,7 +63,6 @@ odoo.define('tour_genius.tour_loader', function (require) { method: 'get_tours_for_registration', args: [], }).catch(function(err) { - console.warn('[Tour Genius] Failed to fetch tours (likely public page/access denied):', err); return []; // Return empty array to allow chain to continue (e.g. for test tours) }).then(function(topics) { // Stop the aggressive clearing once we have data @@ -84,17 +80,13 @@ odoo.define('tour_genius.tour_loader', function (require) { var storedData = window.localStorage.getItem(key); if (storedData) { var testTourData = JSON.parse(storedData); - console.log('[Tour Genius] Found stored test tour data:', tourName); // Register the test tour dynamically if (testTourData.steps && testTourData.steps.length > 0) { - console.log('[Tour Genius] Registering test tour with steps:', testTourData.steps); - console.log('[Tour Genius] Step 0 Content CHECK:', testTourData.steps[0].content); tour.register(tourName, { url: testTourData.url || '/web', wait_for: Promise.resolve(), }, testTourData.steps); - console.log('[Tour Genius] Registered test tour from localStorage:', tourName); // CRITICAL: Add to registeredTours so it goes through activation! registeredTours.push(tourName); @@ -108,15 +100,7 @@ odoo.define('tour_genius.tour_loader', function (require) { } } } catch (e) { - console.warn('[Tour Genius] Error processing stored test tour data:', e); } - - if (!topics || !topics.length) { - console.log('[Tour Genius] No published tours to register (test tours may still work)'); - } else { - console.log('[Tour Genius] Registering', topics.length, 'tours'); - } - // Safely iterate over topics (may be empty array or null) (topics || []).forEach(function(topic) { var tourName = 'genius_tour_' + topic.id; @@ -126,7 +110,6 @@ odoo.define('tour_genius.tour_loader', function (require) { // Skip if already registered (e.g. by test mode loader above) if (tour.tours && tour.tours[tourName]) { - console.log('[Tour Genius] Tour already registered (likely test mode):', tourName); // Only add to registeredTours if NOT already there to prevent double activation if (registeredTours.indexOf(tourName) === -1) { registeredTours.push(tourName); @@ -136,7 +119,6 @@ odoo.define('tour_genius.tour_loader', function (require) { // Skip if no steps if (!topic.steps || !topic.steps.length) { - console.log('[Tour Genius] Skipping tour with no steps:', tourName); return; } @@ -152,7 +134,6 @@ odoo.define('tour_genius.tour_loader', function (require) { // Check if showAppsMenuItem is not already the first step (avoid dupes) var firstStepIsApps = firstStep && firstStep.content === "Home Menu"; // Odoo standard name if (!firstStepIsApps) { - console.log('[Tour Genius] Injecting showAppsMenuItem for tour:', tourName); stepsToRegister.unshift(tour.stepUtils.showAppsMenuItem()); } } @@ -168,7 +149,6 @@ odoo.define('tour_genius.tour_loader', function (require) { registeredTours.push(tourName); - console.log('[Tour Genius] Registered tour:', tourName, 'with', topic.steps.length, 'steps'); }); // CRITICAL FIX: For tours with debugging flags, we need to properly call _register() @@ -188,11 +168,9 @@ odoo.define('tour_genius.tour_loader', function (require) { var debuggingKey = 'debugging_tour_' + tourName; var isDebugging = localStorage.getItem(debuggingKey); - console.log('[Tour Genius] Checking activation for:', tourName, 'debugging:', !!isDebugging); if (isDebugging) { // Tour was started via reset() - call _register to properly initialize it - console.log('[Tour Genius] Activating tour from debugging flag:', tourName); // ============================================================ // DEEP CLEANUP: Force Fresh Start @@ -209,7 +187,6 @@ odoo.define('tour_genius.tour_loader', function (require) { if (idx > -1) { consumed.splice(idx, 1); window.localStorage.setItem(consumedKey, JSON.stringify(consumed)); - console.log('[Tour Genius] Removed from localStorage consumed list:', tourName); } } } catch(e) {} @@ -218,7 +195,6 @@ odoo.define('tour_genius.tour_loader', function (require) { var idx = tour.consumed_tours.indexOf(tourName); if (idx > -1) { tour.consumed_tours.splice(idx, 1); - console.log('[Tour Genius] Removed from memory consumed list:', tourName); } } @@ -229,7 +205,6 @@ odoo.define('tour_genius.tour_loader', function (require) { var stepKey = 'tour_' + tourName + '_current_step'; if (window.localStorage.getItem(stepKey) !== null) { window.localStorage.removeItem(stepKey); - console.log('[Tour Genius] Wiped step persistence key:', stepKey); } // Also clean any other patterns just in case @@ -244,14 +219,12 @@ odoo.define('tour_genius.tour_loader', function (require) { if (key === tourName || key.indexOf(tourName) >= 0 && (key.indexOf('step') >= 0 || key.indexOf('current') >= 0)) { window.localStorage.removeItem(key); - console.log('[Tour Genius] Wiped additional persistence key:', key); } } // 2. Destroy Zombie Tooltips // If a tooltip exists from a previous page load, it might be detached. Kill it. if (tour.active_tooltips[tourName]) { - console.log('[Tour Genius] Destroying zombie tooltip before activation'); try { if (tour.active_tooltips[tourName].destroy) tour.active_tooltips[tourName].destroy(); @@ -271,7 +244,6 @@ odoo.define('tour_genius.tour_loader', function (require) { // Call _register with do_update=true // This will check the debugging flag and call _to_next_step tour._register(true, registeredTour, tourName).then(function() { - console.log('[Tour Genius] Tour registered promise resolved:', tourName); // CRITICAL: Deactivate ALL non-genius tours deactivateNonGeniusTours(); @@ -287,31 +259,25 @@ odoo.define('tour_genius.tour_loader', function (require) { method: 'action_track_start', args: [[tourId]], }).catch(function(e) { - console.warn('[Tour Genius] Failed to track start time:', e); }); } else { - console.log('[Tour Genius] Test mode - skipping progress tracking start'); } // FORCE ACTIVATION CHECK // Sometimes Odoo's _register doesn't trigger _to_next_step if timing is off if (!tour.active_tooltips[tourName]) { - console.warn('[Tour Genius] Tooltip missing after register. Forcing activation of Step 0.'); if (tour.tours[tourName]) { tour.tours[tourName].current_step = 0; // Manually trigger internal method to stage the first tooltip if (tour._to_next_step) { tour._to_next_step(tourName, 0); - console.log('[Tour Genius] Forced _to_next_step(0) success.'); } } } // After registration, call update to display the tooltip setTimeout(function() { - console.log('[Tour Genius] Calling tour.update() for:', tourName); - console.log('[Tour Genius] Active Tooltip State:', tour.active_tooltips[tourName]); tour.update(tourName); }, 500); }); @@ -319,7 +285,6 @@ odoo.define('tour_genius.tour_loader', function (require) { // For non-debugging tours, do nothing - they don't need activation }); - console.log('[Tour Genius] All tours registered and activation checks initiated'); // CRITICAL: Override Odoo's _consume_tour to show our GeniusCelebration patchConsumeTour(registeredTours); @@ -366,7 +331,6 @@ odoo.define('tour_genius.tour_loader', function (require) { isTestMode = window.localStorage.getItem('genius_test_mode_' + tour_name) === 'true'; if (isTestMode) { - console.log('[Tour Genius] Test mode - performing manual cleanup to skip Odoo effects'); // 1. UNCONDITIONAL DELETE: This is critical. // _to_next_step sets the value to undefined but keeps the key. @@ -420,7 +384,6 @@ odoo.define('tour_genius.tour_loader', function (require) { // Show quiz button if quiz has a valid ID var hasQuiz = geniusQuiz && geniusQuiz.id && geniusQuiz.id !== false; - console.log('[Tour Genius] Completed tour:', tourId, 'Has Quiz:', hasQuiz); // Mark as consumed rpc.query({ @@ -453,7 +416,6 @@ odoo.define('tour_genius.tour_loader', function (require) { }; tour._consume_tour_patched = true; - console.log('[Tour Genius] Patched _consume_tour for GeniusCelebration'); } /** @@ -470,7 +432,6 @@ odoo.define('tour_genius.tour_loader', function (require) { try { GeniusTip = require('tour_genius.GeniusTip'); } catch (e) { - console.warn('[Tour Genius] GeniusTip not available, using standard tooltips'); } tour._activate_tip = function(tip, tour_name, $anchor, $alt_trigger) { @@ -519,7 +480,6 @@ odoo.define('tour_genius.tour_loader', function (require) { // Handle genius_skip_tour event tip.widget.on('genius_skip_tour', this, function(data) { - console.log('[Tour Genius] Skip tour triggered:', data.tourName); this._deactivate_tip(tip); this._consume_tour(data.tourName); }); @@ -529,11 +489,9 @@ odoo.define('tour_genius.tour_loader', function (require) { this._to_next_running_step.bind(this, tip, tour_name) ); - console.log('[Tour Genius] Activated GeniusTip for step', stepIndex + 1, 'of', totalSteps); }; tour._activate_tip_patched = true; - console.log('[Tour Genius] Patched _activate_tip for GeniusTip'); } /** @@ -541,7 +499,6 @@ odoo.define('tour_genius.tour_loader', function (require) { * Called when a genius tour is being activated */ function deactivateNonGeniusTours() { - console.log('[Tour Genius] Deactivating non-genius tours...'); if (!tour.active_tooltips) return; @@ -558,7 +515,6 @@ odoo.define('tour_genius.tour_loader', function (require) { try { tip.widget.destroy(); } catch (e) { - console.warn('[Tour Genius] Error destroying tip widget:', e); } } @@ -566,10 +522,8 @@ odoo.define('tour_genius.tour_loader', function (require) { delete tour.active_tooltips[tourName]; deactivatedCount++; - console.log('[Tour Genius] Deactivated tour:', tourName); }); - console.log('[Tour Genius] Deactivated', deactivatedCount, 'non-genius tours'); } /** @@ -601,7 +555,6 @@ odoo.define('tour_genius.tour_loader', function (require) { // NOTE: Tour completion/skip handling is done in patchConsumeTour // - Completion shows GeniusCelebration and marks consumed // - Skip just marks as skipped (no celebration) - console.log('[Tour Genius] Tour consumed (completion listener):', tourName); } // Track that debugging is active