Restructured files, made build script

This commit is contained in:
2022-02-14 16:31:08 +10:00
parent c73184808c
commit 6892b3d34c
58 changed files with 222 additions and 18 deletions

View File

@@ -1,14 +0,0 @@
let showLoginBox = () => showVirtualNumpad('Enter Login Code', 6, true, false, false, authenticate)
let authenticate = (input : string) => {
let login = ajaxSync('/login/authenticateClerk', input)
if(login === 'success'){
location.assign('/floorplan/')
}
else
showLoginBox()
}
$(() => {
showLoginBox()
})

View File

@@ -1,239 +0,0 @@
/// <reference path="./typings/currency.d.ts" />
const Application: ApplicationState = {
keyboard: null,
mode: [],
languageVars: {}
}
/** 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]
replacements.forEach((replacement, index) => {
const correctIndex = index + 1
finalValue = finalValue.replace(`[${correctIndex}]`, replacement)
})
return finalValue
}
/** Check if a variable is defined */
const defined = (variable: any) => {
return typeof variable !== 'undefined'
}
/** 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: 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<string, string>) => {
Application.languageVars = languageVars
const doc = $(document)
doc.on('click', '#alertNo, #alertOk', hideAlerts)
doc.on('click', '.toggle', toggle)
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<x>(this: x[], property: string, value: any) {
return this.filter( item => (item as any)[property] === value)[0] || null
}
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
while(true){
id++
yield id
}
}
const loadTemplate = (templateSelector: string) => {
const content = $(templateSelector)
.clone()
.removeAttr('id')
.prop('content')
return $(content)
}
$(() => ajax('/ajax/languageVars', null, 'GET', setupCore, null, null))

View File

@@ -1,851 +0,0 @@
/// <reference path="./typings/konva.d.ts" />
interface dimensions{
height:number
width:number
}
interface floorplan{
stage: Konva.Stage
transformer: Konva.Transformer
tableLayer: Konva.Layer
rooms: room[]
tables: floorplan_table[]
decorations: decoration[]
activeTableNumbers: number[]
selectedTableNumber: number
selectedDecorationId: number
currentRoom: room
roomToLoad: room
visualScale: number
visualScaleBasis: number
floorplanDiv: JQuery
reservations: reservation[]
}
interface floorplan_data{
tables: floorplan_table[]
decorations: decoration[]
activeTableNumbers: number[]
rooms: room[]
reservations:reservation[]
}
const Floorplan: floorplan = {
rooms: [],
tables: [],
decorations:[],
reservations:[],
activeTableNumbers: [],
stage: null,
transformer:null,
tableLayer: null,
selectedTableNumber: 0,
currentRoom: null,
roomToLoad: null,
visualScale: 1,
visualScaleBasis: 1280,
floorplanDiv: null,
selectedDecorationId: 0
};
$(() => ajax('/floorplan/getFloorplanData/1', null, 'get', setupFloorplan, null, null) )
const setupFloorplanEvents = () => {
const doc = $(document)
doc.on('click', '.roomButton', roomButtonClicked)
doc.on('click', '.editModeButton', editModeButtonClicked)
doc.on('click', '.changeShapeButton', changeTableShape)
doc.on('click', '.addTableButton', showAddTablePopup)
doc.on('click', '.deleteTableButton', confirmDeleteTable)
doc.on('click', '.addDecoration', showDecorator)
doc.on('click', '.deleteDecoration', deleteDecoration)
doc.on('click', '.decoratorItem', addDecoration)
doc.on('click', '.mergeButton', toggleMergeMode)
doc.on('click', '.unmergeButton', unmergeTable)
doc.on('click', '.transferTableButton', toggleTransferMode)
doc.on('click', '.reserveTableButton', reserveTable)
doc.on('click', '.unreserveTableButton', unreserveTable)
doc.on('click', '.placeOrderButton', placeOrderButtonClicked)
}
const placeOrderButtonClicked = () => {
redirect(`/order/${Floorplan.selectedTableNumber}`)
}
const roomButtonClicked = (e: Event) => {
const button = $(e.target)
const roomId = button.data('value')
loadRoom(getRoomById(roomId))
}
const editModeButtonClicked = (e: Event) => {
const button = $(e.target)
button.toggleClass('active')
toggleMode('edit')
if(isInMode('edit')){
Floorplan.stage.find('Group, Image').forEach(table => table.draggable(true))
if(isInMode('tableSelected')){
const selectedTableShape = getTableShapeFromTableNumber(Floorplan.selectedTableNumber)
selectTable(selectedTableShape)
}
} else {
setTransformerNodes([])
Floorplan.stage.find('Group, Image').forEach(table => table.draggable(false))
}
}
const setupFloorplan = (floorplanData : floorplan_data) => {
Floorplan.tables = floorplanData.tables
Floorplan.activeTableNumbers = floorplanData.activeTableNumbers
Floorplan.rooms = floorplanData.rooms
Floorplan.decorations = floorplanData.decorations
Floorplan.reservations = floorplanData.reservations
getDimensions()
setupFloorplanEvents()
loadRoom(Floorplan.rooms[0])
}
const loadRoom = (roomToLoad: room) => {
setRoomBackground(roomToLoad)
setupKonva()
$('.roomButton').removeClass('active')
let button = $(`.roomButton[data-value=${roomToLoad?.id}]`)
button.addClass('active')
const tablesInRoom = Floorplan.tables.filter(table => table.room_id == roomToLoad.id)
const decorationsInRoom = Floorplan.decorations.filter(decoration => decoration.decoration_room == roomToLoad.id)
decorationsInRoom.forEach(decoration => createDecorationShape(decoration, false))
tablesInRoom.forEach(createTableShape)
if(!isInMode('transfer')) {
deselectTables()
}
Floorplan.currentRoom = roomToLoad
}
const getRoomById = (roomId: number) => {
return Floorplan.rooms.find(
(room) => room.id == roomId
)
}
const tableIsOpen = (table: floorplan_table) => Floorplan.activeTableNumbers.includes(table.table_number)
const createTableShape = (table: floorplan_table) => {
const draggable = isInMode('edit')
const tableGroup = new Konva.Group({
x: table.pos_x * Floorplan.visualScale,
y: table.pos_y * Floorplan.visualScale,
draggable: draggable,
listening: true,
id: table.table_number.toString()
});
const fillColor = tableIsOpen(table)
? 'lightblue'
: table.status == 2
? 'lightgreen'
: 'gray'
let tableShape: Konva.Shape
switch(table.shape){
case "circle": // fall-through
case "ellipse": // fall-through
case "longellipse":
tableShape = new Konva.Ellipse({
x: 0,
y: 0,
radiusX: table.width * 0.5 * Floorplan.visualScale,
radiusY: table.height * 0.5 * Floorplan.visualScale,
rotation: table.rotation,
fill: fillColor,
stroke: "black",
strokeWidth: 4,
draggable: false,
listening: true
});
break;
default:
tableShape = new Konva.Rect({
x: 0,
y: 0,
offsetX: table.width * 0.5 * Floorplan.visualScale,
offsetY: table.height * 0.5 * Floorplan.visualScale,
width: table.width * Floorplan.visualScale,
height: table.height * Floorplan.visualScale,
rotation: table.rotation,
fill: fillColor,
stroke: "black",
strokeWidth: 4,
draggable: false,
listening: true
});
break;
}
const label = new Konva.Text({
x: table.width * -0.5 * Floorplan.visualScale,
y: table.height * -0.5 * Floorplan.visualScale,
width: table.width * Floorplan.visualScale,
height: table.height * Floorplan.visualScale,
text: table.table_number.toString(),
fontSize: 40 * Floorplan.visualScale,
fill: "black",
align: "center",
verticalAlign: "middle",
draggable: false,
listening: false
});
tableGroup.add(tableShape, label)
setupTableEvents(tableGroup)
Floorplan.tableLayer.add(tableGroup)
return tableGroup
}
const setupTableEvents = (tableGroup: Konva.Group) => {
const tableShape = getTableShapeFromGroup(tableGroup)
tableGroup.on('click', tableClicked)
tableGroup.on('tap', tableClicked)
tableGroup.on('dbltap', tableDblClicked)
tableGroup.on('dblclick', tableDblClicked)
tableGroup.on('dragend', tableGroupTransformed)
tableShape.on('transformend', tableShapeTransformed)
}
const getTableShapeFromGroup = (group: Konva.Group) => group.getChildren()[0] as Konva.Shape
const getTableGroupFromShape = (shape: Konva.Shape) => shape.parent as Konva.Group
const tableGroupTransformed = (e: Konva.KonvaEventObject<any>) => {
saveTableTransformation(e.target as Konva.Group)
}
const tableShapeTransformed = (e: Konva.KonvaEventObject<any>) => {
let shape = e.target as Konva.Shape
let group = getTableGroupFromShape(shape)
saveTableTransformation(group)
}
const saveTableTransformation = (tableGroup: Konva.Group) => {
const originalTable = getTableDataFromGroup(tableGroup)
const tableShape = getTableShapeFromGroup(tableGroup)
const newTableInfo : floorplan_table = {
table_number : originalTable.table_number,
previous_state : originalTable.previous_state,
merged_children : originalTable.merged_children,
id : originalTable.id,
width : Math.round(tableShape.scaleX() * tableShape.width()/Floorplan.visualScale),
height: Math.round(tableShape.scaleY() * tableShape.height()/Floorplan.visualScale),
pos_x: Math.round(tableGroup.x()/Floorplan.visualScale),
pos_y: Math.round(tableGroup.y()/Floorplan.visualScale),
rotation: Math.round(tableShape.rotation()),
room_id: originalTable.room_id,
status: originalTable.status,
venue_id: originalTable.venue_id,
shape : originalTable.shape,
default_covers: originalTable.default_covers,
}
saveTable(newTableInfo)
redrawTable(tableGroup)
}
const saveTable = (tableToUpdate: floorplan_table) => {
const tables =
Floorplan
.tables
.filter(table => {
return table.id != tableToUpdate.id
})
tables.push(tableToUpdate)
Floorplan.tables = tables
ajax("/floorplan/transformTable", tableToUpdate, 'post', null,null,null)
}
const setTransformerNodes = (nodes: Konva.Shape[]) => {
Floorplan.transformer.moveToTop()
if (nodes.length < 1) Floorplan.transformer.moveToBottom()
Floorplan.transformer.nodes(nodes)
}
const getTableDataFromTableNumber = (tableNumber: number) => {
return Floorplan.tables.filter(table => table.table_number == tableNumber)[0]
}
const getTableDataFromGroup = (tableGroup: Konva.Node) => {
const tableNumber = tableGroup.attrs.id
return Floorplan.tables.find(table => tableNumber == table.table_number)
}
const getTableDataFromShape = (tableShape: Konva.Shape) => getTableDataFromGroup(tableShape.parent)
const getTableShapeFromTableNumber = (tableNumber: number) => {
const tableGroup = Floorplan.stage.find('Group').find((group: Konva.Shape) => {
return group.attrs.id == tableNumber
}) as Konva.Group
return tableGroup.getChildren()[0] as Konva.Shape
}
const getTableGroupFromTableNumber = (tableNumber : number) => {
const tableShape = getTableShapeFromTableNumber(tableNumber)
return getTableGroupFromShape(tableShape)
}
const setReservationStatus = (table: floorplan_table) => {
const reservationText = $('.reservationStatus')
const tableShape = getTableShapeFromTableNumber(table.table_number)
reservationText.text('')
if(table.status == 2) {
tableShape.fill('lightgreen')
const reservations = Floorplan.reservations.filter(reservation => reservation.floorplan_table_id == table.id)
if (reservations.length) {
turnOnMode('reservedTableSelected')
reservationText.text(lang('reserved'))
let reservation = reservations[0]
if (reservation.name != '') {
reservationText.text(lang('reserved_for', reservation.name))
}
}
} else {
let fillColor = tableIsOpen(table) ? 'lightblue' : 'gray'
tableShape.fill(fillColor)
turnOffMode('reservedTableSelected')
}
}
const reserveTable = () => {
showVirtualNumpad(lang('how_many_covers'), 2, false, false, true, createEmptyReservation)
}
const createEmptyReservation = (covers: number) => {
const newReservation: reservation = {
id: 0,
covers: covers,
created_at: 0,
floorplan_table_id: getSelectedTableData().id,
name: '',
time: 0,
}
ajax('/reservations/newEmptyReservation', newReservation,'post', emptyReservationCreated, null, null )
}
const emptyReservationCreated = (reservation: reservation) => {
Floorplan.reservations.push(reservation)
const selectedTable = getSelectedTableData()
selectedTable.status = 2
selectedTable.default_covers = reservation.covers
updateTableData(selectedTable)
updateCoverText(selectedTable)
setReservationStatus(getSelectedTableData())
showVirtualKeyboard(lang('confirm_reservation_name'), 32, false, addReservationName)
}
const addReservationName = (name: string) => {
hideVirtualKeyboard()
const reservation = Floorplan.reservations.filter(reservation => reservation.floorplan_table_id == getSelectedTableData().id)[0]
reservation.name = name
ajax('/reservations/updateReservation', reservation, 'post', reservationNameAdded, null, null)
}
const reservationNameAdded = (updatedReservation: reservation) => {
console.log(updatedReservation)
Floorplan.reservations = Floorplan.reservations.filter(reservation => reservation.id != updatedReservation.id)
Floorplan.reservations.push(updatedReservation)
setReservationStatus(getSelectedTableData())
}
const getReservationsOnTable = (table: floorplan_table) => Floorplan.reservations.filter(reservation => reservation.floorplan_table_id == table.id)
const updateTableData = (tableToRemove: floorplan_table) => {
Floorplan.tables = Floorplan.tables.filter(table => table.id != tableToRemove.id)
Floorplan.tables.push(tableToRemove)
}
const unreserveTable = () => {
const selectedTable = getSelectedTableData()
selectedTable.status = 0
ajax('/reservations/unreserveTable', selectedTable, 'post', tableUnreserved, null, null)
}
const tableUnreserved = (table: floorplan_table) => {
Floorplan.reservations = Floorplan.reservations.filter(reservation => reservation.floorplan_table_id != table.id)
updateTableData(table)
setReservationStatus(table)
}
const getSelectedTableData = () => getTableDataFromTableNumber(Floorplan.selectedTableNumber)
const deselectTables = () => {
Floorplan.stage.find('Rect, Ellipse').forEach( (shape: Konva.Shape) => {
shape.stroke('black')
});
Floorplan.selectedDecorationId = 0
Floorplan.selectedTableNumber = 0
turnOffMode('tableSelected')
turnOffMode('activeTableSelected')
turnOffMode('decorationSelected')
turnOffMode('merge')
turnOffMode('transfer')
setTransformerNodes([])
}
const selectTable = (tableShape: Konva.Shape) => {
tableShape.stroke('yellow')
const table = getTableDataFromShape(tableShape)
Floorplan.selectedTableNumber = table.table_number
if(isInMode('edit')){
setTransformerNodes([tableShape])
}
if(tableIsOpen(table)){
turnOnMode('activeTableSelected')
}
$('.reservationStatus').html('<b>'+lang('active_table', table.table_number.toString()+'</b>'))
updateCoverText(table)
$('.selectedTableNumber').text(lang('active_table', table.table_number.toString()))
setReservationStatus(table)
const unmergeVisibility = table.merged_children ? 'visible' : 'hidden'
$('.unmergeButton').css('visibility', unmergeVisibility)
turnOnMode('tableSelected')
}
const updateCoverText = (table:floorplan_table) => $('.selectedTableCovers').text(lang('covers', table.default_covers.toString()))
const tableDblClicked = (event: Konva.KonvaEventObject<any>) => {
let tableShape = getTableShapeFromGroup(event.currentTarget as Konva.Group)
const table = getTableDataFromShape(tableShape)
redirect(`/order/${table.table_number}`)
}
const tableClicked = (event: Konva.KonvaEventObject<any>) => {
let tableShape = getTableShapeFromGroup(event.currentTarget as Konva.Group)
const table = getTableDataFromShape(tableShape)
if(isInMode('merge')) {
mergeTables(getTableDataFromTableNumber(Floorplan.selectedTableNumber), table)
return;
}
if(isInMode('transfer')){
transferTables(getTableDataFromTableNumber(Floorplan.selectedTableNumber), table)
}
const selectedTableNumber = Floorplan.selectedTableNumber
deselectTables()
if(selectedTableNumber != table.table_number){
selectTable(tableShape)
}
}
const createDecorationShape = (decoration:decoration, select?: boolean) => {
const draggable = isInMode('edit')
const decorationShape = new Image()
decorationShape.onload = () => {
const decorationImage = new Konva.Image({
id: decoration.id.toString(),
x: decoration.decoration_pos_x * Floorplan.visualScale,
y: decoration.decoration_pos_y * Floorplan.visualScale,
image: decorationShape,
offsetX: decoration.decoration_width * 0.5 * Floorplan.visualScale,
offsetY: decoration.decoration_height * 0.5 * Floorplan.visualScale,
rotation: decoration.decoration_rotation,
width: decoration.decoration_width * Floorplan.visualScale,
height: decoration.decoration_height * Floorplan.visualScale,
draggable: draggable,
});
// add the shape to the layer
Floorplan.tableLayer.add(decorationImage)
Floorplan.tableLayer.draw()
decorationImage.moveToBottom()
setupDecorationEvents(decorationImage)
if(select){
decorationImage.moveToTop()
selectDecorationShape(decorationImage)
}
}
decorationShape.src = '/images/decorations/' + decoration.decoration_image
}
const setupDecorationEvents = (decorationShape: Konva.Image) => {
decorationShape.on('click', decorationClicked)
decorationShape.on('tap', decorationClicked)
decorationShape.on('transformend', decorationTransformed)
decorationShape.on('dragend', decorationTransformed)
}
const decorationClicked = (event: Konva.KonvaEventObject<any>) => {
let decorationShape = event.target as Konva.Image
if(isInMode('edit')){
turnOffMode('tableSelected')
if ((isInMode('decorationSelected') && Floorplan.selectedDecorationId != Number(decorationShape.id())) || !isInMode('decorationSelected')) {
selectDecorationShape(decorationShape)
} else {
deselectTables()
decorationShape.moveToBottom()
}
} else {
deselectTables()
}
}
const selectDecorationShape = (decorationShape: Konva.Image) => {
deselectTables()
Floorplan.transformer.nodes([decorationShape])
Floorplan.selectedDecorationId = Number(decorationShape.id())
decorationShape.moveToTop()
Floorplan.transformer.moveToTop()
turnOnMode('decorationSelected')
}
const getDecorationDataById = (id: number) => {
return Floorplan.decorations.find(decoration => id == decoration.id)
}
const decorationTransformed = (event: Konva.KonvaEventObject<MouseEvent>|Konva.KonvaEventObject<TouchEvent|DragEvent|MouseEvent>) => {
let decorationShape = event.currentTarget as Konva.Image
const oldDecorationData = getDecorationDataById(Number(decorationShape.id()))
const newDecoration: decoration = {
id: oldDecorationData.id,
decoration_room: oldDecorationData.decoration_room,
decoration_pos_x: Math.round(decorationShape.x() / Floorplan.visualScale),
decoration_pos_y: Math.round(decorationShape.y() / Floorplan.visualScale),
decoration_rotation: Math.round(decorationShape.rotation()),
decoration_width: Math.round((decorationShape.scaleX() * decorationShape.width()) / Floorplan.visualScale),
decoration_height: Math.round((decorationShape.scaleY() * decorationShape.height()) / Floorplan.visualScale),
decoration_image: oldDecorationData.decoration_image,
venue_id: oldDecorationData.venue_id,
}
saveDecoration(newDecoration)
}
const saveDecoration = (decorationToUpdate: decoration) => {
const decorations =
Floorplan
.decorations
.filter(decoration => {
return decoration.id != decorationToUpdate.id
})
decorations.push(decorationToUpdate)
Floorplan.decorations = decorations
ajax("/floorplan/updateDecoration", decorationToUpdate, 'post', null,null,null)
}
const showDecorator = () => $('#decorator').css('display', 'flex')
const hideDecorator = () => $('#decorator').css('display', 'flex').hide()
const addDecoration = (e: Event) => {
const button = $(e.currentTarget)
const newDecoration: decoration = {
id: 0,
decoration_room: Floorplan.currentRoom.id,
decoration_pos_x: Floorplan.visualScaleBasis / 2,
decoration_pos_y: Floorplan.visualScaleBasis / 2,
decoration_rotation: 0,
decoration_width: 200,
decoration_height: 200,
decoration_image: button.data('image'),
venue_id: Floorplan.currentRoom.venue_id
}
ajax('/floorplan/addDecoration', newDecoration, 'post', decorationAdded, null, null)
}
const decorationAdded = (decoration: decoration) => {
Floorplan.decorations.push(decoration)
createDecorationShape(decoration, true)
hideDecorator()
}
const deleteDecoration = () => ajax(
'/floorplan/deleteDecoration',
getDecorationDataById(Floorplan.selectedDecorationId),
'post', decorationDeleted, null, null)
const decorationDeleted = (deletedDecoration:decoration) => {
Floorplan.decorations = Floorplan.decorations.filter(decoration => decoration.id != deletedDecoration.id)
const decorationShape = Floorplan.stage.findOne(`#${deletedDecoration.id}`)
decorationShape.destroy()
deselectTables()
}
const setRoomBackground = (roomToLoad: room) => {
const width = Floorplan.floorplanDiv.width()
const height = Floorplan.floorplanDiv.height()
if(roomToLoad.background_image) {
Floorplan.floorplanDiv.css("background-image", `url(/images/rooms/${roomToLoad?.background_image})`)
Floorplan.floorplanDiv.css("background-size", `${width}px ${height}px`)
}
}
const setupKonva = () => {
const dimensions = getDimensions()
if(Floorplan.stage !== null) Floorplan.stage.destroy()
Floorplan.stage = new Konva.Stage({
container: 'floorplanCanvas',
width: dimensions.width,
height: dimensions.height,
})
const stageClick = (e: Konva.KonvaEventObject<any> ) => {
if(e.target == Floorplan.stage){
deselectTables()
}
}
Floorplan.stage.on('click', stageClick)
Floorplan.stage.on('tap', stageClick)
Floorplan.transformer = new Konva.Transformer({
rotationSnaps: [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 225, 270, -15, -30, -45, -60, -75, -90, -105, -120, -135, -150, -165, -180, -225, -270, 360, -360],
anchorSize: 30 * Floorplan.visualScale,
ignoreStroke: true,
centeredScaling: true,
anchorCornerRadius: 10,
});
Floorplan.tableLayer = new Konva.Layer()
Floorplan.tableLayer.add(Floorplan.transformer)
Floorplan.stage.add(Floorplan.tableLayer)
}
const resetKonva = setupKonva
const changeTableShape = () => {
if(!Floorplan.selectedTableNumber) return
const table = getTableDataFromTableNumber(Floorplan.selectedTableNumber)
const tableGroup = getTableGroupFromTableNumber(table.table_number)
const order = ['square', 'rect', 'longrect', 'diamond', 'circle', 'ellipse', 'longellipse']
if (order.indexOf(table.shape) === -1)
table.shape = 'square'
const currentIndex = order.indexOf(table.shape)
let nextIndex = currentIndex + 1
if (nextIndex > (order.length) - 1)
nextIndex = 0
table.shape = order[nextIndex]
switch(table.shape) {
case 'square':
case 'circle':
// noinspection JSSuspiciousNameCombination
table.height = table.width
table.rotation = 0
break
case 'diamond':
// noinspection JSSuspiciousNameCombination
table.height = table.width
table.rotation = 45
break
case 'rect':
case 'ellipse':
table.height = table.width * 2
table.rotation = 0
break
case 'longrect':
case 'longellipse':
table.rotation = 90
break
}
saveTable(table)
deselectTables()
redrawTable(tableGroup)
}
const redrawTable = (tableGroup: Konva.Group) => {
deselectTables()
const draggable = tableGroup.draggable()
const table = getTableDataFromGroup(tableGroup)
tableGroup.destroy()
const newTableGroup = createTableShape(table)
const newTableShape = getTableShapeFromTableNumber(table.table_number)
selectTable(newTableShape)
newTableGroup.draggable(draggable)
}
const showAddTablePopup = () => showVirtualNumpad(lang('new_table_number'), 4, false, false, true, addTable);
const addTable = (tableNumber: number) => {
const newTable : floorplan_table = {
id: 0,
table_number: tableNumber,
room_id: Floorplan.currentRoom.id,
default_covers: 2,
width: 200,
height: 200,
rotation: 0,
pos_x: Floorplan.visualScaleBasis / 2,
pos_y: Floorplan.visualScaleBasis / 2,
shape: 'square',
merged_children : '',
previous_state: '',
status: 0,
venue_id: 1
};
ajax('/floorplan/createTable', newTable, 'post', tableAdded, tableNotAdded, null)
}
const tableAdded = (table: floorplan_table) => {
deselectTables()
const newTableGroup = createTableShape(table)
Floorplan.tables.push(table)
selectTable(getTableShapeFromGroup(newTableGroup))
}
const tableNotAdded = (response: string) => {
posAlert(response)
}
const confirmDeleteTable = () => confirmation(
lang('confirm_delete_table', Floorplan.selectedTableNumber.toString()),
Floorplan.selectedTableNumber,
'Confirm', deleteTable)
const deleteTable = (tableNumber: number) => {
if(!tableNumber) return false
const tableToDelete = getTableDataFromTableNumber(tableNumber)
if(tableIsOpen(tableToDelete)){
posAlert(lang('error_delete_existing_table'))
return false
}
ajax(`/floorplan/deleteTable`, tableToDelete, 'post', tableDeleted, null, null);
}
const tableDeleted = (deletedTable: floorplan_table) => {
Floorplan.tables = Floorplan.tables.filter(table => table.table_number != deletedTable.table_number)
const tableGroup = getTableGroupFromTableNumber(deletedTable.table_number)
deselectTables()
tableGroup.destroy()
}
const toggleMergeMode = () => toggleMode('merge')
const mergeTables = (table1: floorplan_table, table2: floorplan_table ) => {
toggleMergeMode()
if(table1.table_number == table2.table_number){
posAlert(lang('error_self_merge'))
return false;
}
ajax('/floorplan/mergeTables', [table1, table2], 'post', tablesMerged, null, null)
}
const tablesMerged = (tables: Record<'child'|'parent'|'merged', floorplan_table>) => {
tableDeleted(tables['child'])
tableDeleted(tables['parent'])
tableAdded(tables['merged'])
deselectTables()
const tableGroup = getTableGroupFromTableNumber(tables['merged'].table_number)
selectTable(getTableShapeFromGroup(tableGroup))
tableGroup.draggable(true)
}
const unmergeTable = () => ajax(`/floorplan/unmergeTable/${Floorplan.selectedTableNumber}`, null, 'get', tablesUnmerged, null, null)
const tablesUnmerged = (tables: Record<'child'|'parent', floorplan_table>) => {
const parentTable = tables['parent']
const childTable = tables['child']
tableDeleted(parentTable)
tableAdded(parentTable)
tableAdded(childTable)
deselectTables()
}
const toggleTransferMode = () => toggleMode('transfer')
const transferTables = (origin: floorplan_table, destination: floorplan_table) => {
if(origin.table_number == destination.table_number){
posAlert(lang('transfer_self_error'))
return
}
ajax(`/floorplan/transferTable/${origin.table_number}/${destination.table_number}`, null, 'get', tableTransferred, null, null)
}
const tableTransferred = (tables: Record<"origin"|"destination", floorplan_table>) => {
const origin = tables['origin']
const destination = tables['destination']
Floorplan.activeTableNumbers = Floorplan.activeTableNumbers.filter(tableNumber => tableNumber != origin.table_number)
Floorplan.activeTableNumbers.push(destination.table_number)
if(Floorplan.currentRoom.id == origin.room_id) {
redrawTable(getTableGroupFromTableNumber(origin.table_number))
}
redrawTable(getTableGroupFromTableNumber(destination.table_number))
}
const getDimensions = () => {
Floorplan.floorplanDiv = $('#floorplanCanvas')
const parentDiv = $('#floorplanCenterColumn .middleCell')
const outerWidth = parentDiv.outerWidth()
const outerHeight = parentDiv.outerHeight()
if (outerWidth >= outerHeight) {
Floorplan.floorplanDiv.css('height', '100%')
} else {
Floorplan.floorplanDiv.css('width','100%')
}
Floorplan.visualScale = Floorplan.floorplanDiv.width() / Floorplan.visualScaleBasis
return {width: Floorplan.floorplanDiv.width(), height:Floorplan.floorplanDiv.height()}
}

View File

@@ -1,604 +0,0 @@
type OrderScreenData = {
order_screen_pages: order_screen_page[]
sales_categories: sales_category[]
print_groups: print_group[]
custom_item: item
}
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
custom_item: item,
selected_cover: number
table: floorplan_table,
}
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,
custom_item: null,
selected_cover: 0,
table: null,
}
const loadPageGroup = (e: Event) => {
const button = $(e.target)
const container = $('#pageGroupContainer')
$('.loadPageGroup').removeClass('active')
button.addClass('active')
let pageGroupId = button.data('page-group-id')
container.find('.pageGroup').hide()
let activeGrid = $(`.pageGroup[data-page-group-id=${pageGroupId}]`)
let navButtons = container.next('.pageNavigation')
navButtons.css('display', 'flex')
activeGrid.find('.gridPage').length > 1
? navButtons.show()
: navButtons.hide()
activeGrid.css('display', 'inline-flex')
}
const setupOrderScreen = (data: OrderScreenData) => {
$('.coverSelector, .gridContainer').hide()
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', '[data-primary-action=grid],[data-secondary-action=grid]', gridButtonClicked)
doc.on('click', '.closeGrid', hideGrids)
doc.on('click', '.freetextButton', freetext)
doc.on('click', '.openItemButton', customItem)
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('click', '.changeCoverNumberButton', changeCoverNumberPrompt)
doc.on('click', '.showCoverSelectorButton', showCoverSelector)
doc.on('click', '.coverSelectorButton', coverSelected)
doc.on('change', '[name=print_override]', printGroupOverride)
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
});
}
/**
* @param direction 1 for forward, -1 for backwards.
* @param button
*/
const navigatePage = (direction: number, button: JQuery) => {
const grid =
button
.parent()
.parent()
.find('.pageGroup:visible')
grid.get()[0].scrollLeft += grid.width() * direction
}
const goToNextPage = (e: JQuery.TriggeredEvent) => navigatePage(1, $(e.target))
const goToPrevPage = (e: JQuery.TriggeredEvent) => navigatePage(-1, $(e.target))
const addItemToOrderBox = (orderItem:orderItem) => {
const orderBox = $('.orderBoxTable tbody')
let selectedRows = orderBox.find('tr.selected')
let lastRow : JQuery = selectedRows.length ? getLastInstructionRow(selectedRows.first()) : orderBox.find('tr').last()
const existingRow = orderBox
.find('tr')
.filterByData('item', orderItem.item)
.filterByData('print_group', orderItem.print_group)
.filterByData('cover', orderItem.cover)
.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)
newRow
.setColumnValue( lang('printgroup_header'), selectedRow.getColumnValue(lang('printgroup_header')) )
if(parentRow.hasClass('selected')){
selectRow(newRow)
} else {
newRow.pulse()
}
scrollToElement(newRow)
}
})
}
const lastRow = orderBox.find('tr').last()
newRow
.setColumnValue(lang('printgroup_header'), lastRow.getColumnValue(lang('printgroup_header')))
.appendTo(orderBox)
.pulse()
scrollToElement(newRow)
}
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,
print_group: printGroup,
cover: OrderScreen.selected_cover,
}
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) => {
const existingQty = Number(row.getColumnValue(lang('qty_header')))
const newQty = qty + existingQty
row.setColumnValue(lang('qty_header'), newQty)
calculateRowTotal(row)
}
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)
newRow.pulse()
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)
const itemCellText = $('<span/>').text(orderItem.item.item_name)
row
.addClass(`${orderItem.item.item_type}Row`)
.setColumnValue(lang('qty_header'), orderItem.qty)
.setColumnValue(lang('price_header'), price)
.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('order-item-id', orderItem.id)
.data('print_group', orderItem.print_group)
.data('cover', orderItem.cover)
.data('item', orderItem.item)
.find('td.itemCell')
.append(itemCellText)
changeCoverOnRow(row, orderItem.cover)
if(orderItem.item.item_type == 'instruction' && price.value <= 0){
row
.find('.totalPriceCell')
.css('font-size', 0)
}
return row
}
const itemButtonClicked = (e: JQuery.TriggeredEvent) => {
hideGrids()
const existingItemRows = $('.itemRow')
const button = $(e.target).closest('.posButton')
const item : item = button.data('item')
if(item.item_type == 'instruction' && existingItemRows.length < 1) return
const qty = OrderScreen.qty_override || 1
OrderScreen.qty_override = 1
addNewItem(item, qty)
}
const gridButtonClicked = (e: JQuery.TriggeredEvent) => {
const button = $(e.target).closest('.posButton')
const grid : number = button.data('grid')
ajax(`/order/getGridHtml/${grid}`, null, null,gridHtmlGenerated, null, null)
}
const hideGrids = () => $('.gridContainer').hide()
const gridHtmlGenerated = (gridData: {gridHtml:string, grid: grid}) => {
const gridContainer = $('.gridContainer')
const gridCellWidth = getGridCellWidth()
const gridCellHeight = getGridCellHeight()
const grid = gridData.grid
const gridHtml = gridData.gridHtml
gridContainer
.show()
.width(gridCellWidth * grid.grid_cols)
.children('.gridContainerHeader')
.children('span')
.text(grid.grid_name)
.parent()
.parent()
.find('.pageGroup')
.html(gridHtml)
.show()
.parent()
.height(gridCellHeight * grid.grid_rows)
.closest('.gridContainer')
.find('.pageNavigation')
.toggle(gridContainer.find('.gridPage').length > 1)
.height(gridCellHeight)
}
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 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')
if(row.hasClass('itemRow') && instructionRows.length){
deselectRow(instructionRows)
}
}
const deleteRow = (row: JQuery) => row.find('*:not(.hidden)').slideUp('fast', () => row.remove())
const voidInstructionRow = (row: JQuery) => {
if(!row.prevAll('.itemRow').first().hasClass('selected'))
deleteRow(row)
}
const voidItemRow = (row : JQuery) => decrementQty(row)
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 = () => {
const orderBox = $('.orderBoxTable tbody')
const allRows = orderBox.find('tr')
if(allRows.length < 1) return
voidRows(allRows.last())
}
const updateOrderBoxTotals = () => {
const allRows = $('.orderBoxTable tbody tr')
const selectedRows = $('.orderBoxTable tbody tr.selected')
const completeTotal = lang('totalPrice', getTotalOfRows(allRows))
const selectedTotal = lang('selectedPrice', getTotalOfRows(selectedRows))
$('.orderBoxTotal').text(completeTotal)
$('.orderBoxSelectedTotal').text(selectedTotal)
}
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) => {
let price = getUnitPrice(row)
let qty = getQty(row)
row.setColumnValue(lang('total_price_header'), price.multiply(qty))
}
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'), existingQty - qty)
calculateRowTotal(row)
}
const scrollToElement = (JQueryElement: JQuery) => {
const element = JQueryElement.get()[0]
const container = JQueryElement.closest('.orderBox').get()[0]
const containerTop = $(container).scrollTop()
const containerBottom = containerTop + $(container).height();
const elemTop = element.offsetTop
const elemBottom = elemTop + $(element).height();
if (elemTop < containerTop) {
$(container).scrollTop(elemTop);
} else if (elemBottom > containerBottom) {
$(container).scrollTop(elemBottom - $(container).height());
}
}
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
}
}
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 = Object.assign({}, OrderScreen.custom_item)
item.item_type = 'instruction'
item.item_name = text
addNewItem(item)
}
const customItem = () => showVirtualKeyboard(lang('enter_item_name'), 32,false, customItemTextSubmitted)
const customItemTextSubmitted = (text: string) => {
const submitFunction = (priceString: string) => {
const price = currency(priceString, {fromCents: false})
const item = Object.assign({}, OrderScreen.custom_item)
item.item_type = 'item'
item.item_name = text
item.price1 = price.intValue
addNewItem(item)
}
showVirtualNumpad(lang('enter_item_price'), 4, false, true, true, submitFunction)
}
const getGridCellHeight = () => $('#pageGroupContainer').height()/8
const getGridCellWidth = () => $('#pageGroupContainer').width()/6
const showCoverSelector = (event: JQuery.TriggeredEvent) => {
const button = $(event.target)
const gridHeight = getGridCellHeight()
const coverSelector = $('.coverSelector')
coverSelector
.toggle(!coverSelector.is(':visible'))
.width(button.width())
.css({
left: button.offset().left + 'px',
top: button.offset().top + button.height() + 'px',
})
.find('.coverSelectorButton')
.height(gridHeight)
}
const coverSelected = (event: JQuery.TriggeredEvent) => {
$('.coverSelector').hide()
const button = $(event.target)
const cover = Number(button.data('cover'))
const selectedRows = $('.orderBoxTable tbody').find('tr.itemRow.selected')
selectedRows.each( (_, selectedRow) => changeCoverOnRow($(selectedRow), cover))
OrderScreen.selected_cover = cover
}
const changeCoverOnRow = (row: JQuery, cover: number) => {
row.data('cover', cover)
const itemCell = row.find('.itemCell')
const existingCoverSpan = itemCell.find('small')
const coverSpan = existingCoverSpan.length > 0
? existingCoverSpan
: $('<small/>').appendTo(itemCell)
coverSpan.text(lang('selected_cover', cover.toString()))
if(cover < 1 || !row.hasClass('itemRow')) {
coverSpan.remove()
}
}
const changeCoverNumberPrompt = () =>
showVirtualNumpad(lang('how_many_covers'), 3, false, false, true, changeCoverNumberPromptSubmitted)
const changeCoverNumberPromptSubmitted = (value: string) => updateCoverNumbers(Number(value))
const updateCoverNumbers = (covers: number) => {
let newTable = Object.assign({}, OrderScreen.table)
newTable.default_covers = covers
ajax('/order/updateCovers', newTable, 'post', coverNumbersUpdated, null, null)
}
const coverNumbersUpdated = (newTable: floorplan_table) => {
const covers = newTable.default_covers
OrderScreen.table = newTable
$('.changeCoverNumberButton').text(lang('covers', covers.toString()))
generateCoverSelector()
}
const generateCoverSelector = () => {
const covers = OrderScreen.table.default_covers
const coverSelector = $('.coverSelector')
coverSelector.hide().children().remove()
for(let cover=0; cover<=covers; cover++) {
const buttonText = cover==0 ? lang('cover_zero') : lang('selected_cover', cover.toString())
loadTemplate('#posButtonTemplate')
.find('a')
.first()
.addClass('coverSelectorButton')
.text(buttonText)
.data('cover', cover)
.appendTo(coverSelector)
}
}
$(() => {
OrderScreen.table = $('#pageContainer').data('table') || null
ajax('/order/getOrderScreenData/1', null, 'get', setupOrderScreen, null, null)
})

View File

@@ -1,53 +0,0 @@
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
pulse() : JQuery
}
$.fn.pulse = function(this: JQuery) {
pulseElement(this)
return this
}
$.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,268 +0,0 @@
type KeyboardRowName = `row${number}${"" | "_"}${string}`;
type KeyboardRow = Partial<Record<KeyboardRowName, string[]>>
interface VirtualKeyboard {
[layoutName: string]: KeyboardRow;
}
let showVirtualNumpad = (heading: string, maxlength = 4, isPassword: boolean, allowDecimals = true, allowClose = true, submitFunction: Function) => {
let numpad = $('#virtualNumpad');
let inputBox = $('#virtualNumpadInput')
let closeKeyboardButton = $('.closeKeyboards')
numpad.css('display', 'flex')
let showCloseButton = allowClose ? 'flex' : 'none'
closeKeyboardButton.css('display', showCloseButton)
$('#virtualNumpadHeading').html(heading)
/*
The numpad always submits to a function.
If a function isn't specified, it will submit
to the same function that called it
*/
numpad.data('value', '');
inputBox.text('');
numpad.data('maxlength', maxlength)
numpad.data('submitfunction', submitFunction)
numpad.data('password', isPassword);
numpad.data('allowdecimals', allowDecimals);
$(document).off('keyup');
$(document).on('keyup', e => {
let key = e.key;
switch (key) {
case 'Backspace':
case 'Delete':
key = 'clear'
break;
case 'Enter':
key = 'submit'
break;
}
virtualNumpadInput(key)
});
}
let hideVirtualKeyboard = () => {
let keyboard = $('#virtualKeyboard');
keyboard.hide()
$('#virtualKeyboardHeading').html('');
$(document).unbind('keyup');
}
let hideVirtualNumpad = () => {
let numpad = $('#virtualNumpad')
numpad.css('display', 'none')
$('#virtualNumpadHeading').html('')
$(document).unbind('keyup')
}
let virtualNumpadInput = (input: string) => {
let inputBox = $('#virtualNumpadInput')
let numpad = $('#virtualNumpad')
let maxlength = numpad.data('maxlength')
let allowDecimals = numpad.data('allowdecimals')
let submitFunction = numpad.data('submitfunction')
let allowedValues = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'submit', 'clear']
let currentValue = numpad.data('value').toString()
if (allowDecimals)
allowedValues.push('.', ',')
let validInput = allowedValues.includes(input);
//If the input is a valid number, decimal point or command.
if (validInput) {
switch (input) {
case 'submit':
hideVirtualNumpad()
let numpadValue: string = numpad.data('value').length > 0 ? numpad.data('value') : "0"
submitFunction(numpadValue)
break;
case 'clear':
clearNumpadInput()
break;
default:
let newText = currentValue + input
let isPassword = numpad.data('password')
let length = input.length + inputBox.text().length
if (length <= maxlength) {
inputBox.append(isPassword ? '*' : input)
numpad.data('value', newText)
}
}
}
}
let clearNumpadInput = () => {
$('#virtualNumpadInput').text("")
$('#virtualNumpad').data('value', '')
}
let setupVirtualNumpad = () => {
$(document).on('click', '.virtualNumpadButton', e => {
virtualNumpadInput($(e.target).data('value').toString())
})
$('.closeKeyboards').on('click', () => {
hideVirtualKeyboard();
hideVirtualNumpad()
});
}
let setupVirtualKeyboard = (keyboardLayouts: VirtualKeyboard) => {
Application.keyboard = {
capsLock: false,
shift: false,
layouts: keyboardLayouts,
currentLayout: 'default',
}
$(document).on('click', '.virtualKeyboardButton', e => {
virtualKeyboardInput($(e.target).data('value'));
})
$(document).on('click', '.forceFocus', (e) => {
$('#virtualKeyboardInput').trigger('focus')
})
setKeyboardLayout('default')
}
let showVirtualKeyboard = (heading: string, maxlength = 32, isPassword = false, submitFunction :Function = () => {hideVirtualKeyboard()}) => {
let keyboard = $('#virtualKeyboard')
let inputBox = $('#virtualKeyboardInput')
keyboard.css('display', 'flex')
$('#virtualKeyboardHeading').html(heading)
$('.forceFocus').trigger('click')
keyboard.data('value', '')
inputBox.val('')
keyboard.data('maxlength', maxlength)
keyboard.data('password', isPassword)
keyboard.data('submitfunction', submitFunction)
inputBox.attr('autofocus', 'autofocus');
inputBox.trigger('focus')
inputBox.trigger('click')
inputBox.trigger('select')
$(document).on('keyup', e => {
let key = e.key
if (key == 'Enter' && inputBox.val().toString().length > 0) {
key = 'submit'
virtualKeyboardInput(key)
}
})
}
let virtualKeyboardInput = (input: string) => {
let inputBox = $('#virtualKeyboardInput')
let keyboard = $('#virtualKeyboard');
let maxlength = keyboard.data('maxlength');
let isPassword = keyboard.data('password');
let length = input.length + inputBox.text().length
switch (input.toLowerCase()) {
case 'backspace':
case 'delete':
let newText = inputBox.text().slice(0, -1);
inputBox.val(newText)
keyboard.data('value', newText);
break;
case 'submit':
hideVirtualKeyboard();
let submitFunction = keyboard.data('submitfunction')
submitFunction(inputBox.val());
break;
case 'shift':
if (Application.keyboard.capsLock) break;
Application.keyboard.shift = !Application.keyboard.shift
Application.keyboard.capsLock = false
setKeyboardLayout('default', Application.keyboard.shift ? 'shift' : '')
break;
case 'capslock':
Application.keyboard.shift = false
Application.keyboard.capsLock = !Application.keyboard.capsLock
let capsLockButton = $('[data-value="capslock"]')
capsLockButton.toggleClass('active')
setKeyboardLayout('default', Application.keyboard.capsLock ? 'shift' : '')
break;
case 'space':
input = ' ';
break;
}
//Stops keys such as F5 being pressed.
if (input.length == 1) {
if (Application.keyboard.shift || Application.keyboard.capsLock) {
input = input.toUpperCase()
//If shift, reload lowercase
if (Application.keyboard.shift) {
Application.keyboard.shift = false
setKeyboardLayout('default');
}
}
let newText = inputBox.val() + input;
keyboard.data('value', newText);
inputBox.val(newText)
}
}
let setKeyboardLayout = (layout: string, modifier = '') => {
if (modifier != '') modifier = `_${modifier}`
Application.keyboard.currentLayout = layout
let layoutToLoad = Application.keyboard.layouts[layout]
$('.virtualKeyboardRow').each((index, row) => {
/*
We start at 1 instead of 0. Makes it easier for non-programmers
and translators making their own language packs
*/
index = index + 1
let currentRow : string[] = layoutToLoad[`row${index}${modifier}`]
$(row).children('a').each((keyIndex, button) => {
let key = $(button);
let keyValue: string = currentRow[keyIndex];
/*
KeyText is the text that appears
in the button. KeyData is the value
submitted when the button is pressed.
*/
let keyText = keyValue;
let keyData = keyValue;
key.addClass('posButton');
key.addClass('virtualKeyboardButton');
let pattern = new RegExp(/\[([^)]+)\]/);
let matches = keyValue.match(pattern);
if (matches) {
keyText = keyValue.replace(pattern, '');
keyData = matches[1];
}
key.html(keyText)
//Use attr() as some keys have CSS dependent on data-value
key.attr('data-value', keyData)
key.data('value', keyData)
})
})
}
$(() => {
setupVirtualNumpad()
ajax('/ajax/getKeyboardLayout/english', null, 'get',setupVirtualKeyboard, null, null)
})

View File

@@ -1,6 +0,0 @@
{
"name": "ts",
"version": "1.0.0",
"dependencies": {
}
}

View File

@@ -1,18 +0,0 @@
{
"compilerOptions":{
"lib":[
"esnext",
"dom"
],
"noImplicitAny":true,
"removeComments":false,
"preserveConstEnums":true,
"outDir":"../js",
"target":"ES2016",
"sourceMap":true,
"moduleResolution": "node"
},
"include":[
"*"
]
}

View File

@@ -1,115 +0,0 @@
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
print_group: print_group
item: item
cover: number
}
interface print_group {
id: number,
name: string,
printer: number,
venue_id: number,
}
interface ajaxResult {
status: string
data : any
}
interface ApplicationState {
keyboard: keyboard
mode: PosModes
languageVars: Record<any, string>
}
interface floorplan_table {
table_number: number,
room_id: number
venue_id: number
pos_x: number
pos_y: number
shape: string
width: number
height: number
default_covers: number
rotation: number
merged_children: string
previous_state: string
status: number
id: number
}
interface decoration {
id: number
decoration_room: number
decoration_pos_x: number
decoration_pos_y: number
decoration_rotation: number
decoration_width: number
decoration_height: number
decoration_image: string
venue_id: number
}
interface room {
id: number
room_name: string
background_image: string
venue_id: number
}
interface reservation {
id: number,
name: string,
time: number,
covers: number,
created_at: number,
floorplan_table_id: number,
}
interface keyboard {
capsLock: boolean
shift: boolean
layouts: VirtualKeyboard
currentLayout: string
}
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,38 +0,0 @@
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
}
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
}
}
interface currency {
add(number: currency.Any): currency;
subtract(number: currency.Any): currency;
multiply(number: currency.Any): currency;
divide(number: currency.Any): currency;
distribute(count: number): Array<currency>;
dollars(): number;
cents(): number;
format(opts?: currency.Options | currency.Format): string;
toString(): string;
toJSON(): number;
readonly intValue: number;
readonly value: number;
}
declare const currency: currency.Constructor;

View File

@@ -1,174 +0,0 @@
// filters
import { Blur } from 'konva/lib/filters/Blur';
import { Brighten } from 'konva/lib/filters/Brighten';
import { Contrast } from 'konva/lib/filters/Contrast';
import { Emboss } from 'konva/lib/filters/Emboss';
import { Enhance } from 'konva/lib/filters/Enhance';
import { Grayscale } from 'konva/lib/filters/Grayscale';
import { HSL } from 'konva/lib/filters/HSL';
import { HSV } from 'konva/lib/filters/HSV';
import { Invert } from 'konva/lib/filters/Invert';
import { Kaleidoscope } from 'konva/lib/filters/Kaleidoscope';
import { Mask } from 'konva/lib/filters/Mask';
import { Noise } from 'konva/lib/filters/Noise';
import { Pixelate } from 'konva/lib/filters/Pixelate';
import { Posterize } from 'konva/lib/filters/Posterize';
import { RGB } from 'konva/lib/filters/RGB';
import { RGBA } from 'konva/lib/filters/RGBA';
import { Sepia } from 'konva/lib/filters/Sepia';
import { Solarize } from 'konva/lib/filters/Solarize';
import { Threshold } from 'konva/lib/filters/Threshold';
declare global {
export namespace Konva {
export let enableTrace: number;
export let pixelRatio: number;
export let dragDistance: number;
export let angleDeg: boolean;
export let showWarnings: boolean;
export let capturePointerEventsEnabled: boolean;
export let dragButtons: Array<number>;
export let hitOnDragEnabled: boolean;
export const isDragging: () => boolean;
export const isDragReady: () => boolean;
export type Vector2d = import('konva/lib/types').Vector2d;
export const Node: typeof import('konva/lib/Node').Node;
export type Node = import('konva/lib/Node').Node;
export type NodeConfig = import('konva/lib/Node').NodeConfig;
export type KonvaEventObject<EventType> =
import('konva/lib/Node').KonvaEventObject<EventType>;
export type KonvaPointerEvent =
import('konva/lib/PointerEvents').KonvaPointerEvent;
export type KonvaEventListener<This, EventType> =
import('konva/lib/Node').KonvaEventListener<This, EventType>;
export const Container: typeof import('konva/lib/Container').Container;
export type Container = import('konva/lib/Container').Container<Node>;
export type ContainerConfig = import('konva/lib/Container').ContainerConfig;
export const Transform: typeof import('konva/lib/Util').Transform;
export type Transform = import('konva/lib/Util').Transform;
export const Util: typeof import('konva/lib/Util').Util;
export const Context: typeof import('konva/lib/Context').Context;
export type Context = import('konva/lib/Context').Context;
export const Stage: typeof import('konva/lib/Stage').Stage;
export type Stage = import('konva/lib/Stage').Stage;
export const stages: typeof import('konva/lib/Stage').stages;
export const Layer: typeof import('konva/lib/Layer').Layer;
export type Layer = import('konva/lib/Layer').Layer;
export type LayerConfig = import('konva/lib/Layer').LayerConfig;
export const FastLayer: typeof import('konva/lib/FastLayer').FastLayer;
export type FastLayer = import('konva/lib/FastLayer').FastLayer;
export const Group: typeof import('konva/lib/Group').Group;
export type Group = import('konva/lib/Group').Group;
export const DD: typeof import('konva/lib/DragAndDrop').DD;
export const Shape: typeof import('konva/lib/Shape').Shape;
export type Shape = import('konva/lib/Shape').Shape;
export type ShapeConfig = import('konva/lib/Shape').ShapeConfig;
export const shapes: typeof import('konva/lib/Shape').shapes;
export const Animation: typeof import('konva/lib/Animation').Animation;
export type Animation = import('konva/lib/Animation').Animation;
export const Tween: typeof import('konva/lib/Tween').Tween;
export type Tween = import('konva/lib/Tween').Tween;
export type TweenConfig = import('konva/lib/Tween').TweenConfig;
export const Easings: typeof import('konva/lib/Tween').Easings;
export const Arc: typeof import('konva/lib/shapes/Arc').Arc;
export type Arc = import('konva/lib/shapes/Arc').Arc;
export type ArcConfig = import('konva/lib/shapes/Arc').ArcConfig;
export const Arrow: typeof import('konva/lib/shapes/Arrow').Arrow;
export type Arrow = import('konva/lib/shapes/Arrow').Arrow;
export type ArrowConfig = import('konva/lib/shapes/Arrow').ArrowConfig;
export const Circle: typeof import('konva/lib/shapes/Circle').Circle;
export type Circle = import('konva/lib/shapes/Circle').Circle;
export type CircleConfig = import('konva/lib/shapes/Circle').CircleConfig;
export const Ellipse: typeof import('konva/lib/shapes/Ellipse').Ellipse;
export type Ellipse = import('konva/lib/shapes/Ellipse').Ellipse;
export type EllipseConfig =
import('konva/lib/shapes/Ellipse').EllipseConfig;
export const Image: typeof import('konva/lib/shapes/Image').Image;
export type Image = import('konva/lib/shapes/Image').Image;
export type ImageConfig = import('konva/lib/shapes/Image').ImageConfig;
export const Label: typeof import('konva/lib/shapes/Label').Label;
export type Label = import('konva/lib/shapes/Label').Label;
export type LabelConfig = import('konva/lib/shapes/Label').LabelConfig;
export const Tag: typeof import('konva/lib/shapes/Label').Tag;
export type Tag = import('konva/lib/shapes/Label').Tag;
export type TagConfig = import('konva/lib/shapes/Label').TagConfig;
export const Line: typeof import('konva/lib/shapes/Line').Line;
export type Line = import('konva/lib/shapes/Line').Line;
export type LineConfig = import('konva/lib/shapes/Line').LineConfig;
export const Path: typeof import('konva/lib/shapes/Path').Path;
export type Path = import('konva/lib/shapes/Path').Path;
export type PathConfig = import('konva/lib/shapes/Path').PathConfig;
export const Rect: typeof import('konva/lib/shapes/Rect').Rect;
export type Rect = import('konva/lib/shapes/Rect').Rect;
export type RectConfig = import('konva/lib/shapes/Rect').RectConfig;
export const RegularPolygon: typeof import('konva/lib/shapes/RegularPolygon').RegularPolygon;
export type RegularPolygon =
import('konva/lib/shapes/RegularPolygon').RegularPolygon;
export type RegularPolygonConfig =
import('konva/lib/shapes/RegularPolygon').RegularPolygonConfig;
export const Ring: typeof import('konva/lib/shapes/Ring').Ring;
export type Ring = import('konva/lib/shapes/Ring').Ring;
export type RingConfig = import('konva/lib/shapes/Ring').RingConfig;
export const Sprite: typeof import('konva/lib/shapes/Sprite').Sprite;
export type Sprite = import('konva/lib/shapes/Sprite').Sprite;
export type SpriteConfig = import('konva/lib/shapes/Sprite').SpriteConfig;
export const Star: typeof import('konva/lib/shapes/Star').Star;
export type Star = import('konva/lib/shapes/Star').Star;
export type StarConfig = import('konva/lib/shapes/Star').StarConfig;
export const Text: typeof import('konva/lib/shapes/Text').Text;
export type Text = import('konva/lib/shapes/Text').Text;
export type TextConfig = import('konva/lib/shapes/Text').TextConfig;
export const TextPath: typeof import('konva/lib/shapes/TextPath').TextPath;
export type TextPath = import('konva/lib/shapes/TextPath').TextPath;
export type TextPathConfig =
import('konva/lib/shapes/TextPath').TextPathConfig;
export const Transformer: typeof import('konva/lib/shapes/Transformer').Transformer;
export type Transformer =
import('konva/lib/shapes/Transformer').Transformer;
export type TransformerConfig =
import('konva/lib/shapes/Transformer').TransformerConfig;
export const Wedge: typeof import('konva/lib/shapes/Wedge').Wedge;
export type Wedge = import('konva/lib/shapes/Wedge').Wedge;
export type WedgeConfig = import('konva/lib/shapes/Wedge').WedgeConfig;
export const Filters: {
Blur: typeof Blur;
Brighten: typeof Brighten;
Contrast: typeof Contrast;
Emboss: typeof Emboss;
Enhance: typeof Enhance;
Grayscale: typeof Grayscale;
HSL: typeof HSL;
HSV: typeof HSV;
Invert: typeof Invert;
Kaleidoscope: typeof Kaleidoscope;
Mask: typeof Mask;
Noise: typeof Noise;
Pixelate: typeof Pixelate;
Posterize: typeof Posterize;
RGB: typeof RGB;
RGBA: typeof RGBA;
Sepia: typeof Sepia;
Solarize: typeof Solarize;
Threshold: typeof Threshold;
};
}
}