From 4de8f20d003df639a6fa63c93dda34382e88eeb4 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 2 Jan 2022 20:05:21 +1000 Subject: [PATCH 1/9] Fixed data-item attribute --- OrderScreen.module.fs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/OrderScreen.module.fs b/OrderScreen.module.fs index a9c7492..da636c1 100644 --- a/OrderScreen.module.fs +++ b/OrderScreen.module.fs @@ -1,5 +1,6 @@ module OrderScreen open System.Security.Cryptography.Xml +open System.Web open DredgeFramework open DredgePos open FSharp.Collections @@ -10,10 +11,9 @@ open Theme let htmlAttributes (attributes: Map) = " " + (attributes |> Map.toArray - |> Array.map (fun (attribute, value) -> attribute+"="+value) + |> Array.map (fun (attribute, value) -> attribute+"=\""+HttpUtility.HtmlEncode value + "\"") |> String.concat " ") - let getAllPageGrids () = Entity.getAllInVenue |> Array.filter(fun pageGroup -> pageGroup.grid_id <> 0) |> Array.map(fun pageGroup -> (Entity.getById pageGroup.grid_id), pageGroup) @@ -24,11 +24,7 @@ let getImageButtonData (button: button) = let extraData = map [ - "data-item-code", item.item_code - "data-item-price", item.price1.ToString() - "data-item-name", item.item_name - "data-item-type", item.item_type - "data-item-category", item.item_category.ToString() + "data-item", jsonEncode item ] |> htmlAttributes {| From 85722fa69274d8257d0b5ea3265561e960ca974b Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 4 Jan 2022 15:04:55 +1000 Subject: [PATCH 2/9] Updates to order screen --- AjaxController.fs | 4 +- DredgePos.fsproj | 1 + Language.module.fs | 6 +- OrderScreen.module.fs | 8 +- Orders.module.fs | 3 + PageController.fs | 22 +- Program.fs | 3 +- Types.fs | 31 +- wwwroot/languages/english/main.json | 2 + wwwroot/scripts/external/currency.min.js | 12 + wwwroot/scripts/ts/dredgepos.core.ts | 367 +++++++++--------- wwwroot/scripts/ts/dredgepos.orderScreen.ts | 232 ++++++++++- wwwroot/scripts/ts/dredgepos.tables.ts | 47 +++ wwwroot/scripts/ts/types.ts | 45 ++- wwwroot/scripts/ts/typings/currency.d.ts | 36 +- wwwroot/styles/sass/dark.theme.sass | 9 + wwwroot/styles/sass/dredgepos.core.sass | 36 +- .../styles/sass/dredgepos.orderScreen.sass | 64 ++- wwwroot/themes/restaurant/orderScreen.tpl.htm | 31 +- .../orderScreen/button_image.tpl.htm | 2 +- .../orderScreen/cover_selector_button.tpl.htm | 1 + 21 files changed, 720 insertions(+), 242 deletions(-) create mode 100644 Orders.module.fs create mode 100644 wwwroot/scripts/external/currency.min.js create mode 100644 wwwroot/scripts/ts/dredgepos.tables.ts create mode 100644 wwwroot/themes/restaurant/orderScreen/cover_selector_button.tpl.htm diff --git a/AjaxController.fs b/AjaxController.fs index 31a4683..f4a1731 100644 --- a/AjaxController.fs +++ b/AjaxController.fs @@ -54,9 +54,9 @@ let getFloorplanData (id: int) = |> json let getOrderScreenData (id: int) = - let pages = Entity.getAllInVenue {| - order_screen_pages = pages + order_screen_pages = Entity.getAllInVenue + sales_categories = Entity.getAllInVenue |} |> ajaxSuccess |> json diff --git a/DredgePos.fsproj b/DredgePos.fsproj index 5c84b5b..18cab05 100644 --- a/DredgePos.fsproj +++ b/DredgePos.fsproj @@ -15,6 +15,7 @@ + diff --git a/Language.module.fs b/Language.module.fs index 311faa2..8528fbc 100644 --- a/Language.module.fs +++ b/Language.module.fs @@ -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 \ No newline at end of file diff --git a/OrderScreen.module.fs b/OrderScreen.module.fs index da636c1..3f02775 100644 --- a/OrderScreen.module.fs +++ b/OrderScreen.module.fs @@ -11,7 +11,7 @@ open Theme let htmlAttributes (attributes: Map) = " " + (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 @@ -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 ] diff --git a/Orders.module.fs b/Orders.module.fs new file mode 100644 index 0000000..6a8ebb7 --- /dev/null +++ b/Orders.module.fs @@ -0,0 +1,3 @@ +module Orders + +let getHighestOrderNumber () = 6 \ No newline at end of file diff --git a/PageController.fs b/PageController.fs index dbc1ae6..a839b1b 100644 --- a/PageController.fs +++ b/PageController.fs @@ -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 - |> Array.filter (fun category -> category.id <> 0) + Entity.getAllInVenue + |> 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] diff --git a/Program.fs b/Program.fs index 2d5f28b..e45cafa 100644 --- a/Program.fs +++ b/Program.fs @@ -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 } diff --git a/Types.fs b/Types.fs index 3c8445e..daf1637 100644 --- a/Types.fs +++ b/Types.fs @@ -29,11 +29,20 @@ type floorplan_table = { } [] -type category = { +type print_group = { id: int - category_name: string - category_print_group: string - category_department: string + name: string + printer: int + venue_id: int +} + +[] +type sales_category = { + id: int + parent: int + name: string + print_group: string + venue_id: int } [] @@ -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} [] -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} [] 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 -} \ No newline at end of file + price1: int + price2: int + price3: int + price4: int + price5: int +} diff --git a/wwwroot/languages/english/main.json b/wwwroot/languages/english/main.json index a1fa358..45f93c3 100644 --- a/wwwroot/languages/english/main.json +++ b/wwwroot/languages/english/main.json @@ -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", diff --git a/wwwroot/scripts/external/currency.min.js b/wwwroot/scripts/external/currency.min.js new file mode 100644 index 0000000..290d3a3 --- /dev/null +++ b/wwwroot/scripts/external/currency.min.js @@ -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 + +const Application: ApplicationState = { + keyboard: null, + mode: [], + languageVars: {} +} - /** Parses a language variable. */ - let lang = (key: string, replacements?: string[] | string) => { - let finalValue = Application.languageVars[key] || '' +/** 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 - finalValue = finalValue.replace(`[${correctIndex}]`, replacement) - }) + replacements.forEach((replacement, index) => { + const correctIndex = index + 1 + finalValue = finalValue.replace(`[${correctIndex}]`, replacement) + }) - return finalValue - } + return finalValue +} - /** Check if a variable is defined */ - let defined = (variable: any) => { - return typeof variable !== 'undefined' - } +/** 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) => { - data = (data == null) ? data : JSON.stringify(data) - return $.ajax({ +/** 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') + successFunction(JSON.parse(response.data)) + 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. + */ +const ajaxSync = (endpoint: string, data?: any, method = 'POST') => { + const response = JSON.parse( + $.ajax({ url: endpoint, method: method, - data: data, - success: (response: ajaxResult) => { - if(successFunction && response.status == 'success') - successFunction(JSON.parse(response.data)) - else if (errorFunction && response.status != 'success'){ - errorFunction(JSON.parse(response.data)) - } - }, - error: (error) => console.log(error.statusCode), - beforeSend: beforeFunction + data: JSON.stringify(data), + async: false, + }).responseText) + + if (response.data) { + response.data = JSON.parse(response.data) + return response.data + } + + return response +} + +/* Redirect to a specific URL */ +const redirect = (url: string): void => location.assign(url) + +const resize = () => { + $('#pageContainer').height(window.innerHeight + "px"); +} + +const setupCore = (languageVars: Record) => { + Application.languageVars = languageVars + const doc = $(document) + doc.on('click', '#alertNo, #alertOk', hideAlerts) + window.addEventListener('resize', resize) + resize() + + setElementVisibilityByMode() +} + + +const posAlert = (message: string, title = 'Message') => { + const alertBox = $('#alert') + alertBox.css('display', 'flex'); + alertBox.data('value', ''); + $('#alertHeading').text(title); + $('#alertMessage').text(message); + + $('#alertOk').css('display', 'flex'); + $('#alertYes').css('display', 'none'); + $('#alertNo').css('display', 'none'); +} + +const confirmation = (message: string, data: any, title = 'Confirm', submitFunction = (data: any) => {hideAlerts()}) => { + const alert = $('#alert') + + $(document).on('click', '#alert #alertYes', () => { + hideAlerts() + submitFunction(data) + $(document).off('click', '#alert #alertYes') + }) + + alert.css('display', 'flex') + $('#alertHeading').html(title) + $('#alertMessage').html(message) + + $('#alertOk').css('display', 'none') + $('#alertYes').css('display', 'flex') + $('#alertNo').css('display', 'flex') +} + + +const hideAlerts = () => $('#alert').hide() + +const turnOnMode = (mode: PosMode) => { + Application.mode.push(mode) + setElementVisibilityByMode() +} + +const turnOffMode = (mode: PosMode) => { + Application.mode = Application.mode.filter((value) => value != mode) + setElementVisibilityByMode() +} + +const toggleMode = (mode: PosMode) => { + if (!isInMode(mode)) + turnOnMode(mode) + else + turnOffMode(mode) +} + +const clearModes = () => { + Application.mode = [] + 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) => { + const element = $(elem) + const visibleInModes: PosModes = element.data('visible-in-mode') + + const showElement = visibleInModes.every(visibleMode => { + return mode.includes(visibleMode) + }); + + if (element.hasClass('useVisibility')) { + if (showElement) { + element.css('visibility', 'visible') + } else element.css('visibility', 'hidden') + } else element.toggle(showElement) + }) + + const invisibleElements = $('[data-invisible-in-mode]') + invisibleElements.each((index, elem) => { + 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) => { + const button = $(elem) + const activeInMode: PosMode = button.data('active-in-mode') + + mode.includes(activeInMode) + ? button.addClass('active') + : button.removeClass('active') + + }) + +} + +const pulseElement = (element: JQuery) => element.addClass('pulse').on('animationend', () => element.removeClass('pulse')) + +Array.prototype.where = function(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 } +} - /* - 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( - $.ajax({ - url: endpoint, - method: method, - data: JSON.stringify(data), - async:false, - }).responseText) - - 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) - - - - const resize = () => { - $('#pageContainer').height(window.innerHeight + "px"); - } - - let setupCore = (languageVars: Record) => { - Application.languageVars = languageVars - const doc = $(document) - doc.on('click', '#alertNo, #alertOk', hideAlerts) - window.addEventListener('resize', resize) - resize() - - setElementVisibilityByMode() - } - - - // @ts-ignore - let posAlert = (message: string, title='Message') => { - let alertBox = $('#alert') - alertBox.css('display', 'flex'); - alertBox.data('value', ''); - $('#alertHeading').text(title); - $('#alertMessage').text(message); - - $('#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') - - $(document).on('click', '#alert #alertYes', () => { - hideAlerts() - submitFunction(data) - $(document).off('click', '#alert #alertYes') - }) - - alert.css('display', 'flex') - $('#alertHeading').html(title) - $('#alertMessage').html(message) - - $('#alertOk').css('display', 'none') - $('#alertYes').css('display', 'flex') - $('#alertNo').css('display', 'flex') - } - - - let hideAlerts = () => $('#alert').hide() - - let turnOnMode = (mode : PosMode) => { - Application.mode.push(mode) - setElementVisibilityByMode() - } - - let turnOffMode = (mode : PosMode) => { - Application.mode = Application.mode.filter((value) => value != mode) - setElementVisibilityByMode() - - } - - let toggleMode = (mode: PosMode) => { - if(!isInMode(mode)) - turnOnMode(mode) - else - turnOffMode(mode) - } - - let clearModes = () => {Application.mode = []} - let isInMode = (mode: PosMode) => Application.mode.includes(mode) - - let 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') - - let showElement = visibleInModes.every( visibleMode => { - return mode.includes(visibleMode) - }); - - if(element.hasClass('useVisibility')){ - if(showElement) { - element.css('visibility', 'visible') - } else element.css('visibility', 'hidden') - } else element.toggle(showElement) - }) - - 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 => { - return mode.includes(invisibleMode) - }) - element.toggle(!hideElement) - }) - - - $('[data-active-in-mode]').each((index, elem) =>{ - const button = $(elem) - const activeInMode : PosMode = button.data('active-in-mode') - - mode.includes(activeInMode) - ? button.addClass('active') - : button.removeClass('active') - - }) - - } - -$( () => ajax('/ajax/languageVars', null, 'GET', setupCore, null, null)) \ No newline at end of file +$(() => ajax('/ajax/languageVars', null, 'GET', setupCore, null, null)) \ No newline at end of file diff --git a/wwwroot/scripts/ts/dredgepos.orderScreen.ts b/wwwroot/scripts/ts/dredgepos.orderScreen.ts index b9f69c6..1a9ad4c 100644 --- a/wwwroot/scripts/ts/dredgepos.orderScreen.ts +++ b/wwwroot/scripts/ts/dredgepos.orderScreen.ts @@ -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 = $('') + 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) ) \ No newline at end of file diff --git a/wwwroot/scripts/ts/dredgepos.tables.ts b/wwwroot/scripts/ts/dredgepos.tables.ts new file mode 100644 index 0000000..16bbe43 --- /dev/null +++ b/wwwroot/scripts/ts/dredgepos.tables.ts @@ -0,0 +1,47 @@ +interface JQuery { + getColumnValue(columnHeading: string) : string + setColumnValue(columnHeading: string, value: any) : JQuery + getColumnIndex(columnHeading: string) : number + EmptyRow() : JQuery + 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 = $('') + headingCells.each( (index, cell) => { + const newCell = $('') + 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 +} + +$.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 + } + ) +} \ No newline at end of file diff --git a/wwwroot/scripts/ts/types.ts b/wwwroot/scripts/ts/types.ts index 3679a00..aeaf028 100644 --- a/wwwroot/scripts/ts/types.ts +++ b/wwwroot/scripts/ts/types.ts @@ -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 { + where(property: string, value: any): T +} \ No newline at end of file diff --git a/wwwroot/scripts/ts/typings/currency.d.ts b/wwwroot/scripts/ts/typings/currency.d.ts index 54769d5..8626874 100644 --- a/wwwroot/scripts/ts/typings/currency.d.ts +++ b/wwwroot/scripts/ts/typings/currency.d.ts @@ -1,26 +1,26 @@ - declare namespace currency { +declare namespace currency { type Any = number | string | currency; type Format = (currency?: currency, opts?: Options) => string; interface Constructor { - (value: currency.Any, opts?: currency.Options): currency, - new(value: currency.Any, opts?: currency.Options): currency + (value: currency.Any, opts?: currency.Options): currency, + new(value: currency.Any, opts?: currency.Options): currency } interface Options { - symbol?: string, - separator?: string, - decimal?: string, - errorOnInvalid?: boolean, - precision?: number, - increment?: number, - useVedic?: boolean, - pattern?: string, - negativePattern?: string, - format?: currency.Format, - fromCents?: boolean + symbol?: string, + separator?: string, + decimal?: string, + errorOnInvalid?: boolean, + precision?: number, + increment?: number, + useVedic?: boolean, + pattern?: string, + negativePattern?: string, + 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; diff --git a/wwwroot/styles/sass/dark.theme.sass b/wwwroot/styles/sass/dark.theme.sass index 0ae68af..325fbf6 100644 --- a/wwwroot/styles/sass/dark.theme.sass +++ b/wwwroot/styles/sass/dark.theme.sass @@ -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) diff --git a/wwwroot/styles/sass/dredgepos.core.sass b/wwwroot/styles/sass/dredgepos.core.sass index 1cfe8b8..f91f57c 100644 --- a/wwwroot/styles/sass/dredgepos.core.sass +++ b/wwwroot/styles/sass/dredgepos.core.sass @@ -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 @@ -85,4 +103,20 @@ body background: var(--void-button-background) .invisible - visibility: hidden \ No newline at end of file + 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) \ No newline at end of file diff --git a/wwwroot/styles/sass/dredgepos.orderScreen.sass b/wwwroot/styles/sass/dredgepos.orderScreen.sass index 2777b1f..f3a3c7a 100644 --- a/wwwroot/styles/sass/dredgepos.orderScreen.sass +++ b/wwwroot/styles/sass/dredgepos.orderScreen.sass @@ -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 @@ -100,7 +101,7 @@ -ms-overflow-style: none ::-webkit-scrollbar - display: none + display: none .pageGroup /*display: inline-flex*/ @@ -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 \ No newline at end of file + @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 \ No newline at end of file diff --git a/wwwroot/themes/restaurant/orderScreen.tpl.htm b/wwwroot/themes/restaurant/orderScreen.tpl.htm index 02a9bc4..e4f7c3b 100644 --- a/wwwroot/themes/restaurant/orderScreen.tpl.htm +++ b/wwwroot/themes/restaurant/orderScreen.tpl.htm @@ -1,9 +1,7 @@ - DredgePOS - - + DredgePOS @@ -12,13 +10,24 @@
-

+

- + + + + + + + + + + + +
@@ -44,8 +53,8 @@
- - + +
@@ -55,8 +64,8 @@
-
- +
+
- + From fc4a5d8624c90ae32db33317e1757920d6aebd15 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 6 Jan 2022 12:01:09 +1000 Subject: [PATCH 4/9] Finalization of order box behavior --- OrderScreen.module.fs | 17 +-- PageController.fs | 1 + Theme.module.fs | 19 ++- wwwroot/languages/english/main.json | 4 +- wwwroot/scripts/ts/dredgepos.floorplan.ts | 11 +- wwwroot/scripts/ts/dredgepos.orderScreen.ts | 110 ++++++++++++++---- .../styles/sass/dredgepos.orderScreen.sass | 28 +++++ .../restaurant/components/posButton.tpl.htm | 3 + wwwroot/themes/restaurant/orderScreen.tpl.htm | 14 +-- 9 files changed, 163 insertions(+), 44 deletions(-) create mode 100644 wwwroot/themes/restaurant/components/posButton.tpl.htm diff --git a/OrderScreen.module.fs b/OrderScreen.module.fs index 3f02775..105ef1a 100644 --- a/OrderScreen.module.fs +++ b/OrderScreen.module.fs @@ -1,18 +1,12 @@ module OrderScreen -open System.Security.Cryptography.Xml -open System.Web open DredgeFramework open DredgePos +open DredgePos.Types open FSharp.Collections open Thoth.Json.Net open Types open Theme -let htmlAttributes (attributes: Map) = - " " + (attributes - |> Map.toArray - |> Array.map (fun (attribute, value) -> attribute+"='"+HttpUtility.HtmlEncode value + "'") - |> String.concat " ") let getAllPageGrids () = Entity.getAllInVenue |> Array.filter(fun pageGroup -> pageGroup.grid_id <> 0) @@ -81,6 +75,15 @@ let renderPageGroup (pageGroup: order_screen_page_group) (pageHTML: string) = ] loadTemplateWithVars "orderScreen/page_group" vars +let categoryPosButton (category: sales_category) = PosButton (language.getAndReplace "print_with" [category.name]) "categoryOverrideButton" "" + +let generateSalesCategoryOverrideButtons () = + Entity.getAllInVenue + |> Array.map categoryPosButton + |> Array.append ([|PosButton (language.getAndReplace "print_with" ["default"]) "categoryOverrideButton" ""|]) + |> String.concat "\n" + + let getPagesHTML (gridInfo: grid * order_screen_page_group) = let grid, pageGroup = gridInfo diff --git a/PageController.fs b/PageController.fs index a839b1b..3bfa88f 100644 --- a/PageController.fs +++ b/PageController.fs @@ -71,6 +71,7 @@ let loadOrderScreen (ctx: HttpContext) (tableNumber: int) : HttpHandler = "orderNumber", orderNumber "coverSelectorButton", coverSelectorButton "covers", coverString + "salesCategoryOverrideButtons", OrderScreen.generateSalesCategoryOverrideButtons () ] let styles = ["dredgepos.orderScreen.css"] diff --git a/Theme.module.fs b/Theme.module.fs index 94b3eb5..3f18516 100644 --- a/Theme.module.fs +++ b/Theme.module.fs @@ -1,6 +1,6 @@ module Theme -open System +open System.Web open System.IO open System.Collections.Generic open System.Text.RegularExpressions @@ -137,4 +137,19 @@ let loadTemplateWithVarsAndScripts templateName vars scripts = let loadTemplateWithVarsAndStyles = loadTemplateWithVarsAndScripts let loadTemplateWithVarsScriptsAndStyles templateName vars scripts styles = - loadTemplateWithVarsArraysScriptsAndStyles templateName vars Map.empty> scripts styles \ No newline at end of file + loadTemplateWithVarsArraysScriptsAndStyles templateName vars Map.empty> scripts styles + + +let htmlAttributes (attributes: Map) = + " " + (attributes + |> Map.toArray + |> Array.map (fun (attribute, value) -> attribute+"='"+HttpUtility.HtmlEncode value + "'") + |> String.concat " ") + +let PosButton (text: string) (classes: string) (attributes: string) = + let vars = map [ + "text", text + "classes", classes + "attributes", attributes + ] + loadTemplateWithVars "components/posButton" vars \ No newline at end of file diff --git a/wwwroot/languages/english/main.json b/wwwroot/languages/english/main.json index 45f93c3..cb21f77 100644 --- a/wwwroot/languages/english/main.json +++ b/wwwroot/languages/english/main.json @@ -87,5 +87,7 @@ "partial_table":"Partial Table", "paying_table":"Paying Table [1]", "msgbox_amount_select":"Enter Amount", - "msgbox_qty_select":"How many would you like to select?" + "msgbox_qty_select":"How many would you like to select?", + + "void_mode": "Void Mode is ON." } \ No newline at end of file diff --git a/wwwroot/scripts/ts/dredgepos.floorplan.ts b/wwwroot/scripts/ts/dredgepos.floorplan.ts index 9e29c97..abbc912 100644 --- a/wwwroot/scripts/ts/dredgepos.floorplan.ts +++ b/wwwroot/scripts/ts/dredgepos.floorplan.ts @@ -221,6 +221,8 @@ const setupTableEvents = (tableGroup: Konva.Group) => { tableGroup.on('click', tableClicked) tableGroup.on('tap', tableClicked) + tableGroup.on('dbltap', tableDblClicked) + tableGroup.on('dblclick', tableDblClicked) tableGroup.on('dragend', tableGroupTransformed) tableShape.on('transformend', tableShapeTransformed) } @@ -438,7 +440,14 @@ const selectTable = (tableShape: Konva.Shape) => { const updateCoverText = (table:table) => $('.selectedTableCovers').text(lang('covers', table.default_covers.toString())) -const tableClicked = (event: Konva.KonvaEventObject) => { +const tableDblClicked = (event: Konva.KonvaEventObject) => { + let tableShape = getTableShapeFromGroup(event.currentTarget as Konva.Group) + const table = getTableDataFromShape(tableShape) + redirect(`/order/${table.table_number}`) +} + + + const tableClicked = (event: Konva.KonvaEventObject) => { let tableShape = getTableShapeFromGroup(event.currentTarget as Konva.Group) const table = getTableDataFromShape(tableShape) diff --git a/wwwroot/scripts/ts/dredgepos.orderScreen.ts b/wwwroot/scripts/ts/dredgepos.orderScreen.ts index e974f11..d34aa60 100644 --- a/wwwroot/scripts/ts/dredgepos.orderScreen.ts +++ b/wwwroot/scripts/ts/dredgepos.orderScreen.ts @@ -68,12 +68,58 @@ const navigatePage = (direction: number) => { const goToNextPage = () => navigatePage(1) const goToPrevPage = () => navigatePage(-1) -const addNewItem = (item: item, qty = 1) => { +const addItemToOrderBox = (orderItem:orderItem) => { const orderBox = $('.orderBoxTable tbody') - const lastRow = orderBox.find('tr').last() - const salesCategory = OrderScreen.sales_categories.where('id', item.item_category) + let selectedRows = orderBox.find('tr.selected') + let lastRow : JQuery = selectedRows.length ? selectedRows.first() : orderBox.find('tr').last() + const existingRow = orderBox.find(`.itemCell:contains("${orderItem.item.item_name}")`).closest('tr').last() - const existingRow = orderBox.find(`.itemCell:contains("${item.item_name}")`).closest('tr').last() + //If accumulating, just increase the quantity of the existing row. + if(existingRow.length > 0 && isInMode('accumulate')){ + incrementRowQty(existingRow, orderItem.qty) + scrollToElement(existingRow) + existingRow.pulse() + } else { + const newRow = createOrderRow(orderItem) + lastRow.length > 0 + ? lastRow.after(newRow) + : orderBox.append(newRow) + scrollToElement(newRow) + newRow.pulse() + } + + deselectRow(orderBox.find('tr')) +} + + +const addInstructionToOrderBox = (instruction: orderItem) => { + const orderBox = $('.orderBoxTable tbody') + let selectedRows = orderBox.find('tr.selected') + const newRow = createOrderRow(instruction) + + //If no items are added, then you can't add an instruction row. + if(!orderBox.find('tr.itemRow').length) return + + if(selectedRows.length > 0){ + selectedRows.each( (_, row) => { + const selectedRow = $(row) + const parentRow = getParentRow(selectedRow) + + if(parentRow.is(selectedRow) || !parentRow.hasClass('selected')) { + const newRow = createOrderRow(instruction) + getLastInstructionRow(selectedRow).after(newRow.pulse()) + } + }) + return + } + + orderBox.append(newRow.pulse()) +} + + +const addNewItem = (item: item, qty = 1) => { + + const salesCategory = OrderScreen.sales_categories.where('id', item.item_category) const orderItem : orderItem = { id: OrderScreen.order_item_id_generator.next().value, @@ -82,17 +128,37 @@ const addNewItem = (item: item, qty = 1) => { sales_category: salesCategory, } - if(existingRow.length > 0 && isInMode('accumulate') && orderItem.item.item_type != "instruction"){ - incrementRowQty(existingRow, qty) - existingRow.pulse() - } else { - const newRow = createOrderRow(orderItem) - lastRow.length > 0 - ? lastRow.after(newRow) - : orderBox.append(newRow) - newRow.pulse() + switch(item.item_type){ + case 'instruction': + addInstructionToOrderBox(orderItem) + break + case 'item': + default: + addItemToOrderBox(orderItem) + break } +} +const getLastInstructionRow = (row: JQuery) => { + let stopCounting = false + let finalRow = row + row.nextAll().each(function (index, activeRow){ + if(!stopCounting){ + if($(activeRow).hasClass('instructionRow')){ + finalRow = $(activeRow) + } else { + stopCounting = true + } + } + }) + + return $(finalRow) +} + +const getParentRow = (row: JQuery) => { + return row.hasClass('instructionRow') + ? row.prevAll('.itemRow').first() + : row } const incrementRowQty = (row: JQuery, qty: number) => { @@ -137,9 +203,6 @@ const createOrderRow = (orderItem: orderItem) => { return row } -const saveOrderItem = (orderItem: orderItem) => {} - - const itemButtonClicked = (e: JQuery.TriggeredEvent) => { const existingItemRows = $('.itemRow') const button = $(e.target).closest('.posButton') @@ -168,10 +231,6 @@ const itemRowClicked = (e: JQuery.TriggeredEvent) => { 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){ @@ -184,7 +243,6 @@ const selectRow = (row: JQuery) => { 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) @@ -194,9 +252,8 @@ const deselectRow = (row: JQuery) => { const deleteRow = (row: JQuery) => row.find('*:not(.hidden)').slideUp('fast', () => row.remove()) const voidInstructionRow = (row: JQuery) => { - const parentRow = row.prevAll('.itemRow').first() - - deleteRow(row) + if(!row.prevAll('.itemRow').first().hasClass('selected')) + deleteRow(row) } const voidItemRow = (row : JQuery) => decrementQty(row) @@ -237,7 +294,9 @@ const calculateRowTotal = (row: JQuery) => { const decrementQty = (row: JQuery) => { const qty = getQty(row) if(qty <= 1){ + const childRows = row.nextUntil('.itemRow') deleteRow(row) + deleteRow(childRows) return } @@ -245,6 +304,7 @@ const decrementQty = (row: JQuery) => { calculateRowTotal(row) } -const getOrderItemRow = (orderItem: orderItem) => $('tr').filterByData('order-item-id', orderItem.id) +const scrollToElement = (element: JQuery) => element.get()[0].scrollIntoView() + $(() => ajax('/orderScreen/getOrderScreenData/1', null, 'get', setupOrderScreen, null, null) ) \ No newline at end of file diff --git a/wwwroot/styles/sass/dredgepos.orderScreen.sass b/wwwroot/styles/sass/dredgepos.orderScreen.sass index f3a3c7a..2678e76 100644 --- a/wwwroot/styles/sass/dredgepos.orderScreen.sass +++ b/wwwroot/styles/sass/dredgepos.orderScreen.sass @@ -29,11 +29,30 @@ overflow-y: auto .orderBoxInfo + @include flex flex-basis: 5% background-color: white + .voidModeWarning + @include flex + @include flex-item + color: red + font-weight: bold + .orderBoxFooter flex-basis: 10% + @include flex-column + + > * + @include flex + @include flex-column-item + justify-content: flex-end + padding: 0 0.7em + + > .orderBoxTotal + align-items: flex-end + > small + align-items: flex-start #rightColumn @include flex-column @@ -239,6 +258,15 @@ text-align: center width: 70% + .qtyCell + width: 10% + + .printGroupCell + width: 10% + + .totalPriceCell + width: 10% + td.itemCell text-align: left diff --git a/wwwroot/themes/restaurant/components/posButton.tpl.htm b/wwwroot/themes/restaurant/components/posButton.tpl.htm new file mode 100644 index 0000000..6cec82e --- /dev/null +++ b/wwwroot/themes/restaurant/components/posButton.tpl.htm @@ -0,0 +1,3 @@ +> + + \ No newline at end of file diff --git a/wwwroot/themes/restaurant/orderScreen.tpl.htm b/wwwroot/themes/restaurant/orderScreen.tpl.htm index e4f7c3b..a452693 100644 --- a/wwwroot/themes/restaurant/orderScreen.tpl.htm +++ b/wwwroot/themes/restaurant/orderScreen.tpl.htm @@ -29,9 +29,12 @@
-
+
+ +
- + $0.00 + $0.00
@@ -44,12 +47,7 @@
- - - - - - +
From 198d609e62002bde8d256d91690928cedf2bce4e Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 7 Jan 2022 10:41:49 +1000 Subject: [PATCH 5/9] Added mutation observer to sum totals --- wwwroot/scripts/ts/dredgepos.core.ts | 2 +- wwwroot/scripts/ts/dredgepos.orderScreen.ts | 27 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/wwwroot/scripts/ts/dredgepos.core.ts b/wwwroot/scripts/ts/dredgepos.core.ts index c60d5ef..e456c6c 100644 --- a/wwwroot/scripts/ts/dredgepos.core.ts +++ b/wwwroot/scripts/ts/dredgepos.core.ts @@ -191,7 +191,7 @@ Array.prototype.where = function(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 money = (amount: number, fromCents=true) => currency(amount, {fromCents: fromCents}) const moneyFromString = (amount: string) => currency(amount) //Id generator. diff --git a/wwwroot/scripts/ts/dredgepos.orderScreen.ts b/wwwroot/scripts/ts/dredgepos.orderScreen.ts index d34aa60..d70176b 100644 --- a/wwwroot/scripts/ts/dredgepos.orderScreen.ts +++ b/wwwroot/scripts/ts/dredgepos.orderScreen.ts @@ -55,6 +55,16 @@ const setupOrderScreen = (data: OrderScreenData) => { turnOnMode('accumulate') $('.loadPageGroup').first().trigger('click') + + let observer = new window.MutationObserver((mutations, observer) => updateOrderBoxTotals()) + + observer.observe($('.orderBoxTable tbody').get()[0], { + subtree: true, + attributes: true, + childList: true + }); + + } /** @@ -283,6 +293,23 @@ const voidLastItem = () => { voidRows(allRows.last()) } +const updateOrderBoxTotals = () => { + const allRows = $('.orderBoxTable tbody tr') + const selectedRows = $('.orderBoxTable tbody tr.selected') + $('.orderBoxTotal').text(getTotalOfRows(allRows)) + $('.orderBoxSelectedTotal').text(getTotalOfRows(selectedRows)) +} + +const getTotalOfRows = (rows: JQuery) => { + return money(rows + .find('td.totalPriceCell') + .get() + .map(cell => Number(cell.innerText)) + .filter(number => !isNaN(number)) + .reduce( (total, number) => total + number , 0), false) + .format() +} + const getQty = (row: JQuery) => Number(row.getColumnValue(lang('qty_header'))) const getUnitPrice = (row: JQuery) => moneyFromString(row.getColumnValue(lang('price_header'))) const calculateRowTotal = (row: JQuery) => { From f656c5ab40fad8317a6e8506fe19dc44e5e81b2f Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 8 Jan 2022 17:53:23 +1000 Subject: [PATCH 6/9] Added print group overrides --- AjaxController.fs | 2 + OrderScreen.module.fs | 9 +- Theme.module.fs | 2 +- Types.fs | 2 +- wwwroot/languages/english/main.json | 1 + wwwroot/scripts/ts/dredgepos.core.ts | 26 ++++++ wwwroot/scripts/ts/dredgepos.orderScreen.ts | 87 +++++++++++++++---- wwwroot/scripts/ts/types.ts | 4 +- wwwroot/styles/sass/dredgepos.core.sass | 1 + .../styles/sass/dredgepos.orderScreen.sass | 29 ++----- wwwroot/themes/restaurant/orderScreen.tpl.htm | 28 +++--- 11 files changed, 133 insertions(+), 58 deletions(-) diff --git a/AjaxController.fs b/AjaxController.fs index f4a1731..e56e032 100644 --- a/AjaxController.fs +++ b/AjaxController.fs @@ -2,6 +2,7 @@ open DredgeFramework open DredgePos +open DredgePos.Types open Floorplan open Microsoft.AspNetCore.Http open Reservations @@ -57,6 +58,7 @@ let getOrderScreenData (id: int) = {| order_screen_pages = Entity.getAllInVenue sales_categories = Entity.getAllInVenue + print_groups = Entity.getAllInVenue |} |> ajaxSuccess |> json diff --git a/OrderScreen.module.fs b/OrderScreen.module.fs index 105ef1a..93a31d8 100644 --- a/OrderScreen.module.fs +++ b/OrderScreen.module.fs @@ -75,12 +75,13 @@ let renderPageGroup (pageGroup: order_screen_page_group) (pageHTML: string) = ] loadTemplateWithVars "orderScreen/page_group" vars -let categoryPosButton (category: sales_category) = PosButton (language.getAndReplace "print_with" [category.name]) "categoryOverrideButton" "" +let printGroupPosButton (printGroup: print_group) = + PosButton (language.getAndReplace "print_with" [printGroup.name]) "printGroupOverrideButton toggle" $"""data-value="{printGroup.id}" """ let generateSalesCategoryOverrideButtons () = - Entity.getAllInVenue - |> Array.map categoryPosButton - |> Array.append ([|PosButton (language.getAndReplace "print_with" ["default"]) "categoryOverrideButton" ""|]) + Entity.getAllInVenue + |> Array.map printGroupPosButton + |> Array.append ([|PosButton (language.getAndReplace "print_with" ["default"]) "printGroupOverrideButton toggle default active" """data-value="0" """|]) |> String.concat "\n" diff --git a/Theme.module.fs b/Theme.module.fs index 3f18516..cc62dac 100644 --- a/Theme.module.fs +++ b/Theme.module.fs @@ -78,7 +78,7 @@ let ParseSimpleLanguageVariables (string:string) = let ParseLanguageVariablesWithReplacements (string: string) = Regex.Replace(string, "", - new MatchEvaluator( + MatchEvaluator( fun matchedVar -> let varName = matchedVar.Groups[1].ToString() let replacements = matchedVar.Groups[2].ToString() diff --git a/Types.fs b/Types.fs index daf1637..3f3ffaf 100644 --- a/Types.fs +++ b/Types.fs @@ -41,7 +41,7 @@ type sales_category = { id: int parent: int name: string - print_group: string + print_group: int venue_id: int } diff --git a/wwwroot/languages/english/main.json b/wwwroot/languages/english/main.json index cb21f77..65561fb 100644 --- a/wwwroot/languages/english/main.json +++ b/wwwroot/languages/english/main.json @@ -35,6 +35,7 @@ "functions_header":"Functions", "close_order_function":"Close Without Saving", "accumulate_function":"Accumulate Items", + "multiplier":"Enter a Number", "void":"Void", "pay_function":"Pay", "print_function":"Save & Print", diff --git a/wwwroot/scripts/ts/dredgepos.core.ts b/wwwroot/scripts/ts/dredgepos.core.ts index e456c6c..321614a 100644 --- a/wwwroot/scripts/ts/dredgepos.core.ts +++ b/wwwroot/scripts/ts/dredgepos.core.ts @@ -79,6 +79,7 @@ const setupCore = (languageVars: Record) => { Application.languageVars = languageVars const doc = $(document) doc.on('click', '#alertNo, #alertOk', hideAlerts) + doc.on('click', '.toggle', toggle) window.addEventListener('resize', resize) resize() @@ -194,6 +195,31 @@ Array.prototype.where = function(this: x[], property: string, value: any) { const money = (amount: number, fromCents=true) => currency(amount, {fromCents: fromCents}) const moneyFromString = (amount: string) => currency(amount) +const toggle = (e: JQuery.TriggeredEvent) => { + const button = $(e.target) + const toggleGroup = button.closest('.toggleGroup') + const input = toggleGroup.find('input.value') + const isActive = button.hasClass('active') + toggleGroup + .find('.toggle') + .removeClass('active') + + let value = !isActive + ? button.addClass('active').data('value') + : toggleGroup + .find('.toggle.default') + .addClass('active') + .data('value') + + input.val(value).trigger('change') +} + +const resetToggle = (input: JQuery) => { + input + .closest('.toggleGroup') + .find('.toggle.default') + .trigger('click') +} //Id generator. function* newestId(){ let id = 0 diff --git a/wwwroot/scripts/ts/dredgepos.orderScreen.ts b/wwwroot/scripts/ts/dredgepos.orderScreen.ts index d70176b..ab6f8bf 100644 --- a/wwwroot/scripts/ts/dredgepos.orderScreen.ts +++ b/wwwroot/scripts/ts/dredgepos.orderScreen.ts @@ -1,25 +1,31 @@ type OrderScreenData = { order_screen_pages: order_screen_page[] sales_categories: sales_category[] + print_groups: print_group[] } - type OrderScreen = { order_screen_pages: order_screen_page[] last_added_item: orderItem order_items: orderItem[] sales_categories: sales_category[] + print_groups: print_group[] order_item_id_generator: Generator selected_item_ids: number[] + qty_override: number + print_group_override: print_group } let OrderScreen : OrderScreen = { order_screen_pages: null, last_added_item: null, order_items: [], + print_groups: [], sales_categories: [], order_item_id_generator: newestId(), selected_item_ids: [], + qty_override: 1, + print_group_override: null } const loadPageGroup = (e: Event) => { @@ -42,6 +48,8 @@ const loadPageGroup = (e: Event) => { const setupOrderScreen = (data: OrderScreenData) => { OrderScreen.order_screen_pages = data.order_screen_pages OrderScreen.sales_categories = data.sales_categories + OrderScreen.print_groups = data.print_groups + updateOrderBoxTotals() let doc = $(document) doc.on('click', '.nextButton', goToNextPage) doc.on('click', '.prevButton', goToPrevPage) @@ -50,7 +58,9 @@ const setupOrderScreen = (data: OrderScreenData) => { doc.on('click', '.orderBoxTable tbody tr', itemRowClicked) doc.on('click', '.voidButton', voidButtonClicked) doc.on('dblclick', '.voidButton', voidLastItem) + doc.on('click', '.numpadButton', overrideQty) doc.on('click', '.accumulateButton', () => toggleMode('accumulate')) + doc.on('change', '[name=print_override]', printGroupOverride) turnOnMode('accumulate') @@ -64,7 +74,6 @@ const setupOrderScreen = (data: OrderScreenData) => { childList: true }); - } /** @@ -82,7 +91,12 @@ const addItemToOrderBox = (orderItem:orderItem) => { const orderBox = $('.orderBoxTable tbody') let selectedRows = orderBox.find('tr.selected') let lastRow : JQuery = selectedRows.length ? selectedRows.first() : orderBox.find('tr').last() - const existingRow = orderBox.find(`.itemCell:contains("${orderItem.item.item_name}")`).closest('tr').last() + const existingRow = orderBox + .find('tr') + .filterByData('item', orderItem.item) + .filterByData('print_group', orderItem.print_group) + .last() + //If accumulating, just increase the quantity of the existing row. if(existingRow.length > 0 && isInMode('accumulate')){ @@ -114,7 +128,6 @@ const addInstructionToOrderBox = (instruction: orderItem) => { selectedRows.each( (_, row) => { const selectedRow = $(row) const parentRow = getParentRow(selectedRow) - if(parentRow.is(selectedRow) || !parentRow.hasClass('selected')) { const newRow = createOrderRow(instruction) getLastInstructionRow(selectedRow).after(newRow.pulse()) @@ -128,14 +141,13 @@ const addInstructionToOrderBox = (instruction: orderItem) => { const addNewItem = (item: item, qty = 1) => { - const salesCategory = OrderScreen.sales_categories.where('id', item.item_category) - + const printGroup = OrderScreen.print_group_override ?? OrderScreen.print_groups.where('id', salesCategory.print_group) const orderItem : orderItem = { id: OrderScreen.order_item_id_generator.next().value, item: item, qty: qty, - sales_category: salesCategory, + print_group: printGroup, } switch(item.item_type){ @@ -202,13 +214,23 @@ const createOrderRow = (orderItem: orderItem) => { 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) + .setColumnValue(lang('id_header'), orderItem.item.id) + .setColumnValue(lang('total_price_header'), price.multiply(orderItem.qty)) + .setColumnValue(lang('printgroup_header'), orderItem.print_group?.name) .data('order-item-id', orderItem.id) + .data('print_group', orderItem.print_group) + .data('item', orderItem.item) + + if(orderItem.item.item_type == 'instruction' && price.value <= 0){ + row + .find('.totalPriceCell') + .css('font-size', 0) + } return row } @@ -220,7 +242,10 @@ const itemButtonClicked = (e: JQuery.TriggeredEvent) => { if(item.item_type == 'instruction' && existingItemRows.length < 1) return - addNewItem(item) + const qty = OrderScreen.qty_override || 1 + OrderScreen.qty_override = 1 + + addNewItem(item, qty) } @@ -296,8 +321,11 @@ const voidLastItem = () => { const updateOrderBoxTotals = () => { const allRows = $('.orderBoxTable tbody tr') const selectedRows = $('.orderBoxTable tbody tr.selected') - $('.orderBoxTotal').text(getTotalOfRows(allRows)) - $('.orderBoxSelectedTotal').text(getTotalOfRows(selectedRows)) + const completeTotal = lang('totalPrice', getTotalOfRows(allRows)) + const selectedTotal = lang('selectedPrice', getTotalOfRows(selectedRows)) + + $('.orderBoxTotal').text(completeTotal) + $('.orderBoxSelectedTotal').text(selectedTotal) } const getTotalOfRows = (rows: JQuery) => { @@ -318,20 +346,45 @@ const calculateRowTotal = (row: JQuery) => { row.setColumnValue(lang('total_price_header'), price.multiply(qty)) } -const decrementQty = (row: JQuery) => { - const qty = getQty(row) - if(qty <= 1){ +const decrementQty = (row: JQuery, qty=1) => { + const existingQty = getQty(row) + + if(existingQty <= 1){ const childRows = row.nextUntil('.itemRow') deleteRow(row) deleteRow(childRows) return } - - row.setColumnValue(lang('qty_header'), qty - 1) + row.setColumnValue(lang('qty_header'), existingQty - qty) calculateRowTotal(row) } const scrollToElement = (element: JQuery) => element.get()[0].scrollIntoView() +const overrideQty = () => showVirtualNumpad(lang('multiplier'), 4, false, true, true, qtyOverridden) + + +const qtyOverridden = (qtyString: string) => OrderScreen.qty_override = Number(qtyString) + +const printGroupOverride = (e: JQuery.TriggeredEvent) => { + const input = $(e.target) + const printGroupId = Number(input.val()) + const orderBox = $('.orderBoxTable tbody') + const selectedRows = orderBox.find('tr.selected') + const newPrintGroup = OrderScreen.print_groups.where('id', printGroupId) + + if(selectedRows.length && newPrintGroup){ + selectedRows.each((index, row) => { + $(row).setColumnValue(lang('printgroup_header'), newPrintGroup.name) + $(row).data('print_group', newPrintGroup) + }) + + OrderScreen.print_group_override = null + resetToggle(input) + + } else { + OrderScreen.print_group_override = newPrintGroup + } +} $(() => ajax('/orderScreen/getOrderScreenData/1', null, 'get', setupOrderScreen, null, null) ) \ No newline at end of file diff --git a/wwwroot/scripts/ts/types.ts b/wwwroot/scripts/ts/types.ts index aeaf028..f0a461c 100644 --- a/wwwroot/scripts/ts/types.ts +++ b/wwwroot/scripts/ts/types.ts @@ -10,11 +10,11 @@ interface order { interface orderItem { id: number, qty: number, - sales_category: sales_category + print_group: print_group item: item } -interface printGroup { +interface print_group { id: number, name: string, printer: number, diff --git a/wwwroot/styles/sass/dredgepos.core.sass b/wwwroot/styles/sass/dredgepos.core.sass index f91f57c..5ca8ff0 100644 --- a/wwwroot/styles/sass/dredgepos.core.sass +++ b/wwwroot/styles/sass/dredgepos.core.sass @@ -46,6 +46,7 @@ input[type=text], select, textarea bottom: 0 left: 0 right: 0 + z-index: 999 @mixin mobile @media screen and (max-width: 900px) diff --git a/wwwroot/styles/sass/dredgepos.orderScreen.sass b/wwwroot/styles/sass/dredgepos.orderScreen.sass index 2678e76..53d695c 100644 --- a/wwwroot/styles/sass/dredgepos.orderScreen.sass +++ b/wwwroot/styles/sass/dredgepos.orderScreen.sass @@ -51,8 +51,10 @@ > .orderBoxTotal align-items: flex-end - > small + font-size: 1.3em + > .orderBoxSelectedTotal align-items: flex-start + font-size: 0.9em #rightColumn @include flex-column @@ -68,24 +70,10 @@ flex-grow: 0 flex-shrink: 0 - .utilityButtons - @include flex-column-item - @include flex - flex-basis: 20% - - > * - @include flex-item - @include flex - flex-basis: 30% - - .logoutButton - flex-basis: 10% - - .functionButtons @include flex-column-item @include flex - flex-basis: 80% + flex-basis: 100% > * @include flex-item @@ -256,13 +244,13 @@ .itemCell text-align: center - width: 70% + width: 60% .qtyCell width: 10% .printGroupCell - width: 10% + width: 20% .totalPriceCell width: 10% @@ -271,9 +259,10 @@ text-align: left tr.instructionRow + td + font-weight: 100 td.itemCell padding-left: 2em - font-weight: 100 - .qtyCell, .totalPriceCell, .printGroupCell + .qtyCell, .printGroupCell font-size: 0 \ No newline at end of file diff --git a/wwwroot/themes/restaurant/orderScreen.tpl.htm b/wwwroot/themes/restaurant/orderScreen.tpl.htm index a452693..7dd837d 100644 --- a/wwwroot/themes/restaurant/orderScreen.tpl.htm +++ b/wwwroot/themes/restaurant/orderScreen.tpl.htm @@ -20,6 +20,7 @@ + @@ -33,30 +34,31 @@
- $0.00 - $0.00 + +
-
- - - - × -
-
- +
+ +
- + + + +
+
-
-
+
+ + +
From 56d42f1339f58772f9510a08d5f08a246af4b13f Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 8 Jan 2022 19:25:01 +1000 Subject: [PATCH 7/9] Added open food/freetext commands --- AjaxController.fs | 1 + DredgeFramework.module.fs | 2 + PageController.fs | 9 ++++ wwwroot/languages/english/main.json | 4 +- wwwroot/scripts/ts/dredgepos.orderScreen.ts | 45 ++++++++++++++++++- wwwroot/styles/sass/dredgepos.core.sass | 1 + wwwroot/themes/restaurant/orderScreen.tpl.htm | 4 +- 7 files changed, 61 insertions(+), 5 deletions(-) diff --git a/AjaxController.fs b/AjaxController.fs index e56e032..b33a1bb 100644 --- a/AjaxController.fs +++ b/AjaxController.fs @@ -59,6 +59,7 @@ let getOrderScreenData (id: int) = order_screen_pages = Entity.getAllInVenue sales_categories = Entity.getAllInVenue print_groups = Entity.getAllInVenue + custom_item = Entity.getAllByColumn "item_code" "OPEN000" |> first |} |> ajaxSuccess |> json diff --git a/DredgeFramework.module.fs b/DredgeFramework.module.fs index 566f8fd..3639052 100644 --- a/DredgeFramework.module.fs +++ b/DredgeFramework.module.fs @@ -38,8 +38,10 @@ let GetFileName (file: string) = Path.GetFileName file let length (variable: 'T[]) = variable.Length let first (array: 'a[]) = array[0] + let last (array: 'a[]) = array[array.Length-1] +let filterFirst (array:'a[]) = if array.Length > 0 then [|array[0]|] else [||] let removeFalseValues (variable: bool[]) = variable |> Array.filter id diff --git a/PageController.fs b/PageController.fs index 3bfa88f..f45c0ff 100644 --- a/PageController.fs +++ b/PageController.fs @@ -2,6 +2,7 @@ open System open DredgePos.Types +open FSharp.Data open Microsoft.AspNetCore.Http open Floorplan open Giraffe @@ -64,6 +65,13 @@ let loadOrderScreen (ctx: HttpContext) (tableNumber: int) : HttpHandler = |> Array.map OrderScreen.getPagesHTML |> String.concat "\n" + let customItem = + Entity.getAllByColumn "item_code" "OPEN000" + |> filterFirst + |> Array.map jsonEncode + |> String.concat "" + + let variables = map [ "title", "Order" "categoryList", categoryList @@ -72,6 +80,7 @@ let loadOrderScreen (ctx: HttpContext) (tableNumber: int) : HttpHandler = "coverSelectorButton", coverSelectorButton "covers", coverString "salesCategoryOverrideButtons", OrderScreen.generateSalesCategoryOverrideButtons () + "custom", OrderScreen.generateSalesCategoryOverrideButtons () ] let styles = ["dredgepos.orderScreen.css"] diff --git a/wwwroot/languages/english/main.json b/wwwroot/languages/english/main.json index 65561fb..1a6444b 100644 --- a/wwwroot/languages/english/main.json +++ b/wwwroot/languages/english/main.json @@ -90,5 +90,7 @@ "msgbox_amount_select":"Enter Amount", "msgbox_qty_select":"How many would you like to select?", - "void_mode": "Void Mode is ON." + "void_mode": "Void Mode is ON.", + "enter_item_name": "Enter Item Name", + "enter_item_price": "Enter Item Price" } \ No newline at end of file diff --git a/wwwroot/scripts/ts/dredgepos.orderScreen.ts b/wwwroot/scripts/ts/dredgepos.orderScreen.ts index ab6f8bf..19b057e 100644 --- a/wwwroot/scripts/ts/dredgepos.orderScreen.ts +++ b/wwwroot/scripts/ts/dredgepos.orderScreen.ts @@ -2,6 +2,7 @@ type OrderScreenData = { order_screen_pages: order_screen_page[] sales_categories: sales_category[] print_groups: print_group[] + custom_item: item } type OrderScreen = { @@ -14,6 +15,7 @@ type OrderScreen = { selected_item_ids: number[] qty_override: number print_group_override: print_group + custom_item: item, } let OrderScreen : OrderScreen = { @@ -25,7 +27,8 @@ let OrderScreen : OrderScreen = { order_item_id_generator: newestId(), selected_item_ids: [], qty_override: 1, - print_group_override: null + print_group_override: null, + custom_item: null } const loadPageGroup = (e: Event) => { @@ -49,12 +52,16 @@ const setupOrderScreen = (data: OrderScreenData) => { OrderScreen.order_screen_pages = data.order_screen_pages OrderScreen.sales_categories = data.sales_categories OrderScreen.print_groups = data.print_groups + OrderScreen.custom_item = data.custom_item + updateOrderBoxTotals() 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', '.freetextButton', freetext) + doc.on('click', '.openItemButton', openItem) doc.on('click', '.orderBoxTable tbody tr', itemRowClicked) doc.on('click', '.voidButton', voidButtonClicked) doc.on('dblclick', '.voidButton', voidLastItem) @@ -131,12 +138,15 @@ const addInstructionToOrderBox = (instruction: orderItem) => { if(parentRow.is(selectedRow) || !parentRow.hasClass('selected')) { const newRow = createOrderRow(instruction) getLastInstructionRow(selectedRow).after(newRow.pulse()) + newRow.setColumnValue(lang('printgroup_header'), selectedRow.getColumnValue(lang('printgroup_header'))) } }) return } + const lastRow = orderBox.find('tr').last() orderBox.append(newRow.pulse()) + newRow.setColumnValue(lang('printgroup_header'), lastRow.getColumnValue(lang('printgroup_header'))) } @@ -363,7 +373,6 @@ const scrollToElement = (element: JQuery) => element.get()[0].scrollIntoView() const overrideQty = () => showVirtualNumpad(lang('multiplier'), 4, false, true, true, qtyOverridden) - const qtyOverridden = (qtyString: string) => OrderScreen.qty_override = Number(qtyString) const printGroupOverride = (e: JQuery.TriggeredEvent) => { @@ -387,4 +396,36 @@ const printGroupOverride = (e: JQuery.TriggeredEvent) => { } } +const freetext = () => showVirtualKeyboard('', 32,false, freetextSubmitted) + +const freetextSubmitted = (text: string) => { + if(text.trim().length < 1) return + + if($('.orderBoxTable tbody tr').length < 1){ + posAlert(lang('freetext_no_order')) + } + + const item = OrderScreen.custom_item + item.item_type = 'instruction' + item.item_name = text + + addNewItem(item) + +} + +const openItem = () => showVirtualKeyboard(lang('enter_item_name'), 32,false, openItemTextSubmitted) + +const openItemTextSubmitted = (text: string) => { + const submitFunction = (priceString: string) => { + const price = currency(priceString) + const item = OrderScreen.custom_item + item.item_type = 'item' + item.item_name = text + item.price1 = price.value + + addNewItem(item) + } + showVirtualNumpad(lang('enter_item_price'), 4, false, true, true, submitFunction) +} + $(() => ajax('/orderScreen/getOrderScreenData/1', null, 'get', setupOrderScreen, null, null) ) \ No newline at end of file diff --git a/wwwroot/styles/sass/dredgepos.core.sass b/wwwroot/styles/sass/dredgepos.core.sass index 5ca8ff0..4496e22 100644 --- a/wwwroot/styles/sass/dredgepos.core.sass +++ b/wwwroot/styles/sass/dredgepos.core.sass @@ -99,6 +99,7 @@ body .posButton.active, .posButton:active border: inset 2px + background: var(--posbutton-background-active) .posButton.voidButton background: var(--void-button-background) diff --git a/wwwroot/themes/restaurant/orderScreen.tpl.htm b/wwwroot/themes/restaurant/orderScreen.tpl.htm index 7dd837d..41a2942 100644 --- a/wwwroot/themes/restaurant/orderScreen.tpl.htm +++ b/wwwroot/themes/restaurant/orderScreen.tpl.htm @@ -48,12 +48,12 @@
- +
- +
From 75c129c5d4fcbea3eb73d375475331496630b0cf Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 11 Jan 2022 06:51:55 +1000 Subject: [PATCH 8/9] Updates to scaling on phones --- wwwroot/languages/english/main.json | 3 ++- wwwroot/scripts/ts/dredgepos.orderScreen.ts | 15 ++++++++------- wwwroot/styles/sass/dredgepos.core.sass | 3 ++- wwwroot/styles/sass/dredgepos.orderScreen.sass | 14 ++++++++++++-- wwwroot/themes/restaurant/orderScreen.tpl.htm | 8 ++++---- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/wwwroot/languages/english/main.json b/wwwroot/languages/english/main.json index 1a6444b..1ab8e7b 100644 --- a/wwwroot/languages/english/main.json +++ b/wwwroot/languages/english/main.json @@ -39,7 +39,8 @@ "void":"Void", "pay_function":"Pay", "print_function":"Save & Print", - "freetext_button":"Keyboard", + "freetext_button":"Custom Instruction", + "custom_item_button":"Custom Item", "numpad_button":"Numpad", "select_covers":"Cover #", "how_many_covers":"How many people are on this table?", diff --git a/wwwroot/scripts/ts/dredgepos.orderScreen.ts b/wwwroot/scripts/ts/dredgepos.orderScreen.ts index 19b057e..26bd96e 100644 --- a/wwwroot/scripts/ts/dredgepos.orderScreen.ts +++ b/wwwroot/scripts/ts/dredgepos.orderScreen.ts @@ -61,7 +61,7 @@ const setupOrderScreen = (data: OrderScreenData) => { doc.on('click', '.loadPageGroup', loadPageGroup) doc.on('click', '[data-primary-action=item]', itemButtonClicked) doc.on('click', '.freetextButton', freetext) - doc.on('click', '.openItemButton', openItem) + doc.on('click', '.openItemButton', customItem) doc.on('click', '.orderBoxTable tbody tr', itemRowClicked) doc.on('click', '.voidButton', voidButtonClicked) doc.on('dblclick', '.voidButton', voidLastItem) @@ -405,7 +405,7 @@ const freetextSubmitted = (text: string) => { posAlert(lang('freetext_no_order')) } - const item = OrderScreen.custom_item + const item = Object.assign({}, OrderScreen.custom_item) item.item_type = 'instruction' item.item_name = text @@ -413,15 +413,16 @@ const freetextSubmitted = (text: string) => { } -const openItem = () => showVirtualKeyboard(lang('enter_item_name'), 32,false, openItemTextSubmitted) +const customItem = () => showVirtualKeyboard(lang('enter_item_name'), 32,false, customItemTextSubmitted) -const openItemTextSubmitted = (text: string) => { +const customItemTextSubmitted = (text: string) => { const submitFunction = (priceString: string) => { - const price = currency(priceString) - const item = OrderScreen.custom_item + const price = currency(priceString, {fromCents: false}) + + const item = Object.assign({}, OrderScreen.custom_item) item.item_type = 'item' item.item_name = text - item.price1 = price.value + item.price1 = price.intValue addNewItem(item) } diff --git a/wwwroot/styles/sass/dredgepos.core.sass b/wwwroot/styles/sass/dredgepos.core.sass index 4496e22..9019fda 100644 --- a/wwwroot/styles/sass/dredgepos.core.sass +++ b/wwwroot/styles/sass/dredgepos.core.sass @@ -19,7 +19,8 @@ box-sizing: border-box font-family: 'manrope', sans-serif scroll-behavior: smooth - cursor: pointer + +*:not(input, textarea) -webkit-touch-callout: none -webkit-user-select: none -khtml-user-select: none diff --git a/wwwroot/styles/sass/dredgepos.orderScreen.sass b/wwwroot/styles/sass/dredgepos.orderScreen.sass index 53d695c..dae2a0b 100644 --- a/wwwroot/styles/sass/dredgepos.orderScreen.sass +++ b/wwwroot/styles/sass/dredgepos.orderScreen.sass @@ -75,13 +75,23 @@ @include flex flex-basis: 100% - > * + > .functionColumn @include flex-item @include flex-column - + flex-basis: 25% > * @include flex-column-item + > .printGroupButtons + flex-basis: 25% + height: 100% + display: grid + grid-template-columns: repeat(2, 1fr) + grid-auto-rows: auto + + > * + padding: 0.5em + #pageList @include flex diff --git a/wwwroot/themes/restaurant/orderScreen.tpl.htm b/wwwroot/themes/restaurant/orderScreen.tpl.htm index 41a2942..49d7165 100644 --- a/wwwroot/themes/restaurant/orderScreen.tpl.htm +++ b/wwwroot/themes/restaurant/orderScreen.tpl.htm @@ -41,19 +41,19 @@
-
+
- -
- + + +
From 445cbbabe5d121646dc4a012381b2b7b6e0aa7f2 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 23 Jan 2022 18:16:18 +1000 Subject: [PATCH 9/9] OrderScreen complete --- AjaxController.fs | 55 ++-- Decorations.module.fs | 2 +- DredgePos.fsproj | 1 + Floorplan.module.fs | 40 ++- GenericEntities.module.fs | 49 ++-- OrderScreen.module.fs | 95 ++++--- PageController.fs | 33 ++- Program.fs | 4 +- Theme.module.fs | 2 +- Types.fs | 16 +- wwwroot/scripts/ts/dredgepos.core.ts | 10 +- wwwroot/scripts/ts/dredgepos.floorplan.ts | 62 ++--- wwwroot/scripts/ts/dredgepos.orderScreen.ts | 212 +++++++++++++-- wwwroot/scripts/ts/types.ts | 17 +- .../styles/sass/dredgepos.orderScreen.sass | 255 +++++++++++------- wwwroot/themes/restaurant/orderScreen.tpl.htm | 14 +- .../change_cover_number_button.tpl.htm | 1 + .../orderScreen/cover_selector.tpl.htm | 3 + .../orderScreen/cover_selector_button.tpl.htm | 1 - .../orderScreen/grid_container.tpl.htm | 13 + .../restaurant/orderScreen/page.tpl.htm | 5 +- 21 files changed, 609 insertions(+), 281 deletions(-) create mode 100644 wwwroot/themes/restaurant/orderScreen/change_cover_number_button.tpl.htm create mode 100644 wwwroot/themes/restaurant/orderScreen/cover_selector.tpl.htm delete mode 100644 wwwroot/themes/restaurant/orderScreen/cover_selector_button.tpl.htm create mode 100644 wwwroot/themes/restaurant/orderScreen/grid_container.tpl.htm diff --git a/AjaxController.fs b/AjaxController.fs index b33a1bb..205f026 100644 --- a/AjaxController.fs +++ b/AjaxController.fs @@ -42,24 +42,25 @@ let unmergeTable tableNumber = let getFloorplanData (id: int) = - let tableList = Entity.getAllInVenue + let tableList = Entity.GetAllInVenue let reservationList = getReservationList tableList {| tables = tableList - decorations = Entity.getAllInVenue + decorations = Entity.GetAllInVenue activeTableNumbers = Floorplan.getActiveTables (getCurrentVenue()) - rooms = Entity.getAllInVenue + rooms = Entity.GetAllInVenue reservations = reservationList |} |> ajaxSuccess |> json -let getOrderScreenData (id: int) = +let getOrderScreenData (tableNumber: int) = {| - order_screen_pages = Entity.getAllInVenue - sales_categories = Entity.getAllInVenue - print_groups = Entity.getAllInVenue - custom_item = Entity.getAllByColumn "item_code" "OPEN000" |> first + order_screen_pages = Entity.GetAllInVenue + sales_categories = Entity.GetAllInVenue + print_groups = Entity.GetAllInVenue + custom_item = Entity.GetAllByColumn "item_code" "OPEN000" |> first + table = getTable tableNumber |} |> ajaxSuccess |> json @@ -73,21 +74,18 @@ let getKeyboardLayout (language: string) = ] |> json let transformTable (table: floorplan_table) = - Entity.updateInDatabase table + Entity.Update table |> ajaxSuccess |> json let createTable (tableData: floorplan_table) = - - let result = - if tableExists tableData.table_number = "False" then - ajaxSuccess (addNewTable tableData) - else ajaxFail (tableExists tableData.table_number) - - result |> json + if tableExists tableData.table_number = "False" then + ajaxSuccess (addNewTable tableData) + else ajaxFail (tableExists tableData.table_number) + |> json let deleteTable (table: floorplan_table) = - Entity.deleteById table.id + Entity.DeleteById table.id |> ignore table |> ajaxSuccess |> json @@ -113,30 +111,30 @@ let AddDecoration (data: floorplan_decoration) = venue_id = data.venue_id } - Entity.addToDatabase decoration + Entity.Create decoration |> ajaxSuccess |> json let UpdateDecoration (data: floorplan_decoration) = - Entity.updateInDatabase data + Entity.Update data |> ignore ajaxSuccess "true" |> json let DeleteDecoration (decorationToDelete: floorplan_decoration) = - Entity.deleteById decorationToDelete.id + Entity.DeleteById decorationToDelete.id |> ajaxSuccess |> json let newEmptyReservation (reservation: reservation) = let newReservation = {reservation with - reservation_created_at = CurrentTime() - reservation_time = CurrentTime() + created_at = CurrentTime() + time = CurrentTime() } - if reservation.reservation_table_id > 0 then - let table = {(getTableById reservation.reservation_table_id) with + if reservation.floorplan_table_id > 0 then + let table = {(getTableById reservation.floorplan_table_id) with status = 2 - default_covers = reservation.reservation_covers} + default_covers = reservation.covers} updateTablePosition table |> ignore let createdReservation = Floorplan.createEmptyReservation newReservation @@ -149,3 +147,10 @@ let unreserveTable (table: floorplan_table) = updateTablePosition newTable |> ignore DeleteReservation newTable.id newTable |> ajaxSuccess |> json + +let loadGrid (gridId: int) = + let grid = Entity.GetById gridId + let gridHtml = OrderScreen.loadGrid gridId + if gridHtml = "Error" then ajaxFail gridHtml + else ajaxSuccess {|grid=grid;gridHtml=gridHtml|} + |> json \ No newline at end of file diff --git a/Decorations.module.fs b/Decorations.module.fs index 1fe2989..a401e14 100644 --- a/Decorations.module.fs +++ b/Decorations.module.fs @@ -8,7 +8,7 @@ open Dapper.FSharp open DredgePos open Types -let decorationsInRoom (roomId: int) = Entity.getAllByColumn "decoration_room" roomId +let decorationsInRoom (roomId: int) = Entity.GetAllByColumn "decoration_room" roomId let getImageName (image: string, path: string) = let imageName = diff --git a/DredgePos.fsproj b/DredgePos.fsproj index 18cab05..a447edc 100644 --- a/DredgePos.fsproj +++ b/DredgePos.fsproj @@ -61,6 +61,7 @@ + diff --git a/Floorplan.module.fs b/Floorplan.module.fs index 2aca4a4..e04be62 100644 --- a/Floorplan.module.fs +++ b/Floorplan.module.fs @@ -1,6 +1,7 @@ module Floorplan open DredgePos +open DredgePos.Types open Org.BouncyCastle.Asn1.X509 open Reservations @@ -116,16 +117,16 @@ let getRoom (roomId: int) = where (eq "id" roomId) } |> db.Select |> first -let updateTablePosition (floorplanTable: floorplan_table) = Entity.updateInDatabase floorplanTable +let updateTablePosition (floorplanTable: floorplan_table) = Entity.Update floorplanTable let createEmptyReservation (reservation: reservation) = update { table "floorplan_tables" set {| status = 2 |} - where(eq "id" reservation.reservation_table_id) + where(eq "id" reservation.floorplan_table_id) } |> db.Update |> ignore - Entity.addToDatabase reservation + Entity.Create reservation let getChildTables tableNumber = let table = getTable tableNumber @@ -165,7 +166,7 @@ let tableExists (tableNumber: int) = match numberOfResults with | 0 -> let allTables = - Entity.getAllInVenue + Entity.GetAllInVenue |> Array.map(findChildTable tableNumber) |> Array.filter(fun tableNumber -> tableNumber <> 0) @@ -192,7 +193,7 @@ let addNewTableWithoutOutput (newTable: floorplan_table) = } |> db.Insert -let addNewTable (newTable: floorplan_table) = Entity.addToDatabase newTable +let addNewTable (newTable: floorplan_table) = Entity.Create newTable let mergeTables parent child = //Merge two tables together if parent = child then false else @@ -246,7 +247,7 @@ let mergeTables parent child = //Merge two tables together where (eq "table_number" parent + eq "venue_id" (getCurrentVenue())) } |> db.Update |> ignore - Entity.deleteById newChildTable.id + Entity.DeleteById newChildTable.id |> ignore true @@ -289,30 +290,19 @@ let makeRoomButton (room: floorplan_room) = Theme.loadTemplateWithVars "roomButton" vars let getReservationList (tableList: floorplan_table[]) = - let tableIds = - tableList - |> Array.map(fun table -> box table.id) - |> List.ofArray - - if tableIds.Length > 0 then - select { - table "reservations" - where (isIn "reservation_table_id" tableIds) - } - |> db.Select - else [||] + tableList |> Array.collect Entity.GetAllRelated let newReservation name time covers = let reservation = { id = 0 - reservation_name = name - reservation_time = time - reservation_covers = covers - reservation_table_id = 0 - reservation_created_at = CurrentTime() + name = name + time = time + covers = covers + floorplan_table_id = 0 + created_at = CurrentTime() } - Entity.addToDatabase reservation + Entity.Create reservation -let tableList () = Entity.getAllInVenue +let tableList () = Entity.GetAllInVenue diff --git a/GenericEntities.module.fs b/GenericEntities.module.fs index 154ba0c..7fb00f9 100644 --- a/GenericEntities.module.fs +++ b/GenericEntities.module.fs @@ -2,13 +2,14 @@ open Dapper.FSharp open DredgeFramework open Pluralize.NET.Core +open FSharp.Reflection -let getDatabaseTable<'x> = +let GetDatabaseTable<'x> = let typeName = typeof<'x>.Name Pluralizer().Pluralize typeName -let addToDatabase (record: 'x)= - let tableName = getDatabaseTable<'x> +let Create (record: 'x)= + let tableName = GetDatabaseTable<'x> insert { table tableName value record @@ -18,46 +19,58 @@ let addToDatabase (record: 'x)= |> first -let inline updateInDatabase (record: ^x) = - let tableName = getDatabaseTable<'x> - let id = ((^x) : (member id : int) (record)) +let inline Update (record: ^x) = + let tableName = GetDatabaseTable<'x> + let id = ((^x) : (member id : int) record) update { table tableName set record where (eq "id" id) + excludeColumn "id" } |> db.Update -let getAll<'x> = - let typeName = typeof<'x>.Name - let tableName = Pluralizer().Pluralize typeName +let GetAll<'x> = + let tableName = GetDatabaseTable<'x> select { table tableName } |> db.Select<'x> -let getAllByColumn<'x> (column: string) (value: obj) = - let typeName = typeof<'x>.Name - let tableName = Pluralizer().Pluralize typeName +let GetAllByColumn<'x> (column: string) (value: obj) = + let tableName = GetDatabaseTable<'x> select { table tableName where (eq column value) } |> db.Select<'x> -let getAllInVenue<'x> = getAllByColumn<'x> "venue_id" (getCurrentVenue ()) -let getById<'x> (id: int) = getAllByColumn<'x> "id" id |> first +let GetAllInVenue<'x> = GetAllByColumn<'x> "venue_id" (getCurrentVenue ()) +let GetById<'x> (id: int) = GetAllByColumn<'x> "id" id |> first -let deleteById<'x> id = +let inline GetRelated<'x, .. > (entity: ^y) = + let columnName = typeof<'x>.Name + "_id" + let primaryKeyValue = typeof<'y>.GetProperty(columnName).GetValue(entity) :?> int + GetById<'x> primaryKeyValue + +let inline GetAllRelated<'x, .. > (entity: ^y) = + let id = typeof<'y>.GetProperty("id").GetValue(entity) :?> int + let columnName = typeof<'y>.Name + "_id" + GetAllByColumn<'x> columnName id + +let DeleteById<'x> id = let typeName = typeof<'x>.Name let tableName = Pluralizer().Pluralize typeName - - let entity = getById<'x> id + let entity = GetById<'x> id delete { table tableName where (eq "id" id) } |> db.Delete |> ignore - entity \ No newline at end of file + entity + +let inline Delete< ^x when ^x: (member id: int) > (entity: ^x) = + typeof<'x>.GetProperty("id").GetValue(entity) :?> int + |> DeleteById<'x> diff --git a/OrderScreen.module.fs b/OrderScreen.module.fs index 93a31d8..c3d6693 100644 --- a/OrderScreen.module.fs +++ b/OrderScreen.module.fs @@ -4,16 +4,19 @@ open DredgePos open DredgePos.Types open FSharp.Collections open Thoth.Json.Net -open Types open Theme -let getAllPageGrids () = Entity.getAllInVenue +let getAllPageGrids () = Entity.GetAllInVenue |> Array.filter(fun pageGroup -> pageGroup.grid_id <> 0) - |> Array.map(fun pageGroup -> (Entity.getById pageGroup.grid_id), pageGroup) + |> Array.map(fun pageGroup -> (Entity.GetById pageGroup.grid_id), pageGroup) let getImageButtonData (button: button) = - let item = Entity.getAllByColumn "item_code" button.primary_action_value + let itemCode = + if button.primary_action = "item" then button.primary_action_value + else button.secondary_action_value + + let item = Entity.GetAllByColumn "item_code" itemCode |> first let extraData = @@ -26,9 +29,31 @@ let getImageButtonData (button: button) = text = item.item_name |} +let getGridButtonData (button: button) = + let gridId = + if button.primary_action = "grid" then button.primary_action_value + else button.secondary_action_value + |> int + + let grid = Entity.GetById gridId + {| + extra_data = map ["data-grid", jsonEncode gridId] |> htmlAttributes + text = grid.grid_name + |} + +let getActionData (button: button) (action: string) = + let actionValue = + if action = "primary" then button.primary_action + else button.secondary_action + + match actionValue with + | "item" -> getImageButtonData button + | "grid" -> getGridButtonData button + | "spacer" -> {|extra_data=""; text=""|} + | _ -> {|extra_data=""; text=""|} let renderButton (buttonId: int) = - let button = Entity.getById