feat: revamp cash units screen
fix: bill math
This commit is contained in:
parent
e194509d10
commit
6474017563
12 changed files with 487 additions and 309 deletions
|
|
@ -56,16 +56,15 @@ const buildBillList = (units, mode) => {
|
||||||
units
|
units
|
||||||
)
|
)
|
||||||
|
|
||||||
const _units = _.cloneDeep(units)
|
const _units = _.filter(it => it.count > 0)(_.cloneDeep(units))
|
||||||
const bills = []
|
const bills = []
|
||||||
|
|
||||||
for(let i = 0; i < amountOfBills; i++) {
|
for(let i = 0; i < amountOfBills; i++) {
|
||||||
const idx = i % _.size(_units)
|
const idx = i % _.size(_units)
|
||||||
if (_units[idx].count > 0) {
|
if (_units[idx].count > 0) {
|
||||||
bills.push(_units[idx].denomination)
|
bills.push(_units[idx].denomination)
|
||||||
}
|
|
||||||
|
|
||||||
_units[idx].count--
|
_units[idx].count--
|
||||||
|
}
|
||||||
|
|
||||||
if (_units[idx].count === 0) {
|
if (_units[idx].count === 0) {
|
||||||
_units.splice(idx, 1)
|
_units.splice(idx, 1)
|
||||||
|
|
@ -82,16 +81,15 @@ const buildBillList = (units, mode) => {
|
||||||
units
|
units
|
||||||
)
|
)
|
||||||
|
|
||||||
const _units = _.orderBy(['denomination'], ['asc'])(_.cloneDeep(units))
|
const _units = _.flow([_.filter(it => it.count > 0), _.orderBy(['denomination'], ['asc'])])(_.cloneDeep(units))
|
||||||
const bills = []
|
const bills = []
|
||||||
|
|
||||||
for(let i = 0; i < amountOfBills; i++) {
|
for(let i = 0; i < amountOfBills; i++) {
|
||||||
const idx = i % _.size(_units)
|
const idx = i % _.size(_units)
|
||||||
if (_units[idx].count > 0) {
|
if (_units[idx].count > 0) {
|
||||||
bills.push(_units[idx].denomination)
|
bills.push(_units[idx].denomination)
|
||||||
}
|
|
||||||
|
|
||||||
_units[idx].count--
|
_units[idx].count--
|
||||||
|
}
|
||||||
|
|
||||||
if (_units[idx].count === 0) {
|
if (_units[idx].count === 0) {
|
||||||
_units.splice(idx, 1)
|
_units.splice(idx, 1)
|
||||||
|
|
@ -108,6 +106,10 @@ const buildBillList = (units, mode) => {
|
||||||
const getSolution = (units, amount, mode) => {
|
const getSolution = (units, amount, mode) => {
|
||||||
const billList = buildBillList(units, mode)
|
const billList = buildBillList(units, mode)
|
||||||
|
|
||||||
|
if (_.sum(billList) < amount.toNumber()) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const solver = sumService.subsetSum(billList, amount.toNumber())
|
const solver = sumService.subsetSum(billList, amount.toNumber())
|
||||||
const solution = _.countBy(Math.floor, solver.next().value)
|
const solution = _.countBy(Math.floor, solver.next().value)
|
||||||
return _.reduce(
|
return _.reduce(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ const Cashbox = ({
|
||||||
percent = 0,
|
percent = 0,
|
||||||
cashOut = false,
|
cashOut = false,
|
||||||
width,
|
width,
|
||||||
|
height,
|
||||||
className,
|
className,
|
||||||
emptyPartClassName,
|
emptyPartClassName,
|
||||||
labelClassName,
|
labelClassName,
|
||||||
|
|
@ -27,6 +28,7 @@ const Cashbox = ({
|
||||||
percent,
|
percent,
|
||||||
cashOut,
|
cashOut,
|
||||||
width,
|
width,
|
||||||
|
height,
|
||||||
applyColorVariant,
|
applyColorVariant,
|
||||||
isLow
|
isLow
|
||||||
})
|
})
|
||||||
|
|
@ -55,35 +57,17 @@ const Cashbox = ({
|
||||||
|
|
||||||
// https://support.lamassu.is/hc/en-us/articles/360025595552-Installing-the-Sintra-Forte
|
// https://support.lamassu.is/hc/en-us/articles/360025595552-Installing-the-Sintra-Forte
|
||||||
// Sintra and Sintra Forte can have up to 500 notes per cashOut box and up to 1000 per cashIn box
|
// Sintra and Sintra Forte can have up to 500 notes per cashOut box and up to 1000 per cashIn box
|
||||||
const CashIn = ({ currency, notes, total }) => {
|
const CashIn = ({
|
||||||
const classes = gridClasses()
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={classes.row}>
|
|
||||||
<div>
|
|
||||||
<div className={classes.innerRow}>
|
|
||||||
<Info2 className={classes.noMarginText}>{notes} notes</Info2>
|
|
||||||
</div>
|
|
||||||
<div className={classes.innerRow}>
|
|
||||||
<Label1 className={classes.noMarginText}>
|
|
||||||
{total} {currency.code}
|
|
||||||
</Label1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const CashOut = ({
|
|
||||||
capacity = 500,
|
capacity = 500,
|
||||||
denomination = 0,
|
|
||||||
currency,
|
currency,
|
||||||
notes,
|
notes,
|
||||||
className,
|
className,
|
||||||
editingMode = false,
|
editingMode = false,
|
||||||
threshold,
|
threshold,
|
||||||
width
|
width,
|
||||||
|
height,
|
||||||
|
total,
|
||||||
|
omitInnerPercentage
|
||||||
}) => {
|
}) => {
|
||||||
const percent = (100 * notes) / capacity
|
const percent = (100 * notes) / capacity
|
||||||
const isLow = percent < threshold
|
const isLow = percent < threshold
|
||||||
|
|
@ -98,6 +82,54 @@ const CashOut = ({
|
||||||
cashOut
|
cashOut
|
||||||
isLow={isLow}
|
isLow={isLow}
|
||||||
width={width}
|
width={width}
|
||||||
|
height={height}
|
||||||
|
omitInnerPercentage={omitInnerPercentage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!editingMode && (
|
||||||
|
<div className={classes.col2}>
|
||||||
|
<div className={classes.innerRow}>
|
||||||
|
<Info2 className={classes.noMarginText}>{notes} notes</Info2>
|
||||||
|
</div>
|
||||||
|
<div className={classes.innerRow}>
|
||||||
|
<Label1 className={classes.noMarginText}>
|
||||||
|
{total} {currency.code}
|
||||||
|
</Label1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CashOut = ({
|
||||||
|
capacity = 500,
|
||||||
|
denomination = 0,
|
||||||
|
currency,
|
||||||
|
notes,
|
||||||
|
className,
|
||||||
|
editingMode = false,
|
||||||
|
threshold,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
omitInnerPercentage
|
||||||
|
}) => {
|
||||||
|
const percent = (100 * notes) / capacity
|
||||||
|
const isLow = percent < threshold
|
||||||
|
const classes = gridClasses()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classes.row}>
|
||||||
|
<div className={classes.col}>
|
||||||
|
<Cashbox
|
||||||
|
className={className}
|
||||||
|
percent={percent}
|
||||||
|
cashOut
|
||||||
|
isLow={isLow}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
omitInnerPercentage={omitInnerPercentage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!editingMode && (
|
{!editingMode && (
|
||||||
|
|
@ -121,4 +153,30 @@ const CashOut = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Cashbox, CashIn, CashOut }
|
const CashOutLite = ({
|
||||||
|
capacity = 500,
|
||||||
|
denomination = 0,
|
||||||
|
currency,
|
||||||
|
notes,
|
||||||
|
threshold,
|
||||||
|
width
|
||||||
|
}) => {
|
||||||
|
const percent = (100 * notes) / capacity
|
||||||
|
const isLow = percent < threshold
|
||||||
|
const classes = gridClasses()
|
||||||
|
return (
|
||||||
|
<div className={classes.col}>
|
||||||
|
<Cashbox
|
||||||
|
percent={percent}
|
||||||
|
cashOut
|
||||||
|
isLow={isLow}
|
||||||
|
width={width}
|
||||||
|
height={15}
|
||||||
|
omitInnerPercentage
|
||||||
|
/>
|
||||||
|
<Chip label={`${denomination} ${currency.code}`} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Cashbox, CashIn, CashOut, CashOutLite }
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const cashboxStyles = {
|
||||||
cashbox: {
|
cashbox: {
|
||||||
borderColor: colorPicker,
|
borderColor: colorPicker,
|
||||||
backgroundColor: colorPicker,
|
backgroundColor: colorPicker,
|
||||||
height: 118,
|
height: ({ height }) => height ?? 118,
|
||||||
width: ({ width }) => width ?? 80,
|
width: ({ width }) => width ?? 80,
|
||||||
border: '2px solid',
|
border: '2px solid',
|
||||||
textAlign: 'end',
|
textAlign: 'end',
|
||||||
|
|
@ -58,7 +58,13 @@ const cashboxStyles = {
|
||||||
|
|
||||||
const gridStyles = {
|
const gridStyles = {
|
||||||
row: {
|
row: {
|
||||||
display: 'flex'
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
innerRow: {
|
innerRow: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { offColor } from 'src/styling/variables'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
cashbox: {
|
|
||||||
height: 36
|
|
||||||
},
|
|
||||||
tBody: {
|
|
||||||
maxHeight: '65vh',
|
|
||||||
overflow: 'auto'
|
|
||||||
},
|
|
||||||
tableWidth: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginRight: 1
|
|
||||||
},
|
|
||||||
descriptions: {
|
|
||||||
color: offColor,
|
|
||||||
marginTop: 0
|
|
||||||
},
|
|
||||||
cashboxReset: {
|
|
||||||
color: offColor,
|
|
||||||
margin: [[13, 0, -5, 20]]
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
marginRight: 12
|
|
||||||
},
|
|
||||||
downloadLogsButton: {
|
|
||||||
marginLeft: 13
|
|
||||||
}
|
|
||||||
}
|
|
||||||
211
new-lamassu-admin/src/pages/Maintenance/CashUnitDetails.js
Normal file
211
new-lamassu-admin/src/pages/Maintenance/CashUnitDetails.js
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import Chip from 'src/components/Chip'
|
||||||
|
import { CashOut } from 'src/components/inputs'
|
||||||
|
import { Label1, TL2 } from 'src/components/typography'
|
||||||
|
import { offDarkColor } from 'src/styling/variables'
|
||||||
|
import { fromNamespace } from 'src/utils/config'
|
||||||
|
import { cashUnitCapacity, modelPrettifier } from 'src/utils/machine'
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
wrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginTop: 12,
|
||||||
|
marginBottom: 16,
|
||||||
|
'& > *': {
|
||||||
|
marginRight: 40
|
||||||
|
},
|
||||||
|
'& > *:last-child': {
|
||||||
|
marginRight: 0
|
||||||
|
},
|
||||||
|
minHeight: 120
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
machineData: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
minWidth: 210
|
||||||
|
},
|
||||||
|
billList: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
minWidth: 160,
|
||||||
|
'& > span': {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
'& > p': {
|
||||||
|
minWidth: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unitList: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
'& > *': {
|
||||||
|
marginRight: 20
|
||||||
|
},
|
||||||
|
'& > *:last-child': {
|
||||||
|
marginRight: 0
|
||||||
|
},
|
||||||
|
marginTop: 10
|
||||||
|
},
|
||||||
|
verticalLine: {
|
||||||
|
height: '100%',
|
||||||
|
width: 1,
|
||||||
|
backgroundColor: offDarkColor
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
marginBottom: 10
|
||||||
|
},
|
||||||
|
loadingBoxes: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
'& > *': {
|
||||||
|
marginBottom: 20
|
||||||
|
},
|
||||||
|
'& > *:last-child': {
|
||||||
|
marginBottom: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const CashUnitDetails = ({ machine, bills, currency, config }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const billCount = R.countBy(it => it.fiat)(bills)
|
||||||
|
const fillingPercentageSettings = fromNamespace('notifications', config)
|
||||||
|
const cashout = fromNamespace('cashOut')(config)
|
||||||
|
const getCashoutSettings = id => fromNamespace(id)(cashout)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.wrapper}>
|
||||||
|
<div className={classes.machineData}>
|
||||||
|
<Label1>Machine Model</Label1>
|
||||||
|
<span>{modelPrettifier[machine.model]}</span>
|
||||||
|
</div>
|
||||||
|
<div className={classes.billList}>
|
||||||
|
<Label1>Cash box</Label1>
|
||||||
|
{R.isEmpty(billCount) && <TL2 noMargin>Empty</TL2>}
|
||||||
|
{R.map(it => (
|
||||||
|
<span>
|
||||||
|
<TL2 noMargin>{billCount[it]}</TL2>
|
||||||
|
<Chip label={`${it} ${currency}`} />
|
||||||
|
</span>
|
||||||
|
))(R.keys(billCount))}
|
||||||
|
</div>
|
||||||
|
<div className={classes.unitList}>
|
||||||
|
{machine.numberOfStackers === 0 &&
|
||||||
|
R.map(it => (
|
||||||
|
<>
|
||||||
|
<div className={classes.col}>
|
||||||
|
<Label1
|
||||||
|
noMargin
|
||||||
|
className={classes.label}>{`Cassette ${it}`}</Label1>
|
||||||
|
<CashOut
|
||||||
|
width={60}
|
||||||
|
height={40}
|
||||||
|
currency={{ code: currency }}
|
||||||
|
notes={machine.cashUnits[`cassette${it}`]}
|
||||||
|
denomination={getCashoutSettings(machine.id)[`cassette${it}`]}
|
||||||
|
threshold={
|
||||||
|
fillingPercentageSettings[`fillingPercentageCassette${it}`]
|
||||||
|
}
|
||||||
|
capacity={cashUnitCapacity[machine.model].cassette}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{it !== machine.numberOfCassettes && (
|
||||||
|
<span className={classes.verticalLine} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
))(R.range(1, machine.numberOfCassettes + 1))}
|
||||||
|
{machine.numberOfStackers > 0 && (
|
||||||
|
<>
|
||||||
|
<div className={classes.col}>
|
||||||
|
<Label1
|
||||||
|
noMargin
|
||||||
|
className={classes.label}>{`Loading boxes`}</Label1>
|
||||||
|
<div className={classes.loadingBoxes}>
|
||||||
|
{R.map(it => (
|
||||||
|
<CashOut
|
||||||
|
width={60}
|
||||||
|
height={40}
|
||||||
|
currency={{ code: currency }}
|
||||||
|
notes={machine.cashUnits[`cassette${it}`]}
|
||||||
|
denomination={
|
||||||
|
getCashoutSettings(machine.id)[`cassette${it}`]
|
||||||
|
}
|
||||||
|
threshold={
|
||||||
|
fillingPercentageSettings[
|
||||||
|
`fillingPercentageCassette${it}`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
capacity={cashUnitCapacity[machine.model].cassette}
|
||||||
|
/>
|
||||||
|
))(R.range(1, machine.numberOfCassettes + 1))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className={classes.verticalLine} />
|
||||||
|
{R.map(it => (
|
||||||
|
<>
|
||||||
|
<div className={classes.col}>
|
||||||
|
<Label1
|
||||||
|
noMargin
|
||||||
|
className={classes.label}>{`Stacker ${it}`}</Label1>
|
||||||
|
<div className={classes.loadingBoxes}>
|
||||||
|
<CashOut
|
||||||
|
width={60}
|
||||||
|
height={40}
|
||||||
|
currency={{ code: currency }}
|
||||||
|
notes={machine.cashUnits[`stacker${it}f`]}
|
||||||
|
denomination={
|
||||||
|
getCashoutSettings(machine.id)[`stacker${it}f`]
|
||||||
|
}
|
||||||
|
threshold={
|
||||||
|
fillingPercentageSettings[
|
||||||
|
`fillingPercentageStacker${it}f`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
capacity={cashUnitCapacity[machine.model].stacker}
|
||||||
|
/>
|
||||||
|
<CashOut
|
||||||
|
width={60}
|
||||||
|
height={40}
|
||||||
|
currency={{ code: currency }}
|
||||||
|
notes={machine.cashUnits[`stacker${it}r`]}
|
||||||
|
denomination={
|
||||||
|
getCashoutSettings(machine.id)[`stacker${it}r`]
|
||||||
|
}
|
||||||
|
threshold={
|
||||||
|
fillingPercentageSettings[
|
||||||
|
`fillingPercentageStacker${it}r`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
capacity={cashUnitCapacity[machine.model].stacker}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{it !== machine.numberOfStackers && (
|
||||||
|
<span className={classes.verticalLine} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
))(R.range(1, machine.numberOfStackers + 1))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CashUnitDetails
|
||||||
|
|
@ -7,10 +7,10 @@ import React, { useState } from 'react'
|
||||||
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
|
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
|
||||||
import Modal from 'src/components/Modal'
|
import Modal from 'src/components/Modal'
|
||||||
import { IconButton, Button } from 'src/components/buttons'
|
import { IconButton, Button } from 'src/components/buttons'
|
||||||
import { Table as EditableTable } from 'src/components/editableTable'
|
|
||||||
import { RadioGroup } from 'src/components/inputs'
|
import { RadioGroup } from 'src/components/inputs'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { EmptyTable } from 'src/components/table'
|
import { EmptyTable } from 'src/components/table'
|
||||||
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
import { P, Label1 } from 'src/components/typography'
|
import { P, Label1 } from 'src/components/typography'
|
||||||
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
||||||
import { ReactComponent as ReverseHistoryIcon } from 'src/styling/icons/circle buttons/history/white.svg'
|
import { ReactComponent as ReverseHistoryIcon } from 'src/styling/icons/circle buttons/history/white.svg'
|
||||||
|
|
@ -19,8 +19,9 @@ import { fromNamespace, toNamespace } from 'src/utils/config'
|
||||||
import { MANUAL, AUTOMATIC } from 'src/utils/constants'
|
import { MANUAL, AUTOMATIC } from 'src/utils/constants'
|
||||||
import { onlyFirstToUpper } from 'src/utils/string'
|
import { onlyFirstToUpper } from 'src/utils/string'
|
||||||
|
|
||||||
import styles from './CashCassettes.styles'
|
import CashUnitDetails from './CashUnitDetails'
|
||||||
import CashCassettesFooter from './CashCassettesFooter'
|
import styles from './CashUnits.styles'
|
||||||
|
import CashCassettesFooter from './CashUnitsFooter'
|
||||||
import CashboxHistory from './CashboxHistory'
|
import CashboxHistory from './CashboxHistory'
|
||||||
import Wizard from './Wizard/Wizard'
|
import Wizard from './Wizard/Wizard'
|
||||||
import helper from './helper'
|
import helper from './helper'
|
||||||
|
|
@ -117,9 +118,6 @@ const CashCassettes = () => {
|
||||||
const [machineId, setMachineId] = useState('')
|
const [machineId, setMachineId] = useState('')
|
||||||
|
|
||||||
const machines = R.path(['machines'])(data) ?? []
|
const machines = R.path(['machines'])(data) ?? []
|
||||||
const [stackerMachines, nonStackerMachines] = R.partition(
|
|
||||||
it => it.numberOfStackers > 0
|
|
||||||
)(machines)
|
|
||||||
const unpairedMachines = R.path(['unpairedMachines'])(data) ?? []
|
const unpairedMachines = R.path(['unpairedMachines'])(data) ?? []
|
||||||
const config = R.path(['config'])(data) ?? {}
|
const config = R.path(['config'])(data) ?? {}
|
||||||
const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, {
|
const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, {
|
||||||
|
|
@ -141,7 +139,6 @@ const CashCassettes = () => {
|
||||||
const fiatCurrency = locale?.fiatCurrency
|
const fiatCurrency = locale?.fiatCurrency
|
||||||
|
|
||||||
const getCashoutSettings = id => fromNamespace(id)(cashout)
|
const getCashoutSettings = id => fromNamespace(id)(cashout)
|
||||||
const isCashOutDisabled = ({ id }) => !getCashoutSettings(id).active
|
|
||||||
|
|
||||||
const onSave = (id, cashUnits) => {
|
const onSave = (id, cashUnits) => {
|
||||||
return setCassetteBills({
|
return setCassetteBills({
|
||||||
|
|
@ -178,8 +175,8 @@ const CashCassettes = () => {
|
||||||
setSelectedRadio(selectedRadio)
|
setSelectedRadio(selectedRadio)
|
||||||
}
|
}
|
||||||
|
|
||||||
const nonStackerElements = helper.getElements(
|
const elements = helper.getElements(
|
||||||
nonStackerMachines,
|
machines,
|
||||||
classes,
|
classes,
|
||||||
config,
|
config,
|
||||||
bills,
|
bills,
|
||||||
|
|
@ -187,13 +184,13 @@ const CashCassettes = () => {
|
||||||
setWizard
|
setWizard
|
||||||
)
|
)
|
||||||
|
|
||||||
const stackerElements = helper.getElements(
|
const InnerCashUnitDetails = ({ it }) => (
|
||||||
stackerMachines,
|
<CashUnitDetails
|
||||||
classes,
|
machine={it}
|
||||||
config,
|
bills={bills[it.id] ?? []}
|
||||||
bills,
|
currency={fiatCurrency}
|
||||||
setMachineId,
|
config={config}
|
||||||
setWizard
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -250,22 +247,13 @@ const CashCassettes = () => {
|
||||||
</TitleSection>
|
</TitleSection>
|
||||||
{!showHistory && (
|
{!showHistory && (
|
||||||
<>
|
<>
|
||||||
<EditableTable
|
<DataTable
|
||||||
error={error?.message}
|
loading={dataLoading}
|
||||||
name="cashboxes"
|
elements={elements}
|
||||||
stripeWhen={isCashOutDisabled}
|
data={machines}
|
||||||
elements={nonStackerElements}
|
Details={InnerCashUnitDetails}
|
||||||
data={nonStackerMachines}
|
emptyText="No machines so far"
|
||||||
tbodyWrapperClass={classes.tBody}
|
expandable
|
||||||
/>
|
|
||||||
|
|
||||||
<EditableTable
|
|
||||||
error={error?.message}
|
|
||||||
name="recyclerCashboxes"
|
|
||||||
stripeWhen={isCashOutDisabled}
|
|
||||||
elements={stackerElements}
|
|
||||||
data={stackerMachines}
|
|
||||||
tbodyWrapperClass={classes.tBody}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{data && R.isEmpty(machines) && (
|
{data && R.isEmpty(machines) && (
|
||||||
56
new-lamassu-admin/src/pages/Maintenance/CashUnits.styles.js
Normal file
56
new-lamassu-admin/src/pages/Maintenance/CashUnits.styles.js
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { offColor, offDarkColor } from 'src/styling/variables'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
cashbox: {
|
||||||
|
height: 36
|
||||||
|
},
|
||||||
|
tBody: {
|
||||||
|
maxHeight: '65vh',
|
||||||
|
overflow: 'auto'
|
||||||
|
},
|
||||||
|
tableWidth: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 1
|
||||||
|
},
|
||||||
|
descriptions: {
|
||||||
|
color: offColor,
|
||||||
|
marginTop: 0
|
||||||
|
},
|
||||||
|
cashboxReset: {
|
||||||
|
color: offColor,
|
||||||
|
margin: [[13, 0, -5, 20]]
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
marginRight: 12
|
||||||
|
},
|
||||||
|
downloadLogsButton: {
|
||||||
|
marginLeft: 13
|
||||||
|
},
|
||||||
|
unitsRow: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
margin: [[10, 0]],
|
||||||
|
'& > *': {
|
||||||
|
marginRight: 30
|
||||||
|
},
|
||||||
|
'& > *:last-child': {
|
||||||
|
marginRight: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
units: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
'& > *': {
|
||||||
|
marginRight: 10
|
||||||
|
},
|
||||||
|
'& > *:last-child': {
|
||||||
|
marginRight: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verticalLine: {
|
||||||
|
height: '100%',
|
||||||
|
width: 1,
|
||||||
|
backgroundColor: offDarkColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,8 @@ import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-ou
|
||||||
import { fromNamespace } from 'src/utils/config'
|
import { fromNamespace } from 'src/utils/config'
|
||||||
import { numberToFiatAmount } from 'src/utils/number.js'
|
import { numberToFiatAmount } from 'src/utils/number.js'
|
||||||
|
|
||||||
import styles from './CashCassettesFooter.styles.js'
|
import styles from './CashUnitsFooter.styles.js'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const CashCassettesFooter = ({
|
const CashCassettesFooter = ({
|
||||||
|
|
@ -1,77 +1,11 @@
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
|
|
||||||
import { IconButton } from 'src/components/buttons'
|
import { IconButton } from 'src/components/buttons'
|
||||||
import { CashOut, CashIn } from 'src/components/inputs/cashbox/Cashbox'
|
import { CashIn, CashOutLite } from 'src/components/inputs/cashbox/Cashbox'
|
||||||
import { NumberInput, CashCassetteInput } from 'src/components/inputs/formik'
|
|
||||||
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
||||||
import { fromNamespace } from 'src/utils/config'
|
import { fromNamespace } from 'src/utils/config'
|
||||||
import { cashUnitCapacity } from 'src/utils/machine'
|
import { cashUnitCapacity } from 'src/utils/machine'
|
||||||
|
|
||||||
const widthsByCashUnits = {
|
|
||||||
2: {
|
|
||||||
machine: 250,
|
|
||||||
cashbox: 260,
|
|
||||||
cassette: 300,
|
|
||||||
unitGraph: 80,
|
|
||||||
editWidth: 90
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
machine: 220,
|
|
||||||
cashbox: 215,
|
|
||||||
cassette: 225,
|
|
||||||
unitGraph: 60,
|
|
||||||
editWidth: 90
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
machine: 190,
|
|
||||||
cashbox: 180,
|
|
||||||
cassette: 185,
|
|
||||||
unitGraph: 50,
|
|
||||||
editWidth: 90
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
machine: 170,
|
|
||||||
cashbox: 140,
|
|
||||||
cassette: 160,
|
|
||||||
unitGraph: 45,
|
|
||||||
editWidth: 90
|
|
||||||
},
|
|
||||||
6: {
|
|
||||||
machine: 150,
|
|
||||||
cashbox: 130,
|
|
||||||
cassette: 142,
|
|
||||||
unitGraph: 45,
|
|
||||||
editWidth: 70
|
|
||||||
},
|
|
||||||
7: {
|
|
||||||
machine: 140,
|
|
||||||
cashbox: 115,
|
|
||||||
cassette: 125,
|
|
||||||
unitGraph: 40,
|
|
||||||
editWidth: 70
|
|
||||||
},
|
|
||||||
8: {
|
|
||||||
machine: 100,
|
|
||||||
cashbox: 115,
|
|
||||||
cassette: 122,
|
|
||||||
unitGraph: 35,
|
|
||||||
editWidth: 70
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMaxNumberOfCassettesMap = machines =>
|
|
||||||
Math.max(...R.map(it => it.numberOfCassettes, machines), 0)
|
|
||||||
|
|
||||||
const getMaxNumberOfStackersMap = machines =>
|
|
||||||
Math.max(...R.map(it => it.numberOfStackers, machines), 0)
|
|
||||||
|
|
||||||
// Each stacker counts as two cash units (front and rear)
|
|
||||||
const getMaxNumberOfCashUnits = machines =>
|
|
||||||
Math.max(
|
|
||||||
...R.map(it => it.numberOfCassettes + it.numberOfStackers * 2, machines),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
const getElements = (
|
const getElements = (
|
||||||
machines,
|
machines,
|
||||||
classes,
|
classes,
|
||||||
|
|
@ -91,156 +25,108 @@ const getElements = (
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
header: 'Machine',
|
header: 'Machine',
|
||||||
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.machine,
|
width: 250,
|
||||||
view: name => <>{name}</>,
|
view: m => <>{m.name}</>,
|
||||||
input: ({ field: { value: name } }) => <>{name}</>
|
input: ({ field: { value: name } }) => <>{name}</>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cashbox',
|
name: 'cashbox',
|
||||||
header: 'Cashbox',
|
header: 'Cashbox',
|
||||||
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cashbox,
|
width: 200,
|
||||||
view: (_, { id, cashUnits }) => (
|
view: m => (
|
||||||
<CashIn
|
<CashIn
|
||||||
currency={{ code: fiatCurrency }}
|
currency={{ code: fiatCurrency }}
|
||||||
notes={cashUnits.cashbox}
|
notes={m.cashUnits.cashbox}
|
||||||
total={R.sum(R.map(it => it.fiat, bills[id] ?? []))}
|
total={R.sum(R.map(it => it.fiat, bills[m.id] ?? []))}
|
||||||
|
width={25}
|
||||||
|
height={45}
|
||||||
|
omitInnerPercentage
|
||||||
|
className={classes.padding}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
input: NumberInput,
|
|
||||||
inputProps: {
|
inputProps: {
|
||||||
decimalPlaces: 0
|
decimalPlaces: 0
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
name: 'cassettes',
|
||||||
R.until(
|
header: 'Cassettes & Recyclers',
|
||||||
R.gt(R.__, getMaxNumberOfCassettesMap(machines)),
|
width: 575,
|
||||||
it => {
|
view: m => {
|
||||||
elements.push({
|
return (
|
||||||
name: `cassette${it}`,
|
<div className={classes.unitsRow}>
|
||||||
header: `Cassette ${it}`,
|
<div className={classes.units}>
|
||||||
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cassette,
|
{R.map(it => (
|
||||||
stripe: true,
|
<CashOutLite
|
||||||
doubleHeader: 'Cash-out',
|
width={'100%'}
|
||||||
view: (_, { id, model, cashUnits }) => (
|
|
||||||
<CashOut
|
|
||||||
className={classes.cashbox}
|
|
||||||
denomination={getCashoutSettings(id)?.[`cassette${it}`]}
|
|
||||||
currency={{ code: fiatCurrency }}
|
currency={{ code: fiatCurrency }}
|
||||||
notes={cashUnits[`cassette${it}`]}
|
notes={m.cashUnits[`cassette${it}`]}
|
||||||
capacity={cashUnitCapacity[model].cassette}
|
denomination={getCashoutSettings(m.id)[`cassette${it}`]}
|
||||||
width={
|
|
||||||
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph
|
|
||||||
}
|
|
||||||
threshold={
|
threshold={
|
||||||
fillingPercentageSettings[`fillingPercentageCassette${it}`]
|
fillingPercentageSettings[`fillingPercentageCassette${it}`]
|
||||||
}
|
}
|
||||||
|
capacity={cashUnitCapacity[m.model].cassette}
|
||||||
/>
|
/>
|
||||||
),
|
))(R.range(1, m.numberOfCassettes + 1))}
|
||||||
isHidden: ({ numberOfCassettes }) => it > numberOfCassettes,
|
</div>
|
||||||
input: CashCassetteInput,
|
<div className={classes.units}>
|
||||||
inputProps: {
|
{R.map(it => (
|
||||||
decimalPlaces: 0,
|
<>
|
||||||
width:
|
<CashOutLite
|
||||||
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph,
|
width={'100%'}
|
||||||
inputClassName: classes.cashbox
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return R.add(1, it)
|
|
||||||
},
|
|
||||||
1
|
|
||||||
)
|
|
||||||
|
|
||||||
R.until(
|
|
||||||
R.gt(R.__, getMaxNumberOfStackersMap(machines)),
|
|
||||||
it => {
|
|
||||||
elements.push(
|
|
||||||
{
|
|
||||||
name: `stacker${it}f`,
|
|
||||||
header: `Stacker ${it}F`,
|
|
||||||
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cassette,
|
|
||||||
stripe: true,
|
|
||||||
view: (_, { id, model, cashUnits }) => (
|
|
||||||
<CashOut
|
|
||||||
className={classes.cashbox}
|
|
||||||
denomination={getCashoutSettings(id)?.[`stacker${it}f`]}
|
|
||||||
currency={{ code: fiatCurrency }}
|
currency={{ code: fiatCurrency }}
|
||||||
notes={cashUnits[`stacker${it}f`]}
|
notes={m.cashUnits[`stacker${it}f`]}
|
||||||
capacity={
|
denomination={getCashoutSettings(m.id)[`stacker${it}f`]}
|
||||||
it === 1
|
|
||||||
? cashUnitCapacity[model].stacker -
|
|
||||||
cashUnitCapacity[model].escrow
|
|
||||||
: cashUnitCapacity[model].stacker
|
|
||||||
}
|
|
||||||
width={
|
|
||||||
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph
|
|
||||||
}
|
|
||||||
threshold={
|
threshold={
|
||||||
fillingPercentageSettings[`fillingPercentageStacker${it}f`]
|
fillingPercentageSettings[
|
||||||
|
`fillingPercentageStacker${it}f`
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
capacity={cashUnitCapacity[m.model].stacker}
|
||||||
/>
|
/>
|
||||||
),
|
<CashOutLite
|
||||||
isHidden: ({ numberOfStackers }) => it > numberOfStackers,
|
width={'100%'}
|
||||||
input: CashCassetteInput,
|
currency={{ code: fiatCurrency }}
|
||||||
|
notes={m.cashUnits[`stacker${it}r`]}
|
||||||
|
denomination={getCashoutSettings(m.id)[`stacker${it}r`]}
|
||||||
|
threshold={
|
||||||
|
fillingPercentageSettings[
|
||||||
|
`fillingPercentageStacker${it}r`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
capacity={cashUnitCapacity[m.model].stacker}
|
||||||
|
/>
|
||||||
|
{it !== m.numberOfStackers && (
|
||||||
|
<span className={classes.verticalLine} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
))(R.range(1, m.numberOfStackers + 1))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
inputProps: {
|
inputProps: {
|
||||||
decimalPlaces: 0,
|
decimalPlaces: 0
|
||||||
width:
|
|
||||||
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph,
|
|
||||||
inputClassName: classes.cashbox
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `stacker${it}r`,
|
|
||||||
header: `Stacker ${it}R`,
|
|
||||||
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cassette,
|
|
||||||
stripe: true,
|
|
||||||
view: (_, { id, model, cashUnits }) => (
|
|
||||||
<CashOut
|
|
||||||
className={classes.cashbox}
|
|
||||||
denomination={getCashoutSettings(id)?.[`stacker${it}r`]}
|
|
||||||
currency={{ code: fiatCurrency }}
|
|
||||||
notes={cashUnits[`stacker${it}r`]}
|
|
||||||
capacity={cashUnitCapacity[model].stacker}
|
|
||||||
width={
|
|
||||||
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph
|
|
||||||
}
|
|
||||||
threshold={
|
|
||||||
fillingPercentageSettings[`fillingPercentageStacker${it}r`]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
isHidden: ({ numberOfStackers }) => it > numberOfStackers,
|
|
||||||
input: CashCassetteInput,
|
|
||||||
inputProps: {
|
|
||||||
decimalPlaces: 0,
|
|
||||||
width:
|
|
||||||
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph,
|
|
||||||
inputClassName: classes.cashbox
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return R.add(1, it)
|
|
||||||
},
|
|
||||||
1
|
|
||||||
)
|
|
||||||
|
|
||||||
elements.push({
|
|
||||||
name: 'edit',
|
name: 'edit',
|
||||||
header: 'Edit',
|
header: 'Edit',
|
||||||
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.editWidth,
|
width: 90,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
view: (_, { id }) => {
|
view: m => {
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMachineId(id)
|
setMachineId(m.id)
|
||||||
setWizard(true)
|
setWizard(true)
|
||||||
}}>
|
}}>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
]
|
||||||
|
|
||||||
return elements
|
return elements
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import Locales from 'src/pages/Locales'
|
||||||
import IndividualDiscounts from 'src/pages/LoyaltyPanel/IndividualDiscounts'
|
import IndividualDiscounts from 'src/pages/LoyaltyPanel/IndividualDiscounts'
|
||||||
import PromoCodes from 'src/pages/LoyaltyPanel/PromoCodes'
|
import PromoCodes from 'src/pages/LoyaltyPanel/PromoCodes'
|
||||||
import MachineLogs from 'src/pages/MachineLogs'
|
import MachineLogs from 'src/pages/MachineLogs'
|
||||||
import CashCassettes from 'src/pages/Maintenance/CashCassettes'
|
import CashUnits from 'src/pages/Maintenance/CashUnits'
|
||||||
import MachineStatus from 'src/pages/Maintenance/MachineStatus'
|
import MachineStatus from 'src/pages/Maintenance/MachineStatus'
|
||||||
import Notifications from 'src/pages/Notifications/Notifications'
|
import Notifications from 'src/pages/Notifications/Notifications'
|
||||||
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
||||||
|
|
@ -48,11 +48,11 @@ const getLamassuRoutes = () => [
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: 'cash_cassettes',
|
key: 'cash_units',
|
||||||
label: 'Cash Cassettes',
|
label: 'Cash Units',
|
||||||
route: '/maintenance/cash-cassettes',
|
route: '/maintenance/cash-units',
|
||||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||||
component: CashCassettes
|
component: CashUnits
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'funding',
|
key: 'funding',
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import Locales from 'src/pages/Locales'
|
||||||
import IndividualDiscounts from 'src/pages/LoyaltyPanel/IndividualDiscounts'
|
import IndividualDiscounts from 'src/pages/LoyaltyPanel/IndividualDiscounts'
|
||||||
import PromoCodes from 'src/pages/LoyaltyPanel/PromoCodes'
|
import PromoCodes from 'src/pages/LoyaltyPanel/PromoCodes'
|
||||||
import MachineLogs from 'src/pages/MachineLogs'
|
import MachineLogs from 'src/pages/MachineLogs'
|
||||||
import CashCassettes from 'src/pages/Maintenance/CashCassettes'
|
import CashUnits from 'src/pages/Maintenance/CashUnits'
|
||||||
import MachineStatus from 'src/pages/Maintenance/MachineStatus'
|
import MachineStatus from 'src/pages/Maintenance/MachineStatus'
|
||||||
import Notifications from 'src/pages/Notifications/Notifications'
|
import Notifications from 'src/pages/Notifications/Notifications'
|
||||||
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
|
||||||
|
|
@ -48,11 +48,11 @@ const getPazuzRoutes = () => [
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: 'cash_cassettes',
|
key: 'cash_units',
|
||||||
label: 'Cash Cassettes',
|
label: 'Cash Units',
|
||||||
route: '/maintenance/cash-cassettes',
|
route: '/maintenance/cash-units',
|
||||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||||
component: CashCassettes
|
component: CashUnits
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'logs',
|
key: 'logs',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue