Updates to order screen

This commit is contained in:
2022-01-04 15:04:55 +10:00
parent 4de8f20d00
commit 85722fa692
21 changed files with 720 additions and 242 deletions

View File

@@ -54,9 +54,9 @@ let getFloorplanData (id: int) =
|> json
let getOrderScreenData (id: int) =
let pages = Entity.getAllInVenue<order_screen_page_group>
{|
order_screen_pages = pages
order_screen_pages = Entity.getAllInVenue<order_screen_page_group>
sales_categories = Entity.getAllInVenue<sales_category>
|}
|> ajaxSuccess
|> json

View File

@@ -15,6 +15,7 @@
<Compile Include="Reservations.module.fs" />
<Compile Include="Floorplan.module.fs" />
<Compile Include="Printer.module.fs" />
<Compile Include="Orders.module.fs" />
<Compile Include="OrderScreen.module.fs" />
<Compile Include="Decorations.module.fs" />
<Compile Include="Clerk.module.fs" />

View File

@@ -23,11 +23,11 @@ let get var =
else
"Missing language variable: " + var
let getAndReplace languageVar replacements =
let getAndReplace languageVar (replacements: 'x list) =
let langString = get languageVar
replacements
|> List.mapi (fun index string
-> index + 1, string)
|> List.mapi (fun index replacement
-> index + 1, replacement.ToString())
|> List.fold (fun (result: string) (index, string)
-> result.Replace($"[{index}]", string)
) langString

View File

@@ -11,7 +11,7 @@ open Theme
let htmlAttributes (attributes: Map<string, string>) =
" " + (attributes
|> Map.toArray
|> Array.map (fun (attribute, value) -> attribute+"=\""+HttpUtility.HtmlEncode value + "\"")
|> Array.map (fun (attribute, value) -> attribute+"='"+HttpUtility.HtmlEncode value + "'")
|> String.concat " ")
let getAllPageGrids () = Entity.getAllInVenue<order_screen_page_group>
@@ -46,9 +46,7 @@ let renderButton (buttonId: int) =
then "invisible"
else ""
let image = if button.image.Length > 0
then loadTemplateWithVars "orderScreen/button_image" (map ["image", button.image])
else ""
let image = if button.image.Length > 0 then loadTemplateWithVars "orderScreen/button_image" (map ["image", button.image]) else ""
let extraClasses = [|imageClass; spacerClass|] |> String.concat " "
@@ -63,7 +61,7 @@ let renderButton (buttonId: int) =
"extra_styles", extra_styles
"primary_action", button.primary_action
"secondary_action", button.secondary_action
"text", if button.text.Length >0 then button.text else action_data.text
"text", if button.text.Length > 0 then button.text else action_data.text
"image", image
"extra_data", action_data.extra_data
]

3
Orders.module.fs Normal file
View File

@@ -0,0 +1,3 @@
module Orders
let getHighestOrderNumber () = 6

View File

@@ -36,12 +36,22 @@ let loadFloorplan (ctx: HttpContext) : HttpHandler =
htmlString <| Theme.loadTemplateWithVarsArraysScriptsAndStyles "floorplan" variables arrays scripts styles
let loadOrderScreen (ctx: HttpContext) : HttpHandler =
let loadOrderScreen (ctx: HttpContext) (tableNumber: int) : HttpHandler =
Session.RequireClerkAuthentication ctx
let covers = if tableNumber > 0 then (getTable tableNumber).default_covers else 0
let coverString = language.getAndReplace "covers" [covers]
let coverSelectorButton = if tableNumber > 0 then Theme.loadTemplateWithVars "orderScreen/cover_selector_button" (map ["covers", coverString]) else ""
let orderNumber =
if tableNumber > 0 then language.getAndReplace "active_table" [tableNumber]
else language.get "new_order"
let categoryList =
Entity.getAll<order_screen_page_group>
|> Array.filter (fun category -> category.id <> 0)
Entity.getAllInVenue<order_screen_page_group>
|> Array.filter (fun page_group -> page_group.id <> 0)
|> Array.sortBy (fun {order=order} -> order)
|> Array.map (fun category ->
let categoryMap = recordToMap category
let categoryArray = map ["page", categoryMap]
@@ -54,15 +64,17 @@ let loadOrderScreen (ctx: HttpContext) : HttpHandler =
|> Array.map OrderScreen.getPagesHTML
|> String.concat "\n"
let variables = map [
"title", "Order"
"categoryList", categoryList
"pageGroups", grids
"orderNumber", orderNumber
"coverSelectorButton", coverSelectorButton
"covers", coverString
]
let styles = ["dredgepos.orderScreen.css"]
let scripts = ["dredgepos.orderScreen.js"]
let scripts = ["dredgepos.tables.js";"../external/currency.min.js";"dredgepos.orderScreen.js"; ]
let currentClerk = recordToMap <| Session.getCurrentClerk ctx
let arrays = map ["clerk", currentClerk]

View File

@@ -52,7 +52,8 @@ module Program =
get "/" (redirectTo true "/login")
get "/login" (warbler (fun _ -> PageController.loadHomePage() ))
get "/floorplan" (warbler (fun ctx -> PageController.loadFloorplan (snd ctx)))
get "/order" (warbler (fun ctx -> PageController.loadOrderScreen (snd ctx)))
get "/order" (warbler (fun ctx -> PageController.loadOrderScreen (snd ctx) 0))
getf "/order/%i" (fun number -> (warbler (fun ctx -> PageController.loadOrderScreen (snd ctx) number)))
forward "/ajax" floorplanRouter
forward "/orderScreen" orderScreenRouter
}

View File

@@ -29,11 +29,20 @@ type floorplan_table = {
}
[<CLIMutable>]
type category = {
type print_group = {
id: int
category_name: string
category_print_group: string
category_department: string
name: string
printer: int
venue_id: int
}
[<CLIMutable>]
type sales_category = {
id: int
parent: int
name: string
print_group: string
venue_id: int
}
[<CLIMutable>]
@@ -64,7 +73,7 @@ type clerk = {id: int; clerk_name: string; clerk_login_code: int; clerk_usergrou
type session = {id: int; session_id: string; clerk_json: string; clerk_id: int; expires: int}
[<CLIMutable>]
type order_screen_page_group = {id: int; venue_id: int; label: string; grid_id: int}
type order_screen_page_group = {id: int; order: int; venue_id: int; label: string; grid_id: int}
[<CLIMutable>]
type grid = {id: int; grid_name: string; grid_rows: int; grid_cols: int; grid_data: string}
@@ -89,9 +98,9 @@ type item = {
item_category: int
item_name: string
item_type: string
price1: float
price2: float
price3: float
price4: float
price5: float
price1: int
price2: int
price3: int
price4: int
price5: int
}

View File

@@ -51,6 +51,8 @@
"merge_table":"Merge Table",
"unmerge_table":"Unmerge Table",
"order_table":"Place Order",
"order_number": "Order [1]",
"new_order": "New Order",
"view_table":"View Table",
"reserve_table":"Reserve Table",
"unreserve_table":"Delete Reservation",

View File

@@ -0,0 +1,12 @@
/*
currency.js - v2.0.4
http://scurker.github.io/currency.js
Copyright (c) 2021 Jason Wilson
Released under MIT license
*/
(function(e,g){"object"===typeof exports&&"undefined"!==typeof module?module.exports=g():"function"===typeof define&&define.amd?define(g):(e=e||self,e.currency=g())})(this,function(){function e(b,a){if(!(this instanceof e))return new e(b,a);a=Object.assign({},m,a);var d=Math.pow(10,a.precision);this.intValue=b=g(b,a);this.value=b/d;a.increment=a.increment||1/d;a.groups=a.useVedic?n:p;this.s=a;this.p=d}function g(b,a){var d=2<arguments.length&&void 0!==arguments[2]?arguments[2]:!0;var c=a.decimal;
var h=a.errorOnInvalid,k=a.fromCents,l=Math.pow(10,a.precision),f=b instanceof e;if(f&&k)return b.intValue;if("number"===typeof b||f)c=f?b.value:b;else if("string"===typeof b)h=new RegExp("[^-\\d"+c+"]","g"),c=new RegExp("\\"+c,"g"),c=(c=b.replace(/\((.*)\)/,"-$1").replace(h,"").replace(c,"."))||0;else{if(h)throw Error("Invalid Input");c=0}k||(c=(c*l).toFixed(4));return d?Math.round(c):c}var m={symbol:"$",separator:",",decimal:".",errorOnInvalid:!1,precision:2,pattern:"!#",negativePattern:"-!#",format:function(b,
a){var d=a.pattern,c=a.negativePattern,h=a.symbol,k=a.separator,l=a.decimal;a=a.groups;var f=(""+b).replace(/^-/,"").split("."),q=f[0];f=f[1];return(0<=b.value?d:c).replace("!",h).replace("#",q.replace(a,"$1"+k)+(f?l+f:""))},fromCents:!1},p=/(\d)(?=(\d{3})+\b)/g,n=/(\d)(?=(\d\d)+\d\b)/g;e.prototype={add:function(b){var a=this.s,d=this.p;return e((this.intValue+g(b,a))/(a.fromCents?1:d),a)},subtract:function(b){var a=this.s,d=this.p;return e((this.intValue-g(b,a))/(a.fromCents?1:d),a)},multiply:function(b){var a=
this.s;return e(this.intValue*b/(a.fromCents?1:Math.pow(10,a.precision)),a)},divide:function(b){var a=this.s;return e(this.intValue/g(b,a,!1),a)},distribute:function(b){var a=this.intValue,d=this.p,c=this.s,h=[],k=Math[0<=a?"floor":"ceil"](a/b),l=Math.abs(a-k*b);for(d=c.fromCents?1:d;0!==b;b--){var f=e(k/d,c);0<l--&&(f=f[0<=a?"add":"subtract"](1/d));h.push(f)}return h},dollars:function(){return~~this.value},cents:function(){return~~(this.intValue%this.p)},format:function(b){var a=this.s;return"function"===
typeof b?b(this,a):a.format(this,Object.assign({},a,b))},toString:function(){var b=this.s,a=b.increment;return(Math.round(this.intValue/this.p/a)*a).toFixed(b.precision)},toJSON:function(){return this.value}};return e});

View File

@@ -1,81 +1,81 @@
let Application : ApplicationState = {
keyboard : null,
/// <reference path="./typings/currency.d.ts" />
const Application: ApplicationState = {
keyboard: null,
mode: [],
languageVars: {}
}
}
/** Parses a language variable. */
let lang = (key: string, replacements?: string[] | string) => {
/** Parses a language variable. */
const lang = (key: string, replacements?: string[] | string) => {
let finalValue = Application.languageVars[key] || ''
if(!replacements) return finalValue
if(typeof replacements === 'string') replacements = [replacements]
if (!replacements) return finalValue
if (typeof replacements === 'string') replacements = [replacements]
replacements.forEach( (replacement, index) => {
let correctIndex = index+1
replacements.forEach((replacement, index) => {
const correctIndex = index + 1
finalValue = finalValue.replace(`[${correctIndex}]`, replacement)
})
return finalValue
}
}
/** Check if a variable is defined */
let defined = (variable: any) => {
/** Check if a variable is defined */
const defined = (variable: any) => {
return typeof variable !== 'undefined'
}
}
/** Call an Ajax function asynchronously */
let ajax = (endpoint : string, data: any, method = 'POST', successFunction : Function , errorFunction : Function, beforeFunction: any) => {
/** Call an Ajax function asynchronously */
const ajax = (endpoint: string, data: any, method = 'POST', successFunction: Function, errorFunction: Function, beforeFunction: any) => {
data = (data == null) ? data : JSON.stringify(data)
return $.ajax({
url: endpoint,
method: method,
data: data,
success: (response: ajaxResult) => {
if(successFunction && response.status == 'success')
if (successFunction && response.status == 'success')
successFunction(JSON.parse(response.data))
else if (errorFunction && response.status != 'success'){
else if (errorFunction && response.status != 'success') {
errorFunction(JSON.parse(response.data))
}
},
error: (error) => console.log(error.statusCode),
beforeSend: beforeFunction
})
}
}
/*
/*
For the flow of the app, synchronous is commonly preferred
though trying to keep its usage as low as possible.
*/
let ajaxSync = (endpoint : string, data?: any, method = 'POST') => {
let response = JSON.parse(
const ajaxSync = (endpoint: string, data?: any, method = 'POST') => {
const response = JSON.parse(
$.ajax({
url: endpoint,
method: method,
data: JSON.stringify(data),
async:false,
async: false,
}).responseText)
if(response.data) {
if (response.data) {
response.data = JSON.parse(response.data)
return response.data
}
return response
}
}
/* Redirect to a specific URL */
let redirect = (url: string) : void => location.assign(url)
/* Redirect to a specific URL */
const redirect = (url: string): void => location.assign(url)
const resize = () => {
const resize = () => {
$('#pageContainer').height(window.innerHeight + "px");
}
}
let setupCore = (languageVars: Record<string, string>) => {
const setupCore = (languageVars: Record<string, string>) => {
Application.languageVars = languageVars
const doc = $(document)
doc.on('click', '#alertNo, #alertOk', hideAlerts)
@@ -83,12 +83,11 @@
resize()
setElementVisibilityByMode()
}
}
// @ts-ignore
let posAlert = (message: string, title='Message') => {
let alertBox = $('#alert')
const posAlert = (message: string, title = 'Message') => {
const alertBox = $('#alert')
alertBox.css('display', 'flex');
alertBox.data('value', '');
$('#alertHeading').text(title);
@@ -97,10 +96,10 @@
$('#alertOk').css('display', 'flex');
$('#alertYes').css('display', 'none');
$('#alertNo').css('display', 'none');
}
}
let confirmation = (message: string, data: any, title='Confirm', submitFunction = (data: any) => {hideAlerts()}) => {
let alert = $('#alert')
const confirmation = (message: string, data: any, title = 'Confirm', submitFunction = (data: any) => {hideAlerts()}) => {
const alert = $('#alert')
$(document).on('click', '#alert #alertYes', () => {
hideAlerts()
@@ -115,46 +114,49 @@
$('#alertOk').css('display', 'none')
$('#alertYes').css('display', 'flex')
$('#alertNo').css('display', 'flex')
}
}
let hideAlerts = () => $('#alert').hide()
const hideAlerts = () => $('#alert').hide()
let turnOnMode = (mode : PosMode) => {
const turnOnMode = (mode: PosMode) => {
Application.mode.push(mode)
setElementVisibilityByMode()
}
}
let turnOffMode = (mode : PosMode) => {
const turnOffMode = (mode: PosMode) => {
Application.mode = Application.mode.filter((value) => value != mode)
setElementVisibilityByMode()
}
}
let toggleMode = (mode: PosMode) => {
if(!isInMode(mode))
const toggleMode = (mode: PosMode) => {
if (!isInMode(mode))
turnOnMode(mode)
else
turnOffMode(mode)
}
}
let clearModes = () => {Application.mode = []}
let isInMode = (mode: PosMode) => Application.mode.includes(mode)
const clearModes = () => {
Application.mode = []
setElementVisibilityByMode()
}
let setElementVisibilityByMode = () => {
const isInMode = (mode: PosMode) => Application.mode.includes(mode)
const setElementVisibilityByMode = () => {
const mode = Application.mode
const elements = $('[data-visible-in-mode]')
elements.each((index, elem) => {
let element = $(elem)
let visibleInModes : PosModes = element.data('visible-in-mode')
const element = $(elem)
const visibleInModes: PosModes = element.data('visible-in-mode')
let showElement = visibleInModes.every( visibleMode => {
const showElement = visibleInModes.every(visibleMode => {
return mode.includes(visibleMode)
});
if(element.hasClass('useVisibility')){
if(showElement) {
if (element.hasClass('useVisibility')) {
if (showElement) {
element.css('visibility', 'visible')
} else element.css('visibility', 'hidden')
} else element.toggle(showElement)
@@ -162,18 +164,18 @@
const invisibleElements = $('[data-invisible-in-mode]')
invisibleElements.each((index, elem) => {
let element = $(elem)
let inVisibleInModes: PosModes = element.data('invisible-in-mode')
let hideElement = inVisibleInModes.some(invisibleMode => {
const element = $(elem)
const inVisibleInModes: PosModes = element.data('invisible-in-mode')
const hideElement = inVisibleInModes.some(invisibleMode => {
return mode.includes(invisibleMode)
})
element.toggle(!hideElement)
})
$('[data-active-in-mode]').each((index, elem) =>{
$('[data-active-in-mode]').each((index, elem) => {
const button = $(elem)
const activeInMode : PosMode = button.data('active-in-mode')
const activeInMode: PosMode = button.data('active-in-mode')
mode.includes(activeInMode)
? button.addClass('active')
@@ -181,6 +183,25 @@
})
}
}
$( () => ajax('/ajax/languageVars', null, 'GET', setupCore, null, null))
const pulseElement = (element: JQuery) => element.addClass('pulse').on('animationend', () => element.removeClass('pulse'))
Array.prototype.where = function<x>(this: x[], property: string, value: any) {
return this.filter( item => (item as any)[property] === value)[0] || null
}
const money = (amount: number) => currency(amount, {fromCents: true})
const moneyFromString = (amount: string) => currency(amount)
//Id generator.
function* newestId(){
let id = 0
while(true){
id++
yield id
}
}
$(() => ajax('/ajax/languageVars', null, 'GET', setupCore, null, null))

View File

@@ -1,9 +1,25 @@
interface OrderScreen{
type OrderScreenData = {
order_screen_pages: order_screen_page[]
sales_categories: sales_category[]
}
type OrderScreen = {
order_screen_pages: order_screen_page[]
last_added_item: orderItem
order_items: orderItem[]
sales_categories: sales_category[]
order_item_id_generator: Generator
selected_item_ids: number[]
}
let OrderScreen : OrderScreen = {
order_screen_pages: null
order_screen_pages: null,
last_added_item: null,
order_items: [],
sales_categories: [],
order_item_id_generator: newestId(),
selected_item_ids: []
}
const loadPageGroup = (e: Event) => {
@@ -14,6 +30,7 @@ const loadPageGroup = (e: Event) => {
$('.pageGroup').hide()
let activeGrid = $(`.pageGroup[data-page-group-id=${pageGroupId}]`)
let navButtons = $('.pageNavigation')
navButtons.css('display', 'flex')
activeGrid.find('.gridPage').length > 1
? navButtons.show()
@@ -22,12 +39,20 @@ const loadPageGroup = (e: Event) => {
activeGrid.css('display', 'inline-flex')
}
const setupOrderScreen = (data: OrderScreen) => {
OrderScreen = data
const setupOrderScreen = (data: OrderScreenData) => {
OrderScreen.order_screen_pages = data.order_screen_pages
OrderScreen.sales_categories = data.sales_categories
let doc = $(document)
doc.on('click', '.nextButton', goToNextPage)
doc.on('click', '.prevButton', goToPrevPage)
doc.on('click', '.loadPageGroup', loadPageGroup)
doc.on('click', '[data-primary-action=item]', itemButtonClicked)
doc.on('click', 'tr', itemRowClicked)
doc.on('click', '.voidButton', voidButtonClicked)
doc.on('dblclick', '.voidButton', voidLastItem)
doc.on('click', '.accumulateButton', () => toggleMode('accumulate'))
turnOnMode('accumulate')
$('.loadPageGroup').first().trigger('click')
}
@@ -43,5 +68,204 @@ const navigatePage = (direction: number) => {
const goToNextPage = () => navigatePage(1)
const goToPrevPage = () => navigatePage(-1)
const addNewItem = (item: item) => {
let salesCategory = OrderScreen.sales_categories.where('id', item.item_category)
const existingOrderItem = isInMode('accumulate') && item.item_type != 'instruction' ? OrderScreen.order_items.where('item', item) : null
let orderItem : orderItem = existingOrderItem || {
id: OrderScreen.order_item_id_generator.next().value,
item: item,
qty: 0,
sales_category: salesCategory,
}
saveOrderItem(orderItem)
renderOrderBox()
}
const renderOrderBox = () => {
const orderBox = $('.orderBoxTable')
const tbody = orderBox.children('tbody')
const newTbody = $('<tbody />')
OrderScreen.order_items.forEach(orderItem => {
const newRow = createOrderRow(orderItem)
newTbody.append(newRow)
if(orderItem.id == OrderScreen.last_added_item?.id){
pulseElement(newRow)
}
if(OrderScreen.selected_item_ids.includes(orderItem.id)){
selectRow(newRow)
}
})
tbody.replaceWith(newTbody)
const element = orderBox.find('tbody tr').last().get()[0]
element.scrollIntoView()
OrderScreen.last_added_item = null
}
const createOrderRow = (orderItem: orderItem) => {
const row = $('.orderBoxTable').EmptyRow()
const price = money(orderItem.item.price1)
row.data('order-item-id', orderItem.id)
row.addClass(`${orderItem.item.item_type}Row`)
row
.setColumnValue(lang('qty_header'), orderItem.qty)
.setColumnValue(lang('item_header'), orderItem.item.item_name)
.setColumnValue(lang('price_header'), price)
.setColumnValue(lang('total_price_header'), price)
.setColumnValue(lang('printgroup_header'), OrderScreen.sales_categories.where('id', orderItem.item.item_category)?.name)
.data('order-item-id', orderItem.id)
return row
}
const saveOrderItem = (orderItem: orderItem) => {
const selectedRows = $('.orderBoxTable tbody tr.selected').get()
const currentQty = orderItem.qty
orderItem.qty = currentQty + 1
if( isInMode('accumulate') && orderItem.qty > 1) {
OrderScreen.order_items = OrderScreen.order_items.map(
existingOrderItem => {
if (existingOrderItem == orderItem) return orderItem
else return existingOrderItem
})
} else if(orderItem.item.item_type == 'instruction' && selectedRows.length > 0){
const selectedOrderItemIds : number[] = selectedRows.map(row => {
const orderItem = OrderScreen.order_items.where('id', $(row).data('order-item-id'))
if (orderItem.item && orderItem.item.item_type != 'instruction') {
return orderItem.id
} else {
return null
}
}).filter(number => number)
selectedOrderItemIds.forEach(id => {
let item = OrderScreen.order_items.where('id', id)
let index = OrderScreen.order_items.indexOf(item) + 1
OrderScreen.order_items.splice(index, 0, orderItem)
})
} else {
OrderScreen.order_items.push(orderItem)
}
OrderScreen.last_added_item = orderItem
return orderItem
}
const itemButtonClicked = (e: JQuery.TriggeredEvent) => {
const existingItemRows = $('.itemRow')
const button = $(e.target).closest('.posButton')
const item : item = button.data('item')
if(item.item_type == 'instruction' && existingItemRows.length < 1) return
addNewItem(item)
}
const itemRowClicked = (e: JQuery.TriggeredEvent) => {
const row = $(e.target).closest('tr')
if(isInMode('void')){
voidRows(row)
turnOffMode('void')
return
}
if(!row.hasClass('selected')) selectRow(row)
else deselectRow(row)
}
const selectRow = (row: JQuery) => {
row.addClass('selected')
const id = row.data('order-item-id')
if(!OrderScreen.selected_item_ids.includes(id))
OrderScreen.selected_item_ids.push(id)
const instructionRows = row.nextUntil('.itemRow')
if(row.hasClass('itemRow') && instructionRows.length){
instructionRows.each((index, row) => {
selectRow($(row))
})
}
}
const deselectRow = (row: JQuery) => {
row.removeClass('selected')
const instructionRows = row.nextUntil('.itemRow')
OrderScreen.selected_item_ids = OrderScreen.selected_item_ids.filter(id => id != row.data('order-item-id'))
if(row.hasClass('itemRow') && instructionRows.length){
deselectRow(instructionRows)
}
}
const deleteRow = (row: JQuery) => row.find('*:not(.hidden)').slideUp('fast', () => {
OrderScreen.order_items = OrderScreen.order_items.filter(orderItem => orderItem.id != row.data('order-item-id'))
row.remove()
})
const voidInstructionRow = (row: JQuery) => {
const parentRow = row.prevAll('.itemRow').first()
const parentOrderItem = OrderScreen.order_items.where('id', parentRow.data('order-item-id'))
if(!parentRow.hasClass('selected') || (parentOrderItem && parentOrderItem?.qty == 0) || !parentOrderItem)
decrementQty(OrderScreen.order_items.where('id', row.data('order-item-id')))
}
const voidItemRow = (row : JQuery) => {
const newQty = Number(row.getColumnValue(lang('qty_header'))) - 1
const orderItem = OrderScreen.order_items.where('id', row.data('order-item-id'))
const instructionRows = row.nextUntil('.itemRow')
if(newQty < 1)
voidRows(instructionRows)
decrementQty(orderItem)
}
const voidRow = (row: JQuery) => {
if(row.hasClass('itemRow')) voidItemRow(row)
else voidInstructionRow(row)
}
const voidRows = (rows: JQuery) => rows.each((index, row) => voidRow($(row)))
const voidButtonClicked = () => {
const selectedRows = $('.orderBox tr.selected')
if(isInMode('void')){
turnOffMode('void')
} else if(selectedRows.length){
voidRows(selectedRows)
} else {
turnOnMode('void')
}
}
const voidLastItem = () => {
if(OrderScreen.order_items.length < 1) return
let orderItem = OrderScreen.order_items[OrderScreen.order_items.length-1]
let row = getOrderItemRow(orderItem)
voidRows(row)
}
const decrementQty = (orderItem: orderItem) => {
const row = getOrderItemRow(orderItem)
if(orderItem.qty <= 1){
OrderScreen.order_items = OrderScreen.order_items.filter(item => item != orderItem)
deleteRow(row)
} else {
orderItem.qty--
row.setColumnValue(lang('qty_header'), orderItem.qty)
}
}
const getOrderItemRow = (orderItem: orderItem) => $('tr').filterByData('order-item-id', orderItem.id)
$(() => ajax('/orderScreen/getOrderScreenData/1', null, 'get', setupOrderScreen, null, null) )

View File

@@ -0,0 +1,47 @@
interface JQuery {
getColumnValue(columnHeading: string) : string
setColumnValue(columnHeading: string, value: any) : JQuery
getColumnIndex(columnHeading: string) : number
EmptyRow() : JQuery<HTMLTableRowElement>
filterByData(prop: string, value: any) : JQuery
}
$.fn.EmptyRow = function(this: JQuery) {
const headingRow = this.find('th').first().closest('tr')
const headingCells = headingRow.find('th')
const newRow = $('<tr/>')
headingCells.each( (index, cell) => {
const newCell = $('<td/>')
const attributes = Array.from(cell.attributes)
newCell.data('column', cell.innerText.trim())
newCell.data('column-index', index)
attributes.forEach(attribute => newCell.attr(attribute.name, attribute.value))
newRow.append(newCell)
})
return newRow as JQuery<HTMLTableRowElement>
}
$.fn.getColumnIndex = function(this: JQuery, columnHeading: string){
return this
.find('td')
.filterByData('column', columnHeading)
.data('column-index')
}
$.fn.getColumnValue = function(this: JQuery, columnHeading: string){
return this.find('td').filterByData('column', columnHeading).text()
}
$.fn.setColumnValue = function(this: JQuery, columnHeading: string, value: any){
this.find('td').filterByData('column', columnHeading).text(value)
return this
}
$.fn.filterByData = function(prop: string, val: any) {
return this.filter(
function() {
return $(this).data(prop)==val
}
)
}

View File

@@ -1,6 +1,25 @@
type PosMode = "edit" | "void" | "transfer" | "default" | "tableSelected" | "decorationSelected" | "activeTableSelected" | "merge" | "reservedTableSelected"
type PosMode = "edit" | "void" | "transfer" | "default" | "tableSelected" | "decorationSelected" | "activeTableSelected" | "merge" | "reservedTableSelected" | "accumulate"
type PosModes = PosMode[]
interface order {
clerk: string
split: boolean
items: orderItem[]
}
interface orderItem {
id: number,
qty: number,
sales_category: sales_category
item: item
}
interface printGroup {
id: number,
name: string,
printer: number,
venue_id: number,
}
interface ajaxResult {
status: string
@@ -69,3 +88,27 @@ interface keyboard {
interface order_screen_page{id: number; order_screen_page_group_id: number; grid_id: number}
interface grid {id: number; grid_name: string; grid_rows: number; grid_cols: number; grid_data: string}
interface item {
id: number
item_code: string
item_category: number
item_name: string
item_type: string
price1: number
price2: number
price3: number
price4: number
price5: number
}
type sales_category = {
id: number
parent: number
name: string
print_group: string
venue_id: number
}
interface Array<T> {
where(property: string, value: any): T
}

View File

@@ -1,4 +1,4 @@
declare namespace currency {
declare namespace currency {
type Any = number | string | currency;
type Format = (currency?: currency, opts?: Options) => string;
interface Constructor {
@@ -18,9 +18,9 @@
format?: currency.Format,
fromCents?: boolean
}
}
}
interface currency {
interface currency {
add(number: currency.Any): currency;
subtract(number: currency.Any): currency;
multiply(number: currency.Any): currency;
@@ -33,6 +33,6 @@
toJSON(): number;
readonly intValue: number;
readonly value: number;
}
}
declare const currency: currency.Constructor;
declare const currency: currency.Constructor;

View File

@@ -15,3 +15,12 @@
--posbutton-background-active: #20282D -webkit-gradient(linear, left top, left bottom, color-stop(3%,#20282D), color-stop(51%,#252E34), color-stop(100%,#222A30)) 0 top
--void-button-background: red -webkit-gradient(linear, left top, left bottom, color-stop(3%,darkred), color-stop(51%,darkred), color-stop(100%,red)) 0 top
/** Order Screen **/
--orderbox-header-background: #888
--orderbox-row-background: var(--pos-header-background)
--orderbox-selected-row-background: #dd7f37
/** Order Box/Payment Splitter Box **/
--pulse-first-color: #ffa93e
--pulse-second-color: #dd7f37
--pulse-final-color: var(--orderbox-row-background)

View File

@@ -3,6 +3,15 @@
src: url("/fonts/OpenSans-Regular.ttf") format('truetype')
font-style: normal
@font-face
font-family: "manrope"
src: url("/fonts/OpenSans-SemiBold.ttf") format('truetype')
font-weight: bold
@font-face
font-family: "manrope"
src: url("/fonts/OpenSans-Light.ttf") format('truetype')
font-weight: 100
*
margin: 0
@@ -10,10 +19,19 @@
box-sizing: border-box
font-family: 'manrope', sans-serif
scroll-behavior: smooth
cursor: pointer
-webkit-touch-callout: none
-webkit-user-select: none
-khtml-user-select: none
-moz-user-select: none
-ms-user-select: none
user-select: none
.rtl
direction: rtl
input[type=text], select, textarea
padding-left: 1em
padding-right: 1em
@@ -86,3 +104,19 @@ body
.invisible
visibility: hidden
.hidden
display: none
.pulse
animation-name: color
animation-duration: 300ms
animation-iteration-count: 1
@keyframes color
0%
background-color: var(--pulse-first-color)
50%
background-color: var(--pulse-second-color)
100%
background-color: var(--pulse-final-color)

View File

@@ -26,6 +26,7 @@
.orderBox
flex-basis: 75%
background: var(--global-bgcolor)
overflow-y: auto
.orderBoxInfo
flex-basis: 5%
@@ -90,7 +91,7 @@
.active
border-bottom: none
#pageContainer
#pageGroupContainer
@include flex-column
@include flex-column-item
justify-content: flex-end
@@ -153,13 +154,20 @@
.buttonImg
padding: 0.6em
flex-basis: 80%
flex-basis: 65%
width: 100%
flex-shrink: 0
flex-grow: 0
.text
@include flex
flex-basis: 20%
align-items: flex-start
flex-grow: 0
flex-shrink: 0
flex-basis: 35%
width: 100%
overflow: hidden
font-size: 0.9em
.hasImage.doubleWidth
flex-direction: row
@@ -189,11 +197,55 @@
flex-basis: 50%
height: 100%
.pageNavigation
@include flex
@include flex-column-item
display: none
flex-basis: 15%
> *
@include flex-item
.orderBoxTable
width: 100%
border-collapse: collapse
tr
background: var(--orderbox-row-background)
.selected
background: var(--orderbox-selected-row-background)
thead tr
background: var(--orderbox-header-background)
th
font-weight: normal
text-align: center
padding: 0.2em 0.5em
tr
td, th
text-align: center
font-size: 0.9em
td
padding: 1em 0.5em
font-weight: bold
.itemCell
text-align: center
width: 70%
td.itemCell
text-align: left
tr.instructionRow
td.itemCell
padding-left: 2em
font-weight: 100
.qtyCell, .totalPriceCell, .printGroupCell
font-size: 0

View File

@@ -2,8 +2,6 @@
<html>
<head>
<title>DredgePOS</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://unpkg.com/current-device/umd/current-device.min.js"></script>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name = "viewport" content = "user-scalable = no ,shrink-to-fit=yes" />
<link rel="manifest" href="/manifest.webmanifest">
@@ -12,13 +10,24 @@
<div id="pageContainer">
<div id="leftColumn">
<h1 class="tableHeading"><!--[lang:active_table]--></h1>
<h1 class="tableHeading"><!--[var: orderNumber]--></h1>
<div class="tableInfo">
<a href="#" class="posButton"><!--[lang:covers]--></a>
<a class="posHeader">Logged in as <!--[arr:clerk|clerk_name]--></a></a>
<!--[var: coverSelectorButton]-->
<a class="posButton">Logged in as <!--[arr:clerk|clerk_name]--></a>
</div>
<div class="orderBox">
<table class="orderBoxTable">
<thead>
<tr>
<th class="orderBoxCell qtyCell"><!--[lang:qty_header]--></th>
<th class="orderBoxCell itemCell"><!--[lang:item_header]--></th>
<th class="orderBoxCell unitPriceCell hidden"><!--[lang:price_header]--></th>
<th class="orderBoxCell totalPriceCell"><!--[lang:total_price_header]--></th>
<th class="orderBoxCell printGroupCell"><!--[lang:printgroup_header]--></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="orderBoxInfo"></div>
<div class="orderBoxFooter">
@@ -44,8 +53,8 @@
</div>
<div class="functionColumn">
<a class="posButton"></a>
<a class="posButton"></a>
<a class="posButton voidButton"><!--[lang:void]--></a>
<a class="posButton accumulateButton" data-active-in-mode="accumulate"><!--[lang:accumulate_function]--></a>
<a class="posButton voidButton" data-active-in-mode="void"><!--[lang:void]--></a>
<a class="posButton"></a>
</div>
<div class="functionColumn"></div>
@@ -55,7 +64,7 @@
<div id="pageList">
<!--[var:categoryList]-->
</div>
<div id="pageContainer">
<div id="pageGroupContainer">
<!--[var:pageGroups]-->
</div>
<div class="pageNavigation">

View File

@@ -1 +1 @@
<span class="buttonImg" style="background-image:url(images/items/<!--[var:image]-->);"></span>
<span class="buttonImg" style="background-image:url(/images/items/<!--[var:image]-->);"></span>

View File

@@ -0,0 +1 @@
<a href="#" class="posButton coverSelector"><!--[var:covers]--></a>