From 00f176fccc9145844e2adc28c09534589acd5272 Mon Sep 17 00:00:00 2001 From: Cesar <26280794+csrapr@users.noreply.github.com> Date: Tue, 17 Nov 2020 16:24:08 +0000 Subject: [PATCH] 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 --- lib/new-admin/graphql/schema.js | 4 +- .../src/components/typography/index.js | 4 +- .../src/pages/Dashboard/Footer/Footer.js | 77 ++++++++- .../pages/Dashboard/Footer/Footer.styles.js | 7 +- .../Graphs/PercentageChart.js | 100 ++++++++++++ .../Graphs/RefScatterplot.js | 14 +- .../SystemPerformance/SystemPerformance.js | 47 +++--- .../Dashboard/SystemStatus/MachinesTable.js | 20 ++- .../MachineComponents/Cassettes/Cassettes.js | 60 ++++++- .../Machines/MachineComponents/Details.js | 124 +-------------- .../Machines/MachineComponents/Overview.js | 147 ++++++++++++++++++ .../Transactions/DataTable.js | 2 +- .../Transactions/Transactions.js | 27 ++-- .../src/pages/Machines/MachineSidebar.js | 27 ++++ .../src/pages/Machines/Machines.js | 104 ++++++++----- .../src/pages/Machines/Machines.styles.js | 2 +- 16 files changed, 539 insertions(+), 227 deletions(-) create mode 100644 new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/PercentageChart.js create mode 100644 new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js create mode 100644 new-lamassu-admin/src/pages/Machines/MachineSidebar.js diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index 8cfcffff..3f322338 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -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(), diff --git a/new-lamassu-admin/src/components/typography/index.js b/new-lamassu-admin/src/components/typography/index.js index a9681372..2cd0a09e 100644 --- a/new-lamassu-admin/src/components/typography/index.js +++ b/new-lamassu-admin/src/components/typography/index.js @@ -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 } diff --git a/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.js b/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.js index 7a62ca5b..b78fc010 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.js +++ b/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.js @@ -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 ( <> -
+
{!loading && data && ( <> - {R.keys(data.rates.withCommissions).map(key => - renderFooterItem(key) + + {R.keys(data.rates.withCommissions).map(key => + renderFooterItem(key) + )} + + {/* {renderFooterItem(R.keys(data.rates.withCommissions)[0])} */} + {showExpandBtn && ( + + + )} diff --git a/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.styles.js b/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.styles.js index b217dd06..7b3b0f1d 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.styles.js +++ b/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.styles.js @@ -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', diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/PercentageChart.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/PercentageChart.js new file mode 100644 index 00000000..6929e747 --- /dev/null +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/PercentageChart.js @@ -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 ( + <> + + {` ${value}%`} + + ) + } + return null + case 'cashOut': + if (value > 20) { + return ( + <> + + {` ${value}%`} + + ) + } + return null + default: + return null + } + } + + return ( + <> + +
+
+ {/* + {` ${value}%`} */} + {buildPercentageView(value, 'cashIn')} +
+
+ {/* + {` ${100 - value}%`} */} + {buildPercentageView(100 - value, 'cashOut')} +
+
+ + ) +} + +export default PercentageChart diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js index d875658a..e63bdc32 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js @@ -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) diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js index 856f93a3..08ddf06a 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js @@ -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 = () => { Direction - - CashIn: - {` ${getDirectionPercent().cashIn}%`} - - - CashOut: - {` ${getDirectionPercent().cashOut}%`} + + diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemStatus/MachinesTable.js b/new-lamassu-admin/src/pages/Dashboard/SystemStatus/MachinesTable.js index 1c5d12a0..61e2c89d 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemStatus/MachinesTable.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemStatus/MachinesTable.js @@ -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 {`${percent}%`} } + + const redirect = name => { + return history.push('/machines', { selectedMachine: name }) + } + return ( <> @@ -66,11 +72,11 @@ const MachinesTable = ({ machines, numToRender }) => { Status
- + {/*
-
+
*/}
@@ -90,6 +96,8 @@ const MachinesTable = ({ machines, numToRender }) => { if (idx < numToRender) { return ( redirect(machine.name)} + style={{ cursor: 'pointer' }} key={machine.deviceId + idx} className={classes.row}> @@ -98,9 +106,9 @@ const MachinesTable = ({ machines, numToRender }) => { - + {/* {makePercentageText(machine.cashbox)} - + */} {makePercentageText(machine.cassette1)} diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js index 5f968a17..8fbb7de4 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js @@ -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 && ( )} diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Details.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Details.js index b0272a54..32c65d01 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Details.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Details.js @@ -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 ( <>
{' '} - Status - {data && data.statuses ? : null} - {/* Machine model -

{data.model}

*/} -
-
- Machine model -

{data.model}

-
-
- {' '} - Software version -

{data.version}

-
- -
- {' '} - Paired at + Paired at

{data.pairedAt ? moment(data.pairedAt).format('YYYY-MM-DD HH:mm:ss') @@ -77,68 +22,15 @@ const Details = ({ data, onActionSuccess }) => {

- {' '} - Last ping -

- {data.lastPing - ? moment(data.lastPing).format('YYYY-MM-DD HH:mm:ss') - : ''} -

+ Machine model +

{data.model}

-
-
{' '} - Actions - {data.name && ( -
- confirmActionDialog('Unpair')}> - Unpair - - confirmActionDialog('Reboot')}> - Reboot - - confirmActionDialog('Shutdown')}> - Shutdown - -
- )} + Software version +

{data.version}

- { - setErrorMessage(null) - machineAction({ - variables: { - deviceId: data.deviceId, - action: `${action}`.toLowerCase() - } - }) - }} - onDissmised={() => { - setConfirmActionDialogOpen(false) - setErrorMessage(null) - }} - /> ) } diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js new file mode 100644 index 00000000..6b2e50a3 --- /dev/null +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js @@ -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 ( + <> +
+
+

{data.name}

+
+
+
+
+ Status + {data && data.statuses ? : null} +
+
+
+
+ Last ping +

{data.lastPing ? makeLastPing() : ''}

+
+
+
+
+ {' '} + Actions + {data.name && ( +
+ confirmActionDialog('Unpair')}> + Unpair + + confirmActionDialog('Reboot')}> + Reboot + + confirmActionDialog('Shutdown')}> + Shutdown + +
+ )} +
+
+ { + setErrorMessage(null) + machineAction({ + variables: { + deviceId: data.deviceId, + action: `${action}`.toLowerCase() + } + }) + }} + onDissmised={() => { + setConfirmActionDialogOpen(false) + setErrorMessage(null) + }} + /> + + ) +} + +export default Overview diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/DataTable.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/DataTable.js index 76a7abd9..18552260 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/DataTable.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/DataTable.js @@ -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 }) diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js index 685c265a..949eaa37 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js @@ -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' ? : ) }, - { - 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 /> diff --git a/new-lamassu-admin/src/pages/Machines/MachineSidebar.js b/new-lamassu-admin/src/pages/Machines/MachineSidebar.js new file mode 100644 index 00000000..24761137 --- /dev/null +++ b/new-lamassu-admin/src/pages/Machines/MachineSidebar.js @@ -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 ( + + {data.map((item, idx) => { + return ( + selectItem(getText(item))}> + + + ) + })} + + ) + + /* return data.map(item => ) */ +} + +export default MachineSidebar diff --git a/new-lamassu-admin/src/pages/Machines/Machines.js b/new-lamassu-admin/src/pages/Machines/Machines.js index 27028013..f3a54e91 100644 --- a/new-lamassu-admin/src/pages/Machines/Machines.js +++ b/new-lamassu-admin/src/pages/Machines/Machines.js @@ -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 ( <> - - it.name === selectedMachine} - displayName={it => it.name} - onClick={it => { - setTouched(true) - setSelectedMachine(it.name) - }} - /> -
-
- {'Details'} -
-
-
- {'Cash cassettes'} - -
-
- {'Latest transactions'} - -
-
- {'Commissions'} - + +
+ }> + + Dashboard + + {selectedMachine} + + +
+
+ + + + + +
+
+ {'Details'} +
+
+
+ {'Cash cassettes'} + +
+
+ {'Latest transactions'} + +
+
+ {'Commissions'} + +
-
+ ) diff --git a/new-lamassu-admin/src/pages/Machines/Machines.styles.js b/new-lamassu-admin/src/pages/Machines/Machines.styles.js index c817a760..bf6373c6 100644 --- a/new-lamassu-admin/src/pages/Machines/Machines.styles.js +++ b/new-lamassu-admin/src/pages/Machines/Machines.styles.js @@ -35,7 +35,7 @@ export default { flexDirection: 'row', color: comet }, - tl2: { + label3: { color: comet, marginTop: 0 },