chore: use monorepo organization

This commit is contained in:
Rafael Taranto 2025-05-12 10:52:54 +01:00
parent deaf7d6ecc
commit a687827f7e
1099 changed files with 8184 additions and 11535 deletions

View file

@ -0,0 +1,106 @@
import { useMutation, gql } from '@apollo/client'
import * as R from 'ramda'
import React, { useState } from 'react'
import DataTable from 'src/components/tables/DataTable'
import CashUnitDetails from 'src/pages/Maintenance/CashUnitDetails'
import Wizard from 'src/pages/Maintenance/Wizard/Wizard'
import helper from 'src/pages/Maintenance/helper'
import { fromNamespace } from 'src/utils/config'
const SET_CASSETTE_BILLS = gql`
mutation MachineAction(
$deviceId: ID!
$action: MachineAction!
$cashUnits: CashUnitsInput
) {
machineAction(deviceId: $deviceId, action: $action, cashUnits: $cashUnits) {
deviceId
cashUnits {
cashbox
cassette1
cassette2
cassette3
cassette4
recycler1
recycler2
recycler3
recycler4
recycler5
recycler6
}
}
}
`
const widths = {
name: 0,
cashbox: 175,
cassettes: 585,
edit: 90
}
const CashCassettes = ({ machine, config, refetchData, bills }) => {
const [wizard, setWizard] = useState(false)
const cashout = config && fromNamespace('cashOut')(config)
const locale = config && fromNamespace('locale')(config)
const fiatCurrency = locale?.fiatCurrency
const getCashoutSettings = deviceId => fromNamespace(deviceId)(cashout)
const elements = R.filter(it => it.name !== 'name')(
helper.getElements(config, bills, setWizard, widths)
)
const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, {
refetchQueries: () => refetchData()
})
const onSave = (_, cashUnits) =>
setCassetteBills({
variables: {
action: 'setCassetteBills',
deviceId: machine.deviceId,
cashUnits
}
})
const InnerCashUnitDetails = ({ it }) => (
<CashUnitDetails
machine={it}
bills={bills[it.deviceId] ?? []}
currency={fiatCurrency}
config={config}
hideMachineData
widths
/>
)
return machine.name ? (
<>
<DataTable
elements={elements}
data={[machine]}
Details={InnerCashUnitDetails}
emptyText="No machines so far"
initialExpanded={0}
tableClassName="min-h-72"
/>
{wizard && (
<Wizard
machine={machine}
cashoutSettings={getCashoutSettings(machine.deviceId)}
onClose={() => {
setWizard(false)
}}
error={error?.message}
save={onSave}
locale={locale}
/>
)}
</>
) : null
}
export default CashCassettes

View file

@ -0,0 +1,2 @@
import Cassettes from './Cassettes'
export default Cassettes

View file

@ -0,0 +1,93 @@
import { useQuery, useMutation, gql } from "@apollo/client";
import * as R from 'ramda'
import React from 'react'
import { Table as EditableTable } from 'src/components/editableTable'
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
import { overrides } from './helper'
const GET_DATA = gql`
query getData {
config
cryptoCurrencies {
code
display
}
machines {
name
deviceId
}
}
`
const SAVE_CONFIG = gql`
mutation Save($config: JSONObject) {
saveConfig(config: $config)
}
`
const Commissions = ({ name: SCREEN_KEY, id: deviceId }) => {
const { data, loading } = useQuery(GET_DATA)
const [saveConfig] = useMutation(SAVE_CONFIG, {
refetchQueries: () => ['getData']
})
const config = data?.config && fromNamespace(SCREEN_KEY)(data.config)
const currency = R.path(['fiatCurrency'])(
fromNamespace(namespaces.LOCALE)(data?.config)
)
const saveOverrides = it => {
const config = toNamespace(SCREEN_KEY)(it)
return saveConfig({ variables: { config } })
}
const getMachineCommissions = () => {
if (loading || !deviceId || !config) return []
const overrides = config.overrides
? R.concat(
R.filter(R.propEq('machine', 'ALL_MACHINES'), config.overrides),
R.filter(R.propEq('machine', deviceId), config.overrides)
)
: []
return R.map(
coin =>
R.reduce(
R.mergeDeepRight,
{
code: coin.code,
name: coin.display,
cashIn: config.cashIn,
cashOut: config.cashOut,
fixedFee: config.fixedFee,
minimumTx: config.minimumTx,
cashOutFixedFee: config.cashOutFixedFee
},
R.project(
['cashIn', 'cashOut', 'fixedFee', 'minimumTx', 'cashOutFixedFee'],
R.filter(
o =>
R.includes(coin.code, o.cryptoCurrencies) ||
R.includes('ALL_COINS', o.cryptoCurrencies),
overrides
)
)
),
data.cryptoCurrencies
)
}
return (
<EditableTable
name="overrides"
save={saveOverrides}
data={getMachineCommissions()}
elements={overrides(currency)}
/>
)
}
export default Commissions

View file

@ -0,0 +1,79 @@
import React from 'react'
import TxInIcon from 'src/styling/icons/direction/cash-in.svg?react'
import TxOutIcon from 'src/styling/icons/direction/cash-out.svg?react'
const cashInAndOutHeaderStyle = { marginLeft: 6 }
const cashInHeader = (
<div>
<TxInIcon />
<span style={cashInAndOutHeaderStyle}>Cash-in</span>
</div>
)
const cashOutHeader = (
<div>
<TxOutIcon />
<span style={cashInAndOutHeaderStyle}>Cash-out</span>
</div>
)
const getOverridesFields = currency => {
return [
{
name: 'name',
width: 280,
size: 'sm',
view: it => `${it}`
},
{
header: cashInHeader,
name: 'cashIn',
display: 'Cash-in',
width: 130,
textAlign: 'right',
suffix: '%'
},
{
header: cashOutHeader,
name: 'cashOut',
display: 'Cash-out',
width: 130,
textAlign: 'right',
suffix: '%',
inputProps: {
decimalPlaces: 3
}
},
{
name: 'fixedFee',
display: 'Fixed fee',
width: 155,
doubleHeader: 'Cash-in only',
textAlign: 'right',
suffix: currency
},
{
name: 'minimumTx',
display: 'Minimum Tx',
width: 155,
doubleHeader: 'Cash-in only',
textAlign: 'right',
suffix: currency
},
{
name: 'cashOutFixedFee',
display: 'Fixed fee',
width: 155,
doubleHeader: 'Cash-out only',
textAlign: 'right',
suffix: currency
}
]
}
const overrides = currency => {
return getOverridesFields(currency)
}
export { overrides }

View file

@ -0,0 +1,3 @@
import Commissions from './Commissions'
export default Commissions

View file

@ -0,0 +1,30 @@
import React from 'react'
import { Label1, P } from 'src/components/typography'
import { modelPrettifier } from 'src/utils/machine'
import { formatDate } from 'src/utils/timezones'
const Details = ({ data, timezone }) => {
return (
<div className="flex gap-30">
<div>
<Label1 className="text-comet mt-0">Paired at</Label1>
<P>
{data.pairedAt
? formatDate(data.pairedAt, timezone, 'yyyy-MM-dd HH:mm:ss')
: ''}
</P>
</div>
<div>
<Label1 className="text-comet mt-0">Machine model</Label1>
<P>{modelPrettifier[data.model]}</P>
</div>
<div>
<Label1 className="text-comet mt-0">Software version</Label1>
<P>{data.version}</P>
</div>
</div>
)
}
export default Details

View file

@ -0,0 +1,64 @@
import BigNumber from 'bignumber.js'
import { formatDistance } from 'date-fns'
import React from 'react'
import { Status } from 'src/components/Status'
import MachineActions from 'src/components/machineActions/MachineActions'
import { H3, Label1, P } from 'src/components/typography'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
const Overview = ({ data, onActionSuccess }) => {
return (
<div className="flex flex-col gap-8">
<H3>{data.name}</H3>
<div>
<Label1 className="text-comet mt-0">Status</Label1>
{data && data.statuses ? <Status status={data.statuses[0]} /> : null}
</div>
<div className="flex gap-6">
<div>
<Label1 className="text-comet mt-0">Ping</Label1>
<P noMargin>
{data.responseTime
? new BigNumber(data.responseTime).toFixed(3).toString() + ' ms'
: 'unavailable'}
</P>
</div>
<div>
<Label1 className="text-comet mt-0">Last ping</Label1>
<P noMargin>
{data.lastPing
? formatDistance(new Date(data.lastPing), new Date(), {
addSuffix: true
})
: 'unknown'}
</P>
</div>
<div>
<Label1 className="text-comet mt-0">Network speed</Label1>
<P noMargin>
{data.downloadSpeed
? new BigNumber(data.downloadSpeed)
.toFixed(data.downloadSpeed < 10 ? 2 : 0)
.toString() + ' MB/s'
: 'unavailable'}
</P>
</div>
</div>
<div>
<div>
<Label1 className="text-comet mt-0">Device ID</Label1>
<P className="wrap-anywhere" noMargin>
<CopyToClipboard>{data.deviceId}</CopyToClipboard>
</P>
</div>
</div>
<div>
<MachineActions
machine={data}
onActionSuccess={onActionSuccess}></MachineActions>
</div>
</div>
)
}
export default Overview

View file

@ -0,0 +1,179 @@
import { useQuery, useLazyQuery, gql } from '@apollo/client'
import { toUnit, formatCryptoAddress } from '@lamassu/coins/lightUtils'
import BigNumber from 'bignumber.js'
import * as R from 'ramda'
import React, { useEffect, useState } from 'react'
import DetailsRow from 'src/pages/Transactions/DetailsCard'
import TxInIcon from 'src/styling/icons/direction/cash-in.svg?react'
import TxOutIcon from 'src/styling/icons/direction/cash-out.svg?react'
import { getStatus } from 'src/pages/Transactions/helper'
import * as Customer from 'src/utils/customer'
import { formatDate } from 'src/utils/timezones'
import DataTable from 'src/components/tables/DataTable'
const NUM_LOG_RESULTS = 5
const GET_TRANSACTIONS = gql`
query transactions(
$limit: Int
$from: DateTimeISO
$until: DateTimeISO
$deviceId: String
) {
transactions(
limit: $limit
from: $from
until: $until
deviceId: $deviceId
) {
id
txClass
txHash
toAddress
commissionPercentage
expired
machineName
operatorCompleted
sendConfirmed
dispense
hasError: error
deviceId
fiat
fixedFee
fiatCode
cryptoAtoms
cryptoCode
toAddress
created
customerName
customerIdCardData
customerIdCardPhotoPath
customerFrontCameraPath
customerPhone
customerEmail
discount
customerId
isAnonymous
rawTickerPrice
profit
}
}
`
const GET_DATA = gql`
query getData {
config
}
`
const Transactions = ({ id }) => {
const [extraHeight, setExtraHeight] = useState(0)
const [clickedId, setClickedId] = useState('')
const [getTx, { data: txResponse, loading: txLoading }] = useLazyQuery(
GET_TRANSACTIONS,
{
variables: {
limit: NUM_LOG_RESULTS,
deviceId: id
}
}
)
const { data: configData, loading: configLoading } = useQuery(GET_DATA)
const timezone = R.path(['config', 'locale_timezone'], configData)
const loading = txLoading || configLoading
if (!loading && txResponse) {
txResponse.transactions = txResponse.transactions.splice(0, 5)
}
useEffect(() => {
if (id !== null) {
getTx()
}
}, [getTx, id])
const elements = [
{
header: '',
width: 0,
size: 'sm',
view: it => (it.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />)
},
{
header: 'Customer',
width: 122,
size: 'sm',
view: Customer.displayName
},
{
header: 'Cash',
width: 144,
textAlign: 'right',
size: 'sm',
view: it => `${Number.parseFloat(it.fiat)} ${it.fiatCode}`
},
{
header: 'Crypto',
width: 164,
textAlign: 'right',
size: 'sm',
view: it =>
`${toUnit(new BigNumber(it.cryptoAtoms), it.cryptoCode).toFormat(5)} ${
it.cryptoCode
}`
},
{
header: 'Address',
view: it => formatCryptoAddress(it.cryptoCode, it.toAddress),
className: 'overflow-hidden whitespace-nowrap text-ellipsis',
size: 'sm',
textAlign: 'left',
width: 140
},
{
header: 'Date',
view: it => formatDate(it.created, timezone, 'yyyyMMdd'),
textAlign: 'left',
size: 'sm',
width: 140
},
{
header: 'Status',
view: it => getStatus(it),
size: 'sm',
width: 20
}
]
const handleClick = e => {
if (clickedId === e.id) {
setClickedId('')
setExtraHeight(0)
} else {
setClickedId(e.id)
setExtraHeight(310)
}
}
return (
<DataTable
extraHeight={extraHeight}
onClick={handleClick}
maxWidth="950"
className="min-h-90"
loading={loading || id === null}
emptyText="No transactions so far"
elements={elements}
data={R.path(['transactions'])(txResponse)}
Details={DetailsRow}
expandable
/>
)
}
export default Transactions

View file

@ -0,0 +1,2 @@
import Transactions from './Transactions'
export default Transactions

View file

@ -0,0 +1,150 @@
import { useQuery, gql } from '@apollo/client'
import Breadcrumbs from '@mui/material/Breadcrumbs'
import NavigateNextIcon from '@mui/icons-material/NavigateNext'
import * as R from 'ramda'
import React, { useState } from 'react'
import { Link, useLocation, useHistory } from 'react-router-dom'
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'
const GET_INFO = gql`
query getMachine($deviceId: ID!, $billFilters: JSONObject) {
machine(deviceId: $deviceId) {
name
deviceId
paired
lastPing
pairedAt
version
model
cashUnits {
cashbox
cassette1
cassette2
cassette3
cassette4
recycler1
recycler2
recycler3
recycler4
recycler5
recycler6
}
numberOfCassettes
numberOfRecyclers
statuses {
label
type
}
downloadSpeed
responseTime
packetLoss
latestEvent {
note
}
}
bills(filters: $billFilters) {
id
fiat
deviceId
created
}
config
}
`
const getMachineID = path => path.slice(path.lastIndexOf('/') + 1)
const MachineRoute = () => {
const location = useLocation()
const history = useHistory()
const id = getMachineID(location.pathname)
const [loading, setLoading] = useState(true)
const { data, refetch } = useQuery(GET_INFO, {
onCompleted: data => {
if (data.machine === null)
return history.push('/maintenance/machine-status')
setLoading(false)
},
variables: {
deviceId: id,
billFilters: {
deviceId: id,
batch: 'none'
}
}
})
const reload = () => {
return history.push(location.pathname)
}
return (
!loading && (
<Machines data={data} refetch={refetch} reload={reload}></Machines>
)
)
}
const Machines = ({ data, refetch, reload }) => {
const timezone = R.path(['config', 'locale_timezone'], data) ?? {}
const machine = R.path(['machine'])(data) ?? {}
const config = R.path(['config'])(data) ?? {}
const bills = R.groupBy(bill => bill.deviceId)(R.path(['bills'])(data) ?? [])
const machineName = R.path(['name'])(machine) ?? null
const machineID = R.path(['deviceId'])(machine) ?? null
return (
<div className="flex flex-1 h-full gap-12">
<div className="basis-1/4 min-w-1/4 pt-8">
<Breadcrumbs separator={<NavigateNextIcon fontSize="small" />}>
<Link to="/dashboard" className="no-underline">
<Label3 noMargin className="text-comet mt-[1px]">
Dashboard
</Label3>
</Link>
<TL2 noMargin className="text-comet">
{machineName}
</TL2>
</Breadcrumbs>
<Overview data={machine} onActionSuccess={reload} />
</div>
<div className="basis-3/4 max-w-3/4 flex flex-col mt-6">
<div>
<TL1 className="text-comet">Details</TL1>
<Details data={machine} timezone={timezone} />
</div>
<div>
<TL1 className="text-comet">Cash box & cassettes</TL1>
<Cassettes
refetchData={refetch}
machine={machine}
config={config ?? false}
bills={bills}
/>
</div>
<div>
<TL1 className="text-comet">Latest transactions</TL1>
<Transactions id={machineID} />
</div>
<div>
<TL1 className="text-comet">Commissions</TL1>
<Commissions name={'commissions'} id={machineID} />
</div>
</div>
</div>
)
}
export default MachineRoute

View file

@ -0,0 +1,3 @@
import Machines from './Machines'
export default Machines