summaryrefslogtreecommitdiff
path: root/addons/pos_restaurant/static/src/js/Screens/FloorScreen
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/pos_restaurant/static/src/js/Screens/FloorScreen
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/pos_restaurant/static/src/js/Screens/FloorScreen')
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditBar.js19
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js60
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js315
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/TableWidget.js73
4 files changed, 467 insertions, 0 deletions
diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditBar.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditBar.js
new file mode 100644
index 00000000..43b16d93
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditBar.js
@@ -0,0 +1,19 @@
+odoo.define('pos_restaurant.EditBar', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+ const { useState } = owl.hooks;
+
+ class EditBar extends PosComponent {
+ constructor() {
+ super(...arguments);
+ this.state = useState({ isColorPicker: false })
+ }
+ }
+ EditBar.template = 'EditBar';
+
+ Registries.Component.add(EditBar);
+
+ return EditBar;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js
new file mode 100644
index 00000000..4aeb781d
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js
@@ -0,0 +1,60 @@
+odoo.define('pos_restaurant.EditableTable', function(require) {
+ 'use strict';
+
+ const { onPatched, onMounted } = owl.hooks;
+ const { useListener } = require('web.custom_hooks');
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class EditableTable extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('resize-end', this._onResizeEnd);
+ useListener('drag-end', this._onDragEnd);
+ onPatched(this._setElementStyle.bind(this));
+ onMounted(this._setElementStyle.bind(this));
+ }
+ _setElementStyle() {
+ const table = this.props.table;
+ function unit(val) {
+ return `${val}px`;
+ }
+ const style = {
+ width: unit(table.width),
+ height: unit(table.height),
+ 'line-height': unit(table.height),
+ top: unit(table.position_v),
+ left: unit(table.position_h),
+ 'border-radius': table.shape === 'round' ? unit(1000) : '3px',
+ };
+ if (table.color) {
+ style.background = table.color;
+ }
+ if (table.height >= 150 && table.width >= 150) {
+ style['font-size'] = '32px';
+ }
+ Object.assign(this.el.style, style);
+ }
+ _onResizeEnd(event) {
+ const { size, loc } = event.detail;
+ const table = this.props.table;
+ table.width = size.width;
+ table.height = size.height;
+ table.position_v = loc.top;
+ table.position_h = loc.left;
+ this.trigger('save-table', this.props.table);
+ }
+ _onDragEnd(event) {
+ const { loc } = event.detail;
+ const table = this.props.table;
+ table.position_v = loc.top;
+ table.position_h = loc.left;
+ this.trigger('save-table', this.props.table);
+ }
+ }
+ EditableTable.template = 'EditableTable';
+
+ Registries.Component.add(EditableTable);
+
+ return EditableTable;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js
new file mode 100644
index 00000000..7c46f22b
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js
@@ -0,0 +1,315 @@
+odoo.define('pos_restaurant.FloorScreen', function (require) {
+ 'use strict';
+
+ const { debounce } = owl.utils;
+ const PosComponent = require('point_of_sale.PosComponent');
+ const { useState, useRef } = owl.hooks;
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ class FloorScreen extends PosComponent {
+ /**
+ * @param {Object} props
+ * @param {Object} props.floor
+ */
+ constructor() {
+ super(...arguments);
+ this._setTableColor = debounce(this._setTableColor, 70);
+ this._setFloorColor = debounce(this._setFloorColor, 70);
+ useListener('select-table', this._onSelectTable);
+ useListener('deselect-table', this._onDeselectTable);
+ useListener('save-table', this._onSaveTable);
+ useListener('create-table', this._createTable);
+ useListener('duplicate-table', this._duplicateTable);
+ useListener('rename-table', this._renameTable);
+ useListener('change-seats-num', this._changeSeatsNum);
+ useListener('change-shape', this._changeShape);
+ useListener('set-table-color', this._setTableColor);
+ useListener('set-floor-color', this._setFloorColor);
+ useListener('delete-table', this._deleteTable);
+ const floor = this.props.floor ? this.props.floor : this.env.pos.floors[0];
+ this.state = useState({
+ selectedFloorId: floor.id,
+ selectedTableId: null,
+ isEditMode: false,
+ floorBackground: floor.background_color,
+ floorMapScrollTop: 0,
+ });
+ this.floorMapRef = useRef('floor-map-ref');
+ }
+ patched() {
+ this.floorMapRef.el.style.background = this.state.floorBackground;
+ this.state.floorMapScrollTop = this.floorMapRef.el.getBoundingClientRect().top;
+ }
+ mounted() {
+ if (this.env.pos.table) {
+ this.env.pos.set_table(null);
+ }
+ this.floorMapRef.el.style.background = this.state.floorBackground;
+ this.state.floorMapScrollTop = this.floorMapRef.el.getBoundingClientRect().top;
+ // call _tableLongpolling once then set interval of 5sec.
+ this._tableLongpolling();
+ this.tableLongpolling = setInterval(this._tableLongpolling.bind(this), 5000);
+ }
+ willUnmount() {
+ clearInterval(this.tableLongpolling);
+ }
+ get activeFloor() {
+ return this.env.pos.floors_by_id[this.state.selectedFloorId];
+ }
+ get activeTables() {
+ return this.activeFloor.tables;
+ }
+ get isFloorEmpty() {
+ return this.activeTables.length === 0;
+ }
+ get selectedTable() {
+ return this.state.selectedTableId !== null
+ ? this.env.pos.tables_by_id[this.state.selectedTableId]
+ : false;
+ }
+ selectFloor(floor) {
+ this.state.selectedFloorId = floor.id;
+ this.state.floorBackground = this.activeFloor.background_color;
+ this.state.isEditMode = false;
+ this.state.selectedTableId = null;
+ }
+ toggleEditMode() {
+ this.state.isEditMode = !this.state.isEditMode;
+ this.state.selectedTableId = null;
+ }
+ async _createTable() {
+ const newTable = await this._createTableHelper();
+ if (newTable) {
+ this.state.selectedTableId = newTable.id;
+ }
+ }
+ async _duplicateTable() {
+ if (!this.selectedTable) return;
+ const newTable = await this._createTableHelper(this.selectedTable);
+ if (newTable) {
+ this.state.selectedTableId = newTable.id;
+ }
+ }
+ async _changeSeatsNum() {
+ const selectedTable = this.selectedTable
+ if (!selectedTable) return;
+ const { confirmed, payload: inputNumber } = await this.showPopup('NumberPopup', {
+ startingValue: selectedTable.seats,
+ cheap: true,
+ title: this.env._t('Number of Seats ?'),
+ });
+ if (!confirmed) return;
+ const newSeatsNum = parseInt(inputNumber, 10) || selectedTable.seats;
+ if (newSeatsNum !== selectedTable.seats) {
+ selectedTable.seats = newSeatsNum;
+ await this._save(selectedTable);
+ }
+ }
+ async _changeShape() {
+ if (!this.selectedTable) return;
+ this.selectedTable.shape = this.selectedTable.shape === 'square' ? 'round' : 'square';
+ this.render();
+ await this._save(this.selectedTable);
+ }
+ async _renameTable() {
+ const selectedTable = this.selectedTable;
+ if (!selectedTable) return;
+ const { confirmed, payload: newName } = await this.showPopup('TextInputPopup', {
+ startingValue: selectedTable.name,
+ title: this.env._t('Table Name ?'),
+ });
+ if (!confirmed) return;
+ if (newName !== selectedTable.name) {
+ selectedTable.name = newName;
+ await this._save(selectedTable);
+ }
+ }
+ async _setTableColor({ detail: color }) {
+ this.selectedTable.color = color;
+ this.render();
+ await this._save(this.selectedTable);
+ }
+ async _setFloorColor({ detail: color }) {
+ this.state.floorBackground = color;
+ this.activeFloor.background_color = color;
+ try {
+ await this.rpc({
+ model: 'restaurant.floor',
+ method: 'write',
+ args: [[this.activeFloor.id], { background_color: color }],
+ });
+ } catch (error) {
+ if (error.message.code < 0) {
+ await this.showPopup('OfflineErrorPopup', {
+ title: this.env._t('Offline'),
+ body: this.env._t('Unable to change background color'),
+ });
+ } else {
+ throw error;
+ }
+ }
+ }
+ async _deleteTable() {
+ if (!this.selectedTable) return;
+ const { confirmed } = await this.showPopup('ConfirmPopup', {
+ title: this.env._t('Are you sure ?'),
+ body: this.env._t('Removing a table cannot be undone'),
+ });
+ if (!confirmed) return;
+ try {
+ const originalSelectedTableId = this.state.selectedTableId;
+ await this.rpc({
+ model: 'restaurant.table',
+ method: 'create_from_ui',
+ args: [{ active: false, id: originalSelectedTableId }],
+ });
+ this.activeFloor.tables = this.activeTables.filter(
+ (table) => table.id !== originalSelectedTableId
+ );
+ // Value of an object can change inside async function call.
+ // Which means that in this code block, the value of `state.selectedTableId`
+ // before the await call can be different after the finishing the await call.
+ // Since we wanted to disable the selected table after deletion, we should be
+ // setting the selectedTableId to null. However, we only do this if nothing
+ // else is selected during the rpc call.
+ if (this.state.selectedTableId === originalSelectedTableId) {
+ this.state.selectedTableId = null;
+ }
+ } catch (error) {
+ if (error.message.code < 0) {
+ await this.showPopup('OfflineErrorPopup', {
+ title: this.env._t('Offline'),
+ body: this.env._t('Unable to delete table'),
+ });
+ } else {
+ throw error;
+ }
+ }
+ }
+ _onSelectTable(event) {
+ const table = event.detail;
+ if (this.state.isEditMode) {
+ this.state.selectedTableId = table.id;
+ } else {
+ this.env.pos.set_table(table);
+ }
+ }
+ _onDeselectTable() {
+ this.state.selectedTableId = null;
+ }
+ async _createTableHelper(copyTable) {
+ let newTable;
+ if (copyTable) {
+ newTable = Object.assign({}, copyTable);
+ newTable.position_h += 10;
+ newTable.position_v += 10;
+ } else {
+ newTable = {
+ position_v: 100,
+ position_h: 100,
+ width: 75,
+ height: 75,
+ shape: 'square',
+ seats: 1,
+ };
+ }
+ newTable.name = this._getNewTableName(newTable.name);
+ delete newTable.id;
+ newTable.floor_id = [this.activeFloor.id, ''];
+ newTable.floor = this.activeFloor;
+ try {
+ await this._save(newTable);
+ this.activeTables.push(newTable);
+ return newTable;
+ } catch (error) {
+ if (error.message.code < 0) {
+ await this.showPopup('ErrorPopup', {
+ title: this.env._t('Offline'),
+ body: this.env._t('Unable to create table because you are offline.'),
+ });
+ return;
+ } else {
+ throw error;
+ }
+ }
+ }
+ _getNewTableName(name) {
+ if (name) {
+ const num = Number((name.match(/\d+/g) || [])[0] || 0);
+ const str = name.replace(/\d+/g, '');
+ const n = { num: num, str: str };
+ n.num += 1;
+ this._lastName = n;
+ } else if (this._lastName) {
+ this._lastName.num += 1;
+ } else {
+ this._lastName = { num: 1, str: 'T' };
+ }
+ return '' + this._lastName.str + this._lastName.num;
+ }
+ async _save(table) {
+ const fields = this.env.pos.models.find((model) => model.model === 'restaurant.table')
+ .fields;
+ const serializeTable = {};
+ for (let field of fields) {
+ if (typeof table[field] !== 'undefined') {
+ serializeTable[field] = table[field];
+ }
+ }
+ serializeTable.id = table.id;
+ const tableId = await this.rpc({
+ model: 'restaurant.table',
+ method: 'create_from_ui',
+ args: [serializeTable],
+ });
+ table.id = tableId;
+ this.env.pos.tables_by_id[tableId] = table;
+ }
+ async _onSaveTable(event) {
+ const table = event.detail;
+ await this._save(table);
+ }
+ async _tableLongpolling() {
+ if (this.state.isEditMode) {
+ return;
+ }
+ try {
+ const result = await this.rpc({
+ model: 'pos.config',
+ method: 'get_tables_order_count',
+ args: [this.env.pos.config.id],
+ });
+ result.forEach((table) => {
+ const table_obj = this.env.pos.tables_by_id[table.id];
+ const unsynced_orders = this.env.pos
+ .get_table_orders(table_obj)
+ .filter(
+ (o) =>
+ o.server_id === undefined &&
+ (o.orderlines.length !== 0 || o.paymentlines.length !== 0) &&
+ // do not count the orders that are already finalized
+ !o.finalized
+ ).length;
+ table_obj.order_count = table.orders + unsynced_orders;
+ });
+ this.render();
+ } catch (error) {
+ if (error.message.code < 0) {
+ await this.showPopup('OfflineErrorPopup', {
+ title: 'Offline',
+ body: 'Unable to get orders count',
+ });
+ } else {
+ throw error;
+ }
+ }
+ }
+ }
+ FloorScreen.template = 'FloorScreen';
+ FloorScreen.hideOrderSelector = true;
+
+ Registries.Component.add(FloorScreen);
+
+ return FloorScreen;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/TableWidget.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/TableWidget.js
new file mode 100644
index 00000000..48dfdad7
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/TableWidget.js
@@ -0,0 +1,73 @@
+odoo.define('pos_restaurant.TableWidget', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class TableWidget extends PosComponent {
+ mounted() {
+ const table = this.props.table;
+ function unit(val) {
+ return `${val}px`;
+ }
+ const style = {
+ width: unit(table.width),
+ height: unit(table.height),
+ 'line-height': unit(table.height),
+ top: unit(table.position_v),
+ left: unit(table.position_h),
+ 'border-radius': table.shape === 'round' ? unit(1000) : '3px',
+ };
+ if (table.color) {
+ style.background = table.color;
+ }
+ if (table.height >= 150 && table.width >= 150) {
+ style['font-size'] = '32px';
+ }
+ Object.assign(this.el.style, style);
+
+ const tableCover = this.el.querySelector('.table-cover');
+ Object.assign(tableCover.style, { height: `${Math.ceil(this.fill * 100)}%` });
+ }
+ get fill() {
+ const customerCount = this.env.pos.get_customer_count(this.props.table);
+ return Math.min(1, Math.max(0, customerCount / this.props.table.seats));
+ }
+ get orderCount() {
+ const table = this.props.table;
+ return table.order_count !== undefined
+ ? table.order_count
+ : this.env.pos
+ .get_table_orders(table)
+ .filter(o => o.orderlines.length !== 0 || o.paymentlines.length !== 0).length;
+ }
+ get orderCountClass() {
+ const notifications = this._getNotifications();
+ return {
+ 'order-count': true,
+ 'notify-printing': notifications.printing,
+ 'notify-skipped': notifications.skipped,
+ };
+ }
+ _getNotifications() {
+ const orders = this.env.pos.get_table_orders(this.props.table);
+
+ let hasChangesCount = 0;
+ let hasSkippedCount = 0;
+ for (let i = 0; i < orders.length; i++) {
+ if (orders[i].hasChangesToPrint()) {
+ hasChangesCount++;
+ } else if (orders[i].hasSkippedChanges()) {
+ hasSkippedCount++;
+ }
+ }
+
+ return hasChangesCount ? { printing: true } : hasSkippedCount ? { skipped: true } : {};
+ }
+ }
+ TableWidget.template = 'TableWidget';
+
+ Registries.Component.add(TableWidget);
+
+ return TableWidget;
+});