From c82f392e152479ee734f89a17685b1b2771c372b Mon Sep 17 00:00:00 2001
From: Alaguraj0361
Date: Tue, 23 Jun 2026 20:38:41 +0530
Subject: [PATCH] Add Dine360 Dashboard Website module with custom controllers
and views
- Created __init__.py and __manifest__.py for the new dine360_dashboard_website module.
- Implemented custom controllers to handle website routing and dashboard rendering.
- Added views for website logo and shop template to enhance the user interface.
- Updated dine360_reservation and dine360_restaurant modules to include dine360_table as a dependency.
- Enhanced restaurant table management with new views and actions for floors and tables.
- Updated docker-compose to change the exposed port for the Odoo service.
---
addons/Dine360_Shivasakthi/__manifest__.py | 1 +
addons/dine360_dashboard/__manifest__.py | 6 +-
addons/dine360_dashboard/controllers/main.py | 227 ++++-----
addons/dine360_dashboard_website/__init__.py | 2 +
.../dine360_dashboard_website/__manifest__.py | 21 +
.../controllers/__init__.py | 2 +
.../controllers/main.py | 45 ++
.../views/shop_template.xml | 10 +
.../views/website_logo.xml | 14 +
addons/dine360_reservation/__manifest__.py | 2 +-
.../views/restaurant_table_views.xml | 18 +
addons/dine360_restaurant/__manifest__.py | 2 +-
addons/dine360_self_order/controllers/main.py | 14 +-
.../static/src/js/self_order.js | 461 +++++++++---------
.../views/self_order_templates.xml | 12 +-
addons/dine360_table/__init__.py | 1 +
addons/dine360_table/__manifest__.py | 22 +
.../dine360_table/static/description/icon.png | Bin 0 -> 403707 bytes
addons/dine360_table/views/menu_items.xml | 21 +
.../views/restaurant_floor_views.xml | 66 +++
.../views/restaurant_table_views.xml | 63 +++
docker-compose.yml | 2 +-
22 files changed, 633 insertions(+), 379 deletions(-)
create mode 100644 addons/dine360_dashboard_website/__init__.py
create mode 100644 addons/dine360_dashboard_website/__manifest__.py
create mode 100644 addons/dine360_dashboard_website/controllers/__init__.py
create mode 100644 addons/dine360_dashboard_website/controllers/main.py
create mode 100644 addons/dine360_dashboard_website/views/shop_template.xml
create mode 100644 addons/dine360_dashboard_website/views/website_logo.xml
create mode 100644 addons/dine360_table/__init__.py
create mode 100644 addons/dine360_table/__manifest__.py
create mode 100644 addons/dine360_table/static/description/icon.png
create mode 100644 addons/dine360_table/views/menu_items.xml
create mode 100644 addons/dine360_table/views/restaurant_floor_views.xml
create mode 100644 addons/dine360_table/views/restaurant_table_views.xml
diff --git a/addons/Dine360_Shivasakthi/__manifest__.py b/addons/Dine360_Shivasakthi/__manifest__.py
index caed817..a2e0df7 100644
--- a/addons/Dine360_Shivasakthi/__manifest__.py
+++ b/addons/Dine360_Shivasakthi/__manifest__.py
@@ -12,6 +12,7 @@
'dine360_theme_shivasakthi',
'dine360_kds',
'dine360_reservation',
+ 'dine360_table',
'dine360_uber',
'dine360_recipe',
'dine360_self_order',
diff --git a/addons/dine360_dashboard/__manifest__.py b/addons/dine360_dashboard/__manifest__.py
index 010530f..a46f01a 100644
--- a/addons/dine360_dashboard/__manifest__.py
+++ b/addons/dine360_dashboard/__manifest__.py
@@ -4,13 +4,11 @@
'license': 'LGPL-3',
'category': 'Website',
'summary': 'Redirect login to home and show icon grid',
- 'depends': ['base', 'web', 'auth_signup', 'website', 'website_sale'],
+ 'depends': ['base', 'web', 'auth_signup'],
'data': [
'views/home_template.xml',
'views/login_templates.xml',
'views/web_title_template.xml',
- 'views/website_logo.xml',
- 'views/shop_template.xml',
'data/branding_data.xml',
],
'assets': {
@@ -23,8 +21,6 @@
'web.assets_frontend': [
'dine360_dashboard/static/src/css/theme_variables.css',
'dine360_dashboard/static/src/css/login_style.css',
- 'dine360_dashboard/static/src/css/website_style.css',
- 'dine360_dashboard/static/src/css/shop_style.css',
],
'web.assets_common': [
'dine360_dashboard/static/src/css/theme_variables.css',
diff --git a/addons/dine360_dashboard/controllers/main.py b/addons/dine360_dashboard/controllers/main.py
index 3511074..81e3334 100644
--- a/addons/dine360_dashboard/controllers/main.py
+++ b/addons/dine360_dashboard/controllers/main.py
@@ -1,9 +1,10 @@
+# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request
from odoo.addons.web.controllers.home import Home
class CustomHome(Home):
- @http.route('/web/login', type='http', auth="public", website=True)
+ @http.route('/web/login', type='http', auth="public")
def web_login(self, *args, **kw):
response = super(CustomHome, self).web_login(*args, **kw)
if request.params.get('login_success') and request.session.uid:
@@ -11,137 +12,109 @@ class CustomHome(Home):
return request.redirect('/')
return response
-from odoo.addons.website.controllers.main import Website
+def render_dashboard(request):
+ # 1. ROLE-BASED AUTO REDIRECTION (FOR STAFF)
+ # Skip the dashboard/website entirely for Chefs and Waiters
+ user = request.env.user.sudo()
+ is_admin = user.has_group('base.group_system') or \
+ user.has_group('dine360_restaurant.group_restaurant_admin')
+
+ if not is_admin:
+ # 1. WAITER / CASHIER -> Priority goes to POS
+ if user.has_group('dine360_restaurant.group_restaurant_waiter') or \
+ user.has_group('dine360_restaurant.group_restaurant_cashier'):
+ return request.redirect('/web#action=point_of_sale.action_client_pos_menu')
-class ImageHome(Website):
- @http.route('/', type='http', auth='public', website=True, sitemap=True)
- def index(self, **kwargs):
- # -----------------------------------------------------------
- # SUPER SAFE EDITOR & IFRAME DETECTION
- # -----------------------------------------------------------
- path = request.httprequest.path
- params = request.params
- headers = request.httprequest.headers
- referer = headers.get('Referer', '')
- fetch_dest = headers.get('Sec-Fetch-Dest', '')
+ # 2. CHEF -> Directly to KDS
+ if user.has_group('dine360_restaurant.group_restaurant_kitchen'):
+ return request.redirect('/web#action=dine360_kds.action_kds_dashboard')
+
+ # Remove sudo() to respect Odoo's standard menu group restrictions
+ menus = request.env['ir.ui.menu'].search([
+ ('parent_id', '=', False)
+ ], order='sequence')
+
+ # User role checks
+ try:
+ is_admin = request.env.user.has_group('base.group_system') or \
+ request.env.user.has_group('dine360_restaurant.group_restaurant_admin')
+ except Exception:
+ is_admin = request.env.user.has_group('base.group_system')
+
+ # User requested to hide all standard apps and POS/KDS.
+ # Only allow specific menus based on user request + admin tools.
+ allowed_menus = ['Online Orders', 'Website', 'Table Reservations', 'Table Management', 'Uber Integration', 'Apps', 'Settings', 'Dine360 SaaS']
+
+ # Hide 'Website' icon from the parent level Dine360Restaurants database
+ if request.env.cr.dbname and 'Dine360Restaurants' in request.env.cr.dbname:
+ 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 = []
+ seen_names = set()
+ for menu in menus:
+ # Match strictly against allowed menus
+ if menu.name not in allowed_menus and menu.name != 'Table Reservation':
+ continue
+
+ # Hide "Apps" and "Settings" for non-admins
+ if menu.name in ['Apps', 'Settings'] and not is_admin:
+ continue
+
+ # De-duplicate by name
+ if menu.name in seen_names:
+ continue
+ seen_names.add(menu.name)
+
+ # Dynamic Icon Override (Dine360 Branding)
+ icon_mapping = {
+ 'Apps': 'dine360_dashboard,static/src/img/icons/apps.svg',
+ 'Settings': 'dine360_dashboard,static/src/img/icons/settings.svg',
+ 'Table Reservation': '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',
+ 'Online Orders': 'dine360_dashboard,static/src/img/icons/website.svg',
+ 'Dine360 SaaS': 'dine360_dashboard,static/src/img/icons/saas.svg',
+ 'SaaS': 'dine360_dashboard,static/src/img/icons/saas.svg',
+ }
- # 1. If not logged in, always show standard homepage
- if not request.session.uid:
- return super(ImageHome, self).index(**kwargs)
+ # Find the best match in the mapping
+ current_name = menu.name
+ for key, icon_path in icon_mapping.items():
+ if key.lower() in current_name.lower():
+ menu.web_icon = icon_path
+ break
- # 2. ROLE-BASED AUTO REDIRECTION (FOR STAFF)
- # Skip the dashboard/website entirely for Chefs and Waiters
- user = request.env.user.sudo()
- is_admin = user.has_group('base.group_system') or \
- user.has_group('dine360_restaurant.group_restaurant_admin')
-
- if not is_admin:
- # 1. WAITER / CASHIER -> Priority goes to POS
- if user.has_group('dine360_restaurant.group_restaurant_waiter') or \
- user.has_group('dine360_restaurant.group_restaurant_cashier'):
- return request.redirect('/web#action=point_of_sale.action_client_pos_menu')
+ filtered_menus.append(menu)
- # 2. CHEF -> Directly to KDS
- if user.has_group('dine360_restaurant.group_restaurant_kitchen'):
- return request.redirect('/web#action=dine360_kds.action_kds_dashboard')
-
- # 3. SUPER SAFE EDITOR & IFRAME DETECTION
- path = request.httprequest.path
- params = request.params
- headers = request.httprequest.headers
- referer = headers.get('Referer', '')
- fetch_dest = headers.get('Sec-Fetch-Dest', '')
-
- # Check for ANY editor or backend signal
- editor_params = ['enable_editor', 'edit', 'path', 'website_id', 'frontend_edit', 'model', 'id']
- is_editor_request = any(p in params for p in editor_params)
- is_from_backend = any(m in referer for m in ['/website/force', 'enable_editor'])
-
- # if it looks like Odoo internal business, return the real website
- if fetch_dest == 'iframe' or is_editor_request or is_from_backend:
- return super(ImageHome, self).index(**kwargs)
-
- if path != '/':
- return super(ImageHome, self).index(**kwargs)
-
- # Remove sudo() to respect Odoo's standard menu group restrictions
- menus = request.env['ir.ui.menu'].search([
- ('parent_id', '=', False)
- ], order='sequence')
-
- # User role checks
- try:
- is_admin = request.env.user.has_group('base.group_system') or \
- request.env.user.has_group('dine360_restaurant.group_restaurant_admin')
- is_kitchen = request.env.user.has_group('dine360_restaurant.group_restaurant_kitchen')
- except Exception:
- is_admin = request.env.user.has_group('base.group_system')
- is_kitchen = False
-
- # User requested to hide all standard apps and POS/KDS.
- # Only allow specific menus based on user request + admin tools.
- allowed_menus = ['Online Orders', 'Website', 'Table Reservations', 'Uber Integration', 'Apps', 'Settings', 'Dine360 SaaS']
-
- # Hide 'Website' icon from the parent level Dine360Restaurants database
- if request.env.cr.dbname and 'Dine360Restaurants' in request.env.cr.dbname:
- if 'Website' in allowed_menus:
- allowed_menus.remove('Website')
-
- filtered_menus = []
- seen_names = set()
- for menu in menus:
- # Match strictly against allowed menus
- if menu.name not in allowed_menus and menu.name != 'Table Reservation':
- continue
-
- # Hide "Apps" and "Settings" for non-admins
- if menu.name in ['Apps', 'Settings'] and not is_admin:
- continue
-
- # De-duplicate by name
- if menu.name in seen_names:
- continue
- seen_names.add(menu.name)
-
- # Dynamic Icon Override (Dine360 Branding)
- icon_mapping = {
- 'Apps': 'dine360_dashboard,static/src/img/icons/apps.svg',
- 'Settings': 'dine360_dashboard,static/src/img/icons/settings.svg',
- 'Table Reservation': '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',
- 'Online Orders': 'dine360_dashboard,static/src/img/icons/website.svg',
- 'Dine360 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
- current_name = menu.name
- for key, icon_path in icon_mapping.items():
- if key.lower() in current_name.lower():
- menu.web_icon = icon_path
- break
-
- filtered_menus.append(menu)
-
- # Low Stock Alerts (Ingredients)
+ # Low Stock Alerts (Ingredients)
+ low_stock_products = []
+ try:
+ ProductTemplate = request.env['product.template'].sudo()
+ if hasattr(ProductTemplate, 'get_low_stock_products'):
+ low_stock_products = ProductTemplate.get_low_stock_products(limit=5)
+ except Exception:
low_stock_products = []
- try:
- ProductTemplate = request.env['product.template'].sudo()
- if hasattr(ProductTemplate, 'get_low_stock_products'):
- low_stock_products = ProductTemplate.get_low_stock_products(limit=5)
- except Exception:
- low_stock_products = []
- return request.render('dine360_dashboard.image_home_template', {
- 'menus': filtered_menus,
- 'user_id': request.env.user,
- 'low_stock_products': low_stock_products
- })
+ values = {
+ 'menus': filtered_menus,
+ 'user_id': request.env.user,
+ 'low_stock_products': low_stock_products
+ }
+ if 'website' in request.env:
+ values['website'] = request.env['website'].get_current_website()
+ return request.render('dine360_dashboard.image_home_template', values)
-
- @http.route('/home', type='http', auth="public", website=True, sitemap=True)
- def website_home(self, **kw):
- # Explicit route for standard Website Homepage
- return request.render('website.homepage')
+class ImageHomeBase(http.Controller):
+ @http.route('/', type='http', auth='user', website=True)
+ def index(self, **kwargs):
+ # Render the dashboard for logged in users when website module is not installed
+ return render_dashboard(request)
diff --git a/addons/dine360_dashboard_website/__init__.py b/addons/dine360_dashboard_website/__init__.py
new file mode 100644
index 0000000..153a9e3
--- /dev/null
+++ b/addons/dine360_dashboard_website/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from . import controllers
diff --git a/addons/dine360_dashboard_website/__manifest__.py b/addons/dine360_dashboard_website/__manifest__.py
new file mode 100644
index 0000000..f34d296
--- /dev/null
+++ b/addons/dine360_dashboard_website/__manifest__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': 'Dine360 Dashboard Website Glue',
+ 'version': '1.0.0',
+ 'category': 'Website',
+ 'summary': 'Glue module for Dine360 Dashboard Website features',
+ 'depends': ['dine360_dashboard', 'website', 'website_sale'],
+ 'data': [
+ 'views/website_logo.xml',
+ 'views/shop_template.xml',
+ ],
+ 'assets': {
+ 'web.assets_frontend': [
+ 'dine360_dashboard/static/src/css/website_style.css',
+ 'dine360_dashboard/static/src/css/shop_style.css',
+ ],
+ },
+ 'auto_install': True,
+ 'installable': True,
+ 'license': 'LGPL-3',
+}
diff --git a/addons/dine360_dashboard_website/controllers/__init__.py b/addons/dine360_dashboard_website/controllers/__init__.py
new file mode 100644
index 0000000..757b12a
--- /dev/null
+++ b/addons/dine360_dashboard_website/controllers/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from . import main
diff --git a/addons/dine360_dashboard_website/controllers/main.py b/addons/dine360_dashboard_website/controllers/main.py
new file mode 100644
index 0000000..ca9d8a0
--- /dev/null
+++ b/addons/dine360_dashboard_website/controllers/main.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+from odoo import http
+from odoo.http import request
+from odoo.addons.web.controllers.home import Home
+from odoo.addons.website.controllers.main import Website
+from odoo.addons.dine360_dashboard.controllers.main import render_dashboard
+
+class CustomHomeWebsite(Home):
+ @http.route('/web/login', type='http', auth="public", website=True)
+ def web_login(self, *args, **kw):
+ response = super(CustomHomeWebsite, self).web_login(*args, **kw)
+ if request.params.get('login_success') and request.session.uid:
+ return request.redirect('/')
+ return response
+
+class ImageHomeWebsite(Website):
+ @http.route('/', type='http', auth='public', website=True, sitemap=True)
+ def index(self, **kwargs):
+ # 1. If not logged in, show website homepage
+ if not request.session.uid:
+ return super(ImageHomeWebsite, self).index(**kwargs)
+
+ # 2. If logged in, check for editor / iframe detection
+ params = request.params
+ headers = request.httprequest.headers
+ referer = headers.get('Referer', '')
+ fetch_dest = headers.get('Sec-Fetch-Dest', '')
+
+ editor_params = ['enable_editor', 'edit', 'path', 'website_id', 'frontend_edit', 'model', 'id']
+ is_editor_request = any(p in params for p in editor_params)
+ is_from_backend = any(m in referer for m in ['/website/force', 'enable_editor'])
+
+ if fetch_dest == 'iframe' or is_editor_request or is_from_backend:
+ return super(ImageHomeWebsite, self).index(**kwargs)
+
+ if request.httprequest.path != '/':
+ return super(ImageHomeWebsite, self).index(**kwargs)
+
+ # Render the dashboard
+ return render_dashboard(request)
+
+ @http.route('/home', type='http', auth="public", website=True, sitemap=True)
+ def website_home(self, **kw):
+ # Explicit route for standard Website Homepage
+ return request.render('website.homepage')
diff --git a/addons/dine360_dashboard_website/views/shop_template.xml b/addons/dine360_dashboard_website/views/shop_template.xml
new file mode 100644
index 0000000..e207bd6
--- /dev/null
+++ b/addons/dine360_dashboard_website/views/shop_template.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/addons/dine360_dashboard_website/views/website_logo.xml b/addons/dine360_dashboard_website/views/website_logo.xml
new file mode 100644
index 0000000..db046c7
--- /dev/null
+++ b/addons/dine360_dashboard_website/views/website_logo.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/dine360_reservation/__manifest__.py b/addons/dine360_reservation/__manifest__.py
index a1e9425..3af7b7e 100644
--- a/addons/dine360_reservation/__manifest__.py
+++ b/addons/dine360_reservation/__manifest__.py
@@ -11,7 +11,7 @@
- WhatsApp/SMS notification hooks
""",
'author': 'Dine360',
- 'depends': ['base', 'website', 'pos_restaurant'],
+ 'depends': ['base', 'website', 'pos_restaurant', 'dine360_table'],
'data': [
'security/ir.model.access.csv',
'data/reservation_sequence.xml',
diff --git a/addons/dine360_reservation/views/restaurant_table_views.xml b/addons/dine360_reservation/views/restaurant_table_views.xml
index 9505ee7..9e6162a 100644
--- a/addons/dine360_reservation/views/restaurant_table_views.xml
+++ b/addons/dine360_reservation/views/restaurant_table_views.xml
@@ -48,4 +48,22 @@
+
+
+
+ restaurant.table.form.inherit.dine360.table
+ restaurant.table
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/dine360_restaurant/__manifest__.py b/addons/dine360_restaurant/__manifest__.py
index 46c63c4..db083d9 100644
--- a/addons/dine360_restaurant/__manifest__.py
+++ b/addons/dine360_restaurant/__manifest__.py
@@ -13,7 +13,7 @@
- Store Keeper
""",
'author': 'Dine360',
- 'depends': ['point_of_sale', 'pos_restaurant', 'purchase', 'stock', 'website_sale'],
+ 'depends': ['point_of_sale', 'pos_restaurant', 'purchase', 'stock'],
'data': [
'security/restaurant_security.xml',
'security/ir.model.access.csv',
diff --git a/addons/dine360_self_order/controllers/main.py b/addons/dine360_self_order/controllers/main.py
index 1ffd792..0a5432c 100644
--- a/addons/dine360_self_order/controllers/main.py
+++ b/addons/dine360_self_order/controllers/main.py
@@ -12,13 +12,19 @@ class Dine360SelfOrderController(http.Controller):
def self_order_menu(self, **kwargs):
"""Displays the self-order menu for a specific table/kiosk"""
table_id = kwargs.get('table_id')
- table = False
+ pos_table = False
if table_id:
- table = request.env['restaurant.table'].sudo().browse(int(table_id))
+ try:
+ pos_table_id = int(table_id)
+ pos_table = request.env['restaurant.table'].sudo().browse(pos_table_id)
+ if not pos_table.exists():
+ pos_table = False
+ except (ValueError, TypeError):
+ pos_table = False
values = {
- 'table': table,
- 'floor': table.floor_id if table else False,
+ 'pos_table': pos_table,
+ 'pos_floor': pos_table.floor_id if pos_table else False,
}
return request.render('dine360_self_order.self_order_menu_template', values)
diff --git a/addons/dine360_self_order/static/src/js/self_order.js b/addons/dine360_self_order/static/src/js/self_order.js
index 3130f3c..760d342 100644
--- a/addons/dine360_self_order/static/src/js/self_order.js
+++ b/addons/dine360_self_order/static/src/js/self_order.js
@@ -1,259 +1,252 @@
-/** @odoo-module */
+/** @odoo-module **/
+import publicWidget from "@web/legacy/js/public/public_widget";
import { jsonrpc } from "@web/core/network/rpc_service";
-// We'll use a standard self-invoking function style since it's a public web module
-(function () {
- 'use strict';
+publicWidget.registry.Dine360SelfOrder = publicWidget.Widget.extend({
+ selector: '#self_order_app',
+ events: {
+ 'input #product_search': '_onSearchInput',
+ 'click #view_cart_btn': '_onViewCartClick',
+ 'click #submit_order_btn': '_onSubmitOrderClick',
+ 'click [data-category]': '_onCategoryClick',
+ 'click .add-to-cart-btn': '_onAddToCartClick',
+ 'click .increase-qty': '_onIncreaseQtyClick',
+ 'click .decrease-qty': '_onDecreaseQtyClick',
+ 'click .service-select': '_onServiceSelectClick',
+ },
- document.addEventListener('DOMContentLoaded', async () => {
- const app = document.querySelector('#self_order_app');
- if (!app) return;
-
- const dataEl = document.querySelector('#self_order_data');
- const config = {
- tableId: dataEl.dataset.tableId,
- tableName: dataEl.dataset.tableName,
- };
-
- const state = {
+ init: function () {
+ this._super.apply(this, arguments);
+ this.config = {};
+ this.state = {
products: [],
cart: [],
activeCategory: 'all',
searchTerm: '',
};
+ },
- // --- UI Elements ---
- const productList = document.querySelector('#product_list');
- const categoryFilter = document.querySelector('#category_filter');
- const cartCount = document.querySelector('#cart_count');
- const cartTotal = document.querySelector('#cart_total');
- const footerCart = document.querySelector('#footer_cart');
- const loadingOverlay = document.querySelector('#loading_overlay');
- const contentArea = document.querySelector('#self_order_content');
+ start: async function () {
+ await this._super.apply(this, arguments);
+ const dataEl = this.el.querySelector('#self_order_data');
+ if (dataEl) {
+ this.config.tableId = dataEl.dataset.tableId;
+ this.config.tableName = dataEl.dataset.tableName;
+ }
- // --- Init ---
try {
const products = await jsonrpc('/dine360/self_order/products', {});
- state.products = products;
- renderCategories();
- renderProducts();
- loadingOverlay.classList.add('d-none');
- contentArea.classList.remove('d-none');
+ this.state.products = products;
+ this._renderCategories();
+ this._renderProducts();
+ this.$('#loading_overlay').addClass('d-none');
+ this.$('#self_order_content').removeClass('d-none');
} catch (e) {
console.error("Failed to load products", e);
alert("Error connecting to server. Please try again later.");
}
+ },
- // --- Functions ---
- function renderCategories() {
- const categories = ['all', ...new Set(state.products.map(p => p.pos_categ_name))];
- categoryFilter.innerHTML = categories.map(cat => `
-
- `).join('');
+ _renderCategories: function () {
+ const categories = ['all', ...new Set(this.state.products.map(p => p.pos_categ_name))];
+ const html = categories.map(cat => `
+
+ `).join('');
+ this.$('#category_filter').html(html);
+ },
- categoryFilter.querySelectorAll('button').forEach(btn => {
- btn.onclick = () => {
- state.activeCategory = btn.dataset.category;
- renderCategories();
- renderProducts();
- };
- });
+ _renderProducts: function () {
+ let filtered = this.state.products;
+ if (this.state.activeCategory !== 'all') {
+ filtered = filtered.filter(p => p.pos_categ_name === this.state.activeCategory);
+ }
+ if (this.state.searchTerm) {
+ const term = this.state.searchTerm.toLowerCase();
+ filtered = filtered.filter(p => p.display_name.toLowerCase().includes(term));
}
- function renderProducts() {
- let filtered = state.products;
- if (state.activeCategory !== 'all') {
- filtered = filtered.filter(p => p.pos_categ_name === state.activeCategory);
- }
- if (state.searchTerm) {
- const term = state.searchTerm.toLowerCase();
- filtered = filtered.filter(p => p.display_name.toLowerCase().includes(term));
- }
-
- productList.innerHTML = filtered.map(p => `
-
-
-
-

-
-
-
-
-
-
${p.display_name}
-
- $${p.list_price.toFixed(2)}
-
-
-
-
-
-
-
- `).join('');
-
- productList.querySelectorAll('.add-to-cart-btn').forEach(btn => {
- btn.onclick = (ev) => {
- ev.stopPropagation();
- const card = btn.closest('.product-card');
- const productId = parseInt(card.dataset.id);
- addToCart(productId);
- };
- });
- }
-
- function addToCart(productId) {
- const product = state.products.find(p => p.id === productId);
- const existing = state.cart.find(item => item.product_id === productId);
- if (existing) {
- existing.qty++;
- } else {
- state.cart.push({
- product_id: product.id,
- display_name: product.display_name,
- price_unit: product.list_price,
- qty: 1
- });
- }
- updateCartUI();
- }
-
- function updateCartUI() {
- const count = state.cart.reduce((acc, item) => acc + item.qty, 0);
- const total = state.cart.reduce((acc, item) => acc + (item.qty * item.price_unit), 0);
-
- cartCount.textContent = count;
- cartTotal.textContent = `$${total.toFixed(2)}`;
-
- if (count > 0) {
- footerCart.classList.remove('d-none');
- setTimeout(() => footerCart.style.transform = 'translateY(0)', 10);
- } else {
- footerCart.style.transform = 'translateY(100%)';
- setTimeout(() => footerCart.classList.add('d-none'), 300);
- }
- }
-
- // --- Search ---
- document.querySelector('#product_search').oninput = (ev) => {
- state.searchTerm = ev.target.value;
- renderProducts();
- };
-
- // --- Cart Modal ---
- const cartModal = new bootstrap.Modal(document.getElementById('cart_modal'));
- document.querySelector('#view_cart_btn').onclick = () => {
- renderCartModal();
- cartModal.show();
- };
-
- function renderCartModal() {
- const list = document.querySelector('#cart_items_list');
- const total = state.cart.reduce((acc, item) => acc + (item.qty * item.price_unit), 0);
-
- list.innerHTML = state.cart.map((item, index) => `
-
-
-
${item.display_name}
-
$${(item.qty * item.price_unit).toFixed(2)}
-
-
-
- ${item.qty}
-
-
-
- `).join('');
-
- document.querySelector('#checkout_subtotal').textContent = `$${total.toFixed(2)}`;
- document.querySelector('#checkout_total').textContent = `$${total.toFixed(2)}`;
-
- list.querySelectorAll('.increase-qty').forEach(btn => {
- btn.onclick = () => {
- state.cart[btn.dataset.index].qty++;
- renderCartModal();
- updateCartUI();
- };
- });
- list.querySelectorAll('.decrease-qty').forEach(btn => {
- btn.onclick = () => {
- const index = btn.dataset.index;
- state.cart[index].qty--;
- if (state.cart[index].qty <= 0) {
- state.cart.splice(index, 1);
- }
- if (state.cart.length === 0) cartModal.hide();
- renderCartModal();
- updateCartUI();
- };
- });
- }
-
- // --- Submit Order ---
- document.querySelector('#submit_order_btn').onclick = async () => {
- const btn = document.querySelector('#submit_order_btn');
- const originalText = btn.innerHTML;
-
- btn.disabled = true;
- btn.innerHTML = 'Sending...';
-
- const fulfilmentType = document.querySelector('input[name="fulfilment"]:checked')?.value || 'dine_in';
-
- const orderData = {
- table_id: config.tableId,
- fulfilment_type: fulfilmentType,
- lines: state.cart
- };
-
- try {
- const response = await jsonrpc('/dine360/self_order/submit_order', { order_data: orderData });
- if (response.success) {
- cartModal.hide();
- state.cart = [];
- updateCartUI();
-
- // Success View
- document.querySelector('#self_order_content').innerHTML = `
-
-
-
Order Received!
-
${response.message}
-
-
Order Number
-
${response.order_name}
-
-