</> NextXbase
1 #include "FiveWin.ch"
2
3 /* ===========================================================================
4 POSfwh -- a full-screen touch POS terminal, FiveWin dialect.
5
6 Laid out the way a till is: a header bar, a row of coloured category tiles,
7 the product tiles under it, and on the right the order (cart), the keypad
8 and a column of payment keys. Generic placeholder menu and prices only --
9 no customer data of any kind.
10
11 Nothing here is drawn by hand. The header is SAYs, the category and
12 payment keys are TILEs, and the product grid, cart and keypad are the
13 engine's POS controls; the look comes from their own cascade keys
14 (NXB_UiSet), which is the same mechanism any application has.
15
16 Each control reports what happened to it and knows nothing about the
17 others. What a tap MEANS -- filter the grid, add to the order, take a
18 payment -- is decided here, in the application.
19 =========================================================================== */
20
21 STATIC s_aHeld := {}
22
23 FUNCTION Main()
24
25 LOCAL oWnd, oFontBig, oFontSmall
26 LOCAL oGrid, oCart, oPad
27 LOCAL aCat, aPay, n
28 LOCAL nNavy := nRGB( 30, 58, 110 )
29
30 DEFINE FONT oFontBig NAME "Segoe UI" SIZE 0, -22 BOLD
31 DEFINE FONT oFontSmall NAME "Segoe UI" SIZE 0, -13
32
33 DEFINE WINDOW oWnd TITLE "NextXbase -- Touch POS (FiveWin dialect)" ;
34 FROM 0, 0 TO 800, 1580 PIXEL
35
36 // ---- header bar -------------------------------------------------------
37 @ 0, 0 SAY "" SIZE 1580, 60 OF oWnd PIXEL COLOR CLR_WHITE, nNavy
38 @ 8, 24 SAY "NextXbase POS" SIZE 400, 28 OF oWnd PIXEL ;
39 COLOR CLR_WHITE, nNavy FONT oFontBig
40 @ 36, 24 SAY "Point of Sale Terminal" SIZE 400, 18 OF oWnd PIXEL ;
41 COLOR nRGB( 191, 219, 254 ), nNavy FONT oFontSmall
42 @ 12, 1150 SAY "Admin" SIZE 220, 20 OF oWnd PIXEL ;
43 COLOR CLR_WHITE, nNavy FONT oFontSmall RIGHT
44 @ 32, 1150 SAY "Manager - Terminal 01" SIZE 220, 18 OF oWnd PIXEL ;
45 COLOR nRGB( 191, 219, 254 ), nNavy FONT oFontSmall RIGHT
46
47 // ---- product tiles (created first so the category keys can filter it) --
48 @ 164, 12 POSPRODUCTGRID oGrid SIZE 1150, 574 OF oWnd PIXEL
49 oGrid:nTileMinWidth := 140
50 oGrid:nImageSize := 96
51 oGrid:AddProduct( "d1", "Cola", 2.50, "img/cola.png", "drinks" )
52 oGrid:AddProduct( "d2", "Lemon Soda", 2.50, "img/lemon_soda.png", "drinks" )
53 oGrid:AddProduct( "d3", "Water", 1.50, "img/water.png", "drinks" )
54 oGrid:AddProduct( "b1", "Coffee", 3.00, "img/coffee.png", "beverages" )
55 oGrid:AddProduct( "b2", "Hot Tea", 2.25, "img/hot_tea.png", "beverages" )
56 oGrid:AddProduct( "f1", "Burger", 8.99, "img/burger.png", "food" )
57 oGrid:AddProduct( "f2", "Fries", 3.50, "img/fries.png", "food" )
58 oGrid:AddProduct( "f3", "Pizza Slice", 5.99, "img/pizza_slice.png", "food" )
59 oGrid:AddProduct( "f4", "Salad", 6.50, "img/salad.png", "food" )
60 oGrid:AddProduct( "s1", "Chips", 2.00, "img/chips.png", "snacks" )
61 oGrid:AddProduct( "s2", "Candy Bar", 1.50, "img/candy_bar.png", "snacks" )
62 oGrid:AddProduct( "x1", "Ice Cream", 4.50, "img/ice_cream.png", "desserts" )
63 oGrid:AddProduct( "x2", "Cake Slice", 5.50, "img/cake_slice.png", "desserts" )
64 oGrid:AddProduct( "k1", "Croissant", 3.00, "img/croissant.png", "bakery" )
65 oGrid:AddProduct( "k2", "Sourdough Loaf", 5.25, "img/sourdough_loaf.png", "bakery" )
66 NXB_UiSet( oGrid, "BACKGROUND", "#f1f5f9" )
67 NXB_UiSet( oGrid, "ROWALIGN", "start" )
68 NXB_UiSet( oGrid, "TITLE_SIZE", "14px" )
69 NXB_UiSet( oGrid, "PRICE_SIZE", "15px" )
70 NXB_UiSet( oGrid, "ITEM_PAD", "14px 10px" )
71
72 // ---- category keys: one coloured tile each ------------------------------
73 aCat := { { "", "All", "bi-grid-3x3-gap-fill", "#6b7280" }, ;
74 { "drinks", "Drinks", "bi-cup-straw", "#3b82f6" }, ;
75 { "food", "Food", "bi-egg-fried", "#22c55e" }, ;
76 { "snacks", "Snacks", "bi-basket-fill", "#eab308" }, ;
77 { "desserts", "Desserts", "bi-cake2-fill", "#ec4899" }, ;
78 { "beverages", "Beverages", "bi-cup-hot-fill", "#a855f7" }, ;
79 { "bakery", "Bakery", "bi-shop", "#f97316" } }
80 FOR n := 1 TO Len( aCat )
81 CatKey( oWnd, oGrid, aCat[ n ], 12 + ( n - 1 ) * 150 )
82 NEXT
83
84 // ---- the order ----------------------------------------------------------
85 @ 68, 1176 POSCART oCart SIZE 264, 390 TAXRATE 0.08 OF oWnd PIXEL
86 oCart:cTitle := "Shopping Cart"
87 oCart:cCheckoutText := "Complete Order"
88 NXB_UiSet( oCart, "BACKGROUND", "#0a0a0a" )
89 NXB_UiSet( oCart, "HDRBACK", "#ffffff" )
90 NXB_UiSet( oCart, "HDRTEXT", "#0f172a" )
91 NXB_UiSet( oCart, "TITLESIZE", "14px" )
92 NXB_UiSet( oCart, "PILLBACK", "#e2e8f0" )
93 NXB_UiSet( oCart, "LINESIZE", "13px" )
94 NXB_UiSet( oCart, "ITEMTEXT", "#f1f5f9" )
95 NXB_UiSet( oCart, "SUBTEXT", "#94a3b8" )
96 NXB_UiSet( oCart, "PRICETEXT", "#22c55e" )
97 NXB_UiSet( oCart, "ROWRULE", "1px solid #1f2937" )
98 NXB_UiSet( oCart, "SUMBACK", "#f8fafc" )
99 NXB_UiSet( oCart, "ROWSIZE", "13px" )
100 NXB_UiSet( oCart, "TOTALSIZE", "16px" )
101 NXB_UiSet( oCart, "ACCENT", "#16a34a" )
102 NXB_UiSet( oCart, "BTNBACK", "#22c55e" )
103 NXB_UiSet( oCart, "BTNSIZE", "13px" )
104
105 // ---- keypad ---------------------------------------------------------------
106 @ 468, 1176 POSNUMPAD oPad SIZE 264, 270 OF oWnd PIXEL
107 NXB_UiSet( oPad, "BACKGROUND", "#1e293b" )
108 NXB_UiSet( oPad, "KEYBACK", "#e2e8f0" )
109 NXB_UiSet( oPad, "KEYTEXT", "#0f172a" )
110 NXB_UiSet( oPad, "KEYSIZE", "20px" )
111 NXB_UiSet( oPad, "CLRBACK", "#eab308" )
112 NXB_UiSet( oPad, "DOTBACK", "#e2e8f0" )
113 NXB_UiSet( oPad, "DOTTEXT", "#0f172a" )
114 NXB_UiSet( oPad, "LCDBACK", "#ffffff" )
115 NXB_UiSet( oPad, "LCDTEXT", "#0f172a" )
116 NXB_UiSet( oPad, "LCDSIZE", "20px" )
117
118 // ---- payment keys ---------------------------------------------------------
119 aPay := { { "CASH", "bi-cash-stack", "#10b981" }, ;
120 { "CARD", "bi-credit-card-fill", "#3b82f6" }, ;
121 { "SPLIT", "bi-scissors", "#a855f7" }, ;
122 { "DISC", "bi-tag-fill", "#f59e0b" }, ;
123 { "HOLD", "bi-pause-fill", "#334155" }, ;
124 { "VOID", "bi-slash-circle", "#ef4444" }, ;
125 { "CANCEL", "bi-x-lg", "#f43f5e" } }
126 FOR n := 1 TO Len( aPay )
127 PayKey( oWnd, oCart, aPay[ n ], 68 + ( n - 1 ) * 96 )
128 NEXT
129
130 // a tap on a product adds one to the order; a typed amount on the keypad
131 // is the quantity for the next tap
132 oGrid:bAction := { | hProd | oCart:AddItem( hProd[ "id" ], hProd[ "title" ], ;
133 hProd[ "price" ], Max( 1, Int( oPad:nValue ) ) ), ;
134 oPad:cEntry := "", oPad:nValue := 0 }
135 oCart:bAction := { || Pay( oCart, "CARD" ) }
136
137 IF GetEnv( "NEXTXBASE_PROBE_MODE" ) == "1"
138 ACTIVATE WINDOW oWnd ON INIT oWnd:End()
139 ELSE
140 ACTIVATE WINDOW oWnd
141 ENDIF
142
143 RETURN NIL
144
145 /* One category key. Built in its own function so each block captures ITS
146 category: a block made inside the FOR loop would share the loop variable. */
147 STATIC FUNCTION CatKey( oWnd, oGrid, aC, nLeft )
148
149 LOCAL oT, cId := aC[ 1 ]
150
151 @ 72, nLeft TILE oT SIZE 140, 80 CAPTION aC[ 2 ] ICON aC[ 3 ] COLOR aC[ 4 ] ;
152 ACTION ( oGrid:cFilter := cId, oGrid:Refresh() ) OF oWnd PIXEL
153
154 RETURN oT
155
156 STATIC FUNCTION PayKey( oWnd, oCart, aP, nTop )
157
158 LOCAL oT, cKey := aP[ 1 ]
159
160 @ nTop, 1452 TILE oT SIZE 96, 88 CAPTION cKey ICON aP[ 2 ] COLOR aP[ 3 ] ;
161 ACTION Pay( oCart, cKey ) OF oWnd PIXEL
162
163 RETURN oT
164
165 STATIC FUNCTION OrderTotal( oCart )
166
167 LOCAL nSum := 0, h
168
169 FOR EACH h IN oCart:aItems
170 nSum += h[ "price" ] * h[ "qty" ]
171 NEXT
172
173 RETURN nSum * ( 1 + oCart:nTaxRate )
174
175 /* What each payment key means is this application's decision. A real
176 deployment takes the payment and prints a receipt where this one only
177 reports the amount. */
178 STATIC FUNCTION Pay( oCart, cKey )
179
180 LOCAL nTotal := OrderTotal( oCart )
181 LOCAL cTotal := "$" + LTrim( Str( nTotal, 12, 2 ) )
182 LOCAL nSub := 0, h
183
184 DO CASE
185 CASE cKey == "HOLD"
186 IF Empty( oCart:aItems ) .AND. ! Empty( s_aHeld )
187 oCart:aItems := s_aHeld
188 s_aHeld := {}
189 ELSEIF ! Empty( oCart:aItems )
190 s_aHeld := oCart:aItems
191 oCart:aItems := {}
192 ENDIF
193
194 CASE Empty( oCart:aItems )
195 MsgInfo( "The order is empty.", cKey )
196 RETURN NIL
197
198 CASE cKey == "CASH" .OR. cKey == "CARD"
199 MsgInfo( "Paid " + cTotal + " by " + Lower( cKey ) + ".", "Order complete" )
200 oCart:aItems := {}