diff --git a/addons/dine360_dashboard/static/src/css/login_style.css b/addons/dine360_dashboard/static/src/css/login_style.css index 1670d2e..3b9e7e8 100644 --- a/addons/dine360_dashboard/static/src/css/login_style.css +++ b/addons/dine360_dashboard/static/src/css/login_style.css @@ -4,7 +4,7 @@ height: 100vh !important; width: 100vw !important; overflow: hidden; - background: url('/dine360_theme_chennora/static/src/img/chen-banner-2.webp') !important; + background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('/dine360_theme_chennora/static/src/img/chen-banner-2.webp') !important; background-repeat: no-repeat !important; background-position: center center !important; background-size: cover !important; @@ -111,8 +111,8 @@ } .form-control { - background: rgba(255, 255, 255, 0.05) !important; - border: 1px solid rgba(255, 255, 255, 0.1) !important; + background: rgba(255, 255, 255, 0.1) !important; + border: 1px solid rgba(255, 255, 255, 0.3) !important; border-radius: 12px !important; padding: 12px 15px !important; height: auto !important; @@ -120,7 +120,14 @@ } .form-control::placeholder { - color: rgba(255, 255, 255, 0.3) !important; + color: rgba(255, 255, 255, 0.6) !important; +} + +/* Accessibility contrast for error messages */ +.alert-danger { + background-color: rgba(220, 53, 69, 0.9) !important; + color: white !important; + border: none !important; } /* Login Button with Gradient */ @@ -157,7 +164,16 @@ .o_login_right_side { background: #0f172a !important; - /* Keep dark background on mobile */ + } + + .o_login_card_wrapper { + width: 90% !important; + } + + .oe_website_login_container .oe_login_form, + .oe_website_login_container .oe_signup_form, + .oe_website_login_container .oe_reset_password_form { + width: 90% !important; } } diff --git a/addons/dine360_dashboard/static/src/css/pos_style.css b/addons/dine360_dashboard/static/src/css/pos_style.css index 533edcd..8b97ab7 100644 --- a/addons/dine360_dashboard/static/src/css/pos_style.css +++ b/addons/dine360_dashboard/static/src/css/pos_style.css @@ -106,17 +106,7 @@ object-fit: contain !important; } -/* "Rush Mode" Label next to logo */ -.pos .pos-logo::after { - content: "RUSH MODE"; - font-size: 14px; - font-weight: 800; - color: #ef4444; - letter-spacing: 2px; - border-left: 2px solid #e5e7eb; - padding-left: 20px; - margin-left: 10px; -} +/* "Rush Mode" Label removed for production */ /* 3. Search Bar - Teal Theme */ .pos .search-bar { @@ -365,7 +355,8 @@ .pos-receipt .pos-receipt-order-data { color: #94a3b8 !important; - font-size: 11px !important; + font-size: 12px !important; + margin-top: 10px !important; } .pos-receipt .receipt-orderlines { @@ -375,11 +366,27 @@ .pos-receipt .orderline { border: none !important; - margin: 5px 0 !important; - padding: 10px 0 !important; + margin: 0 !important; + padding: 8px 0 !important; border-bottom: 1px dashed #e2e8f0 !important; display: flex !important; + flex-wrap: wrap !important; justify-content: space-between !important; + align-items: baseline !important; +} + +.pos-receipt .orderline .product-name { + flex: 1 1 65% !important; + font-weight: 700 !important; + white-space: normal !important; + word-break: break-word !important; + padding-right: 5px !important; +} + +.pos-receipt .orderline .pos-receipt-right-align { + flex: 0 0 auto !important; + text-align: right !important; + font-weight: 800 !important; } .pos-receipt .pos-receipt-total { @@ -392,4 +399,48 @@ .pos-receipt-amount { font-weight: 800 !important; +} + +/* ======================================== + RESPONSIVE MEDIA QUERIES + ======================================== */ +@media (max-width: 1024px) { + .pos .leftpane { + width: 380px !important; + } + .pos .product { + width: 160px !important; + height: 180px !important; + } + .pos .product .product-img { + height: 110px !important; + } + .pos .search-bar { + width: 280px !important; + } +} + +@media (max-width: 768px) { + .product-screen { + flex-direction: column !important; + height: 100vh !important; + } + .pos .leftpane { + width: 100% !important; + height: 50vh !important; + border-left: none !important; + border-top: 2px solid #f1f5f9 !important; + } + .pos .rightpane { + height: 50vh !important; + } + .pos .pos-topheader { + height: auto !important; + flex-wrap: wrap !important; + padding: 10px !important; + } + .pos .search-bar { + width: 100% !important; + margin-top: 10px !important; + } } \ No newline at end of file diff --git a/addons/dine360_dashboard/views/home_template.xml b/addons/dine360_dashboard/views/home_template.xml index ae0bc52..062df91 100644 --- a/addons/dine360_dashboard/views/home_template.xml +++ b/addons/dine360_dashboard/views/home_template.xml @@ -19,14 +19,6 @@ - - AI - - - - - - diff --git a/addons/dine360_online_orders/static/src/css/online_orders.css b/addons/dine360_online_orders/static/src/css/online_orders.css index f619300..9cf2294 100644 --- a/addons/dine360_online_orders/static/src/css/online_orders.css +++ b/addons/dine360_online_orders/static/src/css/online_orders.css @@ -3,14 +3,14 @@ /* ============================================ */ .online-orders-screen { - background: #1a1a2e; + background: #171422; color: #eee; font-family: 'Inter', 'Segoe UI', sans-serif; } /* Header */ .online-orders-header { - background: linear-gradient(135deg, #16213e 0%, #0f3460 100%); + background: #171422; border-bottom: 2px solid rgba(255, 255, 255, 0.1); min-height: 60px; } @@ -58,7 +58,7 @@ /* Body */ .online-orders-body { - background: #1a1a2e; + background: #171422; } /* Left Panel - Orders List */ @@ -87,9 +87,9 @@ } .order-card.selected { - background: rgba(15, 52, 96, 0.5); - border-color: #e94560; - box-shadow: 0 0 15px rgba(233, 69, 96, 0.2); + background: rgba(214, 17, 30, 0.1); + border-color: #d6111e; + box-shadow: 0 0 15px rgba(214, 17, 30, 0.2); } .order-ref { @@ -164,7 +164,7 @@ width: 44px; height: 44px; border-radius: 50%; - background: linear-gradient(135deg, #e94560, #0f3460); + background: linear-gradient(135deg, #d6111e, #1a1d23); display: flex; align-items: center; justify-content: center; @@ -229,8 +229,8 @@ } .kitchen-badge.active { - background: rgba(233, 69, 96, 0.2); - color: #e94560; + background: rgba(214, 17, 30, 0.2); + color: #d6111e; } /* Detail Action Buttons */ @@ -265,9 +265,9 @@ /* Navbar Button */ .online-orders-nav-btn { - background: rgba(233, 69, 96, 0.15); - border: 1px solid rgba(233, 69, 96, 0.3); - color: #e94560; + background: rgba(214, 17, 30, 0.15); + border: 1px solid rgba(214, 17, 30, 0.3); + color: #d6111e; border-radius: 10px; padding: 6px 14px; font-weight: 600; @@ -276,9 +276,9 @@ } .online-orders-nav-btn:hover { - background: #e94560; + background: #d6111e; color: #fff; - border-color: #e94560; + border-color: #d6111e; } /* Scrollbar */ @@ -300,7 +300,7 @@ /* Spinner */ .spinner-border { - color: #e94560 !important; + color: #d6111e !important; } /* Responsive */ diff --git a/addons/dine360_online_orders/static/src/js/online_orders_screen.js b/addons/dine360_online_orders/static/src/js/online_orders_screen.js index 8495753..1a9efc6 100644 --- a/addons/dine360_online_orders/static/src/js/online_orders_screen.js +++ b/addons/dine360_online_orders/static/src/js/online_orders_screen.js @@ -25,6 +25,7 @@ export class OnlineOrdersScreen extends Component { loading: true, selectedOrder: null, confirmingId: null, + error: null, }); console.log("[OnlineOrders] Services obtained:", { @@ -83,6 +84,7 @@ export class OnlineOrdersScreen extends Component { async loadOnlineOrders() { try { this.state.loading = true; + this.state.error = null; const orders = await this.orm.call( "pos.order", "get_online_orders", @@ -94,6 +96,7 @@ export class OnlineOrdersScreen extends Component { } catch (error) { console.error("[OnlineOrders] Error loading orders:", error); this.state.loading = false; + this.state.error = "Failed to load orders. Please check your connection."; } } @@ -104,6 +107,7 @@ export class OnlineOrdersScreen extends Component { async confirmOrder(orderId) { try { this.state.confirmingId = orderId; + this.state.error = null; await this.orm.call( "pos.order", "action_confirm_online_order", @@ -119,12 +123,14 @@ export class OnlineOrdersScreen extends Component { } catch (error) { console.error("[OnlineOrders] Confirm error:", error); this.notification.add("Failed to confirm order"); + this.state.error = "Failed to confirm order. It might have been modified."; this.state.confirmingId = null; } } async rejectOrder(orderId) { try { + this.state.error = null; await this.orm.call( "pos.order", "action_reject_online_order", @@ -138,6 +144,7 @@ export class OnlineOrdersScreen extends Component { } catch (error) { console.error("[OnlineOrders] Reject error:", error); this.notification.add("Failed to reject order"); + this.state.error = "Failed to reject order."; } } diff --git a/addons/dine360_online_orders/static/src/xml/online_orders_screen.xml b/addons/dine360_online_orders/static/src/xml/online_orders_screen.xml index eeed5ea..6422c99 100644 --- a/addons/dine360_online_orders/static/src/xml/online_orders_screen.xml +++ b/addons/dine360_online_orders/static/src/xml/online_orders_screen.xml @@ -24,7 +24,21 @@ -
+
+ + +
+
+
+ + +
+ +
+
+
diff --git a/addons/dine360_order_channels/static/src/js/channel_panel.js b/addons/dine360_order_channels/static/src/js/channel_panel.js index e470f5a..cf8d4ed 100644 --- a/addons/dine360_order_channels/static/src/js/channel_panel.js +++ b/addons/dine360_order_channels/static/src/js/channel_panel.js @@ -20,25 +20,26 @@ export class ChannelPanel extends Component { this.dialog = useService("dialog"); this.SOURCE_LABELS = { - walk_in: '🚶 Walk-In', - phone: '📞 Phone', - whatsapp: '💬 WhatsApp', - social_media: '📱 Social', - online: '🌐 Online', - kiosk: '📟 Kiosk', - qr: '📷 QR Code', - platform: '🛒 Platform', + walk_in: 'Walk-In', + phone: 'Phone', + whatsapp: 'WhatsApp', + social_media: 'Social', + online: 'Online', + kiosk: 'Kiosk', + qr: 'QR Code', + platform: 'Platform', }; this.FULFILMENT_LABELS = { - dine_in: '🍽️ Dine-In', - pickup: '🛍️ Pickup', - delivery: '🚚 Delivery', + dine_in: 'Dine-In', + pickup: 'Pickup', + delivery: 'Delivery', }; this.state = useState({ showDelivery: false, showDetails: false, // For manual fields toggle + isCollapsed: false, // Panel collapse state searchQuery: '', searchResults: [], searching: false, diff --git a/addons/dine360_order_channels/static/src/xml/channel_panel.xml b/addons/dine360_order_channels/static/src/xml/channel_panel.xml index 823d41e..5df98ab 100644 --- a/addons/dine360_order_channels/static/src/xml/channel_panel.xml +++ b/addons/dine360_order_channels/static/src/xml/channel_panel.xml @@ -4,11 +4,20 @@ -
+
+ +
+ Order Channels + +
+ +
-
📋 ORDER SOURCE
+
ORDER SOURCE
+
diff --git a/addons/dine360_pos_navbar/static/src/css/pos_navbar.css b/addons/dine360_pos_navbar/static/src/css/pos_navbar.css index a517bfd..7d2368a 100644 --- a/addons/dine360_pos_navbar/static/src/css/pos_navbar.css +++ b/addons/dine360_pos_navbar/static/src/css/pos_navbar.css @@ -1,30 +1,11 @@ -/* Custom POS Navbar styling */ -.pos .pos-topheader { - background: inherit; -} - -.btn-dashboard:hover { - background: linear-gradient(135deg, #a00d0e 0%, #d61112 100%) !important; - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3) !important; -} - -/* Category Sidebar Red Background */ -.pos .category-button { - background: #d61112 !important; - color: white !important; - border: 1px solid rgba(255, 255, 255, 0.1) !important; - transition: all 0.2s ease; -} - -.pos .category-button:hover { - background: #a00d0e !important; - transform: scale(1.02); -} - -.pos .category-button.active { - background: #111 !important; - /* Contrast active category */ - box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5) !important; - border: 1px solid #FECD4F !important; -} \ No newline at end of file +/* Custom POS Navbar styling */ +.pos .pos-topheader { + background: inherit; +} + +.btn-dashboard:hover { + background: linear-gradient(135deg, #a00d0e 0%, #d61112 100%) !important; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3) !important; +} + \ No newline at end of file diff --git a/addons/dine360_qz_printer/static/src/js/qz_wrapper.js b/addons/dine360_qz_printer/static/src/js/qz_wrapper.js index d47416f..4e63627 100644 --- a/addons/dine360_qz_printer/static/src/js/qz_wrapper.js +++ b/addons/dine360_qz_printer/static/src/js/qz_wrapper.js @@ -52,48 +52,95 @@ function buildReceiptLinesFromOrder(order) { const lines = []; const company = order?.pos?.company; const client = order?.get_partner?.(); + const table = order?.table; + const cashier = order?.employee || order?.pos?.get_cashier?.(); + // 1. HEADER (Centered-ish) if (company?.name) { - lines.push(company.name); + lines.push(company.name.toUpperCase()); } - if (company?.phone) { - lines.push(company.phone); + if (company?.street) lines.push(company.street); + if (company?.city) lines.push(company.city); + if (company?.phone) lines.push(`Tel: ${company.phone}`); + + // Custom Odoo Header + if (order?.pos?.config?.receipt_header) { + lines.push(order.pos.config.receipt_header); } lines.push("-".repeat(RECEIPT_COLUMNS)); - const receiptNumber = order?.name || order?.uid || ""; - if (receiptNumber) { - lines.push(`Receipt: ${receiptNumber}`); + // 2. ORDER INFO + const receiptNumber = order?.name || ""; + if (receiptNumber) lines.push(`Order: ${receiptNumber}`); + + if (table) { + lines.push(`TABLE: ${table.name}`.padEnd(20) + `GUESTS: ${order.customer_count || 1}`); } - lines.push(new Date().toLocaleString()); - if (client?.name) { - lines.push(`Customer: ${client.name}`); + if (cashier) { + lines.push(`SERVER: ${cashier.name}`); } + lines.push(`DATE: ${new Date().toLocaleString()}`); + if (client) { + lines.push(`CUSTOMER: ${client.name}`); + } + + lines.push("=".repeat(RECEIPT_COLUMNS)); + lines.push(leftRight("ITEM", "PRICE")); lines.push("-".repeat(RECEIPT_COLUMNS)); + // 3. ORDER LINES for (const orderline of order?.get_orderlines?.() || []) { const product = orderline.get_product?.(); const name = product?.display_name || product?.name || ""; const qty = orderline.get_quantity?.() || 0; - const total = orderline.get_price_with_tax?.() ?? orderline.get_display_price?.() ?? 0; - for (const wrapped of wrapLine(name, RECEIPT_COLUMNS)) { - lines.push(wrapped); + const priceUnit = orderline.get_unit_display_price?.() || 0; + const total = orderline.get_price_with_tax?.() || 0; + + // "Qty x Name" on left, "Total" on right + const itemLabel = `${qty} x ${name}`; + const itemPrice = money(total, currency); + + if (itemLabel.length + itemPrice.length + 1 > RECEIPT_COLUMNS) { + lines.push(itemLabel); + lines.push(itemPrice.padStart(RECEIPT_COLUMNS)); + } else { + lines.push(leftRight(itemLabel, itemPrice)); + } + + // Show unit price if qty > 1 + if (qty > 1) { + lines.push(` @ ${money(priceUnit, currency)}`); } - lines.push(leftRight(` ${qty} x`, money(total, currency))); } - lines.push("-".repeat(RECEIPT_COLUMNS)); - lines.push(leftRight("TOTAL", money(order?.get_total_with_tax?.(), currency))); + // 4. TOTALS + lines.push("=".repeat(RECEIPT_COLUMNS)); + lines.push(leftRight("SUBTOTAL", money(order?.get_total_without_tax?.(), currency))); + const tax = order?.get_total_tax?.(); if (tax) { - lines.push(leftRight("Tax", money(tax, currency))); + lines.push(leftRight("TAX", money(tax, currency))); } + + lines.push("-".repeat(RECEIPT_COLUMNS)); + lines.push(leftRight("TOTAL", money(order?.get_total_with_tax?.(), currency))); + const change = order?.get_change?.(); if (change) { - lines.push(leftRight("Change", money(change, currency))); + lines.push(leftRight("CHANGE", money(change, currency))); } + lines.push("-".repeat(RECEIPT_COLUMNS)); - lines.push("Thank you"); + + // Custom Odoo Footer + if (order?.pos?.config?.receipt_footer) { + lines.push(order.pos.config.receipt_footer); + } + + lines.push("THANK YOU FOR DINING WITH US!"); + lines.push("PLEASE VISIT AGAIN"); + lines.push(NEWLINE); + return lines; } @@ -108,7 +155,16 @@ function buildReceiptLines(receiptElement, order) { function buildEscPosReceipt(receiptElement, order) { const ESC = "\x1B"; const GS = "\x1D"; - const lines = buildReceiptLines(receiptElement, order); + let lines = buildReceiptLines(receiptElement, order); + + // Safety mechanism: limit receipt length to prevent runaway printing + if (lines.length > 150) { + console.warn("Receipt is suspiciously long, truncating to 150 lines to prevent runaway printing."); + lines.length = 150; + lines.push("-".repeat(RECEIPT_COLUMNS)); + lines.push("TRUNCATED FOR SAFETY"); + } + const body = lines.flatMap((line) => wrapLine(line)).join(NEWLINE); return [ @@ -123,14 +179,6 @@ function buildEscPosReceipt(receiptElement, order) { patch(ReceiptScreen.prototype, { async printReceipt() { if (this.pos.config.use_qz_printer && this.pos.config.qz_printer_name) { - const rawPrintingEnabled = window.localStorage?.getItem("dine360_qz_raw_enabled") === "1"; - if (!rawPrintingEnabled) { - console.warn( - "QZ raw receipt printing is disabled. Set localStorage.dine360_qz_raw_enabled = '1' only while testing a raw ESC/POS printer queue." - ); - return super.printReceipt(...arguments); - } - try { if (!window.qz) { console.error("QZ Tray library not loaded.");