Feat: make percentage chart
feat: footer expands to show more items fix: several style fixes feat: streak through cassettes table if machine doesnt have cashout enabled Revert "feat: streak through cassettes table if machine doesnt have cashout enabled" This reverts commit eaa390be8e9688c557507ff9c2984addc3f25031. feat: Streak through cash cassettes table if cashout not enabled feat: Machine details overview on sidebar feat: machine prof page: breadcrumb, sidebar. dashboard: redirect on machine click feat: Last ping shows seconds/ minutes/ hours/ days ago depending on time past chore: Disabled cashbox % column in dashboard system performance card
This commit is contained in:
parent
19cd086436
commit
00f176fccc
16 changed files with 539 additions and 227 deletions
|
|
@ -356,8 +356,8 @@ const resolvers = {
|
|||
serverLogs.getServerLogs(from, until, limit, offset),
|
||||
serverLogsCsv: (...[, { from, until, limit, offset }]) =>
|
||||
serverLogs.getServerLogs(from, until, limit, offset).then(parseAsync),
|
||||
transactions: (...[, { from, until, limit, offset }]) =>
|
||||
transactions.batch(from, until, limit, offset),
|
||||
transactions: (...[, { from, until, limit, offset, id }]) =>
|
||||
transactions.batch(from, until, limit, offset, id),
|
||||
transactionsCsv: (...[, { from, until, limit, offset }]) =>
|
||||
transactions.batch(from, until, limit, offset).then(parseAsync),
|
||||
config: () => settingsLoader.loadLatestConfigOrNone(),
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@ const TL2 = pBuilder('tl2')
|
|||
const Label1 = pBuilder('label1')
|
||||
const Label2 = pBuilder('label2')
|
||||
const Label3 = pBuilder('label3')
|
||||
const Label4 = pBuilder('regularLabel')
|
||||
|
||||
function pBuilder(elementClass) {
|
||||
return ({ inline, noMargin, className, children, ...props }) => {
|
||||
|
|
@ -125,6 +124,5 @@ export {
|
|||
Mono,
|
||||
Label1,
|
||||
Label2,
|
||||
Label3,
|
||||
Label4
|
||||
Label3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import { useQuery } from '@apollo/react-hooks'
|
||||
import { makeStyles } from '@material-ui/core'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import gql from 'graphql-tag'
|
||||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
import { Label2 } from 'src/components/typography'
|
||||
import { Label1, Label2 } from 'src/components/typography'
|
||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||
import { white } from 'src/styling/variables'
|
||||
import { fromNamespace } from 'src/utils/config'
|
||||
|
||||
import styles from './Footer.styles'
|
||||
|
|
@ -29,8 +31,32 @@ const GET_DATA = gql`
|
|||
const useStyles = makeStyles(styles)
|
||||
const Footer = () => {
|
||||
const { data, loading } = useQuery(GET_DATA)
|
||||
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const [showExpandBtn, setShowExpandBtn] = useState(false)
|
||||
const [buttonName, setButtonName] = useState('Show all')
|
||||
const classes = useStyles()
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data.rates && data.rates.withCommissions) {
|
||||
const numItems = R.keys(data.rates.withCommissions).length
|
||||
if (numItems > 4) {
|
||||
setShowExpandBtn(true)
|
||||
setButtonName(`Show all (${numItems})`)
|
||||
}
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const toggleExpand = () => {
|
||||
if (expanded) {
|
||||
const numItems = R.keys(data.rates.withCommissions).length
|
||||
setExpanded(false)
|
||||
setButtonName(`Show all (${numItems})`)
|
||||
} else {
|
||||
setExpanded(true)
|
||||
setButtonName(`Show less`)
|
||||
}
|
||||
}
|
||||
|
||||
const wallets = fromNamespace('wallets')(data?.config)
|
||||
|
||||
const renderFooterItem = key => {
|
||||
|
|
@ -99,15 +125,54 @@ const Footer = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const makeFooterExpandedClass = () => {
|
||||
return {
|
||||
overflow: 'scroll',
|
||||
// 88px for base height, then add 100 px for each row of items. Each row has 4 items. 5 items makes 2 rows so 288px of height
|
||||
height:
|
||||
88 + Math.ceil(R.keys(data.rates.withCommissions).length / 4) * 100,
|
||||
maxHeight: '50vh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: '100vw',
|
||||
backgroundColor: white,
|
||||
textAlign: 'left',
|
||||
boxShadow: '0px -1px 10px 0px rgba(50, 50, 50, 0.1)'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.footer}>
|
||||
<div
|
||||
className={!expanded ? classes.footer : null}
|
||||
style={expanded ? makeFooterExpandedClass() : null}>
|
||||
<div className={classes.content}>
|
||||
{!loading && data && (
|
||||
<>
|
||||
<Grid container spacing={1}>
|
||||
{R.keys(data.rates.withCommissions).map(key =>
|
||||
renderFooterItem(key)
|
||||
<Grid container item xs={11} style={{ marginBottom: 18 }}>
|
||||
{R.keys(data.rates.withCommissions).map(key =>
|
||||
renderFooterItem(key)
|
||||
)}
|
||||
</Grid>
|
||||
{/* {renderFooterItem(R.keys(data.rates.withCommissions)[0])} */}
|
||||
{showExpandBtn && (
|
||||
<Label1
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
marginBottom: 0,
|
||||
marginTop: 35
|
||||
}}>
|
||||
<Button
|
||||
onClick={toggleExpand}
|
||||
size="small"
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
className={classes.button}>
|
||||
{buttonName}
|
||||
</Button>
|
||||
</Label1>
|
||||
)}
|
||||
</Grid>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -67,11 +67,14 @@ export default {
|
|||
width: '100vw',
|
||||
backgroundColor: white,
|
||||
textAlign: 'left',
|
||||
height: 88
|
||||
height: 88,
|
||||
boxShadow: '0px -1px 10px 0px rgba(50, 50, 50, 0.1)'
|
||||
},
|
||||
content: {
|
||||
width: 1200,
|
||||
margin: '0 auto'
|
||||
margin: '0 auto',
|
||||
backgroundColor: white,
|
||||
marginTop: 4
|
||||
},
|
||||
headerLabels: {
|
||||
whiteSpace: 'pre',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
import { makeStyles } from '@material-ui/core'
|
||||
import Slider from '@material-ui/core/Slider'
|
||||
import React from 'react'
|
||||
|
||||
import { ReactComponent as CashIn } from 'src/styling/icons/direction/cash-in.svg'
|
||||
import { ReactComponent as CashOut } from 'src/styling/icons/direction/cash-out.svg'
|
||||
import {
|
||||
zircon,
|
||||
fontSize3,
|
||||
fontSecondary,
|
||||
fontColor
|
||||
} from 'src/styling/variables'
|
||||
|
||||
const styles = {
|
||||
wrapper: {
|
||||
display: 'flex',
|
||||
height: 130,
|
||||
marginTop: -8
|
||||
},
|
||||
percentageBox: {
|
||||
backgroundColor: zircon,
|
||||
height: 130,
|
||||
borderRadius: 4,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
whiteSpace: 'pre'
|
||||
},
|
||||
label: {
|
||||
fontSize: fontSize3,
|
||||
fontFamily: fontSecondary,
|
||||
fontWeight: 700,
|
||||
color: fontColor
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const PercentageChart = () => {
|
||||
const classes = useStyles()
|
||||
const [value, setValue] = React.useState(50)
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue)
|
||||
}
|
||||
|
||||
const buildPercentageView = (value, direction) => {
|
||||
switch (direction) {
|
||||
case 'cashIn':
|
||||
if (value > 20) {
|
||||
return (
|
||||
<>
|
||||
<CashIn />
|
||||
<span className={classes.label}>{` ${value}%`}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return null
|
||||
case 'cashOut':
|
||||
if (value > 20) {
|
||||
return (
|
||||
<>
|
||||
<CashOut />
|
||||
<span className={classes.label}>{` ${value}%`}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return null
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Slider
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
aria-labelledby="continuous-slider"
|
||||
/>
|
||||
<div className={classes.wrapper}>
|
||||
<div
|
||||
className={classes.percentageBox}
|
||||
style={{ width: `${value}%`, marginRight: 4 }}>
|
||||
{/* <CashIn />
|
||||
<span className={classes.label}>{` ${value}%`}</span> */}
|
||||
{buildPercentageView(value, 'cashIn')}
|
||||
</div>
|
||||
<div
|
||||
className={classes.percentageBox}
|
||||
style={{ width: `${100 - value}%` }}>
|
||||
{/* <CashOut />
|
||||
<span className={classes.label}>{` ${100 - value}%`}</span> */}
|
||||
{buildPercentageView(100 - value, 'cashOut')}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PercentageChart
|
||||
|
|
@ -7,10 +7,8 @@ import { backgroundColor, java, neon } from 'src/styling/variables'
|
|||
|
||||
const RefScatterplot = ({ data: realData, timeFrame }) => {
|
||||
const svgRef = useRef()
|
||||
|
||||
const cashIns = R.filter(R.propEq('txClass', 'cashIn'))(realData)
|
||||
const cashOuts = R.filter(R.propEq('txClass', 'cashOut'))(realData)
|
||||
|
||||
const drawGraph = useCallback(() => {
|
||||
const svg = d3.select(svgRef.current)
|
||||
const margin = { top: 25, right: 0, bottom: 25, left: 15 }
|
||||
|
|
@ -20,6 +18,9 @@ const RefScatterplot = ({ data: realData, timeFrame }) => {
|
|||
// finds maximum value for the Y axis. Minimum value is 100. If value is multiple of 1000, add 100
|
||||
// (this is because the Y axis looks best with multiples of 100)
|
||||
const findMaxY = () => {
|
||||
if (realData.length === 0) {
|
||||
return 100
|
||||
}
|
||||
let maxY = d3.max(realData, t => parseFloat(t.fiat))
|
||||
maxY = 100 * Math.ceil(maxY / 100)
|
||||
if (maxY < 100) {
|
||||
|
|
@ -37,7 +38,7 @@ const RefScatterplot = ({ data: realData, timeFrame }) => {
|
|||
ticks: 4,
|
||||
subtractDays: 1,
|
||||
timeFormat: '%H:%M',
|
||||
timeRange: [0, 500]
|
||||
timeRange: [50, 500]
|
||||
}
|
||||
switch (timeFrame) {
|
||||
case 'Day':
|
||||
|
|
@ -48,7 +49,7 @@ const RefScatterplot = ({ data: realData, timeFrame }) => {
|
|||
nice: 7,
|
||||
ticks: 7,
|
||||
subtractDays: 7,
|
||||
timeFormat: '%d',
|
||||
timeFormat: '%a %d',
|
||||
timeRange: [50, 500]
|
||||
}
|
||||
case 'Month':
|
||||
|
|
@ -98,12 +99,9 @@ const RefScatterplot = ({ data: realData, timeFrame }) => {
|
|||
.scaleTime()
|
||||
.domain([
|
||||
moment()
|
||||
.endOf('day')
|
||||
.add(-xAxisSettings.subtractDays, 'day')
|
||||
.valueOf(),
|
||||
moment()
|
||||
.endOf('day')
|
||||
.valueOf()
|
||||
moment().valueOf()
|
||||
])
|
||||
.range(xAxisSettings.timeRange)
|
||||
.nice(xAxisSettings.nice)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ import moment from 'moment'
|
|||
import * as R from 'ramda'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
import { Label1, Label2 } from 'src/components/typography/index'
|
||||
import { Label2 } from 'src/components/typography/index'
|
||||
import { ReactComponent as TriangleDown } from 'src/styling/icons/arrow/triangle_down.svg'
|
||||
import { ReactComponent as TriangleUp } from 'src/styling/icons/arrow/triangle_up.svg'
|
||||
import { fromNamespace } from 'src/utils/config'
|
||||
|
||||
import PercentageChart from './Graphs/PercentageChart'
|
||||
import LineChart from './Graphs/RefLineChart'
|
||||
import Scatterplot from './Graphs/RefScatterplot'
|
||||
import InfoWithLabel from './InfoWithLabel'
|
||||
|
|
@ -21,8 +22,11 @@ const isNotProp = R.curry(R.compose(R.isNil, R.prop))
|
|||
const getFiats = R.map(R.prop('fiat'))
|
||||
const getProps = propName => R.map(R.prop(propName))
|
||||
const useStyles = makeStyles(styles)
|
||||
const getDateDaysAgo = (days = 0) => {
|
||||
return moment().subtract(days, 'day')
|
||||
const getDateSecondsAgo = (seconds = 0, startDate = null) => {
|
||||
if (startDate) {
|
||||
return moment(startDate).subtract(seconds, 'second')
|
||||
}
|
||||
return moment().subtract(seconds, 'second')
|
||||
}
|
||||
|
||||
// const now = moment()
|
||||
|
|
@ -47,8 +51,6 @@ const GET_DATA = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const currentTime = new Date()
|
||||
|
||||
const SystemPerformance = () => {
|
||||
const classes = useStyles()
|
||||
|
||||
|
|
@ -64,49 +66,55 @@ const SystemPerformance = () => {
|
|||
|
||||
useEffect(() => {
|
||||
const isInRange = (getLastTimePeriod = false) => t => {
|
||||
const now = moment(currentTime)
|
||||
const now = moment()
|
||||
switch (selectedRange) {
|
||||
case 'Day':
|
||||
if (getLastTimePeriod) {
|
||||
return (
|
||||
t.error === null &&
|
||||
moment(t.created).isBetween(
|
||||
getDateDaysAgo(2),
|
||||
now.subtract(25, 'hours')
|
||||
getDateSecondsAgo(2 * 24 * 3600, now),
|
||||
getDateSecondsAgo(24 * 3600, now)
|
||||
)
|
||||
)
|
||||
}
|
||||
return (
|
||||
t.error === null &&
|
||||
moment(t.created).isBetween(getDateDaysAgo(1), now)
|
||||
moment(t.created).isBetween(getDateSecondsAgo(24 * 3600, now), now)
|
||||
)
|
||||
case 'Week':
|
||||
if (getLastTimePeriod) {
|
||||
return (
|
||||
t.error === null &&
|
||||
moment(t.created).isBetween(
|
||||
getDateDaysAgo(14),
|
||||
now.subtract(24 * 7 + 1, 'hours')
|
||||
getDateSecondsAgo(14 * 24 * 3600, now),
|
||||
getDateSecondsAgo(7 * 24 * 3600, now)
|
||||
)
|
||||
)
|
||||
}
|
||||
return (
|
||||
t.error === null &&
|
||||
moment(t.created).isBetween(getDateDaysAgo(7), now)
|
||||
moment(t.created).isBetween(
|
||||
getDateSecondsAgo(7 * 24 * 3600, now),
|
||||
now
|
||||
)
|
||||
)
|
||||
case 'Month':
|
||||
if (getLastTimePeriod) {
|
||||
return (
|
||||
t.error === null &&
|
||||
moment(t.created).isBetween(
|
||||
getDateDaysAgo(60),
|
||||
now.subtract(24 * 30 + 1, 'hours')
|
||||
getDateSecondsAgo(60 * 24 * 3600, now),
|
||||
getDateSecondsAgo(30 * 24 * 3600, now)
|
||||
)
|
||||
)
|
||||
}
|
||||
return (
|
||||
t.error === null &&
|
||||
moment(t.created).isBetween(getDateDaysAgo(30), now)
|
||||
moment(t.created).isBetween(
|
||||
getDateSecondsAgo(30 * 24 * 3600, now),
|
||||
now
|
||||
)
|
||||
)
|
||||
default:
|
||||
return t.error === null && true
|
||||
|
|
@ -272,13 +280,8 @@ const SystemPerformance = () => {
|
|||
<Grid item xs={4}>
|
||||
<Label2>Direction</Label2>
|
||||
<Grid container>
|
||||
<Grid item xs={6}>
|
||||
<Label1>CashIn: </Label1>
|
||||
{` ${getDirectionPercent().cashIn}%`}
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Label1>CashOut: </Label1>
|
||||
{` ${getDirectionPercent().cashOut}%`}
|
||||
<Grid item xs>
|
||||
<PercentageChart data={getDirectionPercent()} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import TableContainer from '@material-ui/core/TableContainer'
|
|||
import TableHead from '@material-ui/core/TableHead'
|
||||
import TableRow from '@material-ui/core/TableRow'
|
||||
import React from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import { Status } from 'src/components/Status'
|
||||
import { Label2, TL2 } from 'src/components/typography'
|
||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||
// import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||
|
||||
import styles from './MachinesTable.styles'
|
||||
|
|
@ -38,7 +39,7 @@ const HeaderCell = withStyles({
|
|||
|
||||
const MachinesTable = ({ machines, numToRender }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const history = useHistory()
|
||||
const getPercent = (notes, capacity = 500) => {
|
||||
return Math.round((notes / capacity) * 100)
|
||||
}
|
||||
|
|
@ -50,6 +51,11 @@ const MachinesTable = ({ machines, numToRender }) => {
|
|||
}
|
||||
return <TL2>{`${percent}%`}</TL2>
|
||||
}
|
||||
|
||||
const redirect = name => {
|
||||
return history.push('/machines', { selectedMachine: name })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer className={classes.table}>
|
||||
|
|
@ -66,11 +72,11 @@ const MachinesTable = ({ machines, numToRender }) => {
|
|||
<Label2 className={classes.label}>Status</Label2>
|
||||
</div>
|
||||
</HeaderCell>
|
||||
<HeaderCell>
|
||||
{/* <HeaderCell>
|
||||
<div className={classes.header}>
|
||||
<TxInIcon />
|
||||
</div>
|
||||
</HeaderCell>
|
||||
</HeaderCell> */}
|
||||
<HeaderCell>
|
||||
<div className={classes.header}>
|
||||
<TxOutIcon />
|
||||
|
|
@ -90,6 +96,8 @@ const MachinesTable = ({ machines, numToRender }) => {
|
|||
if (idx < numToRender) {
|
||||
return (
|
||||
<TableRow
|
||||
onClick={() => redirect(machine.name)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
key={machine.deviceId + idx}
|
||||
className={classes.row}>
|
||||
<StyledCell align="left">
|
||||
|
|
@ -98,9 +106,9 @@ const MachinesTable = ({ machines, numToRender }) => {
|
|||
<StyledCell>
|
||||
<Status status={machine.statuses[0]} />
|
||||
</StyledCell>
|
||||
<StyledCell align="left">
|
||||
{/* <StyledCell align="left">
|
||||
{makePercentageText(machine.cashbox)}
|
||||
</StyledCell>
|
||||
</StyledCell> */}
|
||||
<StyledCell align="left">
|
||||
{makePercentageText(machine.cassette1)}
|
||||
</StyledCell>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { useMutation } from '@apollo/react-hooks'
|
||||
import { makeStyles } from '@material-ui/core'
|
||||
import gql from 'graphql-tag'
|
||||
import React from 'react'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import { Table as EditableTable } from 'src/components/editableTable'
|
||||
import { CashOut } from 'src/components/inputs/cashbox/Cashbox'
|
||||
import { NumberInput } from 'src/components/inputs/formik'
|
||||
import { fromNamespace } from 'src/utils/config'
|
||||
|
||||
import styles from './Cassettes.styles'
|
||||
|
|
@ -24,7 +27,27 @@ const ValidationSchema = Yup.object().shape({
|
|||
.max(500)
|
||||
})
|
||||
|
||||
const CashCassettes = ({ machine, config }) => {
|
||||
const RESET_CASHOUT_BILLS = gql`
|
||||
mutation MachineAction(
|
||||
$deviceId: ID!
|
||||
$action: MachineAction!
|
||||
$cassette1: Int!
|
||||
$cassette2: Int!
|
||||
) {
|
||||
machineAction(
|
||||
deviceId: $deviceId
|
||||
action: $action
|
||||
cassette1: $cassette1
|
||||
cassette2: $cassette2
|
||||
) {
|
||||
deviceId
|
||||
cassette1
|
||||
cassette2
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const CashCassettes = ({ machine, config, refetchData }) => {
|
||||
const data = { machine, config }
|
||||
const classes = useStyles()
|
||||
|
||||
|
|
@ -32,8 +55,9 @@ const CashCassettes = ({ machine, config }) => {
|
|||
const locale = data?.config && fromNamespace('locale')(data.config)
|
||||
const fiatCurrency = locale?.fiatCurrency
|
||||
|
||||
const getCashoutSettings = id => fromNamespace(id)(cashout)
|
||||
// const isCashOutDisabled = ({ id }) => !getCashoutSettings(id).active
|
||||
const getCashoutSettings = deviceId => fromNamespace(deviceId)(cashout)
|
||||
const isCashOutDisabled = ({ deviceId }) =>
|
||||
!getCashoutSettings(deviceId).active
|
||||
|
||||
const elements = [
|
||||
{
|
||||
|
|
@ -48,7 +72,11 @@ const CashCassettes = ({ machine, config }) => {
|
|||
currency={{ code: fiatCurrency }}
|
||||
notes={value}
|
||||
/>
|
||||
)
|
||||
),
|
||||
input: NumberInput,
|
||||
inputProps: {
|
||||
decimalPlaces: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cassette2',
|
||||
|
|
@ -64,17 +92,41 @@ const CashCassettes = ({ machine, config }) => {
|
|||
notes={value}
|
||||
/>
|
||||
)
|
||||
},
|
||||
input: NumberInput,
|
||||
inputProps: {
|
||||
decimalPlaces: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const [resetCashOut, { error }] = useMutation(RESET_CASHOUT_BILLS, {
|
||||
refetchQueries: () => refetchData()
|
||||
})
|
||||
|
||||
const onSave = (...[, { deviceId, cassette1, cassette2 }]) => {
|
||||
return resetCashOut({
|
||||
variables: {
|
||||
action: 'resetCashOutBills',
|
||||
deviceId: deviceId,
|
||||
cassette1,
|
||||
cassette2
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{machine.name && (
|
||||
<EditableTable
|
||||
error={error?.message}
|
||||
stripeWhen={isCashOutDisabled}
|
||||
disableRowEdit={isCashOutDisabled}
|
||||
name="cashboxes"
|
||||
elements={elements}
|
||||
enableEdit
|
||||
data={[machine] || []}
|
||||
save={onSave}
|
||||
validationSchema={ValidationSchema}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,75 +1,20 @@
|
|||
import { useMutation } from '@apollo/react-hooks'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import gql from 'graphql-tag'
|
||||
import moment from 'moment'
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import { ConfirmDialog } from 'src/components/ConfirmDialog'
|
||||
import { Status } from 'src/components/Status'
|
||||
import ActionButton from 'src/components/buttons/ActionButton'
|
||||
import { Label4, P } from 'src/components/typography'
|
||||
import { ReactComponent as RebootReversedIcon } from 'src/styling/icons/button/reboot/white.svg'
|
||||
import { ReactComponent as RebootIcon } from 'src/styling/icons/button/reboot/zodiac.svg'
|
||||
import { ReactComponent as ShutdownReversedIcon } from 'src/styling/icons/button/shut down/white.svg'
|
||||
import { ReactComponent as ShutdownIcon } from 'src/styling/icons/button/shut down/zodiac.svg'
|
||||
import { ReactComponent as UnpairReversedIcon } from 'src/styling/icons/button/unpair/white.svg'
|
||||
import { ReactComponent as UnpairIcon } from 'src/styling/icons/button/unpair/zodiac.svg'
|
||||
import { Label3, P } from 'src/components/typography'
|
||||
|
||||
import styles from '../Machines.styles'
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const MACHINE_ACTION = gql`
|
||||
mutation MachineAction(
|
||||
$deviceId: ID!
|
||||
$action: MachineAction!
|
||||
$newName: String
|
||||
) {
|
||||
machineAction(deviceId: $deviceId, action: $action, newName: $newName) {
|
||||
deviceId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Details = ({ data, onActionSuccess }) => {
|
||||
const [action, setAction] = useState('')
|
||||
const [confirmActionDialogOpen, setConfirmActionDialogOpen] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState(null)
|
||||
const [machineAction] = useMutation(MACHINE_ACTION, {
|
||||
onError: ({ message }) => {
|
||||
const errorMessage = message ?? 'An error ocurred'
|
||||
setErrorMessage(errorMessage)
|
||||
},
|
||||
onCompleted: () => {
|
||||
onActionSuccess && onActionSuccess()
|
||||
setConfirmActionDialogOpen(false)
|
||||
}
|
||||
})
|
||||
const confirmActionDialog = action =>
|
||||
setAction(action) || setConfirmActionDialogOpen(true)
|
||||
const Details = ({ data }) => {
|
||||
const classes = useStyles()
|
||||
return (
|
||||
<>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.rowItem}>
|
||||
{' '}
|
||||
<Label4 className={classes.tl2}>Status</Label4>
|
||||
{data && data.statuses ? <Status status={data.statuses[0]} /> : null}
|
||||
{/* <Label4 className={classes.tl2}>Machine model</Label4>
|
||||
<P>{data.model}</P> */}
|
||||
</div>
|
||||
<div className={classes.rowItem}>
|
||||
<Label4 className={classes.tl2}>Machine model</Label4>
|
||||
<P>{data.model}</P>
|
||||
</div>
|
||||
<div className={classes.rowItem}>
|
||||
{' '}
|
||||
<Label4 className={classes.tl2}>Software version</Label4>
|
||||
<P>{data.version}</P>
|
||||
</div>
|
||||
|
||||
<div className={classes.rowItem}>
|
||||
{' '}
|
||||
<Label4 className={classes.tl2}>Paired at</Label4>
|
||||
<Label3 className={classes.label3}>Paired at</Label3>
|
||||
<P>
|
||||
{data.pairedAt
|
||||
? moment(data.pairedAt).format('YYYY-MM-DD HH:mm:ss')
|
||||
|
|
@ -77,68 +22,15 @@ const Details = ({ data, onActionSuccess }) => {
|
|||
</P>
|
||||
</div>
|
||||
<div className={classes.rowItem}>
|
||||
{' '}
|
||||
<Label4 className={classes.tl2}>Last ping</Label4>
|
||||
<P>
|
||||
{data.lastPing
|
||||
? moment(data.lastPing).format('YYYY-MM-DD HH:mm:ss')
|
||||
: ''}
|
||||
</P>
|
||||
<Label3 className={classes.label3}>Machine model</Label3>
|
||||
<P>{data.model}</P>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.rowItem}>
|
||||
{' '}
|
||||
<Label4 className={classes.tl2}>Actions</Label4>
|
||||
{data.name && (
|
||||
<div className={classes.actionButtonsContainer}>
|
||||
<ActionButton
|
||||
color="primary"
|
||||
className={classes.actionButton}
|
||||
Icon={UnpairIcon}
|
||||
InverseIcon={UnpairReversedIcon}
|
||||
onClick={() => confirmActionDialog('Unpair')}>
|
||||
Unpair
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
color="primary"
|
||||
className={classes.actionButton}
|
||||
Icon={RebootIcon}
|
||||
InverseIcon={RebootReversedIcon}
|
||||
onClick={() => confirmActionDialog('Reboot')}>
|
||||
Reboot
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
className={classes.actionButton}
|
||||
color="primary"
|
||||
Icon={ShutdownIcon}
|
||||
InverseIcon={ShutdownReversedIcon}
|
||||
onClick={() => confirmActionDialog('Shutdown')}>
|
||||
Shutdown
|
||||
</ActionButton>
|
||||
</div>
|
||||
)}
|
||||
<Label3 className={classes.label3}>Software version</Label3>
|
||||
<P>{data.version}</P>
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmDialog
|
||||
open={confirmActionDialogOpen}
|
||||
title={`${action} this machine?`}
|
||||
errorMessage={errorMessage}
|
||||
toBeConfirmed={data.name}
|
||||
onConfirmed={() => {
|
||||
setErrorMessage(null)
|
||||
machineAction({
|
||||
variables: {
|
||||
deviceId: data.deviceId,
|
||||
action: `${action}`.toLowerCase()
|
||||
}
|
||||
})
|
||||
}}
|
||||
onDissmised={() => {
|
||||
setConfirmActionDialogOpen(false)
|
||||
setErrorMessage(null)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
import { useMutation } from '@apollo/react-hooks'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import gql from 'graphql-tag'
|
||||
import moment from 'moment'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import { ConfirmDialog } from 'src/components/ConfirmDialog'
|
||||
import { Status } from 'src/components/Status'
|
||||
import ActionButton from 'src/components/buttons/ActionButton'
|
||||
import { H3, Label3, P } from 'src/components/typography'
|
||||
import { ReactComponent as RebootReversedIcon } from 'src/styling/icons/button/reboot/white.svg'
|
||||
import { ReactComponent as RebootIcon } from 'src/styling/icons/button/reboot/zodiac.svg'
|
||||
import { ReactComponent as ShutdownReversedIcon } from 'src/styling/icons/button/shut down/white.svg'
|
||||
import { ReactComponent as ShutdownIcon } from 'src/styling/icons/button/shut down/zodiac.svg'
|
||||
import { ReactComponent as UnpairReversedIcon } from 'src/styling/icons/button/unpair/white.svg'
|
||||
import { ReactComponent as UnpairIcon } from 'src/styling/icons/button/unpair/zodiac.svg'
|
||||
|
||||
import styles from '../Machines.styles'
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const MACHINE_ACTION = gql`
|
||||
mutation MachineAction(
|
||||
$deviceId: ID!
|
||||
$action: MachineAction!
|
||||
$newName: String
|
||||
) {
|
||||
machineAction(deviceId: $deviceId, action: $action, newName: $newName) {
|
||||
deviceId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Overview = ({ data, onActionSuccess }) => {
|
||||
const [action, setAction] = useState('')
|
||||
const [confirmActionDialogOpen, setConfirmActionDialogOpen] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState(null)
|
||||
const classes = useStyles()
|
||||
|
||||
const [machineAction] = useMutation(MACHINE_ACTION, {
|
||||
onError: ({ message }) => {
|
||||
const errorMessage = message ?? 'An error ocurred'
|
||||
setErrorMessage(errorMessage)
|
||||
},
|
||||
onCompleted: () => {
|
||||
onActionSuccess && onActionSuccess()
|
||||
setConfirmActionDialogOpen(false)
|
||||
}
|
||||
})
|
||||
|
||||
const confirmActionDialog = action =>
|
||||
setAction(action) || setConfirmActionDialogOpen(true)
|
||||
|
||||
const makeLastPing = () => {
|
||||
const now = moment()
|
||||
const secondsAgo = now.diff(data.lastPing, 'seconds')
|
||||
if (secondsAgo < 60) {
|
||||
return `${secondsAgo} ${secondsAgo === 1 ? 'second' : 'seconds'} ago`
|
||||
}
|
||||
if (secondsAgo < 3600) {
|
||||
const minutes = Math.round(secondsAgo / 60)
|
||||
return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`
|
||||
}
|
||||
if (secondsAgo < 3600 * 24) {
|
||||
const hours = Math.round(secondsAgo / 3600)
|
||||
return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`
|
||||
}
|
||||
const days = Math.round(secondsAgo / 3600 / 24)
|
||||
return `${days} ${days === 1 ? 'day' : 'days'} ago`
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.rowItem}>
|
||||
<H3>{data.name}</H3>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.rowItem}>
|
||||
<Label3 className={classes.label3}>Status</Label3>
|
||||
{data && data.statuses ? <Status status={data.statuses[0]} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.rowItem}>
|
||||
<Label3 className={classes.label3}>Last ping</Label3>
|
||||
<P>{data.lastPing ? makeLastPing() : ''}</P>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.rowItem}>
|
||||
{' '}
|
||||
<Label3 className={classes.label3}>Actions</Label3>
|
||||
{data.name && (
|
||||
<div className={classes.actionButtonsContainer}>
|
||||
<ActionButton
|
||||
color="primary"
|
||||
className={classes.actionButton}
|
||||
Icon={UnpairIcon}
|
||||
InverseIcon={UnpairReversedIcon}
|
||||
onClick={() => confirmActionDialog('Unpair')}>
|
||||
Unpair
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
color="primary"
|
||||
className={classes.actionButton}
|
||||
Icon={RebootIcon}
|
||||
InverseIcon={RebootReversedIcon}
|
||||
onClick={() => confirmActionDialog('Reboot')}>
|
||||
Reboot
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
className={classes.actionButton}
|
||||
color="primary"
|
||||
Icon={ShutdownIcon}
|
||||
InverseIcon={ShutdownReversedIcon}
|
||||
onClick={() => confirmActionDialog('Shutdown')}>
|
||||
Shutdown
|
||||
</ActionButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmDialog
|
||||
open={confirmActionDialogOpen}
|
||||
title={`${action} this machine?`}
|
||||
errorMessage={errorMessage}
|
||||
toBeConfirmed={data.name}
|
||||
onConfirmed={() => {
|
||||
setErrorMessage(null)
|
||||
machineAction({
|
||||
variables: {
|
||||
deviceId: data.deviceId,
|
||||
action: `${action}`.toLowerCase()
|
||||
}
|
||||
})
|
||||
}}
|
||||
onDissmised={() => {
|
||||
setConfirmActionDialogOpen(false)
|
||||
setErrorMessage(null)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Overview
|
||||
|
|
@ -105,7 +105,7 @@ const DataTable = ({
|
|||
useEffect(() => setExpanded(initialExpanded), [initialExpanded])
|
||||
|
||||
const coreWidth = R.compose(R.sum, R.map(R.prop('width')))(elements)
|
||||
const expWidth = 1200 - coreWidth
|
||||
const expWidth = 1000 - coreWidth
|
||||
const width = coreWidth + (expandable ? expWidth : 0)
|
||||
|
||||
const classes = useStyles({ width })
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const useStyles = makeStyles(mainStyles)
|
|||
const NUM_LOG_RESULTS = 5
|
||||
|
||||
const GET_TRANSACTIONS = gql`
|
||||
query transactions($limit: Int, $from: Date, $until: Date, $id: String) {
|
||||
query transactions($limit: Int, $from: Date, $until: Date, $id: ID) {
|
||||
transactions(limit: $limit, from: $from, until: $until, id: $id) {
|
||||
id
|
||||
txClass
|
||||
|
|
@ -66,6 +66,10 @@ const Transactions = ({ id }) => {
|
|||
}
|
||||
)
|
||||
|
||||
if (!loading && txResponse) {
|
||||
txResponse.transactions = txResponse.transactions.splice(0, 5)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (id !== null) {
|
||||
getTx()
|
||||
|
|
@ -91,13 +95,6 @@ const Transactions = ({ id }) => {
|
|||
size: 'sm',
|
||||
view: it => (it.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />)
|
||||
},
|
||||
{
|
||||
header: 'Machine',
|
||||
name: 'machineName',
|
||||
width: 180,
|
||||
size: 'sm',
|
||||
view: R.path(['machineName'])
|
||||
},
|
||||
{
|
||||
header: 'Customer',
|
||||
width: 162,
|
||||
|
|
@ -113,7 +110,7 @@ const Transactions = ({ id }) => {
|
|||
},
|
||||
{
|
||||
header: 'Crypto',
|
||||
width: 144,
|
||||
width: 164,
|
||||
textAlign: 'right',
|
||||
size: 'sm',
|
||||
view: it =>
|
||||
|
|
@ -126,14 +123,15 @@ const Transactions = ({ id }) => {
|
|||
view: it => formatCryptoAddress(it.cryptoCode, it.toAddress),
|
||||
className: classes.overflowTd,
|
||||
size: 'sm',
|
||||
width: 140
|
||||
textAlign: 'left',
|
||||
width: 170
|
||||
},
|
||||
{
|
||||
header: 'Date (UTC)',
|
||||
view: it => moment.utc(it.created).format('YYYY-MM-DD HH:mm:ss'),
|
||||
textAlign: 'right',
|
||||
view: it => moment.utc(it.created).format('YYYY-MM-DD'),
|
||||
textAlign: 'left',
|
||||
size: 'sm',
|
||||
width: 200
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
|
|
@ -161,7 +159,8 @@ const Transactions = ({ id }) => {
|
|||
loading={loading || id === null}
|
||||
emptyText="No transactions so far"
|
||||
elements={elements}
|
||||
data={R.path(['transactions'])(txResponse)}
|
||||
// need to splice because back end query could return double NUM_LOG_RESULTS because it doesnt merge the txIn and the txOut results before applying the limit
|
||||
data={R.path(['transactions'])(txResponse)} // .splice(0,NUM_LOG_RESULTS)}
|
||||
Details={DetailsRow}
|
||||
expandable
|
||||
/>
|
||||
|
|
|
|||
27
new-lamassu-admin/src/pages/Machines/MachineSidebar.js
Normal file
27
new-lamassu-admin/src/pages/Machines/MachineSidebar.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import List from '@material-ui/core/List'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import React from 'react'
|
||||
|
||||
const MachineSidebar = ({ data, getText, getKey, isSelected, selectItem }) => {
|
||||
return (
|
||||
<List style={{ height: 400, overflowY: 'auto' }}>
|
||||
{data.map((item, idx) => {
|
||||
return (
|
||||
<ListItem
|
||||
disableRipple
|
||||
key={getKey(item) + idx}
|
||||
button
|
||||
selected={isSelected(getText(item))}
|
||||
onClick={() => selectItem(getText(item))}>
|
||||
<ListItemText primary={getText(item)} />
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
)
|
||||
|
||||
/* return data.map(item => <button key={getKey(item)}>{getText(item)}</button>) */
|
||||
}
|
||||
|
||||
export default MachineSidebar
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
import { useQuery } from '@apollo/react-hooks'
|
||||
import Breadcrumbs from '@material-ui/core/Breadcrumbs'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
|
||||
import gql from 'graphql-tag'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
import Sidebar from 'src/components/layout/Sidebar'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
import { TL1 } from 'src/components/typography'
|
||||
import { TL1, TL2, Label3 } from 'src/components/typography'
|
||||
|
||||
import Cassettes from './MachineComponents/Cassettes'
|
||||
import Commissions from './MachineComponents/Commissions'
|
||||
import Details from './MachineComponents/Details'
|
||||
import Overview from './MachineComponents/Overview'
|
||||
import Transactions from './MachineComponents/Transactions'
|
||||
import Sidebar from './MachineSidebar'
|
||||
import styles from './Machines.styles'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const getMachineInfo = R.compose(R.find, R.propEq('name'))
|
||||
|
|
@ -45,58 +47,76 @@ const getMachines = R.path(['machines'])
|
|||
|
||||
const Machines = () => {
|
||||
const { data, refetch, loading } = useQuery(GET_INFO)
|
||||
const location = useLocation()
|
||||
const [selectedMachine, setSelectedMachine] = useState('')
|
||||
const [touched, setTouched] = useState(false)
|
||||
const classes = useStyles()
|
||||
|
||||
const machines = getMachines(data) ?? []
|
||||
const machineInfo = getMachineInfo(selectedMachine)(machines) ?? {}
|
||||
|
||||
// pre-selects first machine from the list, if there is a machine configured.
|
||||
// Only runs if user hasnt touched the sidebar yet
|
||||
useEffect(() => {
|
||||
if (!loading && data && data.machines && !touched) {
|
||||
setSelectedMachine(R.path(['machines', 0, 'name'])(data) ?? '')
|
||||
if (!loading && data && data.machines) {
|
||||
if (location.state && location.state.selectedMachine) {
|
||||
setSelectedMachine(location.state.selectedMachine)
|
||||
} else {
|
||||
setSelectedMachine(R.path(['machines', 0, 'name'])(data) ?? '')
|
||||
}
|
||||
}
|
||||
}, [data, loading, touched])
|
||||
}, [loading, data, location.state])
|
||||
|
||||
/*
|
||||
const isId = R.either(R.propEq('machine', 'ALL_MACHINES'), R.propEq('machine', 'e139c9021251ecf9c5280379b885983901b3dad14963cf38b6d7c1fb33faf72e'))
|
||||
R.filter(isId)(data.overrides)
|
||||
*/
|
||||
return (
|
||||
<>
|
||||
<TitleSection title="Machine details page" />
|
||||
<Grid container className={classes.grid}>
|
||||
<Sidebar
|
||||
data={machines}
|
||||
isSelected={it => it.name === selectedMachine}
|
||||
displayName={it => it.name}
|
||||
onClick={it => {
|
||||
setTouched(true)
|
||||
setSelectedMachine(it.name)
|
||||
}}
|
||||
/>
|
||||
<div className={classes.content}>
|
||||
<div className={classes.detailItem}>
|
||||
<TL1 className={classes.subtitle}>{'Details'}</TL1>
|
||||
<Details data={machineInfo} onActionSuccess={refetch} />
|
||||
</div>
|
||||
<div className={classes.detailItem}>
|
||||
<TL1 className={classes.subtitle}>{'Cash cassettes'}</TL1>
|
||||
<Cassettes machine={machineInfo} config={data?.config ?? false} />
|
||||
</div>
|
||||
<div className={classes.transactionsItem}>
|
||||
<TL1 className={classes.subtitle}>{'Latest transactions'}</TL1>
|
||||
<Transactions id={machineInfo?.deviceId ?? null} />
|
||||
</div>
|
||||
<div className={classes.detailItem}>
|
||||
<TL1 className={classes.subtitle}>{'Commissions'}</TL1>
|
||||
<Commissions
|
||||
name={'commissions'}
|
||||
id={machineInfo?.deviceId ?? null}
|
||||
<Grid item xs={3}>
|
||||
<Grid item xs={12}>
|
||||
<div style={{ marginTop: 32 }}>
|
||||
<Breadcrumbs separator={<NavigateNextIcon fontSize="small" />}>
|
||||
<Link to="/dashboard" style={{ textDecoration: 'none' }}>
|
||||
<Label3 className={classes.subtitle}>Dashboard</Label3>
|
||||
</Link>
|
||||
<TL2 className={classes.subtitle}>{selectedMachine}</TL2>
|
||||
</Breadcrumbs>
|
||||
<Overview data={machineInfo} onActionSuccess={refetch} />
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Sidebar
|
||||
isSelected={R.equals(selectedMachine)}
|
||||
selectItem={setSelectedMachine}
|
||||
data={machines}
|
||||
getText={R.prop('name')}
|
||||
getKey={R.prop('deviceId')}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<div className={classes.content}>
|
||||
<div className={classes.detailItem} style={{ marginTop: 24 }}>
|
||||
<TL1 className={classes.subtitle}>{'Details'}</TL1>
|
||||
<Details data={machineInfo} />
|
||||
</div>
|
||||
<div className={classes.detailItem}>
|
||||
<TL1 className={classes.subtitle}>{'Cash cassettes'}</TL1>
|
||||
<Cassettes
|
||||
refetchData={refetch}
|
||||
machine={machineInfo}
|
||||
config={data?.config ?? false}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.transactionsItem}>
|
||||
<TL1 className={classes.subtitle}>{'Latest transactions'}</TL1>
|
||||
<Transactions id={machineInfo?.deviceId ?? null} />
|
||||
</div>
|
||||
<div className={classes.detailItem}>
|
||||
<TL1 className={classes.subtitle}>{'Commissions'}</TL1>
|
||||
<Commissions
|
||||
name={'commissions'}
|
||||
id={machineInfo?.deviceId ?? null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default {
|
|||
flexDirection: 'row',
|
||||
color: comet
|
||||
},
|
||||
tl2: {
|
||||
label3: {
|
||||
color: comet,
|
||||
marginTop: 0
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue