@@ -12,9 +12,12 @@ import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
12
12
import { Table } from "@pos_restaurant/app/floor_screen/table" ;
13
13
import { usePos } from "@point_of_sale/app/store/pos_hook" ;
14
14
import { useService } from "@web/core/utils/hooks" ;
15
- import { Component , onMounted , useRef , useState , onWillStart } from "@odoo/owl" ;
15
+ import { Component , onMounted , useRef , useState , onWillStart , useEffect } from "@odoo/owl" ;
16
16
import { ask } from "@point_of_sale/app/store/make_awaitable_dialog" ;
17
17
import { unique } from "@web/core/utils/arrays" ;
18
+ import { loadImage } from "@point_of_sale/utils" ;
19
+ import { getDataURLFromFile } from "@web/core/utils/urls" ;
20
+ import { hasTouch } from "@web/core/browser/feature_detection" ;
18
21
19
22
export class FloorScreen extends Component {
20
23
static components = { Table } ;
@@ -28,16 +31,73 @@ export class FloorScreen extends Component {
28
31
const floor = this . pos . currentFloor ;
29
32
this . state = useState ( {
30
33
selectedFloorId : floor ? floor . id : null ,
34
+ floorHeight : "100%" ,
35
+ floorWidth : "100%" ,
31
36
selectedTableIds : this . getTablesSelectedByDefault ( ) ,
32
37
isColorPicker : false ,
33
38
} ) ;
34
39
this . floorMapRef = useRef ( "floor-map-ref" ) ;
40
+ this . floorScrollBox = useRef ( "floor-map-scroll" ) ;
35
41
const ui = useState ( useService ( "ui" ) ) ;
36
42
const mode = localStorage . getItem ( "floorPlanStyle" ) ;
37
43
this . pos . floorPlanStyle = ui . isSmall || mode == "kanban" ? "kanban" : "default" ;
38
44
this . map = useRef ( "map" ) ;
39
- onMounted ( ( ) => this . pos . openCashControl ( ) ) ;
45
+ onMounted ( ( ) => {
46
+ this . pos . openCashControl ( ) ;
47
+ } ) ;
40
48
onWillStart ( this . onWillStart ) ;
49
+ useEffect (
50
+ ( ) => {
51
+ this . computeFloorSize ( ) ;
52
+ } ,
53
+ ( ) => [ this . activeFloor , this . pos . floorPlanStyle ]
54
+ ) ;
55
+ useEffect (
56
+ ( tableL ) => {
57
+ if ( hasTouch ( ) ) {
58
+ if ( tableL ) {
59
+ this . floorScrollBox . el . classList . remove ( "overflow-scroll" ) ;
60
+ this . floorScrollBox . el . classList . add ( "overflow-hidden" ) ;
61
+ } else {
62
+ this . floorScrollBox . el . classList . remove ( "overflow-hidden" ) ;
63
+ this . floorScrollBox . el . classList . add ( "overflow-scroll" ) ;
64
+ }
65
+ }
66
+ } ,
67
+ ( ) => [ this . state . selectedTableIds . length ]
68
+ ) ;
69
+ }
70
+ computeFloorSize ( ) {
71
+ if ( this . pos . floorPlanStyle === "kanban" ) {
72
+ this . state . floorHeight = "100%" ;
73
+ this . state . floorWidth = window . innerWidth + "px" ;
74
+ return ;
75
+ }
76
+ const tables = this . activeFloor . table_ids ;
77
+ const floorV = this . floorMapRef . el . clientHeight ;
78
+ const floorH = this . floorMapRef . el . offsetWidth ;
79
+ const positionH = Math . max (
80
+ ...tables . map ( ( table ) => table . position_h + table . width ) ,
81
+ floorH
82
+ ) ;
83
+ const positionV = Math . max (
84
+ ...tables . map ( ( table ) => table . position_v + table . height ) ,
85
+ floorV
86
+ ) ;
87
+
88
+ if ( this . activeFloor . floor_background_image ) {
89
+ const img = new Image ( ) ;
90
+ img . onload = ( ) => {
91
+ const height = Math . max ( img . height , positionV ) ;
92
+ const width = Math . max ( img . width , positionH ) ;
93
+ this . state . floorHeight = `${ height } px` ;
94
+ this . state . floorWidth = `${ width } px` ;
95
+ } ;
96
+ img . src = "data:image/png;base64," + this . activeFloor . floor_background_image ;
97
+ } else {
98
+ this . state . floorHeight = `${ positionV } px` ;
99
+ this . state . floorWidth = `${ positionH } px` ;
100
+ }
41
101
}
42
102
getTablesSelectedByDefault ( ) {
43
103
return this . pos . orderToTransfer ? [ this . pos . orderToTransfer . tableId ] : [ ] ;
@@ -67,6 +127,11 @@ export class FloorScreen extends Component {
67
127
}
68
128
await this . pos . unsetTable ( ) ;
69
129
}
130
+ get floorBackround ( ) {
131
+ return this . activeFloor . floor_background_image
132
+ ? "data:image/png;base64," + this . activeFloor . floor_background_image
133
+ : "none" ;
134
+ }
70
135
onClickFloorMap ( ) {
71
136
this . state . selectedTableIds = this . getTablesSelectedByDefault ( ) ;
72
137
this . state . isColorPicker = false ;
@@ -303,7 +368,9 @@ export class FloorScreen extends Component {
303
368
}
304
369
unselectTables ( ) {
305
370
if ( this . selectedTables . length ) {
306
- this . pos . updateTables ( ...this . selectedTables ) ;
371
+ for ( const table of this . selectedTables ) {
372
+ this . pos . data . write ( "restaurant.table" , [ table . id ] , table . serialize ( true ) ) ;
373
+ }
307
374
}
308
375
this . state . selectedTableIds = [ ] ;
309
376
}
@@ -386,8 +453,9 @@ export class FloorScreen extends Component {
386
453
title : _t ( "Table Name ?" ) ,
387
454
getPayload : ( newName ) => {
388
455
if ( newName !== this . selectedTables [ 0 ] . name ) {
389
- this . selectedTables [ 0 ] . name = newName ;
390
- this . pos . updateTables ( this . selectedTables [ 0 ] ) ;
456
+ this . pos . data . write ( "restaurant.table" , [ this . selectedTables [ 0 ] . id ] , {
457
+ name : newName ,
458
+ } ) ;
391
459
}
392
460
} ,
393
461
}
@@ -419,8 +487,9 @@ export class FloorScreen extends Component {
419
487
const newSeatsNum = parseInt ( num , 10 ) ;
420
488
selectedTables . forEach ( ( selectedTable ) => {
421
489
if ( newSeatsNum !== selectedTable . seats ) {
422
- selectedTable . seats = newSeatsNum ;
423
- this . pos . updateTables ( selectedTable ) ;
490
+ this . pos . data . write ( "restaurant.table" , [ selectedTable . id ] , {
491
+ seats : newSeatsNum ,
492
+ } ) ;
424
493
}
425
494
} ) ;
426
495
} ,
@@ -434,15 +503,13 @@ export class FloorScreen extends Component {
434
503
}
435
504
changeShape ( form ) {
436
505
for ( const table of this . selectedTables ) {
437
- table . shape = form ;
506
+ this . pos . data . write ( "restaurant. table" , [ table . id ] , { shape : form } ) ;
438
507
}
439
- this . pos . updateTables ( ...this . selectedTables ) ;
440
508
}
441
509
unlinkTables ( ) {
442
510
for ( const table of this . selectedTables ) {
443
- table . update ( { parent_id : null } ) ;
511
+ this . pos . data . write ( "restaurant.table" , [ table . id ] , { parent_id : false } ) ;
444
512
}
445
- this . pos . updateTables ( ...this . selectedTables ) ;
446
513
}
447
514
linkTables ( ) {
448
515
const parentTable =
@@ -465,14 +532,14 @@ export class FloorScreen extends Component {
465
532
}
466
533
setColor ( color ) {
467
534
if ( this . selectedTables . length > 0 ) {
468
- this . selectedTables . forEach ( ( selectedTable ) => {
469
- selectedTable . color = color ;
470
- } ) ;
471
- this . pos . updateTables ( ...this . selectedTables ) ;
535
+ for ( const table of this . selectedTables ) {
536
+ this . pos . data . write ( "restaurant.table" , [ table . id ] , { color : color } ) ;
537
+ }
472
538
} else {
473
539
this . activeFloor . background_color = color ;
474
540
this . pos . data . write ( "restaurant.floor" , [ this . activeFloor . id ] , {
475
541
background_color : color ,
542
+ floor_background_image : false ,
476
543
} ) ;
477
544
}
478
545
this . state . isColorPicker = false ;
@@ -622,6 +689,33 @@ export class FloorScreen extends Component {
622
689
getChildren ( table ) {
623
690
return this . pos . models [ "restaurant.table" ] . filter ( ( t ) => t . parent_id ?. id === table . id ) ;
624
691
}
692
+ async uploadImage ( event ) {
693
+ const file = event . target . files [ 0 ] ;
694
+ if ( ! file . type . match ( / i m a g e .* / ) ) {
695
+ this . dialog . add ( AlertDialog , {
696
+ title : _t ( "Unsupported File Format" ) ,
697
+ body : _t ( "Only web-compatible Image formats such as .png or .jpeg are supported." ) ,
698
+ } ) ;
699
+ } else {
700
+ const imageUrl = await getDataURLFromFile ( file ) ;
701
+ const loadedImage = await loadImage ( imageUrl ) ;
702
+ if ( loadedImage ) {
703
+ this . env . services . ui . block ( ) ;
704
+ await this . pos . data . ormWrite ( "restaurant.floor" , [ this . activeFloor . id ] , {
705
+ floor_background_image : imageUrl . split ( "," ) [ 1 ] ,
706
+ } ) ;
707
+ // A read is added to be sure that we have the same image as the one in backend
708
+ await this . pos . data . read ( "restaurant.floor" , [ this . activeFloor . id ] ) ;
709
+ this . env . services . ui . unblock ( ) ;
710
+ } else {
711
+ this . dialog . add ( AlertDialog , {
712
+ title : _t ( "Loading Image Error" ) ,
713
+ body : _t ( "Encountered error when loading image. Please try again." ) ,
714
+ } ) ;
715
+ }
716
+ this . state . isColorPicker = false ;
717
+ }
718
+ }
625
719
}
626
720
627
721
registry . category ( "pos_screens" ) . add ( "FloorScreen" , FloorScreen ) ;
0 commit comments