feat: commissions list

This commit is contained in:
Jose Sousa 2020-11-17 09:52:43 +00:00 committed by Josh Harvey
parent e2970bea48
commit e918a62994
8 changed files with 521 additions and 78 deletions

View file

@ -25,14 +25,12 @@ export default {
}
},
buttonIcon: {
'& svg': {
width: 16,
height: 16,
overflow: 'visible',
'& g': {
strokeWidth: 1.8
}
}
},
buttonIconActiveLeft: {
marginRight: 12

View file

@ -105,6 +105,7 @@ const ECol = ({ editing, focus, config, extraPaddingRight, extraPadding }) => {
textAlign,
suffix,
SuffixComponent = TL2,
textStyle = it => {},
view = it => it?.toString(),
inputProps = {}
} = config
@ -144,9 +145,17 @@ const ECol = ({ editing, focus, config, extraPaddingRight, extraPadding }) => {
<Field name={name} component={input} {...innerProps} />
)}
{isEditing && !isField && <config.input name={name} />}
{!isEditing && values && <>{view(values[name], values)}</>}
{!isEditing && values && (
<div style={textStyle(values, isEditing)}>
{view(values[name], values)}
</div>
)}
{suffix && (
<SuffixComponent className={classes.suffix}>{suffix}</SuffixComponent>
<SuffixComponent
className={classes.suffix}
style={isEditing ? {} : textStyle(values, isEditing)}>
{suffix}
</SuffixComponent>
)}
</Td>
)

View file

@ -4,7 +4,7 @@ import { useSelect } from 'downshift'
import React from 'react'
import { ReactComponent as Arrowdown } from 'src/styling/icons/action/arrow/regular.svg'
import { startCase } from 'src/utils/string'
import { toFirstUpper } from 'src/utils/string'
import styles from './Select.styles'
@ -36,16 +36,18 @@ function Select({ label, items, ...props }) {
return (
<div className={classnames(selectClassNames)}>
<label {...getLabelProps()}>{startCase(label)}</label>
<label {...getLabelProps()}>{toFirstUpper(label)}</label>
<button {...getToggleButtonProps()}>
<span className={classes.selectedItem}>{startCase(selectedItem)}</span>
<span className={classes.selectedItem}>
{toFirstUpper(selectedItem)}
</span>
<Arrowdown />
</button>
<ul {...getMenuProps()}>
{isOpen &&
items.map((item, index) => (
<li key={`${item}${index}`} {...getItemProps({ item, index })}>
<span>{startCase(item)}</span>
<span>{toFirstUpper(item)}</span>
</li>
))}
</ul>

View file

@ -1,22 +1,17 @@
import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles, Box } from '@material-ui/core'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { useState } from 'react'
import { Table as EditableTable } from 'src/components/editableTable'
import Section from 'src/components/layout/Section'
import { SubpageButton } from 'src/components/buttons'
import TitleSection from 'src/components/layout/TitleSection'
import { ReactComponent as ExeceptionViewIcon } from 'src/styling/icons/circle buttons/exception-view/white.svg'
import { ReactComponent as ListingViewIcon } from 'src/styling/icons/circle buttons/listing-view/zodiac.svg'
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
import {
mainFields,
overrides,
schema,
getOverridesSchema,
defaults,
overridesDefaults,
getOrder
} from './helper'
import CommissionsDetails from './components/CommissionsDetails'
import CommissionsList from './components/CommissionsList'
const GET_DATA = gql`
query getData {
@ -38,26 +33,46 @@ const SAVE_CONFIG = gql`
}
`
const styles = {
titleWrapper: {
display: 'flex',
alignItems: 'center',
flexDirection: 'row'
},
subpageButton: {
marginLeft: 12
},
colorLabel: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginLeft: 'auto',
'& div': {
width: 12,
height: 12,
borderRadius: 3,
marginRight: 8,
backgroundColor: '#44e188'
}
}
}
const useStyles = makeStyles(styles)
const Commissions = ({ name: SCREEN_KEY }) => {
const [isEditingDefault, setEditingDefault] = useState(false)
const [isEditingOverrides, setEditingOverrides] = useState(false)
const classes = useStyles()
const [showMachines, setShowMachines] = useState(false)
const { data } = useQuery(GET_DATA)
const [saveConfig, { error }] = 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 localeConfig =
data?.config && fromNamespace(namespaces.LOCALE)(data.config)
const commission = config && !R.isEmpty(config) ? config : defaults
const commissionOverrides = commission?.overrides ?? []
const orderedCommissionsOverrides = R.sortWith([
R.ascend(getOrder),
R.ascend(R.prop('machine'))
])(commissionOverrides)
const currency = R.path(['fiatCurrency'])(localeConfig)
const save = it => {
const config = toNamespace(SCREEN_KEY)(it.commissions[0])
@ -69,51 +84,43 @@ const Commissions = ({ name: SCREEN_KEY }) => {
return saveConfig({ variables: { config } })
}
const onEditingDefault = (it, editing) => setEditingDefault(editing)
const onEditingOverrides = (it, editing) => setEditingOverrides(editing)
return (
<>
<Box className={classes.titleWrapper}>
<TitleSection title="Commissions" />
<Section>
<EditableTable
error={error?.message}
title="Default setup"
rowSize="lg"
titleLg
name="commissions"
enableEdit
initialValues={commission}
save={save}
validationSchema={schema}
data={R.of(commission)}
elements={mainFields(currency)}
setEditing={onEditingDefault}
forceDisable={isEditingOverrides}
/>
</Section>
<Section>
<EditableTable
error={error?.message}
title="Overrides"
titleLg
name="overrides"
enableDelete
enableEdit
enableCreate
groupBy={getOrder}
initialValues={overridesDefaults}
save={saveOverrides}
validationSchema={getOverridesSchema(
orderedCommissionsOverrides,
data
<SubpageButton
className={classes.subpageButton}
Icon={ListingViewIcon}
InverseIcon={ExeceptionViewIcon}
toggle={setShowMachines}>
List view
</SubpageButton>
{showMachines && (
<div className={classes.colorLabel}>
<div className={classes.greenSquare} />
<span>Override value</span>
</div>
)}
data={orderedCommissionsOverrides}
elements={overrides(data, currency, orderedCommissionsOverrides)}
setEditing={onEditingOverrides}
forceDisable={isEditingDefault}
</Box>
{!showMachines && (
<CommissionsDetails
config={config}
currency={currency}
data={data}
error={error}
save={save}
saveOverrides={saveOverrides}
/>
</Section>
)}
{showMachines && (
<CommissionsList
config={config}
localeConfig={localeConfig}
currency={currency}
data={data}
error={error}
/>
)}
</>
)
}

View file

@ -0,0 +1,78 @@
import * as R from 'ramda'
import React, { useState, memo } from 'react'
import { Table as EditableTable } from 'src/components/editableTable'
import Section from 'src/components/layout/Section'
import {
mainFields,
overrides,
schema,
getOverridesSchema,
defaults,
overridesDefaults,
getOrder
} from 'src/pages/Commissions/helper'
const CommissionsDetails = memo(
({ config, currency, data, error, save, saveOverrides }) => {
const [isEditingDefault, setEditingDefault] = useState(false)
const [isEditingOverrides, setEditingOverrides] = useState(false)
const commission = config && !R.isEmpty(config) ? config : defaults
const commissionOverrides = commission?.overrides ?? []
const orderedCommissionsOverrides = R.sortWith([
R.ascend(getOrder),
R.ascend(R.prop('machine'))
])(commissionOverrides)
const onEditingDefault = (it, editing) => setEditingDefault(editing)
const onEditingOverrides = (it, editing) => setEditingOverrides(editing)
return (
<>
<Section>
<EditableTable
error={error?.message}
title="Default setup"
rowSize="lg"
titleLg
name="commissions"
enableEdit
initialValues={commission}
save={save}
validationSchema={schema}
data={R.of(commission)}
elements={mainFields(currency)}
setEditing={onEditingDefault}
forceDisable={isEditingOverrides}
/>
</Section>
<Section>
<EditableTable
error={error?.message}
title="Overrides"
titleLg
name="overrides"
enableDelete
enableEdit
enableCreate
groupBy={getOrder}
initialValues={overridesDefaults}
save={saveOverrides}
validationSchema={getOverridesSchema(
orderedCommissionsOverrides,
data
)}
data={orderedCommissionsOverrides}
elements={overrides(data, currency, orderedCommissionsOverrides)}
setEditing={onEditingOverrides}
forceDisable={isEditingDefault}
/>
</Section>
</>
)
}
)
export default CommissionsDetails

View file

@ -0,0 +1,107 @@
import { makeStyles } from '@material-ui/core'
import * as R from 'ramda'
import React, { memo, useState } from 'react'
import { Table as EditableTable } from 'src/components/editableTable'
import { Select } from 'src/components/inputs'
import {
overridesDefaults,
getCommissions,
getMachineCoins,
getListCommissionsSchema,
commissionsList,
sortCommissionsBy,
filterCommissions,
SHOW_ALL,
ORDER_OPTIONS
} from 'src/pages/Commissions/helper'
const styles = {
headerLine: {
display: 'flex',
justifyContent: '',
marginBottom: 24,
'& div': {
marginRight: 24
}
},
tableWrapper: {
flex: 1,
display: 'block',
overflowX: 'auto',
width: '100%',
maxHeight: '70vh'
}
}
const useStyles = makeStyles(styles)
const CommissionsList = memo(
({ config, localeConfig, currency, data, error, saveOverrides }) => {
const classes = useStyles()
const [machineFilter, setMachineFilter] = useState(SHOW_ALL)
const [coinFilter, setCoinFilter] = useState(SHOW_ALL)
const [orderProp, setOrderProp] = useState('Machine name')
const cryptoCurrencies = R.prop('cryptoCurrencies', localeConfig)
const machines = R.prop('machines', data)
const machinesIds = R.map(R.prop('deviceId'))(machines)
const machinesNames = R.map(R.prop('name'))(machines)
const machinesCoins = R.map(m =>
R.xprod([m], getMachineCoins(m, localeConfig))
)(machinesIds)
const machinesCoinsTuples = R.unnest(machinesCoins)
const commissions = R.map(([deviceId, cryptoCode]) =>
getCommissions(cryptoCode, deviceId, config)
)(machinesCoinsTuples)
return (
<div>
<div className={classes.headerLine}>
<Select
onSelectedItemChange={setMachineFilter}
label="Machines"
default={SHOW_ALL}
items={[SHOW_ALL].concat(R.sortBy(R.identity, machinesNames))}
selectedItem={machineFilter}
/>
<Select
onSelectedItemChange={setCoinFilter}
label="Cryptocurrency"
default={SHOW_ALL}
items={[SHOW_ALL].concat(cryptoCurrencies)}
selectedItem={coinFilter}
/>
<Select
onSelectedItemChange={setOrderProp}
label="Sort by"
default={ORDER_OPTIONS[0]}
items={ORDER_OPTIONS}
selectedItem={orderProp}
/>
</div>
<div className={classes.tableWrapper}>
<EditableTable
error={error?.message}
name="comissionsList"
enableEdit
initialValues={overridesDefaults}
validationSchema={getListCommissionsSchema()}
data={R.compose(
sortCommissionsBy(orderProp, machines),
filterCommissions(coinFilter, machineFilter, machines)
)(commissions)}
elements={commissionsList(data, currency)}
/>
</div>
</div>
)
}
)
export default CommissionsList

View file

@ -1,9 +1,12 @@
import * as _ from 'lodash/fp'
import * as R from 'ramda'
import React from 'react'
import { v4 } from 'uuid'
import * as Yup from 'yup'
import { NumberInput } from 'src/components/inputs/formik'
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
import TextInput from 'src/components/inputs/formik/TextInput.js'
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
@ -17,6 +20,17 @@ const ALL_COINS = {
code: 'ALL_COINS'
}
const SHOW_ALL = 'Show all'
const ORDER_OPTIONS = [
'Machine name',
'Cryptocurrency',
'Cash-in',
'Cash-out',
'Fixed Fee',
'Minimum Tx'
]
const cashInAndOutHeaderStyle = { marginLeft: 6 }
const cashInHeader = (
@ -345,6 +359,223 @@ const getOrder = ({ machine, cryptoCurrencies }) => {
return 3
}
const createCommissions = (cryptoCode, deviceId, isDefault, config) => {
return {
minimumTx: config.minimumTx,
fixedFee: config.fixedFee,
cashOut: config.cashOut,
cashIn: config.cashIn,
machine: deviceId,
cryptoCurrencies: [cryptoCode],
default: isDefault,
id: v4()
}
}
const getCommissions = (cryptoCode, deviceId, config) => {
const overrides = R.prop('overrides', config)
if (overrides && !R.isEmpty(overrides)) {
const specificOverride = R.find(
it =>
it.machine === deviceId && _.includes(cryptoCode)(it.cryptoCurrencies)
)(overrides)
if (specificOverride !== undefined)
return createCommissions(cryptoCode, deviceId, false, specificOverride)
const machineOverride = R.find(
it =>
it.machine === deviceId && _.includes('ALL_COINS')(it.cryptoCurrencies)
)(overrides)
if (machineOverride !== undefined)
return createCommissions(cryptoCode, deviceId, false, machineOverride)
const coinOverride = R.find(
it =>
it.machine === 'ALL_MACHINES' &&
_.includes(cryptoCode)(it.cryptoCurrencies)
)(overrides)
if (coinOverride !== undefined)
return createCommissions(cryptoCode, deviceId, false, coinOverride)
}
return createCommissions(cryptoCode, deviceId, true, config)
}
const getMachineCoins = (deviceId, localeConfig) => {
const machineCoins = R.prop('cryptoCurrencies', localeConfig)
const overrides = R.prop('overrides', localeConfig)
if (!R.isEmpty(overrides)) {
const override = R.find(it => it.machine === deviceId)(overrides)
if (override !== undefined) return R.prop('cryptoCurrencies', override)
}
return machineCoins
}
const getListCommissionsSchema = () => {
return Yup.object().shape({
machine: Yup.string()
.label('Machine')
.required(),
cryptoCurrencies: Yup.array()
.label('Crypto Currency')
.required(),
cashIn: Yup.number()
.label('Cash-in')
.min(0)
.max(percentMax)
.required(),
cashOut: Yup.number()
.label('Cash-out')
.min(0)
.max(percentMax)
.required(),
fixedFee: Yup.number()
.label('Fixed Fee')
.min(0)
.max(currencyMax)
.required(),
minimumTx: Yup.number()
.label('Minimum Tx')
.min(0)
.max(currencyMax)
.required()
})
}
const getTextStyle = (obj, isEditing) => {
return { color: obj.default ? '#1b2559' : '#44e188' }
}
const commissionsList = (auxData, currency, auxElements) => {
const getData = R.path(R.__, auxData)
return getListCommissionsFields(getData, currency, defaults)
}
const getListCommissionsFields = (getData, currency, defaults) => {
const machineData = [ALL_MACHINES].concat(getData(['machines']))
// const rawCryptos = getData(['cryptoCurrencies'])
// const cryptoData = R.map(it => ({ display: it.code, code: it.code }))(
// rawCryptos ?? []
// )
return [
{
name: 'machine',
width: 196,
size: 'sm',
view: getView(machineData, 'name', 'deviceId'),
input: TextInput,
editable: false,
inputProps: {
valueProp: 'deviceId',
getLabel: R.path(['name'])
}
},
{
name: 'cryptoCurrencies',
display: 'Crypto Currency',
width: 280,
view: R.prop(0),
size: 'sm',
input: TextInput,
editable: false
},
{
header: cashInHeader,
name: 'cashIn',
display: 'Cash-in',
width: 130,
input: NumberInput,
textAlign: 'right',
suffix: '%',
textStyle: obj => getTextStyle(obj),
inputProps: {
decimalPlaces: 3
}
},
{
header: cashOutHeader,
name: 'cashOut',
display: 'Cash-out',
width: 130,
input: NumberInput,
textAlign: 'right',
greenText: true,
suffix: '%',
textStyle: obj => getTextStyle(obj),
inputProps: {
decimalPlaces: 3
}
},
{
name: 'fixedFee',
display: 'Fixed fee',
width: 144,
input: NumberInput,
doubleHeader: 'Cash-in only',
textAlign: 'right',
suffix: currency,
textStyle: obj => getTextStyle(obj),
inputProps: {
decimalPlaces: 2
}
},
{
name: 'minimumTx',
display: 'Minimun Tx',
width: 144,
input: NumberInput,
doubleHeader: 'Cash-in only',
textAlign: 'right',
suffix: currency,
textStyle: obj => getTextStyle(obj),
inputProps: {
decimalPlaces: 2
}
}
]
}
const filterCommissions = (coinFilter, machineFilter, machines) =>
R.compose(
R.filter(byMachine(machineFilter, machines)),
R.filter(byCoin(coinFilter))
)
const byMachine = (filter, machines) => it =>
(filter === SHOW_ALL) |
(filter === getView(machines, 'name', 'deviceId')(it.machine))
const byCoin = filter => it =>
(filter === SHOW_ALL) | (filter === it.cryptoCurrencies[0])
const sortCommissionsBy = (prop, machines) => {
switch (prop) {
case ORDER_OPTIONS[1]:
return R.sortBy(R.path(['cryptoCurrencies', 0]))
case ORDER_OPTIONS[2]:
return R.sortBy(R.prop('cashIn'))
case ORDER_OPTIONS[3]:
return R.sortBy(R.prop('cashOut'))
case ORDER_OPTIONS[4]:
return R.sortBy(R.prop('fixedFee'))
case ORDER_OPTIONS[5]:
return R.sortBy(R.prop('minimumTx'))
default:
return R.sortBy(
R.compose(getView(machines, 'name', 'deviceId'), R.prop('machine'))
)
}
}
export {
mainFields,
overrides,
@ -352,5 +583,16 @@ export {
getOverridesSchema,
defaults,
overridesDefaults,
getOrder
getOrder,
getCommissions,
getMachineCoins,
getListCommissionsSchema,
commissionsList,
getView,
byMachine,
byCoin,
sortCommissionsBy,
filterCommissions,
SHOW_ALL,
ORDER_OPTIONS
}

View file

@ -26,4 +26,4 @@ const startCase = R.compose(
splitOnUpper
)
export { startCase, onlyFirstToUpper, formatLong }
export { startCase, toFirstUpper, onlyFirstToUpper, formatLong }