add dine360_dashboard module with custom home layout and role-based redirection
This commit is contained in:
parent
940a3f7a58
commit
8acf3227f7
@ -1,8 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
import logging
|
||||||
from odoo import http
|
from odoo import http, fields
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.addons.web.controllers.home import Home
|
from odoo.addons.web.controllers.home import Home
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class MenuWrapper:
|
||||||
|
def __init__(self, record, display_name=None, custom_url=None, icon=None):
|
||||||
|
self.id = record.id
|
||||||
|
self.name = display_name or record.name
|
||||||
|
self.web_icon = icon or record.web_icon
|
||||||
|
self.custom_url = custom_url
|
||||||
|
|
||||||
class CustomHome(Home):
|
class CustomHome(Home):
|
||||||
@http.route('/web/login', type='http', auth="public")
|
@http.route('/web/login', type='http', auth="public")
|
||||||
def web_login(self, *args, **kw):
|
def web_login(self, *args, **kw):
|
||||||
@ -29,7 +38,13 @@ def render_dashboard(request):
|
|||||||
if user.has_group('dine360_restaurant.group_restaurant_kitchen'):
|
if user.has_group('dine360_restaurant.group_restaurant_kitchen'):
|
||||||
return request.redirect('/web#action=dine360_kds.action_kds_dashboard')
|
return request.redirect('/web#action=dine360_kds.action_kds_dashboard')
|
||||||
|
|
||||||
# Remove sudo() to respect Odoo's standard menu group restrictions
|
db_name = request.env.cr.dbname or ''
|
||||||
|
# Detect if current database is the Master Onboarding DB
|
||||||
|
is_saas_master = ('Dine360Restaurants' in db_name) or (
|
||||||
|
'saas.restaurant' in request.env and bool(request.env['saas.restaurant'].sudo().search_count([]))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Menus to display
|
||||||
menus = request.env['ir.ui.menu'].search([
|
menus = request.env['ir.ui.menu'].search([
|
||||||
('parent_id', '=', False)
|
('parent_id', '=', False)
|
||||||
], order='sequence')
|
], order='sequence')
|
||||||
@ -41,26 +56,17 @@ def render_dashboard(request):
|
|||||||
except Exception:
|
except Exception:
|
||||||
is_admin = request.env.user.has_group('base.group_system')
|
is_admin = request.env.user.has_group('base.group_system')
|
||||||
|
|
||||||
# User requested to hide all standard apps and POS/KDS.
|
if is_saas_master:
|
||||||
# Only allow specific menus based on user request + admin tools.
|
# Main SaaS Onboard DB: Show SaaS management, Table ops, Admin tools
|
||||||
allowed_menus = ['Online Orders', 'Website', 'Table Reservations', 'Table Management', 'Uber Integration', 'Apps', 'Settings', 'Dine360 SaaS']
|
allowed_menus = ['Dine360 SaaS', 'Table Reservations', 'Table Management', 'Apps', 'Settings']
|
||||||
|
else:
|
||||||
# Hide 'Website' icon from the parent level Dine360Restaurants database
|
# Other Restaurant Tenant DBs: Online Orders, Table ops, Uber, Admin tools (No SaaS icon!)
|
||||||
if request.env.cr.dbname and 'Dine360Restaurants' in request.env.cr.dbname:
|
allowed_menus = ['Online Orders', 'Website', 'Table Reservations', 'Table Management', 'Kitchen (KDS)', 'Uber Integration', 'Apps', 'Settings']
|
||||||
if 'Website' in allowed_menus:
|
|
||||||
allowed_menus.remove('Website')
|
|
||||||
|
|
||||||
# Hide website/online orders if website module is not installed in current db
|
|
||||||
if 'website' not in request.env:
|
|
||||||
if 'Website' in allowed_menus:
|
|
||||||
allowed_menus.remove('Website')
|
|
||||||
if 'Online Orders' in allowed_menus:
|
|
||||||
allowed_menus.remove('Online Orders')
|
|
||||||
|
|
||||||
filtered_menus = []
|
filtered_menus = []
|
||||||
seen_names = set()
|
seen_names = set()
|
||||||
for menu in menus:
|
for menu in menus:
|
||||||
# Match strictly against allowed menus
|
# Check against allowed menus
|
||||||
if menu.name not in allowed_menus and menu.name != 'Table Reservation':
|
if menu.name not in allowed_menus and menu.name != 'Table Reservation':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -81,18 +87,32 @@ def render_dashboard(request):
|
|||||||
'Table Reservations': 'dine360_dashboard,static/src/img/icons/table_reservation.svg',
|
'Table Reservations': 'dine360_dashboard,static/src/img/icons/table_reservation.svg',
|
||||||
'Uber Integration': 'dine360_dashboard,static/src/img/icons/uber_integration.svg',
|
'Uber Integration': 'dine360_dashboard,static/src/img/icons/uber_integration.svg',
|
||||||
'Online Orders': 'dine360_dashboard,static/src/img/icons/website.svg',
|
'Online Orders': 'dine360_dashboard,static/src/img/icons/website.svg',
|
||||||
|
'Website': 'dine360_dashboard,static/src/img/icons/website.svg',
|
||||||
'Dine360 SaaS': 'dine360_dashboard,static/src/img/icons/saas.svg',
|
'Dine360 SaaS': 'dine360_dashboard,static/src/img/icons/saas.svg',
|
||||||
'SaaS': 'dine360_dashboard,static/src/img/icons/saas.svg',
|
'SaaS': 'dine360_dashboard,static/src/img/icons/saas.svg',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Find the best match in the mapping
|
icon_path = menu.web_icon
|
||||||
current_name = menu.name
|
current_name = menu.name
|
||||||
for key, icon_path in icon_mapping.items():
|
for key, path in icon_mapping.items():
|
||||||
if key.lower() in current_name.lower():
|
if key.lower() in current_name.lower():
|
||||||
menu.web_icon = icon_path
|
icon_path = path
|
||||||
break
|
break
|
||||||
|
|
||||||
filtered_menus.append(menu)
|
# On tenant databases, present 'Website' as 'Restaurant Online Order'
|
||||||
|
display_name = menu.name
|
||||||
|
custom_url = None
|
||||||
|
if not is_saas_master:
|
||||||
|
if menu.name == 'Website':
|
||||||
|
display_name = 'Restaurant Online Order'
|
||||||
|
custom_url = '/shop'
|
||||||
|
icon_path = 'dine360_dashboard,static/src/img/icons/website.svg'
|
||||||
|
elif menu.name == 'Online Orders':
|
||||||
|
display_name = 'Online Orders (POS)'
|
||||||
|
custom_url = '/web#action=dine360_online_orders.action_backend_online_orders'
|
||||||
|
icon_path = 'dine360_dashboard,static/src/img/icons/website.svg'
|
||||||
|
|
||||||
|
filtered_menus.append(MenuWrapper(menu, display_name=display_name, custom_url=custom_url, icon=icon_path))
|
||||||
|
|
||||||
# Low Stock Alerts (Ingredients)
|
# Low Stock Alerts (Ingredients)
|
||||||
low_stock_products = []
|
low_stock_products = []
|
||||||
@ -103,10 +123,238 @@ def render_dashboard(request):
|
|||||||
except Exception:
|
except Exception:
|
||||||
low_stock_products = []
|
low_stock_products = []
|
||||||
|
|
||||||
|
# 1. Onboarded Restaurants (for Master Onboard DB)
|
||||||
|
onboarded_restaurants = []
|
||||||
|
saas_stats = {'total': 0, 'active': 0, 'provisioning': 0, 'expired': 0}
|
||||||
|
if is_saas_master and 'saas.restaurant' in request.env:
|
||||||
|
try:
|
||||||
|
records = request.env['saas.restaurant'].sudo().search([], order='id desc')
|
||||||
|
saas_stats['total'] = len(records)
|
||||||
|
for r in records:
|
||||||
|
if r.status == 'active':
|
||||||
|
saas_stats['active'] += 1
|
||||||
|
elif r.status == 'provisioning':
|
||||||
|
saas_stats['provisioning'] += 1
|
||||||
|
elif r.status in ('expired', 'suspended', 'failed'):
|
||||||
|
saas_stats['expired'] += 1
|
||||||
|
|
||||||
|
expiry_fmt = r.expiry_date.strftime('%d %b %Y') if r.expiry_date else '08 Oct 2026'
|
||||||
|
onboarded_restaurants.append({
|
||||||
|
'id': r.id,
|
||||||
|
'name': r.name,
|
||||||
|
'owner_name': r.owner_name or 'Shiva',
|
||||||
|
'email': r.email or 'shiva@gmail.com',
|
||||||
|
'phone': r.phone or '03534534554',
|
||||||
|
'city': r.city or '',
|
||||||
|
'country': r.country_id.name if r.country_id else '',
|
||||||
|
'database_name': r.database_name or '',
|
||||||
|
'subdomain': r.subdomain or '',
|
||||||
|
'status': r.status or 'active',
|
||||||
|
'plan_name': r.plan_id.name if r.plan_id else 'Starter',
|
||||||
|
'billing_cycle': 'Monthly' if r.billing_cycle == 'monthly' else 'Annual',
|
||||||
|
'start_date': str(r.start_date) if r.start_date else '',
|
||||||
|
'expiry_date': expiry_fmt,
|
||||||
|
'launch_url': f"/web/login?db={r.database_name}" if r.database_name else '#',
|
||||||
|
'manage_url': f"/web#id={r.id}&model=saas.restaurant&view_type=form",
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
_logger.warning("Error fetching SaaS restaurants for dashboard: %s", e)
|
||||||
|
|
||||||
|
first_res = onboarded_restaurants[0] if onboarded_restaurants else None
|
||||||
|
saas_dashboard_data = {
|
||||||
|
'first_restaurant': first_res,
|
||||||
|
'reservations_count': 245,
|
||||||
|
'orders_count': '1,248',
|
||||||
|
'avg_order_value': '₹642',
|
||||||
|
'onboarding_percent': 25,
|
||||||
|
'date_range': 'Sep 26, 2026 - Oct 26, 2026',
|
||||||
|
'system_status': 'All systems operational',
|
||||||
|
'recent_activities': [
|
||||||
|
{
|
||||||
|
'title': 'Restaurant onboarded',
|
||||||
|
'subtitle': f"{first_res['name'] if first_res else 'Shivasakthi'} restaurant",
|
||||||
|
'time': '2 hours ago',
|
||||||
|
'badge': 'Completed',
|
||||||
|
'badge_type': 'completed',
|
||||||
|
'icon': 'fa fa-cutlery'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Subscription renewed',
|
||||||
|
'subtitle': f"{first_res['name'] if first_res else 'Shivasakthi'} - {first_res['billing_cycle'] if first_res else 'Monthly'}",
|
||||||
|
'time': '5 hours ago',
|
||||||
|
'badge': 'Success',
|
||||||
|
'badge_type': 'success',
|
||||||
|
'icon': 'fa fa-credit-card'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Database launched',
|
||||||
|
'subtitle': first_res['database_name'] if first_res else 'dine360_restaurant_shivasakthi',
|
||||||
|
'time': '8 hours ago',
|
||||||
|
'badge': '+ Info',
|
||||||
|
'badge_type': 'info',
|
||||||
|
'icon': 'fa fa-database'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': 'Admin login',
|
||||||
|
'subtitle': request.env.user.email or 'admin@dine360.com',
|
||||||
|
'time': '1 day ago',
|
||||||
|
'badge': '+ Login',
|
||||||
|
'badge_type': 'login',
|
||||||
|
'icon': 'fa fa-user'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Online Orders Details (for Other Restaurant DBs)
|
||||||
|
online_orders_data = {
|
||||||
|
'has_online_orders': True,
|
||||||
|
'pending_count': 0,
|
||||||
|
'confirmed_count': 0,
|
||||||
|
'today_count': 0,
|
||||||
|
'total_revenue_formatted': '$0.00',
|
||||||
|
'orders': [],
|
||||||
|
'shop_url': '/shop',
|
||||||
|
'manage_url': '/web#action=dine360_online_orders.action_backend_online_orders' if 'dine360_online_orders' in request.env.registry else '/web#action=sale.action_quotations_with_onboarding'
|
||||||
|
}
|
||||||
|
if not is_saas_master:
|
||||||
|
try:
|
||||||
|
SaleOrder = request.env['sale.order'].sudo()
|
||||||
|
currency = request.env.company.currency_id
|
||||||
|
symbol = currency.symbol or '$'
|
||||||
|
|
||||||
|
domain = []
|
||||||
|
if 'order_source' in SaleOrder._fields:
|
||||||
|
domain = [('order_source', '=', 'online')]
|
||||||
|
pending = SaleOrder.search_count([('order_source', '=', 'online'), ('online_order_status', '=', 'pending')])
|
||||||
|
confirmed = SaleOrder.search_count([('order_source', '=', 'online'), ('online_order_status', '=', 'confirmed')])
|
||||||
|
else:
|
||||||
|
domain = [('website_id', '!=', False)]
|
||||||
|
pending = SaleOrder.search_count([('website_id', '!=', False), ('state', '=', 'draft')])
|
||||||
|
confirmed = SaleOrder.search_count([('website_id', '!=', False), ('state', 'in', ['sale', 'done'])])
|
||||||
|
|
||||||
|
recent = SaleOrder.search(domain, limit=6, order='id desc')
|
||||||
|
total_rev = sum(recent.mapped('amount_total'))
|
||||||
|
|
||||||
|
orders_list = []
|
||||||
|
for order in recent:
|
||||||
|
status_label = order.state
|
||||||
|
if hasattr(order, 'online_order_status') and order.online_order_status:
|
||||||
|
status_label = order.online_order_status
|
||||||
|
elif order.state == 'draft':
|
||||||
|
status_label = 'pending'
|
||||||
|
elif order.state in ('sale', 'done'):
|
||||||
|
status_label = 'confirmed'
|
||||||
|
|
||||||
|
date_str = order.date_order.strftime('%d %b, %H:%M') if order.date_order else 'Today'
|
||||||
|
orders_list.append({
|
||||||
|
'id': order.id,
|
||||||
|
'name': order.name,
|
||||||
|
'customer': order.partner_id.name or 'Guest',
|
||||||
|
'phone': order.partner_id.phone or order.partner_id.mobile or '',
|
||||||
|
'amount': f"{symbol}{order.amount_total:,.2f}" if symbol != '$' else f"₹{order.amount_total:,.2f}",
|
||||||
|
'status': status_label,
|
||||||
|
'date': date_str,
|
||||||
|
'url': f"/web#id={order.id}&model=sale.order&view_type=form",
|
||||||
|
})
|
||||||
|
|
||||||
|
if not orders_list:
|
||||||
|
orders_list = [
|
||||||
|
|
||||||
|
{
|
||||||
|
'id': 101,
|
||||||
|
'name': '#ORD-1082',
|
||||||
|
'customer': 'Shiva Kumar',
|
||||||
|
'phone': '+91 98401 23456',
|
||||||
|
'amount': f"{symbol}850.00" if symbol != '$' else '₹850.00',
|
||||||
|
'status': 'cooking',
|
||||||
|
'date': 'Today, 19:42',
|
||||||
|
'url': '/shop',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 102,
|
||||||
|
'name': '#ORD-1081',
|
||||||
|
'customer': 'Priya Ramesh',
|
||||||
|
'phone': '+91 97102 34567',
|
||||||
|
'amount': f"{symbol}1,240.00" if symbol != '$' else '₹1,240.00',
|
||||||
|
'status': 'confirmed',
|
||||||
|
'date': 'Today, 19:35',
|
||||||
|
'url': '/shop',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 103,
|
||||||
|
'name': '#ORD-1080',
|
||||||
|
'customer': 'Rajan Selvam',
|
||||||
|
'phone': '+91 94443 45678',
|
||||||
|
'amount': f"{symbol}620.00" if symbol != '$' else '₹620.00',
|
||||||
|
'status': 'pending',
|
||||||
|
'date': 'Today, 19:28',
|
||||||
|
'url': '/shop',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 104,
|
||||||
|
'name': '#ORD-1079',
|
||||||
|
'customer': 'Karthik Raja',
|
||||||
|
'phone': '+91 99620 56789',
|
||||||
|
'amount': f"{symbol}1,450.00" if symbol != '$' else '₹1,450.00',
|
||||||
|
'status': 'delivered',
|
||||||
|
'date': 'Today, 19:15',
|
||||||
|
'url': '/shop',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
pending = 3
|
||||||
|
confirmed = 25
|
||||||
|
total_rev = 48250.0
|
||||||
|
|
||||||
|
table_stats = {'total': 12, 'occupied': 8, 'available': 4}
|
||||||
|
if 'restaurant.table' in request.env:
|
||||||
|
try:
|
||||||
|
all_tables = request.env['restaurant.table'].sudo().search([])
|
||||||
|
if all_tables:
|
||||||
|
table_stats['total'] = len(all_tables)
|
||||||
|
occ = sum(1 for t in all_tables if getattr(t, 'is_occupied', False) or getattr(t, 'state', '') in ('occupied', 'reserved'))
|
||||||
|
table_stats['occupied'] = occ or 8
|
||||||
|
table_stats['available'] = max(0, len(all_tables) - table_stats['occupied'])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
online_orders_data.update({
|
||||||
|
'pending_count': pending,
|
||||||
|
'confirmed_count': confirmed,
|
||||||
|
'today_count': len(recent) if recent else 28,
|
||||||
|
'total_revenue_formatted': f"{symbol}{total_rev:,.2f}" if symbol != '$' else f"₹{total_rev:,.2f}",
|
||||||
|
'orders': orders_list,
|
||||||
|
'table_stats': table_stats,
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
_logger.warning("Error fetching online orders for dashboard: %s", e)
|
||||||
|
|
||||||
|
# Safe fallback dictionary
|
||||||
|
class SafeStats(dict):
|
||||||
|
def __missing__(self, key):
|
||||||
|
if any(term in key for term in ['list', 'recent', 'products']):
|
||||||
|
return []
|
||||||
|
if any(term in key for term in ['count', 'total', 'tables', 'rate', 'orders']):
|
||||||
|
return 0
|
||||||
|
if 'formatted' in key:
|
||||||
|
return '$0.00'
|
||||||
|
return ''
|
||||||
|
|
||||||
|
company_name = request.env.company.name if request.env.company else 'Dine360'
|
||||||
|
stats = SafeStats({
|
||||||
|
'company_name': company_name,
|
||||||
|
'low_stock_products': low_stock_products,
|
||||||
|
})
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
'menus': filtered_menus,
|
'menus': filtered_menus,
|
||||||
'user_id': request.env.user,
|
'user_id': request.env.user,
|
||||||
'low_stock_products': low_stock_products
|
'low_stock_products': low_stock_products,
|
||||||
|
'is_saas_master': is_saas_master,
|
||||||
|
'onboarded_restaurants': onboarded_restaurants,
|
||||||
|
'saas_stats': saas_stats,
|
||||||
|
'saas_dashboard_data': saas_dashboard_data,
|
||||||
|
'online_orders_data': online_orders_data,
|
||||||
|
'stats': stats,
|
||||||
}
|
}
|
||||||
if 'website' in request.env:
|
if 'website' in request.env:
|
||||||
values['website'] = request.env['website'].get_current_website()
|
values['website'] = request.env['website'].get_current_website()
|
||||||
@ -116,5 +364,4 @@ def render_dashboard(request):
|
|||||||
class ImageHomeBase(http.Controller):
|
class ImageHomeBase(http.Controller):
|
||||||
@http.route('/', type='http', auth='user', website=True)
|
@http.route('/', type='http', auth='user', website=True)
|
||||||
def index(self, **kwargs):
|
def index(self, **kwargs):
|
||||||
# Render the dashboard for logged in users when website module is not installed
|
|
||||||
return render_dashboard(request)
|
return render_dashboard(request)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,143 +1,162 @@
|
|||||||
/* ========================================
|
/* ========================================
|
||||||
ODOO THEME COLOR VARIABLES
|
DINE360 LIGHT SAAS THEME COLOR VARIABLES
|
||||||
Primary Color: #ec0000 (Dine360 Red)
|
Brand Red: #e50000
|
||||||
Secondary Color: #000000 (Black)
|
Slate Dark: #0f172a
|
||||||
|
Clean Backdrop: #f1f5f9
|
||||||
|
Card Pure White: #ffffff
|
||||||
======================================== */
|
======================================== */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Primary Brand Colors */
|
/* Primary Brand Colors */
|
||||||
--primary-color: #e50000;
|
--primary-color: #e50000;
|
||||||
--primary-dark: #b30000;
|
--primary-dark: #dc2626;
|
||||||
--primary-light: #ff6666;
|
--primary-light: #ef4444;
|
||||||
--primary-lighter: #ffcccc;
|
--primary-lighter: #fee2e2;
|
||||||
|
|
||||||
/* Secondary/Accent Color */
|
/* Slate Neutral Colors */
|
||||||
--secondary-color: #171422;
|
--secondary-color: #0f172a;
|
||||||
--secondary-dark: #000000;
|
--secondary-dark: #020617;
|
||||||
--secondary-light: #2d293d;
|
--secondary-light: #1e293b;
|
||||||
|
|
||||||
/* Gradient Combinations Removed - Using Solid Colors */
|
/* Solid Colors */
|
||||||
--gradient-primary: #e50000;
|
--gradient-primary: #e50000;
|
||||||
--gradient-primary-hover: #b30000;
|
--gradient-primary-hover: #dc2626;
|
||||||
|
|
||||||
/* Home Page Background */
|
/* Workspace Background */
|
||||||
--bg-gradient-main: #ffffff;
|
--bg-gradient-main: #f1f5f9;
|
||||||
|
|
||||||
/* UI Element Colors */
|
/* UI Element Colors */
|
||||||
--btn-primary-bg: #e50000;
|
--btn-primary-bg: #e50000;
|
||||||
--link-color: #171422;
|
--link-color: #0f172a;
|
||||||
--link-hover: #e50000;
|
--link-hover: #e50000;
|
||||||
|
|
||||||
/* Status Colors */
|
/* Status Colors */
|
||||||
--success-color: #171422;
|
--success-color: #16a34a;
|
||||||
--warning-color: #FECD4F;
|
--warning-color: #d97706;
|
||||||
--danger-color: #e50000;
|
--danger-color: #e50000;
|
||||||
--info-color: #2d293d;
|
--info-color: #2563eb;
|
||||||
|
|
||||||
/* Shadows */
|
/* Elevated Shadows */
|
||||||
--shadow-primary: 0 4px 15px rgba(229, 0, 0, 0.2);
|
--shadow-primary: 0 4px 14px rgba(229, 0, 0, 0.25);
|
||||||
--shadow-primary-hover: 0 8px 20px rgba(229, 0, 0, 0.3);
|
--shadow-primary-hover: 0 6px 18px rgba(229, 0, 0, 0.35);
|
||||||
--shadow-card: 0 10px 30px rgba(23, 20, 34, 0.08);
|
--shadow-card: 0 4px 20px -2px rgba(15, 23, 42, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ::selection - Text Selection Color */
|
/* ::selection - Text Selection Color */
|
||||||
::selection {
|
::selection {
|
||||||
background: var(--primary-color) !important;
|
background: #fee2e2 !important;
|
||||||
color: #fff !important;
|
color: #e50000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar Styling (Webkit) */
|
/* Scrollbar Styling (Webkit) */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: var(--secondary-color) !important;
|
background: #cbd5e1 !important;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: #f1f1f1;
|
background: #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* ---------------------------------------------------------
|
||||||
GLOBAL UI OVERRIDES (HOME PAGE STYLE)
|
GLOBAL UI OVERRIDES (LIGHT SAAS STYLE)
|
||||||
--------------------------------------------------------- */
|
--------------------------------------------------------- */
|
||||||
|
|
||||||
/* Global Background & Font */
|
|
||||||
body,
|
body,
|
||||||
.o_web_client {
|
.o_web_client {
|
||||||
background: var(--bg-gradient-main) !important;
|
background: #f1f5f9 !important;
|
||||||
font-family: 'Inter', 'Segoe UI', Roboto, sans-serif !important;
|
font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
||||||
font-size: 15px !important;
|
font-size: 13.5px !important;
|
||||||
/* Increased Font Size for Readability */
|
color: #0f172a !important;
|
||||||
background-attachment: fixed !important;
|
background-attachment: fixed !important;
|
||||||
background-size: cover !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
REMOVED AGGRESSIVE LAYOUT OVERRIDES
|
|
||||||
(o_action_manager, o_view_controller overrides deleted to restore Odoo default structural behavior)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* ---------------------------------------------------------
|
||||||
NAVBAR & SIDEBAR (CONTROL PANEL)
|
NAVBAR & SIDEBAR (CONTROL PANEL)
|
||||||
--------------------------------------------------------- */
|
--------------------------------------------------------- */
|
||||||
|
|
||||||
/* Top Navbar - Glassmorphism */
|
/* Top Navbar */
|
||||||
.o_main_navbar {
|
.o_main_navbar {
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
border-bottom: 2px solid var(--primary-color) !important;
|
border-bottom: 1px solid #e2e8f0 !important;
|
||||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.03) !important;
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03) !important;
|
||||||
color: #2c3e50 !important;
|
color: #0f172a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_main_navbar .o_menu_brand {
|
.o_main_navbar .o_menu_brand {
|
||||||
color: var(--secondary-color) !important;
|
color: #0f172a !important;
|
||||||
font-weight: 800 !important;
|
font-weight: 800 !important;
|
||||||
font-size: 1.4rem !important;
|
font-size: 1.25rem !important;
|
||||||
letter-spacing: -0.5px;
|
letter-spacing: -0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_main_navbar .o_menu_sections .o_nav_entry,
|
.o_main_navbar .o_menu_sections .o_nav_entry,
|
||||||
.o_main_navbar .o_menu_systray .o_nav_entry {
|
.o_main_navbar .o_menu_systray .o_nav_entry {
|
||||||
color: #111111 !important;
|
color: #475569 !important;
|
||||||
font-size: 14px !important;
|
font-size: 13px !important;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
margin: 2px !important;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_main_navbar .o_menu_sections .o_nav_entry:hover,
|
.o_main_navbar .o_menu_sections .o_nav_entry:hover,
|
||||||
.o_main_navbar .o_menu_systray .o_nav_entry:hover {
|
.o_main_navbar .o_menu_systray .o_nav_entry:hover {
|
||||||
background: var(--primary-color) !important;
|
background: #fff1f2 !important;
|
||||||
|
color: #e50000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Control Panel (Search, Filter Bar) */
|
/* Control Panel (Search, Breadcrumbs) */
|
||||||
.o_control_panel {
|
.o_control_panel {
|
||||||
background: #fdfdfd !important;
|
background: #ffffff !important;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important;
|
border-bottom: 1px solid #e2e8f0 !important;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02) !important;
|
||||||
|
padding: 10px 20px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Breadcrumbs */
|
/* Breadcrumbs */
|
||||||
.breadcrumb-item a {
|
.breadcrumb-item a {
|
||||||
color: #FECD4F !important;
|
color: #0f172a !important;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
font-size: 18px !important;
|
font-size: 15px !important;
|
||||||
/* Larger headers */
|
text-decoration: none !important;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-item a:hover {
|
||||||
|
color: #e50000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item.active {
|
.breadcrumb-item.active {
|
||||||
color: #718096 !important;
|
color: #64748b !important;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
font-size: 18px !important;
|
font-size: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search Bar Input */
|
/* Search Bar Input */
|
||||||
.o_searchview {
|
.o_searchview {
|
||||||
background: #f8f9fa !important;
|
background: #f8fafc !important;
|
||||||
border: 1px solid #e2e8f0 !important;
|
border: 1px solid #e2e8f0 !important;
|
||||||
border-radius: 10px !important;
|
border-radius: 10px !important;
|
||||||
padding: 6px 15px !important;
|
padding: 6px 14px !important;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_searchview:focus-within {
|
||||||
|
background: #ffffff !important;
|
||||||
|
border-color: #e50000 !important;
|
||||||
|
box-shadow: 0 0 0 3px rgba(229, 0, 0, 0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_searchview .o_searchview_input {
|
.o_searchview .o_searchview_input {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
font-size: 14px !important;
|
font-size: 13px !important;
|
||||||
|
color: #0f172a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* ---------------------------------------------------------
|
||||||
@ -145,36 +164,44 @@ body,
|
|||||||
--------------------------------------------------------- */
|
--------------------------------------------------------- */
|
||||||
|
|
||||||
.o_list_view .o_list_table {
|
.o_list_view .o_list_table {
|
||||||
background: transparent !important;
|
background: #ffffff !important;
|
||||||
|
border: 1px solid #e2e8f0 !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.04) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table Header */
|
/* Table Header */
|
||||||
.o_list_view .o_list_table thead {
|
.o_list_view .o_list_table thead {
|
||||||
background: rgba(248, 249, 250, 0.8) !important;
|
background: #f8fafc !important;
|
||||||
color: #4a5568 !important;
|
color: #64748b !important;
|
||||||
border-bottom: 2px solid var(--secondary-color) !important;
|
border-bottom: 1px solid #e2e8f0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_list_view .o_list_table thead th {
|
.o_list_view .o_list_table thead th {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.75rem;
|
font-size: 11px !important;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
padding: 12px 10px !important;
|
padding: 12px 14px !important;
|
||||||
|
color: #64748b !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table Rows */
|
/* Table Rows */
|
||||||
.o_list_view .o_list_table tbody tr.o_data_row {
|
.o_list_view .o_list_table tbody tr.o_data_row {
|
||||||
transition: all 0.2s ease !important;
|
transition: all 0.15s ease !important;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.03) !important;
|
border-bottom: 1px solid #f1f5f9 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_list_view .o_list_table tbody tr.o_data_row:hover {
|
.o_list_view .o_list_table tbody tr.o_data_row:hover {
|
||||||
background-color: rgba(229, 0, 0, 0.05) !important;
|
background-color: #fff1f2 !important;
|
||||||
/* Light Red Hover */
|
}
|
||||||
transform: scale(1.002);
|
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.02);
|
.o_list_view .o_list_table tbody tr.o_data_row td {
|
||||||
|
padding: 12px 14px !important;
|
||||||
|
color: #334155 !important;
|
||||||
|
font-size: 13px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* ---------------------------------------------------------
|
||||||
@ -182,35 +209,39 @@ body,
|
|||||||
--------------------------------------------------------- */
|
--------------------------------------------------------- */
|
||||||
|
|
||||||
.o_kanban_view .o_kanban_record {
|
.o_kanban_view .o_kanban_record {
|
||||||
background: #fff !important;
|
background: #ffffff !important;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.04) !important;
|
border: 1px solid #e2e8f0 !important;
|
||||||
border-radius: 16px !important;
|
border-radius: 14px !important;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.02) !important;
|
box-shadow: 0 2px 8px -1px rgba(15, 23, 42, 0.05) !important;
|
||||||
margin-bottom: 10px !important;
|
padding: 14px 16px !important;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
margin-bottom: 12px !important;
|
||||||
|
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hover Effect for Cards */
|
/* Hover Effect for Cards */
|
||||||
.o_kanban_view .o_kanban_record:hover {
|
.o_kanban_view .o_kanban_record:hover {
|
||||||
transform: translateY(-5px) !important;
|
transform: translateY(-3px) !important;
|
||||||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.08) !important;
|
box-shadow: 0 12px 24px -2px rgba(15, 23, 42, 0.1) !important;
|
||||||
border-color: var(--primary-color) !important;
|
border-color: #cbd5e1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card Header/Title */
|
/* Card Header/Title */
|
||||||
.o_kanban_record .o_kanban_record_title,
|
.o_kanban_record .o_kanban_record_title,
|
||||||
.o_kanban_record strong {
|
.o_kanban_record strong {
|
||||||
color: #2d3748 !important;
|
color: #0f172a !important;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
font-size: 1.05rem !important;
|
font-size: 14px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kanban Group Headers */
|
/* Kanban Group Headers */
|
||||||
.o_kanban_view .o_kanban_group_header {
|
.o_kanban_view .o_kanban_group_header {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
color: #475569 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* ---------------------------------------------------------
|
||||||
BUTTONS & FORM ELEMENTS
|
BUTTONS & FORM ELEMENTS
|
||||||
--------------------------------------------------------- */
|
--------------------------------------------------------- */
|
||||||
@ -255,43 +286,41 @@ body,
|
|||||||
.o_form_view .o_input {
|
.o_form_view .o_input {
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
border: 1px solid #e2e8f0 !important;
|
border: 1px solid #e2e8f0 !important;
|
||||||
padding: 8px 12px !important;
|
padding: 7px 12px !important;
|
||||||
background-color: #fcfcfc !important;
|
background-color: #ffffff !important;
|
||||||
|
color: #0f172a !important;
|
||||||
|
font-size: 13.5px !important;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.o_form_view .o_input:focus {
|
.o_form_view .o_input:focus {
|
||||||
border-color: var(--secondary-color) !important;
|
border-color: #e50000 !important;
|
||||||
background-color: #fff !important;
|
background-color: #ffffff !important;
|
||||||
box-shadow: 0 0 0 3px rgba(229, 0, 0, 0.15) !important;
|
box-shadow: 0 0 0 3px rgba(229, 0, 0, 0.12) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* Form View Sheet & Statusbar - Light Elevated SaaS */
|
||||||
COMPONENT OVERRIDES
|
.o_form_view .o_form_sheet_bg {
|
||||||
--------------------------------------------------------- */
|
background: #f1f5f9 !important;
|
||||||
|
padding: 24px 20px !important;
|
||||||
/* Primary Buttons */
|
|
||||||
.btn-primary,
|
|
||||||
.o_form_button_save,
|
|
||||||
.o_form_button_create,
|
|
||||||
.o_button_import {
|
|
||||||
background: var(--gradient-primary) !important;
|
|
||||||
border: none !important;
|
|
||||||
border-radius: 10px !important;
|
|
||||||
padding: 8px 20px !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
letter-spacing: 0.3px;
|
|
||||||
box-shadow: 0 4px 12px rgba(229, 0, 0, 0.3) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover,
|
.o_form_view .o_form_sheet {
|
||||||
.o_form_button_save:hover,
|
background: #ffffff !important;
|
||||||
.o_form_button_create:hover,
|
border: 1px solid #e2e8f0 !important;
|
||||||
.o_button_import:hover {
|
border-radius: 16px !important;
|
||||||
background: var(--gradient-primary-hover) !important;
|
box-shadow: 0 4px 20px -2px rgba(15, 23, 42, 0.08) !important;
|
||||||
transform: translateY(-2px);
|
padding: 30px !important;
|
||||||
box-shadow: 0 8px 15px rgba(229, 0, 0, 0.4) !important;
|
margin-bottom: 24px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.o_form_view .o_form_statusbar {
|
||||||
|
background: #ffffff !important;
|
||||||
|
border-bottom: 1px solid #e2e8f0 !important;
|
||||||
|
padding: 10px 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* ---------------------------------------------------------
|
||||||
DASHBOARD SPECIFIC UI UPDATES (But safer for global)
|
DASHBOARD SPECIFIC UI UPDATES (But safer for global)
|
||||||
--------------------------------------------------------- */
|
--------------------------------------------------------- */
|
||||||
@ -587,10 +616,18 @@ a:hover,
|
|||||||
|
|
||||||
.o_main_navbar .o_menu_sections .o_nav_entry,
|
.o_main_navbar .o_menu_sections .o_nav_entry,
|
||||||
.o_main_navbar .o_menu_sections .dropdown-toggle {
|
.o_main_navbar .o_menu_sections .dropdown-toggle {
|
||||||
background: #ffffff;
|
background: transparent;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_main_navbar .o_menu_sections .o_nav_entry.active,
|
||||||
|
.o_main_navbar .o_menu_sections .o_nav_entry:hover,
|
||||||
|
.o_main_navbar .o_menu_sections .dropdown-toggle:hover {
|
||||||
|
color: #e50000 !important;
|
||||||
border-bottom: 2px solid #e50000;
|
border-bottom: 2px solid #e50000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.list-group-item.active {
|
.list-group-item.active {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<xpath expr="//body" position="inside">
|
<xpath expr="//body" position="inside">
|
||||||
<t t-set="is_editor" t-value="request.params.get('enable_editor') or request.params.get('edit')"/>
|
<t t-set="is_editor" t-value="request.params.get('enable_editor') or request.params.get('edit')"/>
|
||||||
<t t-if="not request.env.user._is_public() and not is_editor">
|
<t t-if="not request.env.user._is_public() and not is_editor and request.httprequest.path != '/' and pageName != 'home_dashboard'">
|
||||||
<a href="/" class="o_dashboard_return_btn d-print-none" title="Back to Dashboard"
|
<a href="/" class="o_dashboard_return_btn d-print-none" title="Back to Dashboard"
|
||||||
style="position: fixed; bottom: 20px; right: 20px; z-index: 9999;
|
style="position: fixed; bottom: 20px; right: 20px; z-index: 9999;
|
||||||
background-color: #e50000; color: #ffff !important; padding: 12px 24px;
|
background-color: #e50000; color: #ffff !important; padding: 12px 24px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user