Merge pull request #5842 from expsa/14.0-feat-system_dashboard_classic-auto-20251227_191059
[FIX] system_dashboard_classic: enhance data models and user interface
This commit is contained in:
commit
169df0a739
|
|
@ -750,20 +750,28 @@ class SystemDashboard(models.Model):
|
||||||
# Calculate how far outside the zone user is
|
# Calculate how far outside the zone user is
|
||||||
distance_outside = int(closest_zone_distance - allowed_range)
|
distance_outside = int(closest_zone_distance - allowed_range)
|
||||||
|
|
||||||
|
# Smart distance formatting: use km for large distances, meters for small
|
||||||
|
if distance_outside >= 1000:
|
||||||
|
# Convert to km with 1 decimal place
|
||||||
|
distance_km = round(distance_outside / 1000, 1)
|
||||||
|
distance_str_ar = f"{distance_km} كيلو متر"
|
||||||
|
distance_str_en = f"{distance_km} kilometers"
|
||||||
|
else:
|
||||||
|
distance_str_ar = f"{distance_outside} متر"
|
||||||
|
distance_str_en = f"{distance_outside} meters"
|
||||||
|
|
||||||
# Gender-aware Arabic message
|
# Gender-aware Arabic message
|
||||||
# تتواجد (male) / تتواجدين (female)
|
# تتواجد (male) / تتواجدين (female)
|
||||||
# لتتمكن (male) / لتتمكني (female)
|
# لتتمكن (male) / لتتمكني (female)
|
||||||
employee_gender = getattr(employee_object, 'gender', 'male') or 'male'
|
employee_gender = getattr(employee_object, 'gender', 'male') or 'male'
|
||||||
if employee_gender == 'female':
|
if employee_gender == 'female':
|
||||||
ar_msg = "عذراً، أنتِ تتواجدين خارج نطاق الحضور المسموح.\nيرجى الاقتراب مسافة %s متر تقريباً أو أكثر لتتمكني من التسجيل."
|
ar_msg = f"عذراً، أنتِ تتواجدين خارج نطاق الحضور المسموح.\nيرجى الاقتراب مسافة {distance_str_ar} تقريباً أو أكثر لتتمكني من التسجيل."
|
||||||
else:
|
else:
|
||||||
ar_msg = "عذراً، أنت تتواجد خارج نطاق الحضور المسموح.\nيرجى الاقتراب مسافة %s متر تقريباً أو أكثر لتتمكن من التسجيل."
|
ar_msg = f"عذراً، أنت تتواجد خارج نطاق الحضور المسموح.\nيرجى الاقتراب مسافة {distance_str_ar} تقريباً أو أكثر لتتمكن من التسجيل."
|
||||||
|
|
||||||
msg_formats = {
|
en_msg = f"Sorry, you are outside the allowed attendance zone.\nPlease move approximately {distance_str_en} or more closer to be able to check in."
|
||||||
'ar': ar_msg,
|
|
||||||
'en': "Sorry, you are outside the allowed attendance zone.\nPlease move approximately %s meters or more closer to be able to check in."
|
msg = ar_msg if is_arabic else en_msg
|
||||||
}
|
|
||||||
msg = (msg_formats['ar'] if is_arabic else msg_formats['en']) % distance_outside
|
|
||||||
return {
|
return {
|
||||||
'error': True,
|
'error': True,
|
||||||
'message': msg
|
'message': msg
|
||||||
|
|
|
||||||
|
|
@ -542,13 +542,36 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require
|
||||||
|
|
||||||
// Function to build/rebuild approval cards
|
// Function to build/rebuild approval cards
|
||||||
// IMPORTANT: Receives full result object to access card_orders for drag-drop
|
// IMPORTANT: Receives full result object to access card_orders for drag-drop
|
||||||
function buildApprovalCards(resultData) {
|
// FIX: Added DOM-ready check with retry to handle race condition
|
||||||
|
function buildApprovalCards(resultData, retryCount) {
|
||||||
|
retryCount = retryCount || 0;
|
||||||
|
var maxRetries = 30; // 3 seconds max wait (30 * 100ms)
|
||||||
|
|
||||||
var cards = resultData.cards;
|
var cards = resultData.cards;
|
||||||
var cardOrders = resultData.card_orders;
|
var cardOrders = resultData.card_orders;
|
||||||
|
|
||||||
|
// Check if DOM elements exist (template may not be attached yet)
|
||||||
|
var $approveContainer = self.$el.find('div.card-section-approve');
|
||||||
|
var $trackContainer = self.$el.find('div.card-section-track');
|
||||||
|
|
||||||
|
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));
|
||||||
|
setTimeout(function() {
|
||||||
|
buildApprovalCards(resultData, retryCount + 1);
|
||||||
|
}, 100);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.error('[Dashboard] DOM elements not found after ' + maxRetries + ' attempts');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOM is ready - proceed with building cards
|
||||||
// Clear existing cards
|
// Clear existing cards
|
||||||
self.$el.find('div.card-section-approve').empty();
|
$approveContainer.empty();
|
||||||
self.$el.find('div.card-section-track').empty();
|
$trackContainer.empty();
|
||||||
|
|
||||||
$.each(cards, function(index, cardData) {
|
$.each(cards, function(index, cardData) {
|
||||||
if (cardData.type == "approve") {
|
if (cardData.type == "approve") {
|
||||||
|
|
@ -562,8 +585,8 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require
|
||||||
var card2 = buildCardApprove(cardData, index, 'table2');
|
var card2 = buildCardApprove(cardData, index, 'table2');
|
||||||
$(card2).find('.card-header h4 span').html(_t(cardData.name));
|
$(card2).find('.card-header h4 span').html(_t(cardData.name));
|
||||||
// APPENDING CARDS
|
// APPENDING CARDS
|
||||||
self.$el.find('div.card-section-approve').append(card);
|
$approveContainer.append(card);
|
||||||
self.$el.find('div.card-section-track').append(card2);
|
$trackContainer.append(card2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -653,71 +676,131 @@ odoo.define('system_dashboard_classic.dashboard_self_services', function(require
|
||||||
buildApprovalCards(result);
|
buildApprovalCards(result);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// TAB CLICK - Cards are already built, no need to refresh on tab switch
|
// ============================================================
|
||||||
// Polling handles data freshness. Drag-drop order is preserved.
|
// TAB CLICK REFRESH - Refresh data when clicking approval tabs
|
||||||
|
// Fetches fresh data while PRESERVING saved card order
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// ============================================================
|
// Bind to approval tab clicks for data refresh
|
||||||
// POLLING FOR NEW APPROVAL REQUESTS (Feature 7)
|
$('a[href="#to_approve"], a[href="#to_track"]').off('click.refresh').on('click.refresh', function(e) {
|
||||||
// Checks every 60 seconds for new requests and plays notification
|
var $tab = $(this);
|
||||||
// ============================================================
|
|
||||||
var lastApproveCount = self.$el.find('div.card-section-approve .card3').length;
|
|
||||||
var notificationSound = null;
|
|
||||||
var audioUnlocked = false;
|
|
||||||
|
|
||||||
// Simple notification sound as base64 (short beep)
|
// Show loading indicator (optional visual feedback)
|
||||||
var soundDataUri = 'data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YVoGAACBhYqFbF1fdH2AgYB4cWpvdXt/fn55dHFydnl8fHt6eHd3eHl6e3t7enl4d3d4eXp7e3t6eXh3d3h5ent7e3p5eHd3eHl6e3t7enl4d3d4eXp7e3t6eXh3d3h5ent7e3p5eHd3eHl6e3t7enl4d3d4eXp7e3t6eXh3d3h5ent7e3p5eHd3eHl6e3t7';
|
var $approveContainer = self.$el.find('div.card-section-approve');
|
||||||
|
var $trackContainer = self.$el.find('div.card-section-track');
|
||||||
|
|
||||||
// Unlock audio on first user interaction (browser policy)
|
// Fetch fresh data from server
|
||||||
$(document).one('click keydown', function() {
|
|
||||||
try {
|
|
||||||
notificationSound = new Audio(soundDataUri);
|
|
||||||
notificationSound.volume = 0.5;
|
|
||||||
notificationSound.load();
|
|
||||||
audioUnlocked = true;
|
|
||||||
} catch (e) {
|
|
||||||
// Audio notification not supported
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Poll for new approval requests every 60 seconds
|
|
||||||
self.pollingIntervalId = setInterval(function() {
|
|
||||||
self._rpc({
|
self._rpc({
|
||||||
model: 'system_dashboard_classic.dashboard',
|
model: 'system_dashboard_classic.dashboard',
|
||||||
method: 'get_data',
|
method: 'get_data',
|
||||||
}, []).then(function(freshResult) {
|
}, []).then(function(freshResult) {
|
||||||
// Count ACTUAL pending requests from fresh data
|
// Update global result reference
|
||||||
var newApproveCount = 0;
|
window.resultX = freshResult;
|
||||||
|
|
||||||
|
// Get saved order from server (MUST BE FRESH from this request)
|
||||||
|
var freshCardOrders = freshResult.card_orders || {};
|
||||||
|
var savedApprovalOrder = freshCardOrders['dashboard_approval_order'];
|
||||||
|
|
||||||
|
// Clear existing cards
|
||||||
|
$approveContainer.empty();
|
||||||
|
$trackContainer.empty();
|
||||||
|
|
||||||
|
// Rebuild cards from fresh data
|
||||||
|
$.each(freshResult.cards, function(index, cardData) {
|
||||||
|
if (cardData.type == "approve") {
|
||||||
|
// BUILD APPROVE CARD
|
||||||
|
var card = buildCardApprove(cardData, index, 'table1');
|
||||||
|
$(card).find('.card-header h4 span').html(_t(cardData.name));
|
||||||
|
// BUILD FOLLOW CARD
|
||||||
|
var card2 = buildCardApprove(cardData, index, 'table2');
|
||||||
|
$(card2).find('.card-header h4 span').html(_t(cardData.name));
|
||||||
|
// APPEND
|
||||||
|
$approveContainer.append(card);
|
||||||
|
$trackContainer.append(card2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// CRITICAL: Apply saved order AFTER building cards
|
||||||
|
// This preserves user's drag-drop customization
|
||||||
|
setTimeout(function() {
|
||||||
|
// Re-initialize drag-drop with saved order from server
|
||||||
|
initDragDropSortable({
|
||||||
|
containerSelector: 'div.card-section-approve',
|
||||||
|
cardSelector: '.card2',
|
||||||
|
storageKey: 'dashboard_approval_order',
|
||||||
|
rpcContext: self._rpc.bind(self),
|
||||||
|
serverOrder: savedApprovalOrder,
|
||||||
|
linkedContainerSelector: 'div.card-section-track'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply same order to track tab
|
||||||
|
if (savedApprovalOrder && savedApprovalOrder.length > 0) {
|
||||||
|
var $trackCards = $trackContainer.find('.card2');
|
||||||
|
savedApprovalOrder.forEach(function(cardId) {
|
||||||
|
var $matchingCard = $trackCards.filter(function() {
|
||||||
|
var model = $(this).find('.box-1').attr('data-model') || $(this).attr('data-model');
|
||||||
|
return model === cardId;
|
||||||
|
}).first();
|
||||||
|
if ($matchingCard.length > 0) {
|
||||||
|
$trackContainer.append($matchingCard);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebind click handlers for new cards
|
||||||
|
self.$el.find('tr[data-target="record-button"]').off('click').on('click', function(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
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') || '{}';
|
||||||
|
|
||||||
|
try { context = JSON.parse(context.replace(/'/g, '"')); }
|
||||||
|
catch(e) { context = {}; }
|
||||||
|
|
||||||
|
if (count > 0 && domain) {
|
||||||
|
return self.do_action({
|
||||||
|
name: _t(name),
|
||||||
|
type: 'ir.actions.act_window',
|
||||||
|
res_model: model,
|
||||||
|
view_mode: 'tree,form',
|
||||||
|
views: [[list_view, 'list'], [form_view, 'form']],
|
||||||
|
context: context,
|
||||||
|
domain: JSON.parse(domain.replace(/'/g, '"')),
|
||||||
|
target: 'current',
|
||||||
|
flags: { reload: true }
|
||||||
|
}, { on_reverse_breadcrumb: function() { return self.reload(); } });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Update pending count badge
|
||||||
|
var totalPendingCount = 0;
|
||||||
$.each(freshResult.cards, function(index, cardData) {
|
$.each(freshResult.cards, function(index, cardData) {
|
||||||
if (cardData.type == 'approve' && cardData.lines && cardData.lines.length > 0) {
|
if (cardData.type == 'approve' && cardData.lines && cardData.lines.length > 0) {
|
||||||
$.each(cardData.lines, function(lineIdx, line) {
|
$.each(cardData.lines, function(lineIdx, line) {
|
||||||
if (line.count_state_click) {
|
if (line.count_state_click) {
|
||||||
newApproveCount += parseInt(line.count_state_click) || 0;
|
totalPendingCount += parseInt(line.count_state_click) || 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if there are NEW requests
|
if (totalPendingCount > 0) {
|
||||||
if (newApproveCount > lastApproveCount) {
|
$('.pending-count-badge').text(totalPendingCount).show();
|
||||||
// Play notification sound
|
} else {
|
||||||
if (audioUnlocked && notificationSound) {
|
$('.pending-count-badge').hide();
|
||||||
notificationSound.currentTime = 0;
|
|
||||||
notificationSound.play().catch(function(e) {
|
|
||||||
// Ignore errors silently
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild cards and update badge
|
console.log('[Dashboard] Approval cards refreshed on tab click');
|
||||||
buildApprovalCards(freshResult.cards);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update count for next comparison
|
|
||||||
lastApproveCount = newApproveCount;
|
|
||||||
}).catch(function(e) {
|
}).catch(function(e) {
|
||||||
// Silently ignore polling errors
|
console.error('[Dashboard] Error refreshing approval data:', e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, 60000); // Check every 60 seconds
|
|
||||||
}
|
}
|
||||||
// Chart Settings
|
// Chart Settings
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
|
|
||||||
|
|
@ -488,17 +488,18 @@ $border-color_1: #2eac96;
|
||||||
#timesheet-section {
|
#timesheet-section {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
/* RTL Support - Essential rules that rtlcss cannot handle automatically */
|
||||||
.o_rtl {
|
.o_rtl {
|
||||||
.canvasjs-chart-container {
|
.canvasjs-chart-container {
|
||||||
direction: rtl;
|
direction: rtl; /*rtl:ignore*/
|
||||||
text-align: right !important;
|
text-align: right !important; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
.dashboard-header {
|
.dashboard-header {
|
||||||
.dashboard-user-statistics-section {
|
.dashboard-user-statistics-section {
|
||||||
.dashboard-attendance-section {
|
.dashboard-attendance-section {
|
||||||
border-left: 1px solid #d0d0d0;
|
border-left: 1px solid #d0d0d0; /*rtl:ignore*/
|
||||||
border-right: none !important;
|
border-right: none !important; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -509,8 +510,8 @@ $border-color_1: #2eac96;
|
||||||
>a {
|
>a {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
margin-left: 5px;
|
margin-left: 5px; /*rtl:ignore*/
|
||||||
margin-right: 0;
|
margin-right: 0; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -520,13 +521,13 @@ $border-color_1: #2eac96;
|
||||||
.module-box-container {
|
.module-box-container {
|
||||||
p {
|
p {
|
||||||
>span {
|
>span {
|
||||||
margin-right: 0;
|
margin-right: 0; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.card2 {
|
.card2 {
|
||||||
padding-right: 0;
|
padding-right: 0; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lds-roller {
|
.lds-roller {
|
||||||
|
|
@ -637,9 +638,10 @@ $border-color_1: #2eac96;
|
||||||
.canvasjs-chart-canvas {
|
.canvasjs-chart-canvas {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
}
|
}
|
||||||
|
/* RTL responsive override */
|
||||||
.o_rtl {
|
.o_rtl {
|
||||||
.card3 {
|
.card3 {
|
||||||
padding: 0;
|
padding: 0; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -708,15 +710,16 @@ $border-color_1: #2eac96;
|
||||||
.canvasjs-chart-canvas {
|
.canvasjs-chart-canvas {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
}
|
}
|
||||||
|
/* RTL responsive padding for card2 alternating columns */
|
||||||
.o_rtl {
|
.o_rtl {
|
||||||
.card2 {
|
.card2 {
|
||||||
&:nth-child(2n) {
|
&:nth-child(2n) {
|
||||||
padding-right: 15px;
|
padding-right: 15px; /*rtl:ignore*/
|
||||||
padding-left: 0;
|
padding-left: 0; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
&:nth-child(3n) {
|
&:nth-child(3n) {
|
||||||
padding-left: 15px;
|
padding-left: 15px; /*rtl:ignore*/
|
||||||
padding-right: 0;
|
padding-right: 0; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2028,28 +2028,86 @@ p.fn-section.clickable-profile:hover {
|
||||||
|
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
RTL SUPPORT FOR NEW ELEMENTS
|
RTL SUPPORT - Essential Overrides
|
||||||
|
============================================================
|
||||||
|
These are rules that Odoo's rtlcss cannot handle automatically.
|
||||||
|
Only use .o_rtl class (added by Odoo to body for RTL languages).
|
||||||
|
|
||||||
|
Rules that rtlcss DOES handle automatically:
|
||||||
|
- left ↔ right
|
||||||
|
- margin-left ↔ margin-right
|
||||||
|
- padding-left ↔ padding-right
|
||||||
|
- float: left ↔ float: right
|
||||||
|
- text-align: left ↔ text-align: right
|
||||||
|
|
||||||
|
Only add manual overrides when rtlcss produces incorrect results
|
||||||
|
or when specific design requires different behavior.
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
.o_rtl {
|
.o_rtl {
|
||||||
// .genius-search-container {
|
/* === Profile Section RTL === */
|
||||||
// margin-left: 0 !important;
|
.profile-container {
|
||||||
// margin-right: auto !important;
|
.pp-info-section {
|
||||||
// }
|
border-left: none;
|
||||||
|
border-right: 1px solid #9f9f9f; /*rtl:ignore*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Green status dot - keep on left side in RTL */
|
||||||
|
.pp-image-section::after {
|
||||||
|
right: auto !important;
|
||||||
|
left: 8px !important; /*rtl:ignore*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Search Bar RTL === */
|
||||||
.genius-search-icon {
|
.genius-search-icon {
|
||||||
left: auto !important;
|
left: auto !important;
|
||||||
right: 14px !important;
|
right: 14px !important; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.genius-search-input {
|
.genius-search-input {
|
||||||
padding: 10px 38px 10px 40px !important;
|
padding: 10px 38px 10px 40px !important; /*rtl:ignore*/
|
||||||
text-align: right !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.genius-search-clear {
|
.genius-search-clear {
|
||||||
right: auto !important;
|
right: auto !important;
|
||||||
left: 14px !important;
|
left: 14px !important; /*rtl:ignore*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Card2 (Approval Cards) RTL === */
|
||||||
|
.card2 {
|
||||||
|
.card-container .card-header {
|
||||||
|
flex-direction: row-reverse !important; /*rtl:ignore*/
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
margin-left: 12px !important; /*rtl:ignore*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-container .card-body table tr td {
|
||||||
|
&:last-child {
|
||||||
|
div {
|
||||||
|
float: left !important; /*rtl:ignore*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Card3 (Service Cards) RTL === */
|
||||||
|
.card3 .card-body .box-2 {
|
||||||
|
flex-direction: row-reverse !important; /*rtl:ignore*/
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
margin-right: 8px !important; /*rtl:ignore*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Attendance Section RTL === */
|
||||||
|
.dashboard-attendance-section {
|
||||||
|
border-left: none !important;
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.1) !important; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3037,12 +3095,11 @@ p.fn-section.clickable-profile:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RTL Support */
|
/* RTL Support - Notification */
|
||||||
[dir="rtl"] .attendance-notification,
|
.o_rtl .attendance-notification {
|
||||||
body.o_rtl .attendance-notification {
|
|
||||||
.notification-content {
|
.notification-content {
|
||||||
.notification-subtitle {
|
.notification-subtitle {
|
||||||
direction: rtl;
|
direction: rtl; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3141,10 +3198,9 @@ body.o_rtl .attendance-notification {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RTL Support for Error Toast */
|
/* RTL Support for Error Toast */
|
||||||
[dir="rtl"] .attendance-error-toast,
|
.o_rtl .attendance-error-toast {
|
||||||
body.o_rtl .attendance-error-toast {
|
|
||||||
.error-message {
|
.error-message {
|
||||||
direction: rtl;
|
direction: rtl; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3204,7 +3260,7 @@ body.o_rtl .attendance-error-toast {
|
||||||
|
|
||||||
/* Subtle drag handle hint on hover */
|
/* Subtle drag handle hint on hover */
|
||||||
.card3.draggable-card .card-body::after {
|
.card3.draggable-card .card-body::after {
|
||||||
content: '⋮⋮';
|
content: '\22EE\22EE'; /* ⋮⋮ - Unicode for vertical ellipsis */
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 6px;
|
top: 6px;
|
||||||
left: 6px;
|
left: 6px;
|
||||||
|
|
@ -3220,10 +3276,9 @@ body.o_rtl .attendance-error-toast {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RTL Support for drag handle */
|
/* RTL Support for drag handle */
|
||||||
[dir="rtl"] .card3.draggable-card .card-body::after,
|
.o_rtl .card3.draggable-card .card-body::after {
|
||||||
body.o_rtl .card3.draggable-card .card-body::after {
|
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 6px;
|
right: 6px; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disable drag on mobile - touch devices use different gestures */
|
/* Disable drag on mobile - touch devices use different gestures */
|
||||||
|
|
@ -3293,10 +3348,9 @@ body.o_rtl .card3.draggable-card .card-body::after {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RTL Support for stats module-box drag handle */
|
/* RTL Support for stats module-box drag handle */
|
||||||
[dir="rtl"] .module-box.draggable-card .module-box-container::after,
|
.o_rtl .module-box.draggable-card .module-box-container::after {
|
||||||
body.o_rtl .module-box.draggable-card .module-box-container::after {
|
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 4px;
|
right: 4px; /*rtl:ignore*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Service Icon Uniformity and Hover Effects */
|
/* Service Icon Uniformity and Hover Effects */
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@
|
||||||
|
|
||||||
.genius-search-input {
|
.genius-search-input {
|
||||||
padding: 10px 38px 10px 40px !important;
|
padding: 10px 38px 10px 40px !important;
|
||||||
text-align: right !important;
|
// text-align: right !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.genius-search-clear {
|
.genius-search-clear {
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,7 @@
|
||||||
<link rel="stylesheet" type="text/scss" href="/system_dashboard_classic/static/src/scss/core.scss"/>
|
<link rel="stylesheet" type="text/scss" href="/system_dashboard_classic/static/src/scss/core.scss"/>
|
||||||
<link rel="stylesheet" type="text/scss" href="/system_dashboard_classic/static/src/scss/cards.scss"/>
|
<link rel="stylesheet" type="text/scss" href="/system_dashboard_classic/static/src/scss/cards.scss"/>
|
||||||
<link rel="stylesheet" type="text/scss" href="/system_dashboard_classic/static/src/scss/genius-enhancements.scss"/>
|
<link rel="stylesheet" type="text/scss" href="/system_dashboard_classic/static/src/scss/genius-enhancements.scss"/>
|
||||||
<!--RTL-->
|
<!--RTL: Handled by Odoo's rtlcss + essential overrides in genius-enhancements.scss-->
|
||||||
<link rel="stylesheet" type="text/scss" href="/system_dashboard_classic/static/src/scss/rtl-cards.scss"/>
|
|
||||||
<link rel="stylesheet" type="text/scss" href="/system_dashboard_classic/static/src/scss/rtl-core.scss"/>
|
|
||||||
<!--JAVASCRIPT-->
|
<!--JAVASCRIPT-->
|
||||||
<!--<script type="text/javascript" src="/system_dashboard_classic/static/src/js/canvasjs.min.js"></script>-->
|
<!--<script type="text/javascript" src="/system_dashboard_classic/static/src/js/canvasjs.min.js"></script>-->
|
||||||
<script type="text/javascript" src="/system_dashboard_classic/static/src/js/d3.v5.min.js"/>
|
<script type="text/javascript" src="/system_dashboard_classic/static/src/js/d3.v5.min.js"/>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue